Fix: HOT feed chỉ hiện hashtag + rewrite timeout 15s

#1
Files changed (3) hide show
  1. app_v2_entry.py +3 -0
  2. patch_qwen_timeout.py +31 -0
  3. static/app_v2.js +13 -22
app_v2_entry.py CHANGED
@@ -1,5 +1,8 @@
1
  """VNEWS v2 Entry Point - with friendly highlights"""
2
  import sys, os, traceback
 
 
 
3
  from main import app, HEADERS, BONGDA_HEADERS, fetch_bongda_api, HL_LEAGUES
4
  from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
5
  from fastapi.staticfiles import StaticFiles
 
1
  """VNEWS v2 Entry Point - with friendly highlights"""
2
  import sys, os, traceback
3
+ # Áp dụng patch sửa timeout rewrite 75s→15s
4
+ try: import patch_qwen_timeout
5
+ except: pass
6
  from main import app, HEADERS, BONGDA_HEADERS, fetch_bongda_api, HL_LEAGUES
7
  from fastapi.responses import HTMLResponse, JSONResponse, FileResponse
8
  from fastapi.staticfiles import StaticFiles
patch_qwen_timeout.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Patch applied at Space startup: reduce Qwen timeout from 75s to 15s
2
+ # This file is sourced by the entry point
3
+
4
+ import re, os
5
+
6
+ PATCH_DONE_FLAG = "/app/.qwen_timeout_patched"
7
+
8
+ def apply_patch():
9
+ if os.path.exists(PATCH_DONE_FLAG):
10
+ return
11
+ try:
12
+ for fpath in ["/app/main.py", os.path.join(os.path.dirname(__file__), "main.py")]:
13
+ if os.path.exists(fpath):
14
+ with open(fpath, "r") as f:
15
+ content = f.read()
16
+ # Change timeout=75 to timeout=15
17
+ orig = 'r=requests.post("https://router.huggingface.co/v1/chat/completions",headers=headers,json=payload,timeout=75)'
18
+ new = 'r=requests.post("https://router.huggingface.co/v1/chat/completions",headers=headers,json=payload,timeout=15)'
19
+ if orig in content:
20
+ content = content.replace(orig, new)
21
+ with open(fpath, "w") as f:
22
+ f.write(content)
23
+ print("[patch] Applied Qwen timeout patch (75s → 15s) to", fpath)
24
+ break
25
+ # Create flag file
26
+ with open(PATCH_DONE_FLAG, "w") as f:
27
+ f.write("ok")
28
+ except Exception as e:
29
+ print("[patch] Failed to apply patch:", e)
30
+
31
+ apply_patch()
static/app_v2.js CHANGED
@@ -1,8 +1,7 @@
1
  // === VNEWS Frontend v2 - Full Functions ===
2
- // Added: World Cup highlight tab + Friendly highlights on homepage + 🔥 HOT feed
3
- // Updated: HOT feed chỉ hiển thị hashtag chips, click vào hashtag thì hiện tin
4
  // Fix: safeJson parsing to handle non-JSON error responses gracefully
5
- // Fix: timeout cho rewrite calls, không treo vô hạn
6
 
7
  // === SAFE JSON PARSE ===
