bep40 commited on
Commit
d37b735
·
verified ·
1 Parent(s): 8453c4d

Speed up homepage shorts: instant fallback + background YouTube refresh

Browse files
Files changed (1) hide show
  1. ai_runtime_final6.py +82 -35
ai_runtime_final6.py CHANGED
@@ -1,14 +1,17 @@
1
- """Final6: single topic input, web-grounded topic articles, and guaranteed DanTri/SKDS Shorts on home."""
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 topic endpoint to override final5 behavior.
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
- # 1) Use existing ai_ext web_context search (Jina/DuckDuckGo) if available.
30
- try:
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
- excerpt=raw[:1800]
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
- # Remove leading Nguồn label for article body readability.
77
- block=re.sub(r'^Nguồn:\s*','',block)
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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
131
- let liveTopicWall=[];let restoredShorts=[];
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,'&quot;')},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 ensureDantriSkdsShorts(){let home=document.getElementById('view-home');if(!home)return;if(document.getElementById('shorts-restored-final6'))return;let sh=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);if(!sh||!sh.length)return;restoredShorts=sh;let wrap=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">Đã khôi phục</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)}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${esc(a.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let comp=document.querySelector('.ai-compose');if(comp)comp.after(wrap);else home.prepend(wrap);}
136
- 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();},1000);
137
- setTimeout(ensureDantriSkdsShorts,1200);
 
 
 
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 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=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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,'&quot;')},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
  '''