Spaces:
Running
Running
Override root to add fast hashtag UI + topic input same flow"
Browse files- app_run.py +92 -7
app_run.py
CHANGED
|
@@ -1,16 +1,101 @@
|
|
| 1 |
-
"""Wrapper
|
| 2 |
from app_final import *
|
| 3 |
-
from app_final import app, f6, HIGHLIGHT_FULL_OVERRIDE
|
| 4 |
-
from fastapi.responses import JSONResponse
|
| 5 |
from fastapi import Query
|
| 6 |
|
| 7 |
-
# Override /api/hashtag/sources
|
| 8 |
-
app.router.routes=[r for r in app.router.routes if not (
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
@app.get('/api/hashtag/sources')
|
| 11 |
def _hashtag_fast(topic:str=Query(...)):
|
| 12 |
-
"""Return sources FAST — only title/url/via from RSS cache. No og:image scrape."""
|
| 13 |
research=f6._fast_context(topic) if hasattr(f6,'_fast_context') else f6._web_research_context(topic)
|
| 14 |
sources=research.get('sources',[])
|
| 15 |
-
# Return immediately without scraping images — frontend will lazy-load them
|
| 16 |
return JSONResponse({'sources':[{'title':s.get('title',''),'url':s.get('url',''),'via':s.get('via',s.get('source','')),'snippet':s.get('snippet',s.get('excerpt',''))[:200]} for s in sources[:8]],'topic':topic})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Wrapper: faster hashtag + spinner + topic input same flow."""
|
| 2 |
from app_final import *
|
| 3 |
+
from app_final import app, f6, f5, rt, PATCH_INJECT, UNIFIED_INJECT_FIXED, HIGHLIGHT_FULL_OVERRIDE, EXTRA_WALL_FIX
|
| 4 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 5 |
from fastapi import Query
|
| 6 |
|
| 7 |
+
# Override /api/hashtag/sources — NO og:image scrape, instant from RSS cache
|
| 8 |
+
app.router.routes=[r for r in app.router.routes if not (
|
| 9 |
+
(getattr(r,'path',None)=='/api/hashtag/sources' and 'GET' in getattr(r,'methods',set())) or
|
| 10 |
+
(getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))
|
| 11 |
+
)]
|
| 12 |
|
| 13 |
@app.get('/api/hashtag/sources')
|
| 14 |
def _hashtag_fast(topic:str=Query(...)):
|
|
|
|
| 15 |
research=f6._fast_context(topic) if hasattr(f6,'_fast_context') else f6._web_research_context(topic)
|
| 16 |
sources=research.get('sources',[])
|
|
|
|
| 17 |
return JSONResponse({'sources':[{'title':s.get('title',''),'url':s.get('url',''),'via':s.get('via',s.get('source','')),'snippet':s.get('snippet',s.get('excerpt',''))[:200]} for s in sources[:8]],'topic':topic})
|
| 18 |
+
|
| 19 |
+
# Extra JS: override showHashtagSources with spinner + lazy img + topic input integration
|
| 20 |
+
FAST_HASHTAG_JS = r'''
|
| 21 |
+
<style>
|
| 22 |
+
.hashtag-loading{display:flex;align-items:center;gap:8px;padding:12px;color:#888;font-size:12px}
|
| 23 |
+
.hashtag-spinner{width:16px;height:16px;border:2px solid #333;border-top-color:#5cb87a;border-radius:50%;animation:ht-spin .8s linear infinite}
|
| 24 |
+
@keyframes ht-spin{to{transform:rotate(360deg)}}
|
| 25 |
+
.hashtag-src-item img{width:100%;height:100%;object-fit:cover}
|
| 26 |
+
</style>
|
| 27 |
+
<script>
|
| 28 |
+
(function(){
|
| 29 |
+
function esc(s){return String(s||'').replace(/[&<>"']/g,function(m){return{'&':'&','<':'<','>':'>','"':'"',"'":'''}[m]});}
|
| 30 |
+
|
| 31 |
+
// Override showHashtagSources: spinner + lazy image from /api/article
|
| 32 |
+
window.showHashtagSources=async function(topic){
|
| 33 |
+
var home=document.getElementById('view-home');if(!home)return;
|
| 34 |
+
document.getElementById('hashtag-sources-box')?.remove();
|
| 35 |
+
var box=document.createElement('div');box.id='hashtag-sources-box';box.className='hashtag-sources';
|
| 36 |
+
box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div class="hashtag-loading"><div class="hashtag-spinner"></div>Đang tìm nguồn nhanh...</div>';
|
| 37 |
+
var compose=home.querySelector('.ai-compose');
|
| 38 |
+
if(compose)compose.after(box);else home.prepend(box);
|
| 39 |
+
box.scrollIntoView({behavior:'smooth',block:'start'});
|
| 40 |
+
try{
|
| 41 |
+
var r=await fetch('/api/hashtag/sources?topic='+encodeURIComponent(topic));
|
| 42 |
+
var j=await r.json();var sources=j.sources||[];
|
| 43 |
+
if(!sources.length){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#888;font-size:12px;padding:8px">Không tìm được nguồn cho chủ đề này</div>';return;}
|
| 44 |
+
var h='<h3>🔍 '+esc(topic)+' <span style="font-size:10px;color:#888">('+sources.length+' nguồn)</span></h3>';
|
| 45 |
+
sources.forEach(function(s,i){
|
| 46 |
+
h+='<div class="hashtag-src-item" onclick="if(typeof readArticle===\'function\')readArticle(\''+esc(s.url||'')+'\')">';
|
| 47 |
+
h+='<div class="hashtag-src-img" id="ht-img-'+i+'"></div>';
|
| 48 |
+
h+='<div class="hashtag-src-text"><div class="hashtag-src-title">'+esc(s.title)+'</div><div class="hashtag-src-via">'+esc(s.via||'')+'</div>'+(s.snippet?'<div style="font-size:10px;color:#999;margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">'+esc(s.snippet)+'</div>':'')+'</div>';
|
| 49 |
+
h+='</div>';
|
| 50 |
+
});
|
| 51 |
+
h+='<button class="hashtag-rewrite-btn" onclick="rewriteHashtagTopic(\''+esc(topic)+'\')">🤖 Rewrite AI tổng hợp nguồn & đăng tường</button>';
|
| 52 |
+
box.innerHTML=h;
|
| 53 |
+
// Lazy load images from /api/article (non-blocking)
|
| 54 |
+
sources.forEach(function(s,i){
|
| 55 |
+
if(!s.url)return;
|
| 56 |
+
fetch('/api/article?url='+encodeURIComponent(s.url)).then(function(r){return r.json()}).then(function(d){
|
| 57 |
+
if(d&&(d.og_image||d.img)){
|
| 58 |
+
var el=document.getElementById('ht-img-'+i);
|
| 59 |
+
if(el)el.innerHTML='<img src="'+esc(d.og_image||d.img)+'" onerror="this.style.display=\'none\'" loading="lazy">';
|
| 60 |
+
}
|
| 61 |
+
}).catch(function(){});
|
| 62 |
+
});
|
| 63 |
+
}catch(e){box.innerHTML='<h3>🔍 '+esc(topic)+'</h3><div style="color:#e74c3c;font-size:12px;padding:8px">Lỗi: '+esc(e.message)+'</div>';}
|
| 64 |
+
};
|
| 65 |
+
|
| 66 |
+
// Override topic input button to use same flow (show sources first)
|
| 67 |
+
window.createTopicPost=function(){
|
| 68 |
+
var inp=document.getElementById('ai-topic-input');
|
| 69 |
+
var topic=(inp&&inp.value||'').trim();
|
| 70 |
+
if(!topic){alert('Nhập chủ đề trước');return;}
|
| 71 |
+
showHashtagSources(topic);
|
| 72 |
+
if(inp)inp.value='';
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
// Override the final5 topic button too
|
| 76 |
+
window.createTopicPostFinal5=function(){
|
| 77 |
+
var inp=document.getElementById('ai-topic-input-final5')||document.getElementById('ai-topic-input');
|
| 78 |
+
var topic=(inp&&inp.value||'').trim();
|
| 79 |
+
if(!topic){alert('Nhập chủ đề trước');return;}
|
| 80 |
+
showHashtagSources(topic);
|
| 81 |
+
if(inp)inp.value='';
|
| 82 |
+
};
|
| 83 |
+
})();
|
| 84 |
+
</script>
|
| 85 |
+
'''
|
| 86 |
+
|
| 87 |
+
@app.get('/')
|
| 88 |
+
async def _index_run():
|
| 89 |
+
html=f5.f4.f3.f2.f1._load_index_html()
|
| 90 |
+
body=''
|
| 91 |
+
body+=getattr(rt.old,'PATCH_INJECT','')
|
| 92 |
+
body+=f5.f4.f3.f2.f1.FINAL_INJECT+f5.f4.f3.FINAL3_INJECT+f5.f4.FINAL4_INJECT+f5.FINAL5_INJECT
|
| 93 |
+
body+=getattr(f6,'FINAL6_INJECT','')
|
| 94 |
+
body+=getattr(f6,'FINAL6_FAST_HOME_INJECT','')
|
| 95 |
+
body+=getattr(f6,'FINAL6E_INJECT','')
|
| 96 |
+
body+=PATCH_INJECT
|
| 97 |
+
body+=UNIFIED_INJECT_FIXED
|
| 98 |
+
body+=HIGHLIGHT_FULL_OVERRIDE
|
| 99 |
+
body+=EXTRA_WALL_FIX
|
| 100 |
+
body+=FAST_HASHTAG_JS # Last: overrides showHashtagSources and createTopicPost
|
| 101 |
+
return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)
|