Spaces:
Running
Running
Speed up homepage shorts: instant fallback + background YouTube refresh
Browse files- ai_runtime_final6.py +82 -35
ai_runtime_final6.py
CHANGED
|
@@ -1,14 +1,17 @@
|
|
| 1 |
-
"""Final6:
|
| 2 |
-
import re, time, json, html as html_lib
|
| 3 |
from urllib.parse import quote
|
| 4 |
import ai_runtime_final5 as f5
|
| 5 |
-
from ai_runtime_final5 import app, rt, HTMLResponse, JSONResponse, Request
|
| 6 |
|
| 7 |
-
# Remove root and
|
| 8 |
-
_PATCH={('/','GET'),('/api/topic_post','POST')}
|
| 9 |
app.router.routes=[r for r in app.router.routes if not any(getattr(r,'path',None)==p and m in getattr(r,'methods',set()) for p,m in _PATCH)]
|
| 10 |
|
| 11 |
base=f5.base
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def clean(s):
|
| 14 |
return re.sub(r"\s+"," ",html_lib.unescape(s or "")).strip()
|
|
@@ -23,34 +26,83 @@ def _host(url):
|
|
| 23 |
return urlparse(url or '').netloc.replace('www.','')
|
| 24 |
except Exception:return ''
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
def _collect_source_articles(topic, limit=5):
|
| 27 |
-
"""Collect readable source articles/snippets from the open web for topic."""
|
| 28 |
sources=[];ctx_parts=[];seen=set()
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
ctx, items = base.web_context(topic, limit=limit+3)
|
| 32 |
-
except Exception:
|
| 33 |
-
ctx, items = '', []
|
| 34 |
for it in (items or []):
|
| 35 |
-
url=it.get('url') or ''
|
| 36 |
-
title=clean(it.get('title') or '')
|
| 37 |
if not url.startswith('http') or url in seen:continue
|
| 38 |
seen.add(url)
|
| 39 |
excerpt=clean(it.get('excerpt') or it.get('content') or it.get('description') or '')
|
| 40 |
-
# Try full article extraction for better grounding.
|
| 41 |
try:
|
| 42 |
data=base.scrape_any_url(url)
|
| 43 |
raw=clean((data.get('summary','')+' '+data.get('text','')).strip())
|
| 44 |
if len(raw)>180:
|
| 45 |
-
title=clean(data.get('title') or title)
|
| 46 |
-
|
| 47 |
-
except Exception:
|
| 48 |
-
pass
|
| 49 |
if title or excerpt:
|
| 50 |
sources.append({'title':title or url,'url':url,'excerpt':excerpt[:700],'via':_host(url)})
|
| 51 |
ctx_parts.append(f"Nguồn: {title or url} ({_host(url)})\n{excerpt[:1800]}")
|
| 52 |
if len(sources)>=limit:break
|
| 53 |
-
# 2) Fallback: Google News RSS text snippets if full search failed.
|
| 54 |
if not ctx_parts:
|
| 55 |
try:
|
| 56 |
import requests
|
|
@@ -72,12 +124,9 @@ def _fallback_article(topic, ctx, sources):
|
|
| 72 |
lines=[]
|
| 73 |
for block in (ctx or '').split('\n\n'):
|
| 74 |
block=clean(block)
|
| 75 |
-
if block and len(block)>40:
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
lines.append(block)
|
| 79 |
-
intro=f"{topic} đang là chủ đề được quan tâm vì liên quan trực tiếp tới những thay đổi, tranh luận hoặc xu hướng mới trong đời sống. Dưới đây là phần tổng hợp ngắn gọn từ các nguồn công khai để người đọc nắm bối cảnh chính."
|
| 80 |
-
body='\n\n'.join(lines[:5]) if lines else f"Hiện chưa thu thập được nhiều dữ liệu đủ tin cậy về {topic}. Bài viết này chỉ nên được xem là phần giới thiệu khái quát và cần tiếp tục đối chiếu với các nguồn cập nhật."
|
| 81 |
src_line=', '.join([(s.get('via') or _host(s.get('url','')) or s.get('title','Nguồn')) for s in sources[:4]]) or 'nguồn công khai trên internet'
|
| 82 |
return f"{topic}: những điểm chính cần biết\n\n{intro}\n\n{body}\n\nĐiểm cần lưu ý: thông tin trên internet có thể thay đổi theo thời gian, vì vậy người đọc nên đối chiếu thêm với nguồn gốc khi cần quyết định quan trọng.\n\nNguồn tham khảo: {src_line}."
|
| 83 |
|
|
@@ -101,19 +150,14 @@ YÊU CẦU BẮT BUỘC:
|
|
| 101 |
- Dựa trên nguồn đã thu thập; nếu thông tin chưa chắc chắn thì dùng cách nói thận trọng.
|
| 102 |
- Không bịa số liệu, tên người, thời điểm hoặc sự kiện mới nếu nguồn không nêu.
|
| 103 |
- Cuối bài ghi dòng "Nguồn tham khảo:" kèm tên website/nguồn chính.
|
| 104 |
-
- Độ dài khoảng 700-1100 chữ nếu đủ dữ liệu, ngắn hơn nếu nguồn ít.
|
| 105 |
"""
|
| 106 |
text=await base.qwen_generate(prompt,image_url=img,max_tokens=2200)
|
| 107 |
-
if not text or len(clean(text))<250:
|
| 108 |
-
text=_fallback_article(topic,ctx,sources)
|
| 109 |
-
# Clean common chatty prefixes if model returns them.
|
| 110 |
text=re.sub(r'^(Dưới đây là|Tôi có thể viết|Sau đây là)\s*[::-]?\s*','',text.strip(),flags=re.I)
|
| 111 |
-
# Ensure source line exists.
|
| 112 |
if 'Nguồn tham khảo' not in text:
|
| 113 |
src_line=', '.join([(s.get('via') or _host(s.get('url','')) or s.get('title','Nguồn')) for s in sources[:5]]) or 'kiến thức tổng hợp từ internet'
|
| 114 |
text += '\n\nNguồn tham khảo: '+src_line+'.'
|
| 115 |
title=topic
|
| 116 |
-
# Use first non-empty line as title if model wrote one.
|
| 117 |
first=[x.strip('#: -') for x in text.splitlines() if x.strip()]
|
| 118 |
if first and len(first[0])<120:title=first[0]
|
| 119 |
post=base.make_post(title,text,img,'','topic_web',sources=sources or [{'title':'Tổng hợp internet','url':'','via':'Internet'}])
|
|
@@ -128,13 +172,16 @@ FINAL6_INJECT=r'''
|
|
| 128 |
<script>
|
| 129 |
(function(){
|
| 130 |
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 131 |
-
let liveTopicWall=[];
|
| 132 |
function renderLiveTopicWall(){let home=document.getElementById('view-home');if(!home||!liveTopicWall.length)return;document.getElementById('ai-wall-topic-live')?.remove();let wrap=document.createElement('div');wrap.id='ai-wall-topic-live';wrap.className='ai-wall-topic-live';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI mới</span><span class="slider-note">Tổng hợp internet</span></div><div class="slider-track">';liveTopicWall.slice(0,20).forEach((p,i)=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img src="${esc(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="readLiveTopicWall(${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);}
|
| 133 |
window.readLiveTopicWall=function(i){let p=liveTopicWall[i];if(!p)return;showView('view-article');let imgs=(p.images||[]).filter(Boolean);let gal=imgs.length?'<div class="ai-wall-gallery">'+imgs.slice(0,12).map(u=>`<img src="${esc(u)}" loading="lazy">`).join('')+'</div>':(p.img?`<img class="article-img" src="${esc(p.img)}">`:'');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>${gal}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p><div class="article-actions"><button onclick="shareAI?shareAI(${JSON.stringify(p).replace(/"/g,'"')},false):navigator.clipboard.writeText(location.href)">📤 Chia sẻ</button></div></div>`;window.scrollTo(0,0)};
|
| 134 |
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 tổng hợp internet...'}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');liveTopicWall.unshift(j.post);if(window.finalWall)window.finalWall.unshift(j.post);if(window.finalWall3)window.finalWall3.unshift(j.post);if(inp)inp.value='';renderLiveTopicWall();if(window.renderWall)window.renderWall();readLiveTopicWall(0);alert('Đã tạo bài dựa trên chủ đề và nguồn internet.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài từ chủ đề + Internet'}}};
|
| 135 |
-
async function
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
| 138 |
})();
|
| 139 |
</script>
|
| 140 |
'''
|
|
|
|
| 1 |
+
"""Final6: fast homepage shorts, web-grounded topic articles, and single topic input."""
|
| 2 |
+
import re, time, json, html as html_lib, threading
|
| 3 |
from urllib.parse import quote
|
| 4 |
import ai_runtime_final5 as f5
|
| 5 |
+
from ai_runtime_final5 import app, rt, HTMLResponse, JSONResponse, Request, Query
|
| 6 |
|
| 7 |
+
# Remove root, topic endpoint, and slow shorts endpoint to override final5/final4 behavior.
|
| 8 |
+
_PATCH={('/','GET'),('/api/topic_post','POST'),('/api/shorts','GET')}
|
| 9 |
app.router.routes=[r for r in app.router.routes if not any(getattr(r,'path',None)==p and m in getattr(r,'methods',set()) for p,m in _PATCH)]
|
| 10 |
|
| 11 |
base=f5.base
|
| 12 |
+
_FAST_SHORTS={"t":0,"d":[],"refreshing":False}
|
| 13 |
+
_FAST_LOCK=threading.Lock()
|
| 14 |
+
|
| 15 |
|
| 16 |
def clean(s):
|
| 17 |
return re.sub(r"\s+"," ",html_lib.unescape(s or "")).strip()
|
|
|
|
| 26 |
return urlparse(url or '').netloc.replace('www.','')
|
| 27 |
except Exception:return ''
|
| 28 |
|
| 29 |
+
# ===== FAST SHORTS =====
|
| 30 |
+
def _fallback_shorts_fast():
|
| 31 |
+
"""Immediate fallback from final4/main fallback lists; never blocks on YouTube."""
|
| 32 |
+
try:
|
| 33 |
+
data=f5.f4._fallback_shorts()
|
| 34 |
+
if data:return data[:60]
|
| 35 |
+
except Exception:pass
|
| 36 |
+
# Last hard fallback in case imports change.
|
| 37 |
+
hard=[
|
| 38 |
+
('Lu_iCQ5YwNM','Công an lập hồ sơ xử lý người phụ nữ chửi bới, tát tài xế ô tô | Dân trí','baodantri7941'),
|
| 39 |
+
('CwWvijF8BOA','Chú rể bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước | Dân trí','baodantri7941'),
|
| 40 |
+
('7Pd6vZ2Lz1M','Hành động ấm lòng trong tìm kiếm học sinh tử vong ở sông Lô | SKĐS','baosuckhoedoisongboyte'),
|
| 41 |
+
('SlHLt_ZyPiE','Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc - Nam | SKĐS','baosuckhoedoisongboyte')]
|
| 42 |
+
return [{'id':vid,'title':title,'channel':ch,'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'} for vid,title,ch in hard]
|
| 43 |
+
|
| 44 |
+
def _refresh_shorts_bg(force=False):
|
| 45 |
+
with _FAST_LOCK:
|
| 46 |
+
if _FAST_SHORTS.get('refreshing'):return
|
| 47 |
+
if not force and _FAST_SHORTS.get('d') and time.time()-_FAST_SHORTS.get('t',0)<900:return
|
| 48 |
+
_FAST_SHORTS['refreshing']=True
|
| 49 |
+
def run():
|
| 50 |
+
try:
|
| 51 |
+
data=[]
|
| 52 |
+
# f4._fresh_shorts uses yt-dlp/html scrape and may take seconds; do it off request path.
|
| 53 |
+
try:data=f5.f4._fresh_shorts()
|
| 54 |
+
except Exception:data=[]
|
| 55 |
+
if data:
|
| 56 |
+
with _FAST_LOCK:
|
| 57 |
+
_FAST_SHORTS['d']=data[:60]
|
| 58 |
+
_FAST_SHORTS['t']=time.time()
|
| 59 |
+
elif not _FAST_SHORTS.get('d'):
|
| 60 |
+
with _FAST_LOCK:
|
| 61 |
+
_FAST_SHORTS['d']=_fallback_shorts_fast()
|
| 62 |
+
_FAST_SHORTS['t']=time.time()
|
| 63 |
+
finally:
|
| 64 |
+
with _FAST_LOCK:_FAST_SHORTS['refreshing']=False
|
| 65 |
+
threading.Thread(target=run,daemon=True).start()
|
| 66 |
+
|
| 67 |
+
@app.get('/api/shorts')
|
| 68 |
+
def api_shorts_fast(refresh:int=Query(default=0)):
|
| 69 |
+
"""Return shorts immediately. Latest YouTube shorts refresh in background, not blocking homepage."""
|
| 70 |
+
with _FAST_LOCK:
|
| 71 |
+
data=list(_FAST_SHORTS.get('d') or [])
|
| 72 |
+
age=time.time()-_FAST_SHORTS.get('t',0)
|
| 73 |
+
if not data:
|
| 74 |
+
data=_fallback_shorts_fast()
|
| 75 |
+
with _FAST_LOCK:
|
| 76 |
+
_FAST_SHORTS['d']=data
|
| 77 |
+
_FAST_SHORTS['t']=time.time()
|
| 78 |
+
_refresh_shorts_bg(force=True)
|
| 79 |
+
elif refresh or age>900:
|
| 80 |
+
_refresh_shorts_bg(force=bool(refresh))
|
| 81 |
+
return JSONResponse(data[:60])
|
| 82 |
+
|
| 83 |
+
# Start background update shortly after import, without delaying app startup.
|
| 84 |
+
threading.Timer(2.0, lambda:_refresh_shorts_bg(force=True)).start()
|
| 85 |
+
|
| 86 |
+
# ===== WEB-GROUNDED TOPIC POST =====
|
| 87 |
def _collect_source_articles(topic, limit=5):
|
|
|
|
| 88 |
sources=[];ctx_parts=[];seen=set()
|
| 89 |
+
try:ctx, items = base.web_context(topic, limit=limit+3)
|
| 90 |
+
except Exception:ctx, items = '', []
|
|
|
|
|
|
|
|
|
|
| 91 |
for it in (items or []):
|
| 92 |
+
url=it.get('url') or ''; title=clean(it.get('title') or '')
|
|
|
|
| 93 |
if not url.startswith('http') or url in seen:continue
|
| 94 |
seen.add(url)
|
| 95 |
excerpt=clean(it.get('excerpt') or it.get('content') or it.get('description') or '')
|
|
|
|
| 96 |
try:
|
| 97 |
data=base.scrape_any_url(url)
|
| 98 |
raw=clean((data.get('summary','')+' '+data.get('text','')).strip())
|
| 99 |
if len(raw)>180:
|
| 100 |
+
title=clean(data.get('title') or title); excerpt=raw[:1800]
|
| 101 |
+
except Exception:pass
|
|
|
|
|
|
|
| 102 |
if title or excerpt:
|
| 103 |
sources.append({'title':title or url,'url':url,'excerpt':excerpt[:700],'via':_host(url)})
|
| 104 |
ctx_parts.append(f"Nguồn: {title or url} ({_host(url)})\n{excerpt[:1800]}")
|
| 105 |
if len(sources)>=limit:break
|
|
|
|
| 106 |
if not ctx_parts:
|
| 107 |
try:
|
| 108 |
import requests
|
|
|
|
| 124 |
lines=[]
|
| 125 |
for block in (ctx or '').split('\n\n'):
|
| 126 |
block=clean(block)
|
| 127 |
+
if block and len(block)>40:lines.append(re.sub(r'^Nguồn:\s*','',block))
|
| 128 |
+
intro=f"{topic} đang là chủ đề được quan tâm vì liên quan tới những thay đổi, tranh luận hoặc xu hướng mới trong đời sống. Bài viết này tổng hợp các thông tin công khai để người đọc nắm bối cảnh chính."
|
| 129 |
+
body='\n\n'.join(lines[:5]) if lines else f"Hiện chưa thu thập được nhiều dữ liệu đủ tin cậy về {topic}. Nội dung này nên được xem là phần giới thiệu khái quát và cần đối chiếu thêm với nguồn cập nhật."
|
|
|
|
|
|
|
|
|
|
| 130 |
src_line=', '.join([(s.get('via') or _host(s.get('url','')) or s.get('title','Nguồn')) for s in sources[:4]]) or 'nguồn công khai trên internet'
|
| 131 |
return f"{topic}: những điểm chính cần biết\n\n{intro}\n\n{body}\n\nĐiểm cần lưu ý: thông tin trên internet có thể thay đổi theo thời gian, vì vậy người đọc nên đối chiếu thêm với nguồn gốc khi cần quyết định quan trọng.\n\nNguồn tham khảo: {src_line}."
|
| 132 |
|
|
|
|
| 150 |
- Dựa trên nguồn đã thu thập; nếu thông tin chưa chắc chắn thì dùng cách nói thận trọng.
|
| 151 |
- Không bịa số liệu, tên người, thời điểm hoặc sự kiện mới nếu nguồn không nêu.
|
| 152 |
- Cuối bài ghi dòng "Nguồn tham khảo:" kèm tên website/nguồn chính.
|
|
|
|
| 153 |
"""
|
| 154 |
text=await base.qwen_generate(prompt,image_url=img,max_tokens=2200)
|
| 155 |
+
if not text or len(clean(text))<250:text=_fallback_article(topic,ctx,sources)
|
|
|
|
|
|
|
| 156 |
text=re.sub(r'^(Dưới đây là|Tôi có thể viết|Sau đây là)\s*[::-]?\s*','',text.strip(),flags=re.I)
|
|
|
|
| 157 |
if 'Nguồn tham khảo' not in text:
|
| 158 |
src_line=', '.join([(s.get('via') or _host(s.get('url','')) or s.get('title','Nguồn')) for s in sources[:5]]) or 'kiến thức tổng hợp từ internet'
|
| 159 |
text += '\n\nNguồn tham khảo: '+src_line+'.'
|
| 160 |
title=topic
|
|
|
|
| 161 |
first=[x.strip('#: -') for x in text.splitlines() if x.strip()]
|
| 162 |
if first and len(first[0])<120:title=first[0]
|
| 163 |
post=base.make_post(title,text,img,'','topic_web',sources=sources or [{'title':'Tổng hợp internet','url':'','via':'Internet'}])
|
|
|
|
| 172 |
<script>
|
| 173 |
(function(){
|
| 174 |
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]));}
|
| 175 |
+
let liveTopicWall=[];
|
| 176 |
function renderLiveTopicWall(){let home=document.getElementById('view-home');if(!home||!liveTopicWall.length)return;document.getElementById('ai-wall-topic-live')?.remove();let wrap=document.createElement('div');wrap.id='ai-wall-topic-live';wrap.className='ai-wall-topic-live';let h='<div class="slider-header"><span class="slider-label">🧱 Tường AI mới</span><span class="slider-note">Tổng hợp internet</span></div><div class="slider-track">';liveTopicWall.slice(0,20).forEach((p,i)=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img src="${esc(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="readLiveTopicWall(${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);}
|
| 177 |
window.readLiveTopicWall=function(i){let p=liveTopicWall[i];if(!p)return;showView('view-article');let imgs=(p.images||[]).filter(Boolean);let gal=imgs.length?'<div class="ai-wall-gallery">'+imgs.slice(0,12).map(u=>`<img src="${esc(u)}" loading="lazy">`).join('')+'</div>':(p.img?`<img class="article-img" src="${esc(p.img)}">`:'');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>${gal}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p><div class="article-actions"><button onclick="shareAI?shareAI(${JSON.stringify(p).replace(/"/g,'"')},false):navigator.clipboard.writeText(location.href)">📤 Chia sẻ</button></div></div>`;window.scrollTo(0,0)};
|
| 178 |
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 tổng hợp internet...'}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');liveTopicWall.unshift(j.post);if(window.finalWall)window.finalWall.unshift(j.post);if(window.finalWall3)window.finalWall3.unshift(j.post);if(inp)inp.value='';renderLiveTopicWall();if(window.renderWall)window.renderWall();readLiveTopicWall(0);alert('Đã tạo bài dựa trên chủ đề và nguồn internet.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài từ chủ đề + Internet'}}};
|
| 179 |
+
async function fetchShortsFast(force){return fetch('/api/shorts'+(force?'?refresh=1':''),{cache:'no-store'}).then(r=>r.json()).catch(()=>[])}
|
| 180 |
+
function renderShorts(sh){let home=document.getElementById('view-home');if(!home||!sh||!sh.length)return;let old=document.getElementById('shorts-restored-final6');let wrap=old||document.createElement('div');wrap.id='shorts-restored-final6';wrap.className='slider-wrap';let h='<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS</span><span class="shorts-restore-note">Nhanh + tự cập nhật</span></div><div class="slider-track">';sh.slice(0,30).forEach((a,i)=>{h+=`<div class="slider-item shorts-item" onclick="openShortsFinal5?openShortsFinal5(${i}):openTikTok('shorts',${i})"><div class="slider-thumb shorts-thumb">${a.img?`<img src="${esc(a.img)}" loading="lazy">`:''}<div class="card-play">▶</div></div><div class="slider-title">${esc(a.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;if(!old){let comp=document.querySelector('.ai-compose');if(comp)comp.after(wrap);else home.prepend(wrap);}}
|
| 181 |
+
async function ensureDantriSkdsShorts(){let home=document.getElementById('view-home');if(!home)return;if(!document.getElementById('shorts-restored-final6')){let sh=await fetchShortsFast(false);renderShorts(sh);} }
|
| 182 |
+
async function refreshDantriSkdsShorts(){let sh=await fetchShortsFast(true);if(sh&&sh.length)renderShorts(sh);}
|
| 183 |
+
setInterval(()=>{document.querySelectorAll('#ai-topic-input-final3,.topic-final3,#ai-topic-input-final4,.topic-final4').forEach(e=>(e.closest('.topic-final3,.topic-final4,.ai-compose-row')||e).remove());let b=document.getElementById('ai-topic-btn-final5');if(b){b.style.display='block';b.textContent='✨ Tạo bài từ chủ đề + Internet';}if(document.getElementById('view-home')?.classList.contains('active'))ensureDantriSkdsShorts();},2000);
|
| 184 |
+
setTimeout(ensureDantriSkdsShorts,200);setTimeout(refreshDantriSkdsShorts,4500);setInterval(()=>{if(document.getElementById('view-home')?.classList.contains('active'))refreshDantriSkdsShorts();},15*60*1000);
|
| 185 |
})();
|
| 186 |
</script>
|
| 187 |
'''
|