bep40 commited on
Commit
b9b6ba4
·
verified ·
1 Parent(s): d4a0b48

Limit topics to requested VN sources and speed homepage

Browse files
Files changed (1) hide show
  1. ai_runtime_final6.py +182 -0
ai_runtime_final6.py CHANGED
@@ -1141,3 +1141,185 @@ def _candidate_urls(topic):
1141
  seen.add(it['url']);items.append(it)
1142
  if len(items)>=12:break
1143
  return items[:20]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1141
  seen.add(it['url']);items.append(it)
1142
  if len(items)>=12:break
1143
  return items[:20]
1144
+
1145
+
1146
+ # ===== FINAL6G: SOURCE-LIMITED FAST TOPICS AND FAST HOME =====
1147
+ # Limit hot hashtags/topic context to requested sources and make homepage APIs return quickly.
1148
+ _SOURCE_FEEDS = [
1149
+ ('VnExpress','https://vnexpress.net/rss/tin-moi-nhat.rss','vnexpress.net'),
1150
+ ('VnExpress Thời sự','https://vnexpress.net/rss/thoi-su.rss','vnexpress.net'),
1151
+ ('VnExpress Thế giới','https://vnexpress.net/rss/the-gioi.rss','vnexpress.net'),
1152
+ ('VnExpress Kinh doanh','https://vnexpress.net/rss/kinh-doanh.rss','vnexpress.net'),
1153
+ ('VnExpress Công nghệ','https://vnexpress.net/rss/so-hoa.rss','vnexpress.net'),
1154
+ ('VnExpress Thể thao','https://vnexpress.net/rss/the-thao.rss','vnexpress.net'),
1155
+ ('Dân trí','https://dantri.com.vn/rss/home.rss','dantri.com.vn'),
1156
+ ('Dân trí Xã hội','https://dantri.com.vn/rss/xa-hoi.rss','dantri.com.vn'),
1157
+ ('Dân trí Kinh doanh','https://dantri.com.vn/rss/kinh-doanh.rss','dantri.com.vn'),
1158
+ ('Dân trí Thể thao','https://dantri.com.vn/rss/the-thao.rss','dantri.com.vn'),
1159
+ ('Dân trí Công nghệ','https://dantri.com.vn/rss/suc-manh-so.rss','dantri.com.vn'),
1160
+ ('VTV','https://vtv.vn/rss/trang-chu.rss','vtv.vn'),
1161
+ ('VTV Thời sự','https://vtv.vn/rss/thoi-su.rss','vtv.vn'),
1162
+ ('GenK','https://genk.vn/rss/home.rss','genk.vn'),
1163
+ ('GenK AI','https://genk.vn/ai.rss','genk.vn'),
1164
+ ('VnReview','https://vnreview.vn/rss/tin-moi-nhat.rss','vnreview.vn'),
1165
+ ('VnReview Công nghệ','https://vnreview.vn/rss/cong-nghe.rss','vnreview.vn'),
1166
+ ('Vật Vờ Studio','https://vatvostudio.vn/feed/','vatvostudio.vn'),
1167
+ ('Thể thao văn hóa','https://thethaovanhoa.vn/rss/home.rss','thethaovanhoa.vn'),
1168
+ ('Thể thao văn hóa World Cup','https://thethaovanhoa.vn/rss/world-cup-2026.rss','thethaovanhoa.vn'),
1169
+ ]
1170
+ _SOURCE_CACHE={'t':0,'items':[]}
1171
+ _FAST_ROUTE_CACHE={}
1172
+
1173
+ def _feed_items_source_limited(max_per_feed=10):
1174
+ now=time.time()
1175
+ if _SOURCE_CACHE['items'] and now-_SOURCE_CACHE['t']<600:return _SOURCE_CACHE['items']
1176
+ items=[];seen=set()
1177
+ def one(feed):
1178
+ name,url,dom=feed;out=[]
1179
+ try:
1180
+ r=requests.get(url,headers=UA,timeout=3.5);r.encoding='utf-8'
1181
+ soup=BeautifulSoup(r.text,'xml')
1182
+ for it in soup.find_all('item')[:max_per_feed*2]:
1183
+ title=clean(it.find('title').get_text(' ',strip=True) if it.find('title') else '')
1184
+ link=clean(it.find('link').get_text(strip=True) if it.find('link') else '')
1185
+ desc_raw=it.find('description').get_text(' ',strip=True) if it.find('description') else ''
1186
+ ds=BeautifulSoup(desc_raw,'lxml')
1187
+ img='';im=ds.find('img')
1188
+ if im:img=im.get('src','') or im.get('data-src','')
1189
+ desc=clean(ds.get_text(' ',strip=True))[:700]
1190
+ if title and link:
1191
+ out.append({'title':title,'url':link,'link':link,'source':name,'via':name,'domain':dom,'snippet':desc,'img':img})
1192
+ if len(out)>=max_per_feed:break
1193
+ except Exception:pass
1194
+ return out
1195
+ try:
1196
+ from concurrent.futures import ThreadPoolExecutor, as_completed
1197
+ with ThreadPoolExecutor(max_workers=8) as ex:
1198
+ futs=[ex.submit(one,f) for f in _SOURCE_FEEDS]
1199
+ for f in as_completed(futs,timeout=5.5):
1200
+ try:
1201
+ for it in f.result() or []:
1202
+ if it['url'] not in seen:
1203
+ seen.add(it['url']);items.append(it)
1204
+ except Exception:pass
1205
+ except Exception:
1206
+ for f in _SOURCE_FEEDS[:8]:
1207
+ for it in one(f):
1208
+ if it['url'] not in seen:
1209
+ seen.add(it['url']);items.append(it)
1210
+ _SOURCE_CACHE.update({'t':now,'items':items})
1211
+ return items
1212
+
1213
+ def _score_topic_source(topic,it):
1214
+ toks=[w.lower() for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+',topic or '') if len(w)>1 and w.lower() not in STOP_WORDS]
1215
+ hay=(it.get('title','')+' '+it.get('snippet','')+' '+it.get('source','')).lower()
1216
+ if not toks:return 0
1217
+ score=sum((3 if len(t)>3 else 1) for t in toks if t in hay)
1218
+ if topic.lower().strip() and topic.lower().strip() in hay:score+=12
1219
+ return score
1220
+
1221
+ def _hot_topics():
1222
+ now=time.time()
1223
+ if _HOT_CACHE['d'] and now-_HOT_CACHE['t']<600:return _HOT_CACHE['d']
1224
+ freq={};display={}
1225
+ for it in _feed_items_source_limited(8)[:180]:
1226
+ title=re.sub(r'\s+-\s+.*$','',it.get('title',''))
1227
+ kws=[]
1228
+ for m in re.findall(r'([A-ZĐÀ-Ỹ][A-Za-zÀ-ỹ0-9]+(?:\s+[A-ZĐÀ-ỸA-Za-zÀ-ỹ0-9][A-Za-zÀ-ỹ0-9]+){1,4})',title):
1229
+ if len(m)>=6:kws.append(m)
1230
+ kws += _keywords_from_title(title)
1231
+ for kw in kws[:4]:
1232
+ words=[w for w in clean(kw).split() if w.lower() not in STOP_WORDS]
1233
+ if len(words)<2:continue
1234
+ kw=' '.join(words[:5])
1235
+ if 6<=len(kw)<=55:
1236
+ key=kw.lower();freq[key]=freq.get(key,0)+1;display[key]=kw
1237
+ topics=[]
1238
+ for key,_ in sorted(freq.items(),key=lambda x:x[1],reverse=True):
1239
+ kw=display[key];topics.append({'label':'#'+re.sub(r'\s+','',kw.title()),'topic':kw})
1240
+ if len(topics)>=24:break
1241
+ for kw in ['Giá vàng trong nước','AI tại Việt Nam','Bóng đá Việt Nam','Kinh tế Việt Nam','Công nghệ AI','Vật Vờ Studio','World Cup 2026','Sức khỏe cộng đồng']:
1242
+ if not any(t['topic'].lower()==kw.lower() for t in topics):topics.append({'label':'#'+re.sub(r'\s+','',kw.title()),'topic':kw})
1243
+ _HOT_CACHE.update({'t':now,'d':topics[:24]})
1244
+ return _HOT_CACHE['d']
1245
+
1246
+ def _fast_context(topic):
1247
+ now=time.time();key='source_limited_ctx:'+topic.lower().strip()
1248
+ if key in _FAST_TOPIC_CACHE and now-_FAST_TOPIC_CACHE[key]['t']<600:return _FAST_TOPIC_CACHE[key]['d']
1249
+ pool=_feed_items_source_limited(12)
1250
+ scored=[]
1251
+ for it in pool:
1252
+ sc=_score_topic_source(topic,it)
1253
+ if sc>0:scored.append((sc,it))
1254
+ if not scored:
1255
+ # Try broader matching by first token only before giving up.
1256
+ first=(_fast_topic_tokens(topic) or [''])[0]
1257
+ if first:
1258
+ for it in pool:
1259
+ if first in (it.get('title','')+' '+it.get('snippet','')).lower():scored.append((1,it))
1260
+ picked=[it for sc,it in sorted(scored,key=lambda x:(x[0],len(x[1].get('snippet',''))),reverse=True)[:8]]
1261
+ if not picked:picked=pool[:6]
1262
+ blocks=[];src=[]
1263
+ for it in picked:
1264
+ content=it.get('snippet','') or it.get('title','')
1265
+ blocks.append(f"NGUỒN: {it.get('source','')}\nTIÊU ĐỀ: {it.get('title','')}\nTÓM TẮT RSS:\n{content}")
1266
+ src.append({'title':it.get('title',''),'url':it.get('url',''),'via':it.get('source',''),'snippet':content})
1267
+ data={'context':'\n\n---\n\n'.join(blocks),'sources':src,'count':len(blocks)}
1268
+ _FAST_TOPIC_CACHE[key]={'t':now,'d':data}
1269
+ return data
1270
+
1271
+ # Override slow search functions to never crawl open web during topic generation.
1272
+ def _web_research_context(topic):
1273
+ return _fast_context(topic)
1274
+
1275
+ def _candidate_urls(topic):
1276
+ return _fast_context(topic).get('sources',[])
1277
+
1278
+ # Fast homepage endpoints from requested source RSS; no slow HTML scrapers.
1279
+ def _fast_homepage_sources():
1280
+ now=time.time();key='home_sources'
1281
+ if key in _FAST_ROUTE_CACHE and now-_FAST_ROUTE_CACHE[key]['t']<600:return _FAST_ROUTE_CACHE[key]['d']
1282
+ groups=[];seen=set()
1283
+ group_map=[('Tin mới','https://vnexpress.net/rss/tin-moi-nhat.rss','vne'),('Thời Sự','https://vnexpress.net/rss/thoi-su.rss','vne'),('Kinh Doanh','https://vnexpress.net/rss/kinh-doanh.rss','vne'),('Công Nghệ','https://vnexpress.net/rss/so-hoa.rss','vne'),('Dân Trí','https://dantri.com.vn/rss/home.rss','dantri'),('GenK','https://genk.vn/rss/home.rss','genk'),('VnReview','https://vnreview.vn/rss/tin-moi-nhat.rss','vnreview')]
1284
+ for g,u,s in group_map:
1285
+ for it in _rss_articles_fast(u,g,s,6) if '_rss_articles_fast' in globals() else []:
1286
+ if it['link'] not in seen:
1287
+ seen.add(it['link']);groups.append(it)
1288
+ _FAST_ROUTE_CACHE[key]={'t':now,'d':groups}
1289
+ return groups
1290
+
1291
+ for _p in ['/api/homepage','/api/dantri_hot','/api/vne_video','/api/highlights','/api/hot_topics','/api/topic_sources']:
1292
+ app.router.routes=[r for r in app.router.routes if not (getattr(r,'path',None)==_p and 'GET' in getattr(r,'methods',set()))]
1293
+ @app.get('/api/homepage')
1294
+ def api_homepage_source_fast():return JSONResponse(_fast_homepage_sources())
1295
+ @app.get('/api/dantri_hot')
1296
+ def api_dantri_hot_source_fast():
1297
+ data=[{**it,'source':'dantri','link':it.get('url') or it.get('link')} for it in _feed_items_source_limited(8) if it.get('domain')=='dantri.com.vn'][:12]
1298
+ return JSONResponse(data)
1299
+ @app.get('/api/vne_video')
1300
+ def api_vne_video_source_fast():
1301
+ return JSONResponse([]) # do not block homepage if VnEgo is slow
1302
+ @app.get('/api/highlights')
1303
+ def api_highlights_source_fast():return JSONResponse([])
1304
+ @app.get('/api/hot_topics')
1305
+ def api_hot_topics_source_fast():return JSONResponse({'topics':_hot_topics(),'sources':'vn_only'})
1306
+ @app.get('/api/topic_sources')
1307
+ def api_topic_sources_source_fast(topic:str=Query(...)):
1308
+ data=_fast_context(clean(topic));return JSONResponse({'count':data.get('count',0),'sources':data.get('sources',[]),'has_context':bool(data.get('context')),'mode':'source_limited_rss'})
1309
+
1310
+ # Override root: include all existing UI but add a script that prevents forced shorts refresh on initial load.
1311
+ ROOT_FAST_INJECT="""
1312
+ <script>
1313
+ (function(){
1314
+ const oldFetch=window.fetch;window.__allowShortRefresh=false;
1315
+ window.fetch=function(url,opts){try{let u=String(url||'');if(u.includes('/api/shorts?refresh=1')&&!window.__allowShortRefresh)url='/api/shorts';}catch(e){}return oldFetch.call(this,url,opts)};
1316
+ setTimeout(()=>{window.__allowShortRefresh=true;},8000);
1317
+ })();
1318
+ </script>
1319
+ """
1320
+ app.router.routes=[r for r in app.router.routes if not (getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()))]
1321
+ @app.get('/')
1322
+ async def index_final_fast_sources():
1323
+ html=f5.f4.f3.f2.f1._load_index_html()
1324
+ body=getattr(rt.old,'PATCH_INJECT','')+f5.f4.f3.f2.f1.FINAL_INJECT+f5.f4.f3.FINAL3_INJECT+f5.f4.FINAL4_INJECT+f5.FINAL5_INJECT+FINAL6_INJECT+globals().get('FINAL6_FAST_HOME_INJECT','')+globals().get('FINAL6E_INJECT','')+globals().get('FINAL6F_INJECT','')+ROOT_FAST_INJECT
1325
+ return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)