Spaces:
Running
Running
Fix: add /api/topic/rewrite handler and natural AI ask prompt
Browse files- ai_runtime_patch_fast.py +27 -29
ai_runtime_patch_fast.py
CHANGED
|
@@ -51,7 +51,9 @@ def _bg_refresh():
|
|
| 51 |
|
| 52 |
@app.on_event("startup")
|
| 53 |
async def startup_prefetch():threading.Thread(target=_bg_refresh,daemon=True).start()
|
| 54 |
-
|
|
|
|
|
|
|
| 55 |
|
| 56 |
# ===== OVERRIDE ENDPOINTS =====
|
| 57 |
app.router.routes=[r for r in app.router.routes if not (
|
|
@@ -104,7 +106,7 @@ async def post_short_comment(request:Request):
|
|
| 104 |
db=_load_json(SHORT_COMMENTS_FILE,{});comments=db.get(vid,[]);comments.insert(0,{'text':text[:300],'ts':int(time.time())})
|
| 105 |
db[vid]=comments[:100];_save_json(SHORT_COMMENTS_FILE,db);return JSONResponse({'comments':db[vid]})
|
| 106 |
|
| 107 |
-
# ===== ARTICLE ASK AI — natural
|
| 108 |
@app.post('/api/article/ask')
|
| 109 |
async def article_ask(request:Request):
|
| 110 |
body=await request.json();url=clean(body.get('url',''));question=clean(body.get('question',''));context=clean(body.get('context',''))
|
|
@@ -117,49 +119,49 @@ async def article_ask(request:Request):
|
|
| 117 |
except Exception:pass
|
| 118 |
if not raw and context:raw=context[:12000]
|
| 119 |
if not raw:raw=question
|
| 120 |
-
prompt=f"""Bạn là trợ lý thông minh của VNEWS. Người dùng đang đọc một bài viết/video và muốn hỏi bạn.
|
| 121 |
|
| 122 |
-
Nội dung bài viết/video:
|
|
|
|
| 123 |
Tiêu đề: {title}
|
| 124 |
-
|
|
|
|
| 125 |
|
| 126 |
-
Câu hỏi
|
| 127 |
|
| 128 |
-
Hãy trả lời bằng tiếng Việt
|
| 129 |
-
-
|
| 130 |
-
- Giải thích
|
| 131 |
-
- Nếu câu hỏi liên quan đến bài
|
| 132 |
-
- Nếu câu hỏi
|
| 133 |
-
-
|
| 134 |
"""
|
| 135 |
ans=await f5.base.qwen_generate(prompt,max_tokens=1200)
|
| 136 |
if not ans:ans='AI chưa trả lời được lúc này. Bạn thử hỏi lại nhé.'
|
| 137 |
return JSONResponse({'answer':ans,'title':title})
|
| 138 |
|
| 139 |
-
# ===== TOPIC REWRITE
|
| 140 |
@app.post('/api/topic/rewrite')
|
| 141 |
async def topic_rewrite(request:Request):
|
| 142 |
-
"""Rewrite/summarize a topic post's content and source articles into a new wall post."""
|
| 143 |
body=await request.json();post_id=str(body.get('post_id','')).strip()
|
| 144 |
if not post_id:return JSONResponse({'error':'missing post_id'},status_code=400)
|
| 145 |
posts=f5.base._load_ai_wall();p=next((x for x in posts if str(x.get('id'))==post_id),None)
|
| 146 |
-
if not p:return JSONResponse({'error':'
|
| 147 |
-
|
| 148 |
-
all_content=p.get('text') or ''
|
| 149 |
for s in (p.get('source_details') or []):
|
| 150 |
-
all_content+='\n\n'+s.get('content','')
|
| 151 |
title=p.get('title') or 'Bài viết AI'
|
| 152 |
-
prompt=f"""Bạn là biên tập viên VNEWS.
|
| 153 |
|
| 154 |
Tiêu đề gốc: {title}
|
| 155 |
-
Nội dung gốc và nguồn:
|
| 156 |
{all_content[:16000]}
|
| 157 |
|
| 158 |
Yêu cầu:
|
| 159 |
-
- Viết lại
|
| 160 |
-
- Giữ đúng sự thật, không bịa.
|
| 161 |
- 1 tiêu đề mới hấp dẫn.
|
| 162 |
-
-
|
|
|
|
| 163 |
- Cuối bài ghi nguồn tham khảo.
|
| 164 |
"""
|
| 165 |
text=None
|
|
@@ -169,7 +171,7 @@ Yêu cầu:
|
|
| 169 |
text=f"Tóm tắt: {title}\n\n{all_content[:1500]}\n\nNguồn tham khảo: VNEWS AI"
|
| 170 |
new_post=f5.base.make_post('Rewrite: '+title,text,p.get('img',''),'','rewrite_topic',sources=p.get('sources',[]))
|
| 171 |
new_post['images']=p.get('images',[])
|
| 172 |
-
|
| 173 |
return JSONResponse({'post':new_post})
|
| 174 |
|
| 175 |
# ===== TOPIC POST =====
|
|
@@ -235,7 +237,6 @@ h+=`</div>`;
|
|
| 235 |
document.getElementById('view-article').innerHTML=h;
|
| 236 |
let art=document.querySelector('.article-view');if(art&&p.source_details)renderSourceDetails(p,art);
|
| 237 |
window.scrollTo(0,0);}
|
| 238 |
-
|
| 239 |
window.readAIWallPost=readAIWallPost;window.aiReadWallPatched=window.aiReadWall=window.readWallPost=function(i){readAIWallPost(i)};
|
| 240 |
|
| 241 |
window.rewriteTopicPost=async function(postId){
|
|
@@ -247,12 +248,10 @@ window.askAIWall=async function(i){
|
|
| 247 |
let q=document.getElementById('article-ai-q')?.value.trim();if(!q)return alert('Nhập câu hỏi');
|
| 248 |
let ans=document.getElementById('article-ai-ans');ans.textContent='Đang hỏi AI...';
|
| 249 |
let wall=(await fetch('/api/ai_wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];
|
| 250 |
-
let p=wall[i]||{};let context=(p.text||'');
|
| 251 |
-
for(let s of (p.source_details||[]))context+='\n'+s.content;
|
| 252 |
let url=(p.source_details||[]).find(s=>s.url)?.url||(p.sources||[]).find(s=>s.url)?.url||'';
|
| 253 |
try{let r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url,question:q,context:context.slice(0,12000)})});let j=await r.json();ans.textContent=j.answer||'Không có trả lời';}catch(e){ans.textContent='Lỗi: '+e.message}};
|
| 254 |
|
| 255 |
-
// === Short comments ===
|
| 256 |
window.openShortComments=async function(id){
|
| 257 |
let panel=document.getElementById('short-cmt-panel');
|
| 258 |
let j=await fetch('/api/short/comments?id='+encodeURIComponent(id)).then(r=>r.json()).catch(()=>({comments:[]}));
|
|
@@ -267,7 +266,6 @@ document.getElementById('cmt-text').value='';}
|
|
| 267 |
|
| 268 |
function patchShortButtons(){document.querySelectorAll('.tiktok-slide').forEach(sl=>{if(sl.dataset.cmtDone)return;sl.dataset.cmtDone='1';let id=sl.dataset.id||'';if(!id)return;let right=sl.querySelector('.tiktok-right,.short-action-panel');if(!right)return;if(right.querySelector('[data-cmt]'))return;let btn=document.createElement('button');btn.className='short-action-btn';btn.setAttribute('data-cmt','1');btn.innerHTML='<div class="ico" style="width:42px;height:42px;border-radius:50%;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;font-size:20px">💬</div><span>BL</span>';btn.onclick=function(e){e.stopPropagation();openShortComments(id);};right.appendChild(btn);});}
|
| 269 |
|
| 270 |
-
// === Single rewrite + ask AI for regular articles ===
|
| 271 |
function addSingleRewriteBtn(){let art=document.querySelector('#view-article .article-view');if(!art)return;let btns=art.querySelectorAll('[data-rewrite]');if(btns.length>1)for(let i=1;i<btns.length;i++)btns[i].remove();if(btns.length>=1)return;let actions=art.querySelector('.article-actions');if(!actions)return;let btn=document.createElement('button');btn.className='primary';btn.setAttribute('data-rewrite','1');btn.textContent='🤖 Tóm tắt AI đăng tường';btn.onclick=function(){if(typeof rewriteCurrentArticle==='function')rewriteCurrentArticle();else alert('Chức năng rewrite chưa sẵn sàng.')};actions.insertBefore(btn,actions.firstChild);}
|
| 272 |
function addArticleAskBox(){let art=document.querySelector('#view-article .article-view');if(!art||art.querySelector('.article-ai-ask'))return;let box=document.createElement('div');box.className='article-ai-ask';box.innerHTML='<h3 style="font-size:14px;color:#5cb87a;margin-bottom:6px">🤖 Hỏi AI về bài viết</h3><textarea id="article-ai-question" placeholder="Hỏi bất cứ điều gì về nội dung này..."></textarea><button onclick="askArticleAI()">Hỏi AI</button><div id="article-ai-answer" class="article-ai-answer"></div>';art.appendChild(box);}
|
| 273 |
window.askArticleAI=async function(){let q=document.getElementById('article-ai-question')?.value.trim();if(!q)return alert('Nhập câu hỏi');let ans=document.getElementById('article-ai-answer');ans.textContent='Đang hỏi AI...';let url=(window._currentArticle&&window._currentArticle.url)||'';let context=document.querySelector('.article-view')?.innerText?.slice(0,12000)||'';try{let r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url,question:q,context})});let j=await r.json();ans.textContent=j.answer||'Không có trả lời';}catch(e){ans.textContent='Lỗi: '+e.message}}
|
|
|
|
| 51 |
|
| 52 |
@app.on_event("startup")
|
| 53 |
async def startup_prefetch():threading.Thread(target=_bg_refresh,daemon=True).start()
|
| 54 |
+
def _periodic():
|
| 55 |
+
while True:time.sleep(600);_bg_refresh()
|
| 56 |
+
threading.Thread(target=_periodic,daemon=True).start()
|
| 57 |
|
| 58 |
# ===== OVERRIDE ENDPOINTS =====
|
| 59 |
app.router.routes=[r for r in app.router.routes if not (
|
|
|
|
| 106 |
db=_load_json(SHORT_COMMENTS_FILE,{});comments=db.get(vid,[]);comments.insert(0,{'text':text[:300],'ts':int(time.time())})
|
| 107 |
db[vid]=comments[:100];_save_json(SHORT_COMMENTS_FILE,db);return JSONResponse({'comments':db[vid]})
|
| 108 |
|
| 109 |
+
# ===== ARTICLE ASK AI — natural and friendly =====
|
| 110 |
@app.post('/api/article/ask')
|
| 111 |
async def article_ask(request:Request):
|
| 112 |
body=await request.json();url=clean(body.get('url',''));question=clean(body.get('question',''));context=clean(body.get('context',''))
|
|
|
|
| 119 |
except Exception:pass
|
| 120 |
if not raw and context:raw=context[:12000]
|
| 121 |
if not raw:raw=question
|
| 122 |
+
prompt=f"""Bạn là trợ lý thông minh của VNEWS. Người dùng đang đọc một bài viết/xem video và muốn hỏi bạn.
|
| 123 |
|
| 124 |
+
Nội dung bài viết/video mà người dùng đang xem:
|
| 125 |
+
---
|
| 126 |
Tiêu đề: {title}
|
| 127 |
+
{raw[:10000]}
|
| 128 |
+
---
|
| 129 |
|
| 130 |
+
Câu hỏi: {question}
|
| 131 |
|
| 132 |
+
Hãy trả lời bằng tiếng Việt, tự nhiên và thân thiện như đang trò chuyện:
|
| 133 |
+
- Lấy thông tin từ nội dung bài viết/video ở trên làm cơ sở chính.
|
| 134 |
+
- Giải thích dễ hiểu, có ví dụ nếu cần.
|
| 135 |
+
- Nếu câu hỏi liên quan đến bài → trả lời chi tiết từ nội dung bài.
|
| 136 |
+
- Nếu câu hỏi mở rộng hơn → bổ sung kiến thức chung nhưng nói rõ "ngoài bài viết, theo hiểu biết chung thì..."
|
| 137 |
+
- Không trả lời khô khan, máy móc. Hãy viết như một người bạn am hiểu đang giải thích.
|
| 138 |
"""
|
| 139 |
ans=await f5.base.qwen_generate(prompt,max_tokens=1200)
|
| 140 |
if not ans:ans='AI chưa trả lời được lúc này. Bạn thử hỏi lại nhé.'
|
| 141 |
return JSONResponse({'answer':ans,'title':title})
|
| 142 |
|
| 143 |
+
# ===== TOPIC REWRITE — takes post_id, rewrites using all source content =====
|
| 144 |
@app.post('/api/topic/rewrite')
|
| 145 |
async def topic_rewrite(request:Request):
|
|
|
|
| 146 |
body=await request.json();post_id=str(body.get('post_id','')).strip()
|
| 147 |
if not post_id:return JSONResponse({'error':'missing post_id'},status_code=400)
|
| 148 |
posts=f5.base._load_ai_wall();p=next((x for x in posts if str(x.get('id'))==post_id),None)
|
| 149 |
+
if not p:return JSONResponse({'error':'Không tìm thấy bài viết.'},status_code=404)
|
| 150 |
+
all_content=(p.get('text') or '')
|
|
|
|
| 151 |
for s in (p.get('source_details') or []):
|
| 152 |
+
all_content+='\n\n--- Nguồn: '+s.get('title','')+' ---\n'+s.get('content','')
|
| 153 |
title=p.get('title') or 'Bài viết AI'
|
| 154 |
+
prompt=f"""Bạn là biên tập viên VNEWS. Viết lại nội dung bên dưới thành BẢN TÓM TẮT MỚI, hấp dẫn, đăng lên Tường AI.
|
| 155 |
|
| 156 |
Tiêu đề gốc: {title}
|
| 157 |
+
Nội dung gốc và nguồn tham khảo:
|
| 158 |
{all_content[:16000]}
|
| 159 |
|
| 160 |
Yêu cầu:
|
| 161 |
+
- Viết lại tự nhiên, mạch lạc, không sao chép nguyên văn.
|
|
|
|
| 162 |
- 1 tiêu đề mới hấp dẫn.
|
| 163 |
+
- 1 đoạn mở đầu + 3-5 ý chính.
|
| 164 |
+
- Giữ sự thật, không bịa.
|
| 165 |
- Cuối bài ghi nguồn tham khảo.
|
| 166 |
"""
|
| 167 |
text=None
|
|
|
|
| 171 |
text=f"Tóm tắt: {title}\n\n{all_content[:1500]}\n\nNguồn tham khảo: VNEWS AI"
|
| 172 |
new_post=f5.base.make_post('Rewrite: '+title,text,p.get('img',''),'','rewrite_topic',sources=p.get('sources',[]))
|
| 173 |
new_post['images']=p.get('images',[])
|
| 174 |
+
all_posts=f5.base._load_ai_wall();all_posts.insert(0,new_post);f5.base._save_ai_wall(all_posts)
|
| 175 |
return JSONResponse({'post':new_post})
|
| 176 |
|
| 177 |
# ===== TOPIC POST =====
|
|
|
|
| 237 |
document.getElementById('view-article').innerHTML=h;
|
| 238 |
let art=document.querySelector('.article-view');if(art&&p.source_details)renderSourceDetails(p,art);
|
| 239 |
window.scrollTo(0,0);}
|
|
|
|
| 240 |
window.readAIWallPost=readAIWallPost;window.aiReadWallPatched=window.aiReadWall=window.readWallPost=function(i){readAIWallPost(i)};
|
| 241 |
|
| 242 |
window.rewriteTopicPost=async function(postId){
|
|
|
|
| 248 |
let q=document.getElementById('article-ai-q')?.value.trim();if(!q)return alert('Nhập câu hỏi');
|
| 249 |
let ans=document.getElementById('article-ai-ans');ans.textContent='Đang hỏi AI...';
|
| 250 |
let wall=(await fetch('/api/ai_wall').then(r=>r.json()).catch(()=>({posts:[]}))).posts||[];
|
| 251 |
+
let p=wall[i]||{};let context=(p.text||'');for(let s of (p.source_details||[]))context+='\n'+s.content;
|
|
|
|
| 252 |
let url=(p.source_details||[]).find(s=>s.url)?.url||(p.sources||[]).find(s=>s.url)?.url||'';
|
| 253 |
try{let r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url,question:q,context:context.slice(0,12000)})});let j=await r.json();ans.textContent=j.answer||'Không có trả lời';}catch(e){ans.textContent='Lỗi: '+e.message}};
|
| 254 |
|
|
|
|
| 255 |
window.openShortComments=async function(id){
|
| 256 |
let panel=document.getElementById('short-cmt-panel');
|
| 257 |
let j=await fetch('/api/short/comments?id='+encodeURIComponent(id)).then(r=>r.json()).catch(()=>({comments:[]}));
|
|
|
|
| 266 |
|
| 267 |
function patchShortButtons(){document.querySelectorAll('.tiktok-slide').forEach(sl=>{if(sl.dataset.cmtDone)return;sl.dataset.cmtDone='1';let id=sl.dataset.id||'';if(!id)return;let right=sl.querySelector('.tiktok-right,.short-action-panel');if(!right)return;if(right.querySelector('[data-cmt]'))return;let btn=document.createElement('button');btn.className='short-action-btn';btn.setAttribute('data-cmt','1');btn.innerHTML='<div class="ico" style="width:42px;height:42px;border-radius:50%;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;font-size:20px">💬</div><span>BL</span>';btn.onclick=function(e){e.stopPropagation();openShortComments(id);};right.appendChild(btn);});}
|
| 268 |
|
|
|
|
| 269 |
function addSingleRewriteBtn(){let art=document.querySelector('#view-article .article-view');if(!art)return;let btns=art.querySelectorAll('[data-rewrite]');if(btns.length>1)for(let i=1;i<btns.length;i++)btns[i].remove();if(btns.length>=1)return;let actions=art.querySelector('.article-actions');if(!actions)return;let btn=document.createElement('button');btn.className='primary';btn.setAttribute('data-rewrite','1');btn.textContent='🤖 Tóm tắt AI đăng tường';btn.onclick=function(){if(typeof rewriteCurrentArticle==='function')rewriteCurrentArticle();else alert('Chức năng rewrite chưa sẵn sàng.')};actions.insertBefore(btn,actions.firstChild);}
|
| 270 |
function addArticleAskBox(){let art=document.querySelector('#view-article .article-view');if(!art||art.querySelector('.article-ai-ask'))return;let box=document.createElement('div');box.className='article-ai-ask';box.innerHTML='<h3 style="font-size:14px;color:#5cb87a;margin-bottom:6px">🤖 Hỏi AI về bài viết</h3><textarea id="article-ai-question" placeholder="Hỏi bất cứ điều gì về nội dung này..."></textarea><button onclick="askArticleAI()">Hỏi AI</button><div id="article-ai-answer" class="article-ai-answer"></div>';art.appendChild(box);}
|
| 271 |
window.askArticleAI=async function(){let q=document.getElementById('article-ai-question')?.value.trim();if(!q)return alert('Nhập câu hỏi');let ans=document.getElementById('article-ai-answer');ans.textContent='Đang hỏi AI...';let url=(window._currentArticle&&window._currentArticle.url)||'';let context=document.querySelector('.article-view')?.innerText?.slice(0,12000)||'';try{let r=await fetch('/api/article/ask',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url,question:q,context})});let j=await r.json();ans.textContent=j.answer||'Không có trả lời';}catch(e){ans.textContent='Lỗi: '+e.message}}
|