Spaces:
Running
Running
| """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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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 | |
| 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}") |