bep40 commited on
Commit
53a9533
·
verified ·
1 Parent(s): 065c747

Clean remaining topic instruction and add rich source cards with in-app viewing/rewrite

Browse files
Files changed (1) hide show
  1. ai_runtime_final11.py +149 -0
ai_runtime_final11.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Final11: remove remaining leaked sentence, rich source cards, in-app source reading + rewrite."""
2
+ import re, requests
3
+ from urllib.parse import quote
4
+ import ai_runtime_final10 as f10
5
+ from ai_runtime_final10 import app, base, rt, HTMLResponse, JSONResponse, Request, Query
6
+
7
+ EXACT_LEAKS = [
8
+ r'Bài cần có tiêu đề,?\s*sapo ngắn,?\s*các đoạn phân tích/bối cảnh/tác động,?\s*và mục Nguồn tham khảo ngắn\.?',
9
+ r'Bài cần có tiêu đề,?\s*sapo.*?Nguồn tham khảo ngắn\.?' ,
10
+ ]
11
+
12
+ def clean(s):
13
+ import html as html_lib
14
+ return re.sub(r"\s+", " ", html_lib.unescape(s or "")).strip()
15
+
16
+ def _clean_more(text):
17
+ text=f10._clean_topic_text(text or '')
18
+ for p in EXACT_LEAKS:
19
+ text=re.sub(p,'',text,flags=re.I|re.S)
20
+ lines=[]
21
+ for ln in text.splitlines():
22
+ nb=re.sub(r'^[\s•\-\*\d\.\)]+','',ln.strip()).strip()
23
+ if any(re.search(p,nb,flags=re.I|re.S) for p in EXACT_LEAKS):continue
24
+ lines.append(ln.strip())
25
+ out='\n'.join(lines)
26
+ out=re.sub(r'\n{3,}','\n\n',out).strip()
27
+ if 'Nguồn tham khảo:' not in out:
28
+ out+='\n\nNguồn tham khảo: Qwen2.5-VL / kiến thức tổng hợp.'
29
+ return out
30
+
31
+ def _source_meta(url, fallback=''):
32
+ meta={'img':'','desc':fallback or ''}
33
+ if not url:return meta
34
+ try:
35
+ from bs4 import BeautifulSoup
36
+ r=requests.get(url,headers=getattr(base,'HEADERS',{}),timeout=8);r.encoding='utf-8'
37
+ soup=BeautifulSoup(r.text,'lxml')
38
+ og=soup.find('meta',property='og:image') or soup.find('meta',attrs={'name':'twitter:image'})
39
+ if og:meta['img']=og.get('content','') or ''
40
+ desc=soup.find('meta',property='og:description') or soup.find('meta',attrs={'name':'description'})
41
+ if desc:meta['desc']=clean(desc.get('content',''))[:320]
42
+ except Exception:pass
43
+ if not meta['desc']:meta['desc']=clean(fallback)[:320]
44
+ return meta
45
+
46
+ def _enrich_details(details):
47
+ out=[]
48
+ for d in (details or [])[:10]:
49
+ nd=dict(d)
50
+ meta=_source_meta(nd.get('url',''),nd.get('content',''))
51
+ nd['img']=nd.get('img') or meta.get('img','')
52
+ nd['desc']=nd.get('desc') or meta.get('desc','') or clean(nd.get('content',''))[:260]
53
+ out.append(nd)
54
+ return out
55
+
56
+ # Remove routes to override.
57
+ _PATCH={('/api/topic_post','POST'),('/api/topic_rewrite','POST'),('/api/source_article','GET'),('/','GET')}
58
+ app.router.routes=[r for r in app.router.routes if not any(getattr(r,'path',None)==p and m in getattr(r,'methods',set()) for p,m in _PATCH)]
59
+
60
+ @app.post('/api/topic_post')
61
+ async def topic_post_links_rich(request:Request):
62
+ body=await request.json();topic=clean(body.get('topic',''))
63
+ if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
64
+ try:img=f10.f9.f6._topic_image(topic)
65
+ except Exception:img=base.pollinations_image_url(topic)
66
+ context,sources,details=f10._topic_sources(topic)
67
+ details=_enrich_details(details)
68
+ source_brief='\n\n'.join([f"[{i+1}] {d.get('title','')} ({d.get('via','')})\n{d.get('content','')[:1400]}" for i,d in enumerate(details[:8])]) or 'Không có nguồn web đủ rõ; dùng kiến thức tổng hợp thận trọng.'
69
+ prompt=f"""Viết một bài báo tiếng Việt hoàn chỉnh về chủ đề: {topic}
70
+
71
+ Dữ liệu tham khảo:
72
+ {source_brief[:16000]}
73
+
74
+ Chỉ viết phần bài báo cuối cùng cho độc giả. Không nhắc lại yêu cầu. Không liệt kê chỉ dẫn. Không mở đầu bằng 'Dưới đây là'.
75
+ Bài gồm tiêu đề, sapo, phân tích/bối cảnh/tác động và nguồn tham khảo."""
76
+ text=await base.qwen_generate(prompt,image_url=img,max_tokens=1700)
77
+ if not text:text=f"{topic}: những điểm đáng chú ý\n\n{topic} là chủ đề cần được nhìn qua bối cảnh, tác động và các thông tin liên quan. Nội dung dưới đây tổng hợp các ý chính để người đọc nắm vấn đề nhanh hơn.\n\nNguồn tham khảo: Qwen2.5-VL / kiến thức tổng hợp."
78
+ text=_clean_more(text)
79
+ if details:
80
+ vias=', '.join([d.get('via') or '' for d in details[:5] if d.get('via')])
81
+ text=re.sub(r'Nguồn tham khảo:.*$', 'Nguồn tham khảo: '+(vias or 'Qwen2.5-VL / kiến thức tổng hợp.'), text, flags=re.I|re.S)
82
+ post=base.make_post(topic,text,img,'','topic_qwen_links',sources=[s for s in sources if s.get('url')])
83
+ post['images']=[img]
84
+ post['source_details']=details
85
+ posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
86
+ return JSONResponse({'post':post})
87
+
88
+ @app.post('/api/topic_rewrite')
89
+ async def topic_rewrite_clean(request:Request):
90
+ body=await request.json();pid=str(body.get('id',''));tone=clean(body.get('tone','mạch lạc, sắc gọn'))
91
+ posts=base._load_ai_wall();p=next((x for x in posts if str(x.get('id'))==pid),None)
92
+ if not p:return JSONResponse({'error':'post not found'},status_code=404)
93
+ details=_enrich_details(p.get('source_details') or [])
94
+ src='\n\n'.join([f"{i+1}. {d.get('title','')} ({d.get('via','')})\n{d.get('content','')[:1200]}" for i,d in enumerate(details[:8])])
95
+ prompt=f"""Viết lại bài chủ đề sau cho hay hơn, rõ hơn, bỏ câu thừa.
96
+
97
+ Phong cách: {tone}
98
+
99
+ Bài hiện tại:
100
+ {(p.get('text') or '')[:12000]}
101
+
102
+ Nguồn tham khảo:
103
+ {src[:12000]}
104
+
105
+ Chỉ trả về bài viết cuối cùng."""
106
+ text=await base.qwen_generate(prompt,image_url=(p.get('img') or None),max_tokens=1700)
107
+ if not text:text=p.get('text','')
108
+ p['text']=_clean_more(text)
109
+ p['source_details']=details
110
+ base._save_ai_wall(posts)
111
+ return JSONResponse({'post':p})
112
+
113
+ @app.get('/api/source_article')
114
+ def source_article(url:str=Query(...)):
115
+ if not url.startswith('http'):return JSONResponse({'error':'missing url'},status_code=400)
116
+ try:
117
+ data=base.scrape_any_url(url)
118
+ except Exception as e:
119
+ return JSONResponse({'error':'Không đọc được nguồn: '+str(e)[:160]},status_code=422)
120
+ text=data.get('text','') or data.get('summary','') or ''
121
+ body=[]
122
+ for para in [x.strip() for x in re.split(r'\n+',text) if len(x.strip())>30][:80]:
123
+ body.append({'type':'p','text':para})
124
+ return JSONResponse({'title':data.get('title') or url,'summary':data.get('summary',''),'og_image':data.get('image',''),'body':body,'url':url,'source':'generic'})
125
+
126
+ FINAL11_INJECT=r'''
127
+ <style>
128
+ .source-card-grid{display:grid;grid-template-columns:1fr;gap:8px;margin-top:12px}.source-card{display:flex;gap:9px;background:#181818;border:1px solid #2b2b2b;border-radius:10px;padding:8px;cursor:pointer}.source-card img{width:110px;aspect-ratio:16/9;object-fit:cover;border-radius:7px;background:#222}.source-card-title{font-size:12px;font-weight:800;color:#eee;line-height:1.35}.source-card-desc{font-size:11px;color:#aaa;line-height:1.35;margin-top:3px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.source-card-via{font-size:10px;color:#5cb87a;margin-top:3px}.source-rewrite-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px}.source-rewrite-actions button,.source-rewrite-actions select{background:#222;border:1px solid #444;color:#eee;border-radius:10px;padding:8px}.source-rewrite-actions button{background:#2d8659;border:0;color:#fff}
129
+ </style>
130
+ <script>
131
+ (function(){
132
+ function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
133
+ let lastTopicPost11=null;
134
+ function sourceCardsHtml(p){let arr=p.source_details||p.sources||[];if(!arr.length)return '';let h='<div class="source-links-box"><h3>🔗 Nguồn tham khảo</h3><div class="source-card-grid">';arr.slice(0,10).forEach((s,i)=>{let url=s.url||'';h+=`<div class="source-card" onclick="${url?`readSourceOnVnews('${esc(url).replace(/'/g,"\\'")}')`:'void(0)'}">${s.img?`<img src="${esc(s.img)}">`:'<img>'}<div><div class="source-card-title">${i+1}. ${esc(s.title||url||'Nguồn')}</div><div class="source-card-desc">${esc(s.desc||s.content||'')}</div><div class="source-card-via">${esc(s.via||'')}</div></div></div>`});h+='</div></div>';return h;}
135
+ function rewriteTopicBox(p){return `<div class="source-rewrite-actions"><select id="topic-rewrite-tone"><option value="mạch lạc, sắc gọn, báo điện tử">Mạch lạc</option><option value="phân tích sâu, chuyên gia">Phân tích sâu</option><option value="dễ hiểu, phổ thông">Dễ hiểu</option><option value="sôi nổi, thu hút">Thu hút</option></select><button onclick="rewriteTopicPost11('${p.id}')">✍️ Rewrite bài chủ đề</button></div>`}
136
+ function renderTopic11(p){lastTopicPost11=p;showView('view-article');let imgs=(p.images||[]).filter(Boolean);let gal=imgs.length?'<div class="ai-wall-gallery">'+imgs.slice(0,12).map(u=>`<img src="${esc(u)}" loading="lazy">`).join('')+'</div>':(p.img?`<img class="article-img" src="${esc(p.img)}">`:'');document.getElementById('view-article').innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI Chủ đề</span><h1 class="article-title">${esc(p.title)}</h1>${gal}<p class="article-p" style="white-space:pre-wrap">${esc(p.text)}</p>${sourceCardsHtml(p)}${rewriteTopicBox(p)}</div>`;window.scrollTo(0,0)}
137
+ window.rewriteTopicPost11=window.rewriteTopicPost=async function(id){let tone=document.getElementById('topic-rewrite-tone')?.value||'mạch lạc';let btn=event?.target;if(btn){btn.disabled=true;btn.textContent='Đang rewrite...'}try{let r=await fetch('/api/topic_rewrite',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,tone})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi rewrite');renderTopic11(j.post);alert('Đã rewrite bài chủ đề.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✍️ Rewrite bài chủ đề'}}}
138
+ window.readSourceOnVnews=async function(url){showView('view-article');document.getElementById('view-article').innerHTML='<div class="loading">Đang tải nguồn...</div>';try{let d=await fetch('/api/source_article?url='+encodeURIComponent(url)).then(r=>r.json());if(d.error)throw new Error(d.error);let h=`<button class="back-btn" onclick="history.back();switchCat('home')">← Quay lại</button><div class="article-view"><h1 class="article-title">${esc(d.title)}</h1>${d.og_image?`<img class="article-img" src="${esc(d.og_image)}">`:''}${d.summary?`<div class="article-summary">${esc(d.summary)}</div>`:''}`;(d.body||[]).forEach(b=>{if(b.type==='p')h+=`<p class="article-p">${esc(b.text)}</p>`});h+=`<div class="article-actions"><button onclick="rewriteSourceOnWall('${esc(url).replace(/'/g,"\\'")}')">🤖 Rewrite nguồn này lên Tường AI</button><button onclick="window.open('${esc(url)}','_blank')">🔗 Mở gốc</button></div></div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0)}catch(e){document.getElementById('view-article').innerHTML='<button class="back-btn" onclick="switchCat(\'home\')">← Quay lại</button><div class="loading">'+esc(e.message)+'</div>'}}
139
+ window.rewriteSourceOnWall=async function(url){try{let r=await fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');alert('Đã rewrite nguồn lên Tường AI');}catch(e){alert(e.message)}}
140
+ window.createTopicPostFinal5=async function(){let inp=document.getElementById('ai-topic-input-final5');let topic=(inp&&inp.value||'').trim();if(!topic)return alert('Nhập chủ đề trước');let btn=document.getElementById('ai-topic-btn-final5');if(btn){btn.disabled=true;btn.textContent='Đang tạo bài...'}try{let r=await fetch('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})});let j=await r.json();if(!r.ok||j.error)throw new Error(j.error||'Lỗi');if(inp)inp.value='';renderTopic11(j.post);alert('Đã tạo bài chủ đề kèm ảnh, mô tả và link nguồn.');}catch(e){alert(e.message)}finally{if(btn){btn.disabled=false;btn.textContent='✨ Tạo bài kiến thức bằng Qwen'}}}
141
+ })();
142
+ </script>
143
+ '''
144
+
145
+ @app.get('/')
146
+ async def index_final11():
147
+ html=f10.f9.f6.f5.f4.f3.f2.f1._load_index_html()
148
+ body=getattr(rt.old,'PATCH_INJECT','')+f10.f9.f6.f5.f4.f3.f2.f1.FINAL_INJECT+f10.f9.f6.f5.f4.f3.FINAL3_INJECT+f10.f9.f6.f5.f4.FINAL4_INJECT+f10.f9.f6.f5.FINAL5_INJECT+f10.f9.f6.FINAL6_INJECT+f10.FINAL10_INJECT+FINAL11_INJECT
149
+ return HTMLResponse(html.replace('</body>',body+'\n</body>') if '</body>' in html else html+body)