8
  async function safeJson(resp){
@@ -10,7 +9,6 @@ async function safeJson(resp){
10
  const text=await resp.text();
11
  try{return JSON.parse(text);}
12
  catch(e){
13
- // Server returned non-JSON (e.g., HTML error page)
14
  console.warn('Non-JSON response:', text.slice(0,200));
15
  return {error: 'Server error', _status: resp.status, _raw: text.slice(0,200)};
16
  }
@@ -73,34 +71,29 @@ async function loadHotTopics(){
73
  const topicText=t.topic||t.label.replace(/^#/,'');
74
  return`<button class="hot-chip" onclick="searchTopic('${topicText.replace(/'/g,"\\'")}')">${esc(t.label)}</button>`;
75
  }).join('');
76
- if(_allHotTopics&&_allHotTopics[0]){
77
- const firstTopic=_allHotTopics[0].topic||_allHotTopics[0].label.replace(/^#/,'');
78
- setTimeout(()=>searchTopic(firstTopic),800);
79
- }
80
  }
81
 
82
  function searchTopic(topic){
83
  if(!topic){topic=document.getElementById('topic-input')?.value.trim();if(!topic){alert('Nhập chủ đề');return;}}
84
  document.getElementById('topic-input').value='';
85
  _htTopic=topic;_htPage=0;
86
- // Chỉ tìm tin của hashtag được click, không tổng hợp
87
  showHashtagSources(topic, 0, '');
88
  }
89
 
90
  async function showHashtagSources(topic, page, extraTopics){
91
  const box=document.getElementById('hashtag-box');if(!box)return;
92
- let topicLabel=esc(topic);
93
- if(page===0)box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${topicLabel}</h3><div class="hashtag-loading"><div class="hashtag-spinner"></div>Đang tìm nguồn tin...</div></div>`;
94
  try{
95
  let url=`/api/hashtag/sources?topic=${encodeURIComponent(topic)}&page=${page}`;
96
  if(extraTopics) url+=`&extra_topics=${encodeURIComponent(extraTopics)}`;
97
  const r=await fetch(url);const j=await safeJson(r);
98
  if(j.error){box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${esc(topic)}</h3><div style="color:#e74c3c;padding:8px">Lỗi: ${esc(j.error)}</div></div>`;return;}
99
  const sources=j.sources||[];
100
- if(!sources.length&&page===0){box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${topicLabel}</h3><div style="color:#888;padding:8px">Không tìm được bài viết liên quan</div></div>`;return;}
101
  let h='';
102
  if(page===0){
103
- h=`<div class="hashtag-sources"><h3>🔍 ${topicLabel}</h3>`;
104
  h+=`<div style="font-size:10px;color:#888;margin-bottom:6px">${j.total} bài từ nhiều nguồn</div>`;
105
  h+=`<div id="ht-list">`;
106
  }
@@ -116,36 +109,34 @@ async function rewriteHashtag(topic){
116
  const btn=event?.target;
117
  if(btn){btn.disabled=true;btn.textContent='Đang xử lý...';}
118
  try{
119
- // Timeout 60s cho rewrite
120
  const r=await fetchWithTimeout('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})},60000);
121
  const j=await safeJson(r);
122
- if(!r.ok||j.error)throw new Error(j.error||'Lỗi');
123
  toast('✅ Đã đăng Tường AI!');
124
  if(btn)btn.textContent='✅ Đã đăng!';
125
  }catch(e){
126
- if(e.message==='timeout'){toast('❌ Quá lâu, thử lại');}
127
- else{toast('❌ '+e.message);}
128
  if(btn){btn.disabled=false;btn.textContent='🤖 Rewrite AI';}
129
  }
130
  }
131
 
132
- // ===== 🔥 HOT FEED — chỉ hiển thị danh sách hashtag, click vào hashtag thì hiện tin =====
133
  async function loadHotFeed(){
134
  const box=document.getElementById('hot-feed');if(!box)return;
135
- box.innerHTML=`<div class="hot-feed-section"><div class="hot-feed-header">🔥 HOT <span style="font-size:10px;color:#f0c040;font-weight:400">Đang tải hashtag...</span></div><div class="hot-feed-loading"><div class="hashtag-spinner"></div>Đang tải...</div></div>`;
136
  try{
137
  const ht=await fetch('/api/hot_topics').then(r=>safeJson(r)).catch(()=>({topics:[]}));
138
  const allTopics=(ht.topics||[]).slice(0,10);
139
  if(!allTopics.length){box.innerHTML='';return;}
140
  const topicCount=allTopics.length;
141
- const headerLabel=topicCount>1?`🔥 HOT <span style="font-size:11px;color:#f0c040;font-weight:600">${topicCount} hashtag trending nhất</span>`:`🔥 HOT <span style="font-size:10px;color:#f0c040;font-weight:400">Tin hot nhất hôm nay</span>`;
142
  const topicChipsHtml=allTopics.map(t=>{
143
  const lbl=t.label||t.topic||'';
144
  const tp=t.topic||t.label.replace(/^#/,'');
145
  return`<span class="hot-feed-chip" onclick="searchTopic('${esc(tp).replace(/'/g,"\\'")}')">${esc(lbl)}</span>`;
146
  }).join('');
147
- let h=`<div class="hot-feed-section"><div class="hot-feed-header">${headerLabel}</div><div class="hot-feed-topics">${topicChipsHtml}</div>`;
148
- h+=`</div>`;box.innerHTML=h;
149
  }catch(e){box.innerHTML='';}
150
  }
151
 
 
1
  // === VNEWS Frontend v2 - Full Functions ===
2
+ // HOT feed: chỉ hiển thị hashtag chips, click vào hashtag thì hiện tin
3
+ // Fix: timeout cho rewrite calls (60s), không treo hạn
4
  // Fix: safeJson parsing to handle non-JSON error responses gracefully
 
5
 
6
  // === SAFE JSON PARSE ===
