Spaces:
Running
Running
fix: add hashlib import + multilingual voice mapping
Browse files- 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="
|
| 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={
|
| 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}})};
|