Spaces:
Running
Running
Fix AI wall persistence on homepage and no reload for topic posts
Browse files- restore_patch_topic_shorts.py +11 -19
restore_patch_topic_shorts.py
CHANGED
|
@@ -18,22 +18,17 @@ CACHE={'t':0,'d':[]}
|
|
| 18 |
SHORT_CHANNELS=['baodantri7941','baosuckhoedoisongboyte']
|
| 19 |
TEXT_MODELS=['Qwen/Qwen3-30B-A3B-Instruct-2507:fastest','Qwen/Qwen2.5-72B-Instruct:fastest','Qwen/Qwen2.5-7B-Instruct:fastest']
|
| 20 |
|
| 21 |
-
|
| 22 |
def clean(s):
|
| 23 |
import html
|
| 24 |
return re.sub(r'\s+',' ',html.unescape(s or '')).strip()
|
| 25 |
|
| 26 |
-
|
| 27 |
def _topic_image(topic):
|
| 28 |
try:return base.pollinations_image_url(topic)
|
| 29 |
except Exception:return 'https://image.pollinations.ai/prompt/'+quote('Vietnamese magazine editorial illustration '+topic)+'?width=1024&height=576&nologo=true'
|
| 30 |
|
| 31 |
async def qwen_article(topic):
|
| 32 |
token=os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACEHUB_API_TOKEN') or ''
|
| 33 |
-
system=("Bạn là biên tập viên tiếng Việt chuyên viết bài báo phổ thông chất lượng cao. "
|
| 34 |
-
"Từ một chủ đề ngắn, viết thành bài hoàn chỉnh, tự nhiên, giàu thông tin. "
|
| 35 |
-
"Không tự nhận là AI. Không viết kiểu 'Dưới đây là bài viết'. Không giải thích quy trình. "
|
| 36 |
-
"Không bịa số liệu/tên nguồn/trích dẫn. Văn phong hiện đại, mạch lạc, có chiều sâu.")
|
| 37 |
user=f"""Chủ đề: {topic}
|
| 38 |
|
| 39 |
Hãy viết một bài tiếng Việt hoàn chỉnh khoảng 900-1400 từ.
|
|
@@ -62,12 +57,9 @@ Yêu cầu chất lượng:
|
|
| 62 |
txt=re.sub(r'<think>.*?</think>\s*','',txt,flags=re.S).strip()
|
| 63 |
if len(txt)>500:return txt
|
| 64 |
except Exception:pass
|
| 65 |
-
|
| 66 |
-
prompt=system+'\n\n'+user
|
| 67 |
-
txt=await base.qwen_generate(prompt,max_tokens=1800)
|
| 68 |
return txt or (f"# {topic}\n\n{topic} là một chủ đề có nhiều lớp nghĩa cần được nhìn từ bối cảnh, tác động và ứng dụng thực tế. Bài viết này cung cấp góc nhìn tổng hợp, giúp người đọc hiểu rõ bản chất vấn đề thay vì chỉ nắm vài ý rời rạc.\n\nNguồn tham khảo: Qwen / kiến thức tổng hợp.")
|
| 69 |
|
| 70 |
-
|
| 71 |
def ytdlp_shorts(handle,count=24):
|
| 72 |
try:
|
| 73 |
import yt_dlp
|
|
@@ -109,7 +101,6 @@ def fresh_shorts():
|
|
| 109 |
if v['id'] not in seen:seen.add(v['id']);out.append(v)
|
| 110 |
return out[:60]
|
| 111 |
|
| 112 |
-
# remove conflicting routes
|
| 113 |
app.router.routes=[r for r in app.router.routes if getattr(r,'path',None) not in {'/api/shorts','/api/topic_post','/'}]
|
| 114 |
|
| 115 |
@app.get('/api/shorts')
|
|
@@ -122,31 +113,32 @@ def api_shorts(refresh:int=Query(default=0)):
|
|
| 122 |
async def topic_post(request:Request):
|
| 123 |
body=await request.json();topic=clean(body.get('topic',''))
|
| 124 |
if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
|
| 125 |
-
img=_topic_image(topic)
|
| 126 |
-
text=await qwen_article(topic)
|
| 127 |
post=base.make_post(topic,text,img,'','topic_article',sources=[{'title':'Qwen text model / kiến thức tổng hợp','url':'','via':'Qwen'}]);post['images']=[img]
|
| 128 |
posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
|
| 129 |
return JSONResponse({'post':post})
|
| 130 |
|
| 131 |
PATCH=r'''
|
| 132 |
-
<style>
|
| 133 |
<script>
|
| 134 |
(function(){
|
| 135 |
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 136 |
-
let shorts=[];
|
| 137 |
async function loadShorts(){shorts=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);return shorts;}
|
|
|
|
|
|
|
|
|
|
| 138 |
function dedupeShortSlides(){let found=false;document.querySelectorAll('.slider-wrap').forEach(w=>{let label=w.querySelector('.slider-label')?.textContent||'';if(label.includes('Shorts')){if(found)w.remove();else{found=true;w.id='main-shorts-wrap';w.querySelectorAll('.slider-item').forEach((el,i)=>el.setAttribute('onclick',`openShortsDedupe(${i})`));}}});}
|
| 139 |
window.openShortsDedupe=async function(start){let data=shorts.length?shorts:await loadShorts();if(!data.length)return alert('Không tải được Shorts');let ordered=start>0?data.slice(start).concat(data.slice(0,start)):data;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Shorts</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((v,i)=>{let src='https://www.youtube.com/embed/'+v.id+'?autoplay=1&rel=0&playsinline=1';h+=`<div class="tiktok-slide"><iframe data-src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" style="width:100%;height:100%;border:0"></iframe><div class="tiktok-bottom"><span class="badge badge-fpt">YT</span><p class="tiktok-title">${esc(v.title)}</p></div><span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;initShortFeed();}
|
| 140 |
function initShortFeed(){let feed=document.getElementById('tiktok-feed');if(!feed)return;let slides=feed.querySelectorAll('.tiktok-slide');let cur=-1;function act(i){if(i===cur)return;slides.forEach((sl,idx)=>{let f=sl.querySelector('iframe');if(idx===i){if(f&&!f.src)f.src=f.dataset.src}else if(f)f.src=''});cur=i}let t;feed.addEventListener('scroll',()=>{clearTimeout(t);t=setTimeout(()=>{let rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,b=0,d=1e9;slides.forEach((sl,i)=>{let dd=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(dd<d){d=dd;b=i}});act(b)},120)});setTimeout(()=>act(0),200)}
|
| 141 |
let oldOpen=window.openTikTok;window.openTikTok=function(type,idx){if(type==='shorts')return openShortsDedupe(idx||0);return oldOpen?oldOpen(type,idx):null};
|
| 142 |
-
window.createTopicPostFinal5=async function(){let inp=document.getElementById('ai-topic-input-final5');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');let btn=document.getElementById('ai-topic-btn-final5');if(btn){btn.disabled=true;btn.textContent='Đang viết bài...'}try{let r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');if(inp)inp.value='';alert('Đã tạo bài viết bằng model text Qwen chất lượng hơn.');
|
| 143 |
-
setTimeout(async()=>{await loadShorts();dedupeShortSlides();},1000);setInterval(dedupeShortSlides,
|
| 144 |
})();
|
| 145 |
</script>
|
| 146 |
'''
|
| 147 |
|
| 148 |
@app.get('/')
|
| 149 |
async def index_patched():
|
| 150 |
-
html=await restored.index_final6()
|
| 151 |
-
text=html.body.decode('utf-8') if hasattr(html,'body') else str(html)
|
| 152 |
return HTMLResponse(text.replace('</body>',PATCH+'\n</body>') if '</body>' in text else text+PATCH)
|
|
|
|
| 18 |
SHORT_CHANNELS=['baodantri7941','baosuckhoedoisongboyte']
|
| 19 |
TEXT_MODELS=['Qwen/Qwen3-30B-A3B-Instruct-2507:fastest','Qwen/Qwen2.5-72B-Instruct:fastest','Qwen/Qwen2.5-7B-Instruct:fastest']
|
| 20 |
|
|
|
|
| 21 |
def clean(s):
|
| 22 |
import html
|
| 23 |
return re.sub(r'\s+',' ',html.unescape(s or '')).strip()
|
| 24 |
|
|
|
|
| 25 |
def _topic_image(topic):
|
| 26 |
try:return base.pollinations_image_url(topic)
|
| 27 |
except Exception:return 'https://image.pollinations.ai/prompt/'+quote('Vietnamese magazine editorial illustration '+topic)+'?width=1024&height=576&nologo=true'
|
| 28 |
|
| 29 |
async def qwen_article(topic):
|
| 30 |
token=os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACEHUB_API_TOKEN') or ''
|
| 31 |
+
system=("Bạn là biên tập viên tiếng Việt chuyên viết bài báo phổ thông chất lượng cao. Từ một chủ đề ngắn, viết thành bài hoàn chỉnh, tự nhiên, giàu thông tin. Không tự nhận là AI. Không viết kiểu 'Dưới đây là bài viết'. Không giải thích quy trình. Không bịa số liệu/tên nguồn/trích dẫn. Văn phong hiện đại, mạch lạc, có chiều sâu.")
|
|
|
|
|
|
|
|
|
|
| 32 |
user=f"""Chủ đề: {topic}
|
| 33 |
|
| 34 |
Hãy viết một bài tiếng Việt hoàn chỉnh khoảng 900-1400 từ.
|
|
|
|
| 57 |
txt=re.sub(r'<think>.*?</think>\s*','',txt,flags=re.S).strip()
|
| 58 |
if len(txt)>500:return txt
|
| 59 |
except Exception:pass
|
| 60 |
+
txt=await base.qwen_generate(system+'\n\n'+user,max_tokens=1800)
|
|
|
|
|
|
|
| 61 |
return txt or (f"# {topic}\n\n{topic} là một chủ đề có nhiều lớp nghĩa cần được nhìn từ bối cảnh, tác động và ứng dụng thực tế. Bài viết này cung cấp góc nhìn tổng hợp, giúp người đọc hiểu rõ bản chất vấn đề thay vì chỉ nắm vài ý rời rạc.\n\nNguồn tham khảo: Qwen / kiến thức tổng hợp.")
|
| 62 |
|
|
|
|
| 63 |
def ytdlp_shorts(handle,count=24):
|
| 64 |
try:
|
| 65 |
import yt_dlp
|
|
|
|
| 101 |
if v['id'] not in seen:seen.add(v['id']);out.append(v)
|
| 102 |
return out[:60]
|
| 103 |
|
|
|
|
| 104 |
app.router.routes=[r for r in app.router.routes if getattr(r,'path',None) not in {'/api/shorts','/api/topic_post','/'}]
|
| 105 |
|
| 106 |
@app.get('/api/shorts')
|
|
|
|
| 113 |
async def topic_post(request:Request):
|
| 114 |
body=await request.json();topic=clean(body.get('topic',''))
|
| 115 |
if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
|
| 116 |
+
img=_topic_image(topic);text=await qwen_article(topic)
|
|
|
|
| 117 |
post=base.make_post(topic,text,img,'','topic_article',sources=[{'title':'Qwen text model / kiến thức tổng hợp','url':'','via':'Qwen'}]);post['images']=[img]
|
| 118 |
posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
|
| 119 |
return JSONResponse({'post':post})
|
| 120 |
|
| 121 |
PATCH=r'''
|
| 122 |
+
<style>.topic-final3,.topic-final4{display:none!important}.topic-final5{display:flex!important}.wall-live-fixed{margin:6px 4px;background:#1a1a1a;border:1px solid #2a2a2a;border-radius:8px;overflow:hidden}</style>
|
| 123 |
<script>
|
| 124 |
(function(){
|
| 125 |
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 126 |
+
let shorts=[];let wall=[];
|
| 127 |
async function loadShorts(){shorts=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);return shorts;}
|
| 128 |
+
async function loadWall(){wall=(await fetch('/api/ai_wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];renderWallFixed();return wall;}
|
| 129 |
+
function renderWallFixed(){let home=document.getElementById('view-home');if(!home)return;document.getElementById('wall-live-fixed')?.remove();if(!wall.length)return;let wrap=document.createElement('div');wrap.id='wall-live-fixed';wrap.className='wall-live-fixed';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Đã lưu</span></div><div class="slider-track">';wall.slice(0,30).forEach((p,i)=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img src="${p.img}">`:''}</div><div class="wall-title">${esc(p.title)}</div><div class="wall-text">${esc(p.text)}</div><div class="wall-actions"><button class="primary" onclick="readWallFixed(${i})">Xem</button></div></div>`});h+='</div>';wrap.innerHTML=h;let comp=document.querySelector('.ai-compose');if(comp)comp.after(wrap);else home.prepend(wrap);}
|
| 130 |
+
window.readWallFixed=function(i){let p=wall[i];if(!p)return;showView('view-article');document.getElementById('view-article').innerHTML=`<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}">`:''}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p></div>`;window.scrollTo(0,0)}
|
| 131 |
function dedupeShortSlides(){let found=false;document.querySelectorAll('.slider-wrap').forEach(w=>{let label=w.querySelector('.slider-label')?.textContent||'';if(label.includes('Shorts')){if(found)w.remove();else{found=true;w.id='main-shorts-wrap';w.querySelectorAll('.slider-item').forEach((el,i)=>el.setAttribute('onclick',`openShortsDedupe(${i})`));}}});}
|
| 132 |
window.openShortsDedupe=async function(start){let data=shorts.length?shorts:await loadShorts();if(!data.length)return alert('Không tải được Shorts');let ordered=start>0?data.slice(start).concat(data.slice(0,start)):data;showView('view-tiktok');let h='<button class="back-btn" onclick="switchCat(\'home\')">← Shorts</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';ordered.forEach((v,i)=>{let src='https://www.youtube.com/embed/'+v.id+'?autoplay=1&rel=0&playsinline=1';h+=`<div class="tiktok-slide"><iframe data-src="${src}" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" style="width:100%;height:100%;border:0"></iframe><div class="tiktok-bottom"><span class="badge badge-fpt">YT</span><p class="tiktok-title">${esc(v.title)}</p></div><span class="tiktok-counter">${i+1}/${ordered.length}</span></div>`});h+='</div></div>';document.getElementById('view-tiktok').innerHTML=h;initShortFeed();}
|
| 133 |
function initShortFeed(){let feed=document.getElementById('tiktok-feed');if(!feed)return;let slides=feed.querySelectorAll('.tiktok-slide');let cur=-1;function act(i){if(i===cur)return;slides.forEach((sl,idx)=>{let f=sl.querySelector('iframe');if(idx===i){if(f&&!f.src)f.src=f.dataset.src}else if(f)f.src=''});cur=i}let t;feed.addEventListener('scroll',()=>{clearTimeout(t);t=setTimeout(()=>{let rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,b=0,d=1e9;slides.forEach((sl,i)=>{let dd=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(dd<d){d=dd;b=i}});act(b)},120)});setTimeout(()=>act(0),200)}
|
| 134 |
let oldOpen=window.openTikTok;window.openTikTok=function(type,idx){if(type==='shorts')return openShortsDedupe(idx||0);return oldOpen?oldOpen(type,idx):null};
|
| 135 |
+
window.createTopicPostFinal5=async function(){let inp=document.getElementById('ai-topic-input-final5');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');let btn=document.getElementById('ai-topic-btn-final5');if(btn){btn.disabled=true;btn.textContent='Đang viết bài...'}try{let r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');wall.unshift(j.post);renderWallFixed();readWallFixed(0);if(inp)inp.value='';alert('Đã tạo bài viết bằng model text Qwen chất lượng hơn.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài kiến thức bằng Qwen'}}};
|
| 136 |
+
setTimeout(async()=>{await loadShorts();await loadWall();dedupeShortSlides();},1000);setInterval(()=>{dedupeShortSlides();if(document.getElementById('view-home')?.classList.contains('active'))renderWallFixed();},2500);
|
| 137 |
})();
|
| 138 |
</script>
|
| 139 |
'''
|
| 140 |
|
| 141 |
@app.get('/')
|
| 142 |
async def index_patched():
|
| 143 |
+
html=await restored.index_final6();text=html.body.decode('utf-8') if hasattr(html,'body') else str(html)
|
|
|
|
| 144 |
return HTMLResponse(text.replace('</body>',PATCH+'\n</body>') if '</body>' in text else text+PATCH)
|