bep40 commited on
Commit
71c5b97
·
verified ·
1 Parent(s): 518c068

fix: add hashlib import + multilingual voice mapping

Browse files
Files changed (1) hide show
  1. ai_patch.py +33 -2
ai_patch.py CHANGED
@@ -6,6 +6,7 @@ import json
6
  import html as html_lib
7
  import subprocess
8
  import requests
 
9
  import ai_ext as base
10
  from ai_ext import app
11
  from fastapi import Request
@@ -641,11 +642,41 @@ async def patched_ai_short(post_id: str, request: Request):
641
  try:
642
  base._download_image(post.get('img'), post.get('title', 'AI news'), img)
643
  edge_voice = {
 
644
  'nam': 'vi-VN-NamMinhNeural',
645
  'male': 'vi-VN-NamMinhNeural',
646
  'nu': 'vi-VN-HoaiMyNeural',
647
  'female': 'vi-VN-HoaiMyNeural',
648
  'mien-nam': 'vi-VN-HoaiMyNeural',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  }.get(voice, 'vi-VN-HoaiMyNeural')
650
  part_files=[]
651
  for idx, seg in enumerate(segments):
@@ -734,8 +765,8 @@ async function loadPatchedWall(){try{const r=await fetch('/api/ai_wall');const j
734
  function renderAiShorts(){const home=document.getElementById('view-home');if(!home)return;document.getElementById('ai-shorts-patched')?.remove();if(!aiShorts.length)return;let wrap=document.createElement('div');wrap.id='ai-shorts-patched';wrap.className='ai-wall-patched';let h='<div class="slider-header"><span class="slider-label">🎬 Short AI</span><span class="slider-note">Video đã tạo</span></div><div class="slider-track">';aiShorts.slice(0,30).forEach((p,i)=>{h+=`<div class="ai-short-card" onclick="aiReadShortPatched(${i})"><div class="ai-short-video"><video src="${p.video}" muted playsinline preload="metadata"></video></div><div class="slider-title">${esc(p.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let wall=document.getElementById('ai-wall-patched');if(wall)wall.after(wrap);else home.prepend(wrap);}
735
  function renderPatchedWall(){const home=document.getElementById('view-home');if(!home)return;document.getElementById('ai-wall-patched')?.remove();if(!patchedWall.length)return;let wrap=document.createElement('div');wrap.id='ai-wall-patched';wrap.className='ai-wall-patched';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Mỗi nguồn = một bài tóm tắt</span></div><div class="slider-track">';patchedWall.slice(0,30).forEach((p,i)=>{h+=`<div class="ai-wall-card"><div class="ai-wall-img">${p.img?`<img src="${p.img}">`:''}</div><div class="ai-wall-title">${esc(p.title)}</div><div class="ai-wall-text">${esc(p.text)}</div><div class="ai-wall-actions"><button onclick="aiReadWallPatched(${i})">Xem</button><button class="primary" onclick="aiMakeShortPatched(${i})">Shorts</button></div></div>`});h+='</div>';wrap.innerHTML=h;let after=document.querySelector('.ai-compose');if(after)after.after(wrap);else home.prepend(wrap);}
736
  window.aiReadShortPatched=function(i){const p=aiShorts[i];if(!p)return;showView('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">Short AI</span><h1 class="article-title">${esc(p.title)}</h1><video class="article-img" src="${p.video}" controls playsinline autoplay></video><p class="article-p" style="white-space:pre-wrap">${esc(p.text||'')}</p><div class="article-actions"><button onclick="window.open('${p.video}','_blank')">⬇ Mở video</button>${p.url?`<button onclick="window.open('${p.url}','_blank')">🔗 Nguồn</button>`:''}</div></div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0)};
737
- window.aiReadWallPatched=function(i){const p=patchedWall[i];if(!p)return;showView('view-article');let sources='';if(p.sources&&p.sources.length){sources='<div class="article-summary"><b>Nguồn tham khảo:</b><br>'+p.sources.slice(0,5).map(s=>`• ${esc(s.title||s.url||'Nguồn')} ${s.url?`(${esc(new URL(s.url).hostname.replace('www.',''))})`:''}`).join('<br>')+'</div>'}let voiceBox=`<div class="article-actions"><select id="ai-short-voice"><option value="nu">Giọng nữ Việt</option><option value="nam">Giọng nam Việt</option><option value="mien-nam">Giọng miền Nam</option></select><select id="ai-short-emotion"><option value="neutral">Trung tính</option><option value="urgent">Tin nhanh</option><option value="warm">Ấm áp</option><option value="serious">Nghiêm túc</option><option value="energetic">Sôi nổi</option></select><button onclick="aiMakeShortPatched(${i})">🎬 Tạo video shorts</button></div>`;let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI</span><h1 class="article-title">${esc(p.title)}</h1>${p.img?`<img class="article-img" src="${p.img}">`:''}${sources}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p>${p.video?`<video class="article-img" src="${p.video}" controls playsinline></video>`:''}<div class="article-actions">${p.url?`<button onclick="window.open('${p.url}','_blank')">🔗 Nguồn</button>`:''}</div>${voiceBox}</div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0)};
738
- window.aiMakeShortPatched=async function(i){const p=patchedWall[i];if(!p)return;let voice=document.getElementById('ai-short-voice')?.value||'nu';let emotion=document.getElementById('ai-short-emotion')?.value||'neutral';let voiceName={nu:'Giọng nữ Việt',nam:'Giọng nam Việt','mien-nam':'Giọng miền Nam'}[voice]||voice;let emotionName={neutral:'Trung tính',urgent:'Tin nhanh',warm:'Ấm áp',serious:'Nghiêm túc',energetic:'Sôi nổi'}[emotion]||emotion;let ok=confirm(`Quy trình tạo short AI:\n\n1) Dùng ảnh đại diện của bài hoặc tạo ảnh minh họa nếu thiếu.\n2) Rút gọn nội dung tóm tắt thành kịch bản đọc ngắn.\n3) Tự ngắt câu theo dấu câu và xuống dòng hợp lý.\n4) Tạo giọng đọc tiếng Việt: ${voiceName}.\n5) Áp dụng cảm xúc/kịch bản: ${emotionName}.\n6) Tăng tốc giọng đọc 1.2 lần.\n7) Mỗi đoạn tóm tắt sẽ là một cảnh riêng theo thời lượng đọc.\n8) Không thêm phụ đề; video chỉ có chữ cảnh và giọng đọc.\n9) Sau khi xong, video xuất hiện ở slide "Short AI".\n\nQuá trình có thể mất 1-3 phút. Bạn muốn bắt đầu?`);if(!ok)return;try{showProgress(`Bước 1/5: Chuẩn bị ảnh và căn chữ full width...<br>Bước 2/5: Tạo kịch bản, tự ngắt câu/xuống dòng...<br>Bước 3/5: Tạo giọng đọc ${voiceName}, cảm xúc ${emotionName}.<br>Bước 4/5: Tăng tốc 1.2x và ghép từng cảnh riêng, không phụ đề.<br>Bước 5/5: Lưu vào slide "Short AI".`);const r=await fetch('/api/ai/short/'+p.id,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice,emotion,speed:1.2})});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');p.video=j.video;hideProgress();alert('Hoàn tất: video shorts đã được tạo và thêm vào slide "Short AI".');aiReadWallPatched(i);loadPatchedWall();}catch(e){hideProgress();alert('Không tạo được shorts: '+e.message)}};
739
  window.createTopicPost=function(){let inp=document.getElementById('ai-topic-input');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&(j.posts||j.post)){let arr=j.posts||[j.post];patchedWall=arr.concat(patchedWall.filter(x=>!arr.find(y=>y.id===x.id)));renderPatchedWall();if(inp)inp.value='';alert(`Đã lọc và tóm tắt ${arr.length} bài viết theo chủ đề lên Tường AI`);}else alert(j.error||'Lỗi tạo bài')}).catch(e=>alert(e.message||'Lỗi tạo bài'));};
740
  window.createUrlPost=function(){let inp=document.getElementById('ai-url-input');let url=(inp&&inp.value||'').trim();if(!url)return alert('Dán URL trước');if(!/^https?:\/\//i.test(url))return alert('URL cần bắt đầu bằng http:// hoặc https://');fetch('/api/url_wall',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&j.post){patchedWall=[j.post].concat(patchedWall.filter(x=>x.id!==j.post.id));renderPatchedWall();if(inp)inp.value='';alert('Đã tóm tắt URL và đăng lên Tường AI');}else alert(j.error||'Lỗi URL')}).catch(e=>alert(e.message||'Lỗi URL'));};
741
  window.rewriteCurrentArticle=function(){if(!window._currentArticle&&typeof _currentArticle!=='undefined')window._currentArticle=_currentArticle;let cur=window._currentArticle||_currentArticle;if(!cur)return;let btn=document.querySelector('.article-actions button.primary');if(btn){btn.textContent='Đang tóm tắt...';btn.disabled=true}fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:cur.url})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&j.post){document.getElementById('rewrite-result').innerHTML=`<div class="rewrite-box"><div class="rewrite-title">Đã tóm tắt và đăng Tường AI</div><div class="rewrite-text">${esc(j.post.text||'')}</div></div>`;patchedWall=[j.post].concat(patchedWall.filter(x=>x.id!==j.post.id));renderPatchedWall();alert('Đã tóm tắt lên Tường AI');}else alert(j.error||'Không tóm tắt được')}).catch(e=>alert(e.message||'Lỗi tóm tắt')).finally(()=>{if(btn){btn.textContent='🤖 Tóm tắt AI & đăng tường';btn.disabled=false}})};
 
6
  import html as html_lib
7
  import subprocess
8
  import requests
9
+ import hashlib
10
  import ai_ext as base
11
  from ai_ext import app
12
  from fastapi import Request
 
642
  try:
643
  base._download_image(post.get('img'), post.get('title', 'AI news'), img)
644
  edge_voice = {
645
+ # Vietnamese
646
  'nam': 'vi-VN-NamMinhNeural',
647
  'male': 'vi-VN-NamMinhNeural',
648
  'nu': 'vi-VN-HoaiMyNeural',
649
  'female': 'vi-VN-HoaiMyNeural',
650
  'mien-nam': 'vi-VN-HoaiMyNeural',
651
+ # Multilingual - Andrew
652
+ 'andrew': 'en-US-AndrewNeural',
653
+ 'en_andrew': 'en-US-AndrewNeural',
654
+ # Multilingual - Jenny
655
+ 'jenny': 'en-US-JennyNeural',
656
+ 'en_jenny': 'en-US-JennyNeural',
657
+ # Portuguese - Thalita Multilingual
658
+ 'thalita': 'pt-BR-ThalitaMultilingualNeural',
659
+ 'pt_thalita': 'pt-BR-ThalitaMultilingualNeural',
660
+ 'pt': 'pt-BR-FranciscoNeural',
661
+ # Spanish
662
+ 'ela': 'es-ES-ElaNeural',
663
+ 'es_ela': 'es-ES-ElaNeural',
664
+ 'es': 'es-ES-CarlosNeural',
665
+ # French
666
+ 'denise': 'fr-FR-DeniseNeural',
667
+ 'fr': 'fr-FR-DeniseNeural',
668
+ # German
669
+ 'katja': 'de-DE-KatjaNeural',
670
+ 'de': 'de-DE-KatjaNeural',
671
+ # Japanese
672
+ 'nanami': 'ja-JP-NanamiNeural',
673
+ 'ja': 'ja-JP-NanamiNeural',
674
+ # Korean
675
+ 'sunhee': 'ko-KR-SunHeeNeural',
676
+ 'ko': 'ko-KR-SunHeeNeural',
677
+ # Chinese
678
+ 'xiaochen': 'zh-CN-XiaochenNeural',
679
+ 'zh': 'zh-CN-XiaochenNeural',
680
  }.get(voice, 'vi-VN-HoaiMyNeural')
681
  part_files=[]
682
  for idx, seg in enumerate(segments):
 
765
  function renderAiShorts(){const home=document.getElementById('view-home');if(!home)return;document.getElementById('ai-shorts-patched')?.remove();if(!aiShorts.length)return;let wrap=document.createElement('div');wrap.id='ai-shorts-patched';wrap.className='ai-wall-patched';let h='<div class="slider-header"><span class="slider-label">🎬 Short AI</span><span class="slider-note">Video đã tạo</span></div><div class="slider-track">';aiShorts.slice(0,30).forEach((p,i)=>{h+=`<div class="ai-short-card" onclick="aiReadShortPatched(${i})"><div class="ai-short-video"><video src="${p.video}" muted playsinline preload="metadata"></video></div><div class="slider-title">${esc(p.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let wall=document.getElementById('ai-wall-patched');if(wall)wall.after(wrap);else home.prepend(wrap);}
766
  function renderPatchedWall(){const home=document.getElementById('view-home');if(!home)return;document.getElementById('ai-wall-patched')?.remove();if(!patchedWall.length)return;let wrap=document.createElement('div');wrap.id='ai-wall-patched';wrap.className='ai-wall-patched';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Mỗi nguồn = một bài tóm tắt</span></div><div class="slider-track">';patchedWall.slice(0,30).forEach((p,i)=>{h+=`<div class="ai-wall-card"><div class="ai-wall-img">${p.img?`<img src="${p.img}">`:''}</div><div class="ai-wall-title">${esc(p.title)}</div><div class="ai-wall-text">${esc(p.text)}</div><div class="ai-wall-actions"><button onclick="aiReadWallPatched(${i})">Xem</button><button class="primary" onclick="aiMakeShortPatched(${i})">Shorts</button></div></div>`});h+='</div>';wrap.innerHTML=h;let after=document.querySelector('.ai-compose');if(after)after.after(wrap);else home.prepend(wrap);}
767
  window.aiReadShortPatched=function(i){const p=aiShorts[i];if(!p)return;showView('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">Short AI</span><h1 class="article-title">${esc(p.title)}</h1><video class="article-img" src="${p.video}" controls playsinline autoplay></video><p class="article-p" style="white-space:pre-wrap">${esc(p.text||'')}</p><div class="article-actions"><button onclick="window.open('${p.video}','_blank')">⬇ Mở video</button>${p.url?`<button onclick="window.open('${p.url}','_blank')">🔗 Nguồn</button>`:''}</div></div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0)};
768
+ window.aiReadWallPatched=function(i){const p=patchedWall[i];if(!p)return;showView('view-article');let sources='';if(p.sources&&p.sources.length){sources='<div class="article-summary"><b>Nguồn tham khảo:</b><br>'+p.sources.slice(0,5).map(s=>`• ${esc(s.title||s.url||'Nguồn')} ${s.url?`(${esc(new URL(s.url).hostname.replace('www.',''))})`:''}`).join('<br>')+'</div>'}let voiceBox=`<div class="article-actions"><select id="ai-short-voice"><option value="hoaimy">Hoài My (VI)</option><option value="namminh">Nam Minh (VI)</option><option value="andrew">Andrew (Multilingual)</option><option value="jenny">Jenny (EN)</option><option value="thalita">Thalita (PT)</option><option value="ela">Ela (ES)</option><option value="denise">Denise (FR)</option><option value="katja">Katja (DE)</option><option value="nanami">Nanami (JA)</option><option value="sunhee">Sun Hee (KO)</option><option value="xiaochen">Xiao Chen (ZH)</option></select><select id="ai-short-emotion"><option value="neutral">Trung tính</option><option value="urgent">Tin nhanh</option><option value="warm">Ấm áp</option><option value="serious">Nghiêm túc</option><option value="energetic">Sôi nổi</option></select><button onclick="aiMakeShortPatched(${i})">🎬 Tạo video shorts</button></div>`;let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI</span><h1 class="article-title">${esc(p.title)}</h1>${p.img?`<img class="article-img" src="${p.img}">`:''}${sources}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p>${p.video?`<video class="article-img" src="${p.video}" controls playsinline></video>`:''}<div class="article-actions">${p.url?`<button onclick="window.open('${p.url}','_blank')">🔗 Nguồn</button>`:''}</div>${voiceBox}</div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0)};
769
+ window.aiMakeShortPatched=async function(i){const p=patchedWall[i];if(!p)return;let voice=document.getElementById('ai-short-voice')?.value||'nu';let emotion=document.getElementById('ai-short-emotion')?.value||'neutral';let voiceName={hoaimy:'Hoài My (VI)',namminh:'Nam Minh (VI)',andrew:'Andrew (Multilingual)',jenny:'Jenny (EN)',thalita:'Thalita (PT)',ela:'Ela (ES)',denise:'Denise (FR)',katja:'Katja (DE)',nanami:'Nanami (JA)',sunhee:'Sun Hee (KO)',xiaochen:'Xiao Chen (ZH)'}[voice]||voice;let emotionName={neutral:'Trung tính',urgent:'Tin nhanh',warm:'Ấm áp',serious:'Nghiêm túc',energetic:'Sôi nổi'}[emotion]||emotion;let ok=confirm(`Quy trình tạo short AI:\n\n1) Dùng ảnh đại diện của bài hoặc tạo ảnh minh họa nếu thiếu.\n2) Rút gọn nội dung tóm tắt thành kịch bản đọc ngắn.\n3) Tự ngắt câu theo dấu câu và xuống dòng hợp lý.\n4) Tạo giọng đọc tiếng Việt: ${voiceName}.\n5) Áp dụng cảm xúc/kịch bản: ${emotionName}.\n6) Tăng tốc giọng đọc 1.2 lần.\n7) Mỗi đoạn tóm tắt sẽ là một cảnh riêng theo thời lượng đọc.\n8) Không thêm phụ đề; video chỉ có chữ cảnh và giọng đọc.\n9) Sau khi xong, video xuất hiện ở slide "Short AI".\n\nQuá trình có thể mất 1-3 phút. Bạn muốn bắt đầu?`);if(!ok)return;try{showProgress(`Bước 1/5: Chuẩn bị ảnh và căn chữ full width...<br>Bước 2/5: Tạo kịch bản, tự ngắt câu/xuống dòng...<br>Bước 3/5: Tạo giọng đọc ${voiceName}, cảm xúc ${emotionName}.<br>Bước 4/5: Tăng tốc 1.2x và ghép từng cảnh riêng, không phụ đề.<br>Bước 5/5: Lưu vào slide "Short AI".`);const r=await fetch('/api/ai/short/'+p.id,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({voice,emotion,speed:1.2})});const j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');p.video=j.video;hideProgress();alert('Hoàn tất: video shorts đã được tạo và thêm vào slide "Short AI".');aiReadWallPatched(i);loadPatchedWall();}catch(e){hideProgress();alert('Không tạo được shorts: '+e.message)}};
770
  window.createTopicPost=function(){let inp=document.getElementById('ai-topic-input');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&(j.posts||j.post)){let arr=j.posts||[j.post];patchedWall=arr.concat(patchedWall.filter(x=>!arr.find(y=>y.id===x.id)));renderPatchedWall();if(inp)inp.value='';alert(`Đã lọc và tóm tắt ${arr.length} bài viết theo chủ đề lên Tường AI`);}else alert(j.error||'Lỗi tạo bài')}).catch(e=>alert(e.message||'Lỗi tạo bài'));};
771
  window.createUrlPost=function(){let inp=document.getElementById('ai-url-input');let url=(inp&&inp.value||'').trim();if(!url)return alert('Dán URL trước');if(!/^https?:\/\//i.test(url))return alert('URL cần bắt đầu bằng http:// hoặc https://');fetch('/api/url_wall',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&j.post){patchedWall=[j.post].concat(patchedWall.filter(x=>x.id!==j.post.id));renderPatchedWall();if(inp)inp.value='';alert('Đã tóm tắt URL và đăng lên Tường AI');}else alert(j.error||'Lỗi URL')}).catch(e=>alert(e.message||'Lỗi URL'));};
772
  window.rewriteCurrentArticle=function(){if(!window._currentArticle&&typeof _currentArticle!=='undefined')window._currentArticle=_currentArticle;let cur=window._currentArticle||_currentArticle;if(!cur)return;let btn=document.querySelector('.article-actions button.primary');if(btn){btn.textContent='Đang tóm tắt...';btn.disabled=true}fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:cur.url})}).then(r=>r.json().then(j=>({ok:r.ok,j}))).then(({ok,j})=>{if(ok&&j.post){document.getElementById('rewrite-result').innerHTML=`<div class="rewrite-box"><div class="rewrite-title">Đã tóm tắt và đăng Tường AI</div><div class="rewrite-text">${esc(j.post.text||'')}</div></div>`;patchedWall=[j.post].concat(patchedWall.filter(x=>x.id!==j.post.id));renderPatchedWall();alert('Đã tóm tắt lên Tường AI');}else alert(j.error||'Không tóm tắt được')}).catch(e=>alert(e.message||'Lỗi tóm tắt')).finally(()=>{if(btn){btn.textContent='🤖 Tóm tắt AI & đăng tường';btn.disabled=false}})};