7
  async function safeJson(resp){
 
9
  const text=await resp.text();
10
  try{return JSON.parse(text);}
11
  catch(e){
 
12
  console.warn('Non-JSON response:', text.slice(0,200));
13
  return {error: 'Server error', _status: resp.status, _raw: text.slice(0,200)};
14
  }
 
71
  const topicText=t.topic||t.label.replace(/^#/,'');
72
  return`<button class="hot-chip" onclick="searchTopic('${topicText.replace(/'/g,"\\'")}')">${esc(t.label)}</button>`;
73
  }).join('');
 
 
 
 
74
  }
75
 
76
  function searchTopic(topic){
77
  if(!topic){topic=document.getElementById('topic-input')?.value.trim();if(!topic){alert('Nhập chủ đề');return;}}
78
  document.getElementById('topic-input').value='';
79
  _htTopic=topic;_htPage=0;
80
+ // Chỉ tìm tin của 1 hashtag được click
81
  showHashtagSources(topic, 0, '');
82
  }
83
 
84
  async function showHashtagSources(topic, page, extraTopics){
85
  const box=document.getElementById('hashtag-box');if(!box)return;
86
+ if(page===0)box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${esc(topic)}</h3><div class="hashtag-loading"><div class="hashtag-spinner"></div>Đang tìm nguồn tin...</div></div>`;
 
87
  try{
88
  let url=`/api/hashtag/sources?topic=${encodeURIComponent(topic)}&page=${page}`;
89
  if(extraTopics) url+=`&extra_topics=${encodeURIComponent(extraTopics)}`;
90
  const r=await fetch(url);const j=await safeJson(r);
91
  if(j.error){box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${esc(topic)}</h3><div style="color:#e74c3c;padding:8px">Lỗi: ${esc(j.error)}</div></div>`;return;}
92
  const sources=j.sources||[];
93
+ if(!sources.length&&page===0){box.innerHTML=`<div class="hashtag-sources"><h3>🔍 ${esc(topic)}</h3><div style="color:#888;padding:8px">Không tìm được bài viết</div></div>`;return;}
94
  let h='';
95
  if(page===0){
96
+ h=`<div class="hashtag-sources"><h3>🔍 ${esc(topic)}</h3>`;
97
  h+=`<div style="font-size:10px;color:#888;margin-bottom:6px">${j.total} bài từ nhiều nguồn</div>`;
98
  h+=`<div id="ht-list">`;
99
  }
 
109
  const btn=event?.target;
110
  if(btn){btn.disabled=true;btn.textContent='Đang xử lý...';}
111
  try{
 
112
  const r=await fetchWithTimeout('/api/topic_post',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({topic})},60000);
113
  const j=await safeJson(r);
114
+ if(!r.ok||j.error)throw new Error(j.error||'Lỗi server');
115
  toast('✅ Đã đăng Tường AI!');
116
  if(btn)btn.textContent='✅ Đã đăng!';
117
  }catch(e){
118
+ if(e.message==='timeout')toast('❌ Quá lâu, thử lại');
119
+ else toast('❌ '+e.message);
120
  if(btn){btn.disabled=false;btn.textContent='🤖 Rewrite AI';}
121
  }
122
  }
123
 
124
+ // ===== 🔥 HOT FEED — chỉ hiển thị hashtag chips =====
125
  async function loadHotFeed(){
126
  const box=document.getElementById('hot-feed');if(!box)return;
127
+ box.innerHTML=`<div class="hot-feed-section"><div class="hot-feed-header">🔥 HOT <span style="font-size:10px;color:#f0c040;font-weight:400">Đang tải...</span></div><div class="hot-feed-loading"><div class="hashtag-spinner"></div></div></div>`;
128
  try{
129
  const ht=await fetch('/api/hot_topics').then(r=>safeJson(r)).catch(()=>({topics:[]}));
130
  const allTopics=(ht.topics||[]).slice(0,10);
131
  if(!allTopics.length){box.innerHTML='';return;}
132
  const topicCount=allTopics.length;
133
+ const headerLabel=topicCount>1?`🔥 HOT <span style="font-size:11px;color:#f0c040;font-weight:600">${topicCount} hashtag trending nhất</span>`:`🔥 HOT`;
134
  const topicChipsHtml=allTopics.map(t=>{
135
  const lbl=t.label||t.topic||'';
136
  const tp=t.topic||t.label.replace(/^#/,'');
137
  return`<span class="hot-feed-chip" onclick="searchTopic('${esc(tp).replace(/'/g,"\\'")}')">${esc(lbl)}</span>`;
138
  }).join('');
139
+ box.innerHTML=`<div class="hot-feed-section"><div class="hot-feed-header">${headerLabel}</div><div class="hot-feed-topics">${topicChipsHtml}</div></div>`;
 
140
  }catch(e){box.innerHTML='';}
141
  }
142