Spaces:
Running
Running
Upload app_final.py with huggingface_hub
Browse files- app_final.py +213 -0
app_final.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Final wrapper with complete highlight override including interaction buttons.
|
| 2 |
+
PLUS: Hashtag inline sources on homepage with rewrite button."""
|
| 3 |
+
import json, os, time
|
| 4 |
+
from app_patch_unified import *
|
| 5 |
+
from app_patch_unified import app, UNIFIED_INJECT, f5, f6, rt, PATCH_INJECT
|
| 6 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 7 |
+
from fastapi import Request, Query
|
| 8 |
+
|
| 9 |
+
DATA_DIR="/data" if os.path.isdir('/data') else "/app/data"
|
| 10 |
+
os.makedirs(DATA_DIR,exist_ok=True)
|
| 11 |
+
HL_STATS_FILE=os.path.join(DATA_DIR,'highlight_stats.json')
|
| 12 |
+
|
| 13 |
+
def _load_hl():
|
| 14 |
+
try:
|
| 15 |
+
if os.path.exists(HL_STATS_FILE):return json.load(open(HL_STATS_FILE,'r',encoding='utf-8'))
|
| 16 |
+
except:pass
|
| 17 |
+
return {}
|
| 18 |
+
def _save_hl(db):
|
| 19 |
+
try:open(HL_STATS_FILE+'.tmp','w',encoding='utf-8').write(json.dumps(db,ensure_ascii=False));os.replace(HL_STATS_FILE+'.tmp',HL_STATS_FILE)
|
| 20 |
+
except:pass
|
| 21 |
+
|
| 22 |
+
app.router.routes=[r for r in app.router.routes if not (
|
| 23 |
+
(getattr(r,'path',None)=='/api/highlight/interact' and 'POST' in getattr(r,'methods',set())) or
|
| 24 |
+
(getattr(r,'path',None)=='/api/highlight/stats' and 'GET' in getattr(r,'methods',set())) or
|
| 25 |
+
(getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
|
| 26 |
+
(getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))
|
| 27 |
+
)]
|
| 28 |
+
|
| 29 |
+
@app.post('/api/highlight/interact')
|
| 30 |
+
async def _hl_act(request:Request):
|
| 31 |
+
b=await request.json();vid=str(b.get('id','')).strip();action=str(b.get('action','')).strip()
|
| 32 |
+
if not vid or action not in ('view','like','share'):return JSONResponse({'error':'invalid'},status_code=400)
|
| 33 |
+
db=_load_hl();st=db.get(vid,{'views':0,'likes':0,'shares':0})
|
| 34 |
+
st[action+'s']=st.get(action+'s',0)+1
|
| 35 |
+
db[vid]=st;_save_hl(db);return JSONResponse({'stats':st})
|
| 36 |
+
|
| 37 |
+
@app.get('/api/highlight/stats')
|
| 38 |
+
def _hl_stats(ids:str=Query(default='')):
|
| 39 |
+
db=_load_hl();out={}
|
| 40 |
+
for vid in ids.split(','):
|
| 41 |
+
vid=vid.strip()
|
| 42 |
+
if vid:out[vid]=db.get(vid,{'views':0,'likes':0,'shares':0})
|
| 43 |
+
return JSONResponse({'stats':out})
|
| 44 |
+
|
| 45 |
+
@app.get('/api/hashtag/sources')
|
| 46 |
+
def _hashtag_sources(topic:str=Query(...)):
|
| 47 |
+
"""Return sources for a hashtag topic to display inline on homepage."""
|
| 48 |
+
research=f6._fast_context(topic) if hasattr(f6,'_fast_context') else f6._web_research_context(topic)
|
| 49 |
+
sources=research.get('sources',[])
|
| 50 |
+
# Add og:image for each source
|
| 51 |
+
from ai_runtime_patch_fast import _scrape
|
| 52 |
+
for s in sources[:6]:
|
| 53 |
+
if s.get('url') and not s.get('img'):
|
| 54 |
+
try:_,_,img=_scrape(s['url'],500)
|
| 55 |
+
except:img=''
|
| 56 |
+
s['img']=img if img and len(img)>20 else ''
|
| 57 |
+
return JSONResponse({'sources':sources[:6],'topic':topic})
|
| 58 |
+
|
| 59 |
+
# PRE_KILL fix
|
| 60 |
+
UNIFIED_INJECT_FIXED = UNIFIED_INJECT.replace(
|
| 61 |
+
"""Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});""",
|
| 62 |
+
"""Object.defineProperty(window,'renderAIShorts7',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 63 |
+
Object.defineProperty(window,'renderPatchedWall',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 64 |
+
Object.defineProperty(window,'renderAiShorts',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 65 |
+
Object.defineProperty(window,'renderWall',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 66 |
+
Object.defineProperty(window,'renderAIShorts',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 67 |
+
Object.defineProperty(window,'loadPatchedWall',{get:function(){return function(){}},set:function(){},configurable:true});
|
| 68 |
+
Object.defineProperty(window,'refreshFinalWall3',{get:function(){return function(){}},set:function(){},configurable:true});"""
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
# Fix highlight fetch
|
| 72 |
+
UNIFIED_INJECT_FIXED = UNIFIED_INJECT_FIXED.replace(
|
| 73 |
+
"var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){el.innerHTML=",
|
| 74 |
+
"var articles=(window._hlLeagueData||{})[league]||[];\n if(!articles.length){try{var _r=await fetch('/api/highlights/'+league);articles=await _r.json();if(!Array.isArray(articles))articles=[];}catch(e){articles=[];}}\n if(!articles.length){el.innerHTML="
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
# Highlight full override (same as 5a5b626)
|
| 78 |
+
HIGHLIGHT_FULL_OVERRIDE = r'''
|
| 79 |
+
<style>
|
| 80 |
+
.tiktok-slide.ratio-wide video,.tiktok-slide.ratio-wide iframe{object-fit:contain!important}
|
| 81 |
+
.hl-ask-panel{position:fixed;bottom:0;left:0;right:0;max-height:50vh;background:#181818;border-radius:16px 16px 0 0;z-index:99999;padding:14px;display:none;overflow-y:auto}.hl-ask-panel.active{display:block}.hl-ask-panel textarea,.hl-ask-panel input{width:100%;background:#222;border:1px solid #444;color:#eee;border-radius:10px;padding:9px;margin:6px 0}.hl-ask-panel button{background:#2d8659;border:0;color:#fff;border-radius:10px;padding:8px 12px;margin:4px}.hl-ask-answer{white-space:pre-wrap;color:#ccc;font-size:12px;margin-top:8px}
|
| 82 |
+
.hashtag-sources{margin:8px 4px;background:#1a1a1a;border:1px solid #2a2a2a;border-radius:10px;padding:10px}.hashtag-sources h3{font-size:13px;color:#5cb87a;margin-bottom:8px}.hashtag-src-item{display:flex;gap:8px;padding:8px;background:#202020;border-radius:8px;margin:6px 0;cursor:pointer}.hashtag-src-item:active{opacity:.8}.hashtag-src-img{flex:0 0 80px;aspect-ratio:16/9;background:#333;border-radius:6px;overflow:hidden}.hashtag-src-img img{width:100%;height:100%;object-fit:cover}.hashtag-src-text{flex:1;min-width:0}.hashtag-src-title{font-size:12px;font-weight:700;color:#eee;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.hashtag-src-via{font-size:10px;color:#888;margin-top:2px}.hashtag-rewrite-btn{width:100%;margin-top:8px;background:#2d8659;border:0;color:#fff;padding:9px;border-radius:10px;font-size:12px;font-weight:700;cursor:pointer}
|
| 83 |
+
</style>
|
| 84 |
+
<div id="hl-ask-panel" class="hl-ask-panel"></div>
|
| 85 |
+
<script>
|
| 86 |
+
(function(){
|
| 87 |
+
function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]});}
|
| 88 |
+
|
| 89 |
+
// === HASHTAG INLINE: click hashtag → show sources on homepage + rewrite button ===
|
| 90 |
+
window.showHashtagSources=async function(topic){
|
| 91 |
+
var home=document.getElementById('view-home');if(!home)return;
|
| 92 |
+
document.getElementById('hashtag-sources-box')?.remove();
|
| 93 |
+
var box=document.createElement('div');box.id='hashtag-sources-box';box.className='hashtag-sources';
|
| 94 |
+
box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#888;font-size:11px">Đang tìm nguồn...</div>';
|
| 95 |
+
var compose=home.querySelector('.ai-compose');
|
| 96 |
+
if(compose)compose.after(box);else home.prepend(box);
|
| 97 |
+
try{
|
| 98 |
+
var r=await fetch('/api/hashtag/sources?topic='+encodeURIComponent(topic));
|
| 99 |
+
var j=await r.json();var sources=j.sources||[];
|
| 100 |
+
if(!sources.length){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#888;font-size:12px">Không tìm được nguồn</div>';return;}
|
| 101 |
+
var h='<h3>🔍 '+esc(topic)+' <span style="font-size:10px;color:#888">('+sources.length+' nguồn)</span></h3>';
|
| 102 |
+
sources.forEach(function(s){
|
| 103 |
+
h+='<div class="hashtag-src-item" onclick="if(typeof readArticle===\'function\')readArticle(\''+esc(s.url||'')+'\')">';
|
| 104 |
+
h+='<div class="hashtag-src-img">'+(s.img?'<img src="'+esc(s.img)+'" onerror="this.style.display=\'none\'">':'')+'</div>';
|
| 105 |
+
h+='<div class="hashtag-src-text"><div class="hashtag-src-title">'+esc(s.title)+'</div><div class="hashtag-src-via">'+esc(s.via||s.source||'')+'</div></div>';
|
| 106 |
+
h+='</div>';
|
| 107 |
+
});
|
| 108 |
+
h+='<button class="hashtag-rewrite-btn" onclick="rewriteHashtagTopic(\''+esc(topic)+'\')">🤖 Rewrite AI tổng hợp nguồn & đăng tường</button>';
|
| 109 |
+
box.innerHTML=h;
|
| 110 |
+
}catch(e){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#e74c3c;font-size:12px">Lỗi: '+esc(e.message)+'</div>';}
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
window.rewriteHashtagTopic=async function(topic){
|
| 114 |
+
var btn=event?.target;if(btn){btn.disabled=true;btn.textContent='Đang tổng hợp...';}
|
| 115 |
+
try{
|
| 116 |
+
var r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic:topic})});
|
| 117 |
+
var j=await r.json();
|
| 118 |
+
if(!r.ok||j.error)throw new Error(j.error||'Lỗi');
|
| 119 |
+
if(btn)btn.textContent='✅ Đã đăng lên Tường AI!';
|
| 120 |
+
setTimeout(function(){document.getElementById('hashtag-sources-box')?.remove();},2000);
|
| 121 |
+
}catch(e){
|
| 122 |
+
if(btn){btn.disabled=false;btn.textContent='❌ '+e.message;}
|
| 123 |
+
}
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
// Override hashtag chip click to use showHashtagSources instead of topic input
|
| 127 |
+
setTimeout(function(){
|
| 128 |
+
document.querySelectorAll('.hot-chip').forEach(function(chip){
|
| 129 |
+
chip.onclick=function(e){
|
| 130 |
+
e.preventDefault();e.stopPropagation();
|
| 131 |
+
var topic=chip.textContent.replace(/^#/,'').trim();
|
| 132 |
+
if(topic)showHashtagSources(topic);
|
| 133 |
+
};
|
| 134 |
+
});
|
| 135 |
+
},3000);
|
| 136 |
+
// Re-patch after hot topics load
|
| 137 |
+
setInterval(function(){
|
| 138 |
+
document.querySelectorAll('.hot-chip:not([data-patched])').forEach(function(chip){
|
| 139 |
+
chip.dataset.patched='1';
|
| 140 |
+
chip.onclick=function(e){
|
| 141 |
+
e.preventDefault();e.stopPropagation();
|
| 142 |
+
var topic=chip.textContent.replace(/^#/,'').trim();
|
| 143 |
+
if(topic)showHashtagSources(topic);
|
| 144 |
+
};
|
| 145 |
+
});
|
| 146 |
+
},2000);
|
| 147 |
+
|
| 148 |
+
// === FULL openLeaguePlayer override (same as before) ===
|
| 149 |
+
window.openLeaguePlayer=async function(league,idx){
|
| 150 |
+
showView('view-tiktok');document.querySelectorAll('.cat').forEach(function(x){x.classList.remove('active')});
|
| 151 |
+
var el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải highlight...</div>';
|
| 152 |
+
var cfg=(window.HL_CONFIG||{})[league]||{name:league,emoji:'🎬'};
|
| 153 |
+
var articles=(window._hlLeagueData||{})[league]||[];
|
| 154 |
+
if(!articles.length){try{var resp=await fetch('/api/highlights/'+league);articles=await resp.json();if(!Array.isArray(articles))articles=[];}catch(e){articles=[];}}
|
| 155 |
+
if(!articles.length){el.innerHTML='<div class="loading">Không có video</div>';return;}
|
| 156 |
+
var vids=[];var results=await Promise.all(articles.map(async function(a,i){try{var r=await fetch('/api/video_url?url='+encodeURIComponent(a.link));var v=await r.json();if(v&&v.src)return Object.assign({},a,v,{_idx:i});}catch(e){}return null;}));results.forEach(function(r){if(r)vids.push(r);});vids.sort(function(a,b){return a._idx-b._idx;});
|
| 157 |
+
if(!vids.length){el.innerHTML='<div class="loading">Không tìm thấy video</div>';return;}
|
| 158 |
+
var ti=vids.findIndex(function(v){return v._idx===idx;});if(ti<0)ti=0;var ordered=ti>0?vids.slice(ti).concat(vids.slice(0,ti)):vids;
|
| 159 |
+
var h='<button class="back-btn" onclick="switchCat(\'home\')">← '+esc(cfg.emoji)+' '+esc(cfg.name)+'</button><div class="tiktok-container"><div class="tiktok-feed" id="tiktok-feed">';
|
| 160 |
+
ordered.forEach(function(v,i){var hlid=encodeURIComponent(v.link||v.title);var isYT=v.type==='youtube';var isHLS=!isYT&&v.src&&v.src.indexOf('.m3u8')>-1;var poster=v.poster?' poster="'+v.poster+'"':'';var vtag=isYT?'<iframe data-yt-src="'+v.src+'" allowfullscreen allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" style="width:100%;height:100%;border:none"></iframe>':isHLS?'<video playsinline preload="none"'+poster+' data-hls="'+v.src+'" loop controls style="width:100%;height:100%;object-fit:cover"></video>':'<video playsinline preload="none"'+poster+' loop controls style="width:100%;height:100%;object-fit:cover"><source src="'+v.src+'" type="video/mp4"></video>';h+='<div class="tiktok-slide" id="tslide-'+i+'" data-hlid="'+hlid+'">'+vtag+'<div class="tiktok-bottom"><span class="badge badge-fpt">'+esc(cfg.name)+'</span><p class="tiktok-title">'+esc(v.title)+'</p></div><div class="tiktok-right"><button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'view\')"><div class="icon">👁</div><div class="count" data-a="views">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'like\')"><div class="icon">❤️</div><div class="count" data-a="likes">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();openHlComments(\''+hlid+'\')"><div class="icon">💬</div><div class="count">BL</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();openHlAsk(\''+hlid+'\',\''+esc(v.title)+'\')"><div class="icon">🤖</div><div class="count">Hỏi</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();hlAct(this,\'share\');if(typeof doShareVideo===\'function\')doShareVideo(\''+esc(v.title)+'\',\''+esc(v.link||'')+'\',\''+esc(v.poster||v.img||'')+'\',\'highlights\')"><div class="icon">📤</div><div class="count" data-a="shares">0</div></button><button class="tiktok-right-btn" onclick="event.stopPropagation();toggleHlRatio(this)"><div class="icon">⬜</div><div class="count">16:9</div></button></div><span class="tiktok-counter">'+(i+1)+'/'+ordered.length+'</span></div>';});
|
| 161 |
+
h+='</div></div>';el.innerHTML=h;
|
| 162 |
+
var feed=document.getElementById('tiktok-feed');if(!feed)return;var slides=feed.querySelectorAll('.tiktok-slide');var cur=-1;
|
| 163 |
+
function act(i){if(i===cur)return;slides.forEach(function(sl,idx){var v=sl.querySelector('video');var fr=sl.querySelector('iframe');if(idx===i){if(v&&v.dataset.hls){if(!v._hls&&typeof Hls!=='undefined'&&Hls.isSupported()){var hls=new Hls();hls.loadSource(v.dataset.hls);hls.attachMedia(v);hls.on(Hls.Events.MANIFEST_PARSED,function(){v.play().catch(function(){});});v._hls=hls;}else if(v._hls)v.play().catch(function(){});}else if(v)v.play().catch(function(){});if(fr&&!fr.src&&fr.dataset.ytSrc)fr.src=fr.dataset.ytSrc;hlAct(sl.querySelector('.tiktok-right .tiktok-right-btn'),'view');}else{if(v){v.pause();if(v._hls){v._hls.destroy();v._hls=null;}}if(fr&&fr.src)fr.src='';}});cur=i;}
|
| 164 |
+
var sT;feed.addEventListener('scroll',function(){clearTimeout(sT);sT=setTimeout(function(){var rect=feed.getBoundingClientRect(),ctr=rect.top+rect.height/2,best=-1,bestD=1e9;slides.forEach(function(sl,i){var d=Math.abs(sl.getBoundingClientRect().top+sl.getBoundingClientRect().height/2-ctr);if(d<bestD){bestD=d;best=i;}});if(best>=0)act(best);},150);});
|
| 165 |
+
setTimeout(function(){act(0);},400);slides.forEach(function(sl){var v=sl.querySelector('video');if(v)v.addEventListener('click',function(e){e.preventDefault();v.paused?v.play().catch(function(){}):v.pause();});});
|
| 166 |
+
var ids=[];slides.forEach(function(sl){if(sl.dataset.hlid)ids.push(sl.dataset.hlid);});
|
| 167 |
+
if(ids.length)fetch('/api/highlight/stats?ids='+ids.join(',')).then(function(r){return r.json()}).then(function(j){var stats=j.stats||{};slides.forEach(function(sl){var st=stats[sl.dataset.hlid];if(!st)return;var r=sl.querySelector('.tiktok-right');if(!r)return;var vc=r.querySelector('[data-a="views"]');if(vc)vc.textContent=st.views||0;var lc=r.querySelector('[data-a="likes"]');if(lc)lc.textContent=st.likes||0;var sc=r.querySelector('[data-a="shares"]');if(sc)sc.textContent=st.shares||0;});}).catch(function(){});
|
| 168 |
+
};
|
| 169 |
+
window.hlAct=async function(btn,action){var slide=btn?btn.closest('.tiktok-slide'):null;var id=slide?slide.dataset.hlid:'';if(!id)return;try{var r=await fetch('/api/highlight/interact',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,action:action})});var j=await r.json();if(j.stats&&slide){var right=slide.querySelector('.tiktok-right');if(right){var vc=right.querySelector('[data-a="views"]');if(vc)vc.textContent=j.stats.views||0;var lc=right.querySelector('[data-a="likes"]');if(lc)lc.textContent=j.stats.likes||0;var sc=right.querySelector('[data-a="shares"]');if(sc)sc.textContent=j.stats.shares||0;}}}catch(e){}};
|
| 170 |
+
window.toggleHlRatio=function(btn){var slide=btn.closest('.tiktok-slide');if(!slide)return;slide.classList.toggle('ratio-wide');var label=btn.querySelector('.count');if(label)label.textContent=slide.classList.contains('ratio-wide')?'1:1':'16:9';};
|
| 171 |
+
window.openHlComments=async function(id){var panel=document.getElementById('hl-ask-panel');var j=await fetch('/api/short/comments?id='+id).then(function(r){return r.json()}).catch(function(){return{comments:[]}});var cmts=j.comments||[];panel.innerHTML='<h3 style="color:#5cb87a;font-size:14px">💬 Bình luận</h3><div id="hl-cmt-list">'+(cmts.map(function(c){return'<div style="background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px">'+esc(c.text)+'</div>'}).join('')||'<div style="color:#777;font-size:12px">Chưa có</div>')+'</div><textarea id="hl-cmt-text" placeholder="Bình luận..."></textarea><button onclick="submitHlCmt(\''+id+'\')">Gửi</button><button onclick="document.getElementById(\'hl-ask-panel\').classList.remove(\'active\')">Đóng</button>';panel.classList.add('active');};
|
| 172 |
+
window.submitHlCmt=async function(id){var t=document.getElementById('hl-cmt-text');if(!t||!t.value.trim())return;var j=await fetch('/api/short/comment',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id:id,text:t.value.trim()})}).then(function(r){return r.json()}).catch(function(){return{comments:[]}});document.getElementById('hl-cmt-list').innerHTML=(j.comments||[]).map(function(c){return'<div style="background:#222;border-radius:8px;padding:7px;margin:5px 0;color:#ccc;font-size:12px">'+esc(c.text)+'</div>'}).join('');t.value='';};
|
| 173 |
+
window.openHlAsk=function(id,title){var panel=document.getElementById('hl-ask-panel');panel.innerHTML='<h3 style="color:#5cb87a;font-size:14px">🤖 Hỏi AI</h3><input id="hl-ask-q" placeholder="Hỏi về: '+esc(title)+'..."><div id="hl-ask-ans" class="hl-ask-answer"></div><button onclick="submitHlAsk(\''+id+'\',\''+esc(title)+'\')">Hỏi</button><button onclick="document.getElementById(\'hl-ask-panel\').classList.remove(\'active\')">Đóng</button>';panel.classList.add('active');};
|
| 174 |
+
window.submitHlAsk=async function(id,title){var q=document.getElementById('hl-ask-q');if(!q||!q.value.trim())return;var ans=document.getElementById('hl-ask-ans');ans.textContent='Đang hỏi...';try{var r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({question:q.value.trim(),context:'Video highlight: '+decodeURIComponent(title||id)})});var j=await r.json();ans.textContent=j.answer||'Không trả lời được';}catch(e){ans.textContent='Lỗi: '+e.message}};
|
| 175 |
+
})();
|
| 176 |
+
</script>
|
| 177 |
+
'''
|
| 178 |
+
|
| 179 |
+
EXTRA_WALL_FIX = r'''
|
| 180 |
+
<style>[data-wall-live="1"]{display:none!important}</style>
|
| 181 |
+
<script>
|
| 182 |
+
(function(){
|
| 183 |
+
var _wc=setInterval(function(){
|
| 184 |
+
var home=document.getElementById('view-home');if(!home||!home.classList.contains('active'))return;
|
| 185 |
+
var has=document.getElementById('short-ai-final-slide');
|
| 186 |
+
if(!has&&typeof renderShortAISlide==='function')renderShortAISlide();
|
| 187 |
+
if(!document.querySelector('.slider-wrap[data-wall-live]')){
|
| 188 |
+
fetch('/api/ai_wall').then(function(r){return r.json()}).then(function(j){
|
| 189 |
+
var posts=(j&&j.posts)||[];if(!posts.length)return;
|
| 190 |
+
if(typeof window._serverWall!=='undefined')window._serverWall=posts;
|
| 191 |
+
if(typeof prependWallPost==='function')prependWallPost(posts[0]);
|
| 192 |
+
}).catch(function(){});
|
| 193 |
+
}
|
| 194 |
+
},4000);
|
| 195 |
+
setTimeout(function(){clearInterval(_wc);},30000);
|
| 196 |
+
})();
|
| 197 |
+
</script>
|
| 198 |
+
'''
|
| 199 |
+
|
| 200 |
+
@app.get('/')
|
| 201 |
+
async def _index_fixed():
|
| 202 |
+
html=f5.f4.f3.f2.f1._load_index_html()
|
| 203 |
+
body=''
|
| 204 |
+
body+=getattr(rt.old,'PATCH_INJECT','')
|
| 205 |
+
body+=f5.f4.f3.f2.f1.FINAL_INJECT+f5.f4.f3.FINAL3_INJECT+f5.f4.FINAL4_INJECT+f5.FINAL5_INJECT
|
| 206 |
+
body+=getattr(f6,'FINAL6_INJECT','')
|
| 207 |
+
body+=getattr(f6,'FINAL6_FAST_HOME_INJECT','')
|
| 208 |
+
body+=getattr(f6,'FINAL6E_INJECT','')
|
| 209 |
+
body+=PATCH_INJECT
|
| 210 |
+
body+=UNIFIED_INJECT_FIXED
|
| 211 |
+
body+=HIGHLIGHT_FULL_OVERRIDE
|
| 212 |
+
body+=EXTRA_WALL_FIX
|
| 213 |
+
return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)
|