VNEWS / ai_runtime_patch_final.py
bep40's picture 5d7b3a9 verified
Raw
History Blame Contribute Delete
6.71 kB
"""Final patch: homepage fix + AI topics at top + SSE auto-update"""
import re, json, time
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi import Query
# Import chain - must be after ai_runtime_final6
try:
import ai_runtime_final6 as f6
from ai_runtime_final6 import app, f5
from main import rt
except Exception as e:
print(f"[ERROR] f6 import: {e}")
f6 = None
f5 = None
rt = None
PATCH_CSS_JS = r'''
<style>
.short-cmt-panel{position:fixed;bottom:0;left:0;right:0;max-height:55vh;background:#181818;border-radius:16px 16px 0 0;z-index:99999;padding:14px;display:none;overflow-y:auto}.short-cmt-panel.active{display:block}.short-cmt-panel textarea{width:100%;background:#222;border:1px solid #444;color:#eee;border-radius:10px;padding:9px;margin:6px 0;min-height:60px}.short-cmt-panel button{background:#2d8659;border:0;color:#fff;border-radius:10px;padding:8px 12px;margin:4px}.cmt-item{background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px}
.storage-warn{background:#332200;border:1px solid #664400;color:#ffcc00;padding:8px 12px;border-radius:8px;font-size:11px;margin:6px 0}
button[onclick*="rewriteCurrentArticle"]{display:none!important}
#ai-short-home,.ai-short-home,.ai-short-card-final{display:none!important}
</style>
<div id="short-cmt-panel" class="short-cmt-panel"></div>
<script>
(function(){
function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
fetch('/api/storage_status').then(r=>r.json()).then(j=>{if(!j.persistent){let h=document.getElementById('view-home');if(h){let w=document.createElement('div');w.className='storage-warn';w.innerHTML='⚠️ Persistent Storage chưa bật. Bật: Space Settings → Persistent Storage → Small.';h.prepend(w);}}});
// ===== AI HOT TOPICS PREPEND =====
const AI_HOT_TOPICS = ['Công nghệ AI', 'World Cup 2026', 'Kinh tế Việt Nam', 'Bóng đá châu Âu'];
async function ensureHotTopics(){let inp=document.getElementById('ai-topic-input-final5');if(!inp||document.getElementById('hot-topic-row-ai'))return;let row=document.createElement('div');row.id='hot-topic-row-ai';row.style.cssText='display:flex;gap:6px;overflow-x:auto;padding:4px 0;margin:6px 0';let topics=[];try{let j=await fetch('/api/hot_topics').then(r=>r.json()).catch(()=>({topics:[]}));topics=j.topics||[];}catch(e){topics=[];}AI_HOT_TOPICS.forEach(ai=>{if(!topics.find(t=>(t.topic||'').toLowerCase()===ai.toLowerCase())){topics.unshift({label:'#'+ai.replace(/\s+/g,''),topic:ai,count:0});}});row.innerHTML=topics.slice(0,14).map(t=>`<button class="hot-chip" style="flex:0 0 auto;background:#222;border:1px solid #333;color:#ddd;border-radius:16px;padding:5px 10px;font-size:11px;cursor:pointer" onclick="document.getElementById('ai-topic-input-final5').value='${esc(t.topic).replace(/'/g,'\\''}';document.getElementById('ai-topic-input-final5').focus();searchTopic('${esc(t.topic).replace(/'/g,'\\''}')">${esc(t.label)}</button>`).join('');inp.insertAdjacentElement('afterend',row);}
// ===== SSE AUTO UPDATE =====
let _sseSource=null;
function connectSSE(){try{_sseSource=new EventSource('/api/events');_sseSource.onmessage=e=>{try{const d=JSON.parse(e.data);if(d.type==='new_post'||d.type==='new_short'){fetch('/api/ai_wall').then(r=>r.json()).then(j=>{_wallPosts=j.posts||[];if(typeof renderShortAISlide==='function')renderShortAISlide();const track=document.getElementById('ai-wall-track');if(track)_wallPosts.length?track.innerHTML=_wallPosts.slice(0,20).map((p,i)=>makeWallItem(p,i)).join(''):null;});}}catch{}};_sseSource.onerror=()=>setTimeout(connectSSE,5000);}catch(e){}}
let _lastWallLen=0;
setInterval(()=>{fetch('/api/ai_wall').then(r=>r.json()).then(j=>{const w=j.posts||[];if(w.length!==_lastWallLen){_lastWallLen=w.length;_wallPosts=w;const track=document.getElementById('ai-wall-track');if(track)w.length?track.innerHTML=w.slice(0,20).map((p,i)=>makeWallItem(p,i)).join(''):null;if(typeof renderShortAISlide==='function')renderShortAISlide();}}).catch(()=>{});},30000);
connectSSE();
// ===== Short AI slide =====
async function renderShortAISlide(){let home=document.getElementById('view-home');if(!home)return;document.getElementById('short-ai-final-slide')?.remove();let wall=(await fetch('/api/ai_wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];let vids=wall.filter(p=>p.video);if(!vids.length)return;let wrap=document.createElement('div');wrap.id='short-ai-final-slide';wrap.className='slider-wrap';wrap.innerHTML='<div class="slider-header"><span class="slider-label">🎬 Short AI</span></div><div class="slider-track">'+vids.slice(0,30).map((p,i)=>`<div class="slider-item shorts-item" onclick="openAIShortFeed(${i})"><div class="slider-thumb shorts-thumb"><video src="${p.video}" muted preload="metadata" style="width:100%;height:100%;object-fit:cover"></video><div class="card-play">▶</div></div><div class="slider-title">${esc(p.title)}</div></div>`).join('')+'</div>';let comp=home.querySelector('.ai-compose');(comp&&comp.nextSibling?comp.parentNode.insertBefore(wrap,comp.nextSibling):home.prepend(wrap));}
setTimeout(renderShortAISlide,2500);
setInterval(ensureHotTopics,2000);
})();
</script>
'''
# Register route if possible
if f6 and app:
try:
# Remove duplicate / route to avoid conflict
original_routes = [r for r in app.router.routes if not (getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))]
app.router.routes = original_routes
@app.get('/')
async def patch_homepage():
html = f5.f4.f3.f2.f1._load_index_html() if f5 else "<html><body></body></html>"
body = ""
if hasattr(rt,'old') and hasattr(rt.old,'PATCH_INJECT'):
body += getattr(rt.old,'PATCH_INJECT','')
if f5:
body += getattr(f5.f4.f3.f2.f1,'FINAL_INJECT','') if hasattr(f5,'f4') else ''
body += getattr(f5.f4.f3,'FINAL3_INJECT','') if hasattr(f5,'f4') else ''
body += getattr(f5.f4,'FINAL4_INJECT','') if hasattr(f5,'f4') else ''
body += getattr(f5,'FINAL5_INJECT','') if hasattr(f5,'f4') else ''
body += getattr(f6,'FINAL6_INJECT','') if f6 else ''
body += getattr(f6,'FINAL6_FAST_HOME_INJECT','') if f6 else ''
body += getattr(f6,'FINAL6E_INJECT','') if f6 else ''
body += PATCH_CSS_JS
if '</body>' in html:
html = html.replace('</body>', body + '\n</body>')
else:
html = html + body
return HTMLResponse(html)
except Exception as e:
print(f"[ERROR] register route: {e}")