bep40 commited on
Commit
ee1caac
·
verified ·
1 Parent(s): 15845af

Use server-side AI wall and server rewrite endpoint

Browse files
Files changed (1) hide show
  1. static/index.html +7 -12
static/index.html CHANGED
@@ -22,29 +22,24 @@
22
  <div id="view-tiktok" class="view"></div>
23
  <div id="view-article" class="view"></div>
24
  <script>
25
- const SPACE=location.origin;let _cats=[],_tikData=[],_localWall=[],_currentArticle=null;
26
  function escapeHtml(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
27
  function doShareVideo(title,videoUrl,img,type){const shareUrl=SPACE+'/v?url='+encodeURIComponent(videoUrl)+'&title='+encodeURIComponent(title)+'&img='+encodeURIComponent(img||'')+'&type='+encodeURIComponent(type||'highlights');if(navigator.share)navigator.share({title:'🎬 '+title,url:shareUrl}).catch(()=>{});else navigator.clipboard.writeText(shareUrl).then(()=>alert('Đã sao chép link video!')).catch(()=>{});}
28
  function doShare(title,articleUrl,img){const shareUrl=SPACE+'/s?url='+encodeURIComponent(articleUrl)+'&title='+encodeURIComponent(title)+'&img='+encodeURIComponent(img||'');if(navigator.share)navigator.share({title,url:shareUrl}).catch(()=>{});else navigator.clipboard.writeText(shareUrl).then(()=>alert('Đã sao chép link!')).catch(()=>{});}
29
  function fetchJsonTimeout(url,ms=8000){const c=new AbortController();const t=setTimeout(()=>c.abort(),ms);return fetch(url,{signal:c.signal}).then(r=>r.json()).finally(()=>clearTimeout(t));}
30
- function loadLocalWall(){try{return JSON.parse(localStorage.getItem('vnews_ai_wall')||'[]')}catch{return[]}}
31
- function saveLocalWall(arr){try{localStorage.setItem('vnews_ai_wall',JSON.stringify(arr.slice(0,80)))}catch{}}
32
  async function init(){_cats=await fetchJsonTimeout('/api/categories',5000).catch(()=>[]);let bar='<div class="cat active" data-cat="home">🏠</div><div class="cat" data-cat="video">🎬 Video</div>';_cats.forEach(c=>{bar+=`<div class="cat" data-cat="${c.id}">${c.name}</div>`;});document.getElementById('cat-bar').innerHTML=bar;document.querySelectorAll('.cat').forEach(t=>{t.onclick=()=>switchCat(t.dataset.cat);});await loadHomeFast();const pendingVideo=localStorage.getItem('pending_video');if(pendingVideo){localStorage.removeItem('pending_video');try{const pv=JSON.parse(pendingVideo);if(pv.url)openTikTokByUrl(pv.url,pv.type||'highlights');}catch(e){}}const pending=localStorage.getItem('pending_article');if(pending){localStorage.removeItem('pending_article');readArticle(pending);}}
33
  function switchCat(id){document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));document.querySelector(`[data-cat="${id}"]`)?.classList.add('active');document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));document.querySelectorAll('video').forEach(v=>{try{v.pause()}catch(e){}});document.querySelectorAll('iframe[data-yt-src]').forEach(f=>{f.src='';});if(id==='home'){document.getElementById('view-home').classList.add('active');loadHomeFast();}else if(id==='video'){document.getElementById('view-video').classList.add('active');loadVideos();}else{document.getElementById('view-cat').classList.add('active');loadCat(id);}}
34
  function showView(id){document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));document.getElementById(id).classList.add('active');}
35
- async function loadHomeFast(){const home=document.getElementById('view-home');home.innerHTML='<div id="home-wall"></div><div id="home-fast"></div><div id="home-lazy"></div><div class="loading" id="home-loadnote">Đang tải tin chính...</div>';renderLocalWall();fetchJsonTimeout('/api/homepage',9000).then(news=>renderNews(news||[])).catch(()=>{let n=document.getElementById('home-loadnote');if(n)n.textContent='Không tải được tin chính';});setTimeout(loadLazySections,80);}
36
- function renderLocalWall(){_localWall=loadLocalWall();const el=document.getElementById('home-wall');if(!el)return;if(!_localWall.length){el.innerHTML='';return;}el.innerHTML=renderWallSlide(_localWall);}
 
37
  function renderNews(news){const note=document.getElementById('home-loadnote');if(note)note.remove();let h='';const groups={};(news||[]).forEach(a=>{if(!groups[a.group])groups[a.group]=[];groups[a.group].push(a);});for(const[g,arts] of Object.entries(groups)){h+=`<div class="section-title">${g}</div><div class="grid">`;arts.slice(0,6).forEach(a=>{const bg=a.source==='bbc'?'badge-bbc':a.source==='dantri'?'badge-dt':a.source==='genk'?'badge-genk':'badge-vne';const lb=a.source==='bbc'?'BBC':a.source==='dantri'?'DT':a.source==='genk'?'GenK':'VnE';h+=`<div class="card" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','${a.source}')"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="card-body"><span class="badge ${bg}">${lb}</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';}document.getElementById('home-fast').innerHTML=h||'<div class="loading">Không có dữ liệu</div>';}
38
  async function loadLazySections(){const lazy=document.getElementById('home-lazy');Promise.allSettled([fetchJsonTimeout('/api/dantri_hot',7000),fetchJsonTimeout('/api/shorts',9000),fetchJsonTimeout('/api/vne_video',7000),fetchJsonTimeout('/api/highlights',9000)]).then(res=>{let dantri=res[0].value||[],shorts=res[1].value||[],vneVid=res[2].value||[],hl=res[3].value||[];let out='';if(dantri.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🔥 Tin Nổi Bật</span></div><div class="slider-track">';dantri.forEach(a=>{out+=`<div class="slider-item" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','dantri')"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="slider-title">${a.title}</div></div>`;});out+='</div></div>';}let all=[...shorts,...vneVid];if(all.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">📱 Video · Shorts</span></div><div class="slider-track">';all.forEach((a,i)=>{if(a.source==='vne-video'){out+=`<div class="slider-item" onclick="window.open('${a.link}','_blank')"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;}else{out+=`<div class="slider-item shorts-item" onclick="openTikTok('shorts',${i})"><div class="slider-thumb shorts-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;}});out+='</div></div>';}if(hl.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🎬 Highlight</span></div><div class="slider-track">';hl.slice(0,20).forEach((a,i)=>{out+=`<div class="slider-item" onclick="openTikTok('highlights',${i})"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;});out+='</div></div>';}lazy.innerHTML=out;});}
39
- function renderWallSlide(posts){let h='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Rewrite dạng tóm tắt</span></div><div class="slider-track">';posts.slice(0,30).forEach(p=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img loading="lazy" src="${p.img}">`:''}</div><div class="wall-title">${escapeHtml(p.title)}</div><div class="wall-tone">Giọng: ${escapeHtml(p.toneLabel||p.tone||'Tóm tắt')}</div><div class="wall-text">${escapeHtml(p.text)}</div><div class="wall-actions"><button class="primary" onclick="readWallPost('${p.id}')">Xem</button><button onclick="deleteWallPost('${p.id}')">Xóa</button></div></div>`;});h+='</div></div>';return h;}
40
- function readWallPost(id){let p=loadLocalWall().find(x=>x.id===id);if(!p)return;showView('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI Rewrite</span><h1 class="article-title">${escapeHtml(p.title)}</h1>${p.img?`<img class="article-img" src="${p.img}">`:''}<div class="article-summary">Giọng: ${escapeHtml(p.toneLabel||p.tone||'Tóm tắt')}</div><p class="article-p" style="white-space:pre-wrap">${escapeHtml(p.text)}</p><div class="article-actions"><button onclick="doShare('${escapeHtml(p.title)}','${p.url||location.href}','${p.img||''}')">📤 Chia sẻ</button>${p.url?`<button onclick="readArticle('${p.url.replace(/'/g,"\\'")}')">Đọc bài gốc</button>`:''}</div></div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0);}
41
- function deleteWallPost(id){if(!confirm('Xóa bài khỏi Tường AI?'))return;let arr=loadLocalWall().filter(x=>x.id!==id);saveLocalWall(arr);renderLocalWall();}
42
  async function loadCat(id){const el=document.getElementById('view-cat');el.innerHTML='<div class="loading">Đang tải...</div>';const arts=await fetchJsonTimeout('/api/category/'+id,9000).catch(()=>[]);if(!arts.length){el.innerHTML='<div class="loading">Không có tin</div>';return;}let h='<div class="grid">';arts.forEach(a=>{const bg=a.source==='bbc'?'badge-bbc':a.source==='dantri'?'badge-dt':a.source==='genk'?'badge-genk':'badge-vne';h+=`<div class="card" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','${a.source}')"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="card-body"><span class="badge ${bg}">${a.source}</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';el.innerHTML=h;}
43
  async function readArticle(url,source){const supported=url.includes('vnexpress.net')||url.includes('bbc.com')||url.includes('dantri.com.vn')||url.includes('genk.vn')||url.includes('thethaovanhoa.vn');if(!supported){window.open(url,'_blank');return;}showView('view-article');const el=document.getElementById('view-article');el.innerHTML='<div class="loading">Đang tải...</div>';const data=await fetchJsonTimeout('/api/article?url='+encodeURIComponent(url),12000).catch(()=>null);if(!data||data.error||!data.body||!data.body.length){el.innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="loading"><a href="${url}" target="_blank" style="color:#5cb87a">Mở link gốc</a></div>`;return;}_currentArticle={url,data};let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><h1 class="article-title">${data.title}</h1>`;if(data.summary)h+=`<div class="article-summary">${data.summary}</div>`;let lastImg='';data.body.forEach(b=>{if(b.type==='p')h+=`<p class="article-p">${b.text}</p>`;else if(b.type==='img'&&b.src&&b.src!==lastImg){lastImg=b.src;h+=`<img loading="lazy" class="article-img" src="${b.src}">`;}else if(b.type==='heading')h+=`<h2 class="article-h2">${b.text}</h2>`;});h+=`<div class="article-actions"><select id="rewrite-tone"><option value="vui-ve">Vui vẻ</option><option value="nghiem-tuc">Nghiêm túc</option><option value="nghi-luan">Nghị luận</option><option value="hoi-dap">Hỏi đáp</option><option value="soi-noi">Sôi nổi</option><option value="thu-hut">Thu hút</option><option value="phan-tich">Phân tích</option><option value="chuyen-gia">Chuyên gia</option></select><button class="primary" onclick="rewriteCurrentArticle()">🤖 Rewrite AI</button><button onclick="doShare('${(data.title||'').replace(/'/g,"\\'")}','${url.replace(/'/g,"\\'")}','${(data.og_image||'').replace(/'/g,"\\'")}')">📤 Chia sẻ</button><button onclick="window.open('${url}','_blank')">🔗 Gốc</button></div><div id="rewrite-result"></div></div>`;el.innerHTML=h;window.scrollTo(0,0);}
44
- const toneLabels={'vui-ve':'Vui vẻ','nghiem-tuc':'Nghiêm túc','nghi-luan':'Nghị luận','hoi-dap':'Hỏi đáp','soi-noi':'Sôi nổi','thu-hut':'Thu hút','phan-tich':'Phân tích','chuyen-gia':'Chuyên gia'};
45
- function summarizeSentences(text,n){let s=text.replace(/\s+/g,' ').split(/(?<=[.!?])\s+/).map(x=>x.trim()).filter(x=>x.length>35);return s.slice(0,n);}
46
- function rewriteByTone(data,tone){let title=data.title||'';let summary=data.summary||'';let ps=(data.body||[]).filter(b=>b.type==='p').map(b=>b.text).join(' ');let points=summarizeSentences((summary?summary+'. ':'')+ps,4);if(!points.length)points=[title];let lead=points[0]||title;let rest=points.slice(1,4);if(tone==='vui-ve')return `😊 ${title}\n\n${lead}\n\nTóm lại, câu chuyện này có điểm đáng chú ý là: ${rest.join(' ')}\n\nMột bản tin nhanh, dễ hiểu và đáng để theo dõi.`;if(tone==='nghiem-tuc')return `TÓM TẮT NGHIÊM TÚC\n\n${title}\n\n${lead}\n\nCác ý chính:\n${rest.map(x=>'• '+x).join('\n')}`;if(tone==='nghi-luan')return `Góc nhìn nghị luận: ${title}\n\n${lead}\n\nVấn đề đặt ra là ${rest.join(' ')}\n\nĐiều này cho thấy sự việc cần được nhìn nhận trong bối cảnh rộng hơn, thay vì chỉ dừng ở một chi tiết đơn lẻ.`;if(tone==='hoi-dap')return `Hỏi nhanh đáp gọn về: ${title}\n\nHỏi: Sự việc chính là gì?\nĐáp: ${lead}\n\nHỏi: Có điểm nào đáng chú ý?\nĐáp: ${rest.join(' ')}\n\nHỏi: Vì sao nên quan tâm?\nĐáp: Vì thông tin này có thể ảnh hưởng đến cách chúng ta nhìn nhận vấn đề hiện tại.`;if(tone==='soi-noi')return `🔥 Đáng chú ý: ${title}\n\n${lead}\n\n${rest.join(' ')}\n\nDiễn biến này đang tạo nhiều sự quan tâm và có thể còn tiếp tục được cập nhật.`;if(tone==='thu-hut')return `Bạn có thể đã bỏ lỡ tin này: ${title}\n\n${lead}\n\nĐiểm khiến câu chuyện đáng chú ý nằm ở chỗ: ${rest.join(' ')}\n\nTheo dõi tiếp để không bỏ qua các diễn biến mới.`;if(tone==='phan-tich')return `Phân tích nhanh: ${title}\n\n1. Bối cảnh: ${lead}\n\n2. Diễn biến chính: ${rest[0]||''}\n\n3. Điểm đáng chú ý: ${rest.slice(1).join(' ')}\n\nNhìn chung, đây là thông tin cần được theo dõi thêm.`;if(tone==='chuyen-gia')return `Nhận định chuyên gia: ${title}\n\n${lead}\n\nTừ dữ liệu hiện có, có thể rút ra một số điểm chính:\n${rest.map(x=>'• '+x).join('\n')}\n\nĐánh giá sơ bộ: sự việc có giá trị thông tin cao và nên được tiếp tục cập nhật.`;return `${title}\n\n${points.join('\n')}`;}
47
- function rewriteCurrentArticle(){if(!_currentArticle)return;let tone=document.getElementById('rewrite-tone')?.value||'nghiem-tuc';let data=_currentArticle.data;let text=rewriteByTone(data,tone);let img=data.og_image||((data.body||[]).find(b=>b.type==='img')||{}).src||'';let post={id:'ai_'+Date.now(),url:_currentArticle.url,title:data.title||'AI Rewrite',img,text,tone,toneLabel:toneLabels[tone]||tone,ts:Date.now()};let arr=loadLocalWall();arr.unshift(post);saveLocalWall(arr);document.getElementById('rewrite-result').innerHTML=`<div class="rewrite-box"><div class="rewrite-title">Đã rewrite và đăng lên Tường AI · ${post.toneLabel}</div><div class="rewrite-text">${escapeHtml(text)}</div></div>`;alert('Đã đăng bài rewrite lên Tường AI');}
48
  async function loadVideos(){const el=document.getElementById('view-video');if(el.dataset.loaded)return;el.innerHTML='<div class="loading">Đang tải...</div>';const[hl,bdp]=await Promise.all([fetchJsonTimeout('/api/highlights',9000).catch(()=>[]),fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[])]);let h='<div class="section-title">🎬 Highlight</div><div class="grid">';hl.forEach((a,i)=>{h+=`<div class="card" onclick="openTikTok('highlights',${i})"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="card-body"><span class="badge badge-fpt">HL</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';if(bdp.length){h+='<div class="section-title">⚽ BDP</div><div class="grid">';bdp.forEach((a,i)=>{h+=`<div class="card" onclick="openTikTok('bdp',${i})"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="card-body"><span class="badge badge-fpt">BDP</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';}el.innerHTML=h;el.dataset.loaded='1';}
49
  async function openTikTokByUrl(targetUrl,type){showView('view-tiktok');document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải video...</div>';let articles;if(type==='shorts')articles=await fetchJsonTimeout('/api/shorts',9000).catch(()=>[]);else if(type==='highlights')articles=await fetchJsonTimeout('/api/highlights',9000).catch(()=>[]);else articles=await fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[]);let startIdx=0;for(let i=0;i<articles.length;i++){if(articles[i].link===targetUrl){startIdx=i;break;}}await buildTikTokPlayer(articles,startIdx,type);}
50
  async function openTikTok(type,startIdx){showView('view-tiktok');document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải video...</div>';let articles;if(type==='shorts')articles=await fetchJsonTimeout('/api/shorts',9000).catch(()=>[]);else if(type==='highlights')articles=await fetchJsonTimeout('/api/highlights',9000).catch(()=>[]);else articles=await fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[]);await buildTikTokPlayer(articles,startIdx,type);}
 
22
  <div id="view-tiktok" class="view"></div>
23
  <div id="view-article" class="view"></div>
24
  <script>
25
+ const SPACE=location.origin;let _cats=[],_tikData=[],_serverWall=[],_currentArticle=null;
26
  function escapeHtml(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
27
  function doShareVideo(title,videoUrl,img,type){const shareUrl=SPACE+'/v?url='+encodeURIComponent(videoUrl)+'&title='+encodeURIComponent(title)+'&img='+encodeURIComponent(img||'')+'&type='+encodeURIComponent(type||'highlights');if(navigator.share)navigator.share({title:'🎬 '+title,url:shareUrl}).catch(()=>{});else navigator.clipboard.writeText(shareUrl).then(()=>alert('Đã sao chép link video!')).catch(()=>{});}
28
  function doShare(title,articleUrl,img){const shareUrl=SPACE+'/s?url='+encodeURIComponent(articleUrl)+'&title='+encodeURIComponent(title)+'&img='+encodeURIComponent(img||'');if(navigator.share)navigator.share({title,url:shareUrl}).catch(()=>{});else navigator.clipboard.writeText(shareUrl).then(()=>alert('Đã sao chép link!')).catch(()=>{});}
29
  function fetchJsonTimeout(url,ms=8000){const c=new AbortController();const t=setTimeout(()=>c.abort(),ms);return fetch(url,{signal:c.signal}).then(r=>r.json()).finally(()=>clearTimeout(t));}
 
 
30
  async function init(){_cats=await fetchJsonTimeout('/api/categories',5000).catch(()=>[]);let bar='<div class="cat active" data-cat="home">🏠</div><div class="cat" data-cat="video">🎬 Video</div>';_cats.forEach(c=>{bar+=`<div class="cat" data-cat="${c.id}">${c.name}</div>`;});document.getElementById('cat-bar').innerHTML=bar;document.querySelectorAll('.cat').forEach(t=>{t.onclick=()=>switchCat(t.dataset.cat);});await loadHomeFast();const pendingVideo=localStorage.getItem('pending_video');if(pendingVideo){localStorage.removeItem('pending_video');try{const pv=JSON.parse(pendingVideo);if(pv.url)openTikTokByUrl(pv.url,pv.type||'highlights');}catch(e){}}const pending=localStorage.getItem('pending_article');if(pending){localStorage.removeItem('pending_article');readArticle(pending);}}
31
  function switchCat(id){document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));document.querySelector(`[data-cat="${id}"]`)?.classList.add('active');document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));document.querySelectorAll('video').forEach(v=>{try{v.pause()}catch(e){}});document.querySelectorAll('iframe[data-yt-src]').forEach(f=>{f.src='';});if(id==='home'){document.getElementById('view-home').classList.add('active');loadHomeFast();}else if(id==='video'){document.getElementById('view-video').classList.add('active');loadVideos();}else{document.getElementById('view-cat').classList.add('active');loadCat(id);}}
32
  function showView(id){document.querySelectorAll('.view').forEach(x=>x.classList.remove('active'));document.getElementById(id).classList.add('active');}
33
+ async function loadHomeFast(){const home=document.getElementById('view-home');home.innerHTML='<div id="home-wall"></div><div id="home-fast"></div><div id="home-lazy"></div><div class="loading" id="home-loadnote">Đang tải tin chính...</div>';loadServerWall();fetchJsonTimeout('/api/homepage',9000).then(news=>renderNews(news||[])).catch(()=>{let n=document.getElementById('home-loadnote');if(n)n.textContent='Không tải được tin chính';});setTimeout(loadLazySections,80);}
34
+ async function loadServerWall(){try{let j=await fetchJsonTimeout('/api/wall',5000);_serverWall=(j&&j.posts)||[];renderServerWall();}catch(e){_serverWall=[];renderServerWall();}}
35
+ function renderServerWall(){const el=document.getElementById('home-wall');if(!el)return;if(!_serverWall.length){el.innerHTML='';return;}el.innerHTML=renderWallSlide(_serverWall);}
36
  function renderNews(news){const note=document.getElementById('home-loadnote');if(note)note.remove();let h='';const groups={};(news||[]).forEach(a=>{if(!groups[a.group])groups[a.group]=[];groups[a.group].push(a);});for(const[g,arts] of Object.entries(groups)){h+=`<div class="section-title">${g}</div><div class="grid">`;arts.slice(0,6).forEach(a=>{const bg=a.source==='bbc'?'badge-bbc':a.source==='dantri'?'badge-dt':a.source==='genk'?'badge-genk':'badge-vne';const lb=a.source==='bbc'?'BBC':a.source==='dantri'?'DT':a.source==='genk'?'GenK':'VnE';h+=`<div class="card" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','${a.source}')"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="card-body"><span class="badge ${bg}">${lb}</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';}document.getElementById('home-fast').innerHTML=h||'<div class="loading">Không có dữ liệu</div>';}
37
  async function loadLazySections(){const lazy=document.getElementById('home-lazy');Promise.allSettled([fetchJsonTimeout('/api/dantri_hot',7000),fetchJsonTimeout('/api/shorts',9000),fetchJsonTimeout('/api/vne_video',7000),fetchJsonTimeout('/api/highlights',9000)]).then(res=>{let dantri=res[0].value||[],shorts=res[1].value||[],vneVid=res[2].value||[],hl=res[3].value||[];let out='';if(dantri.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🔥 Tin Nổi Bật</span></div><div class="slider-track">';dantri.forEach(a=>{out+=`<div class="slider-item" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','dantri')"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="slider-title">${a.title}</div></div>`;});out+='</div></div>';}let all=[...shorts,...vneVid];if(all.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">📱 Video · Shorts</span></div><div class="slider-track">';all.forEach((a,i)=>{if(a.source==='vne-video'){out+=`<div class="slider-item" onclick="window.open('${a.link}','_blank')"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;}else{out+=`<div class="slider-item shorts-item" onclick="openTikTok('shorts',${i})"><div class="slider-thumb shorts-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;}});out+='</div></div>';}if(hl.length){out+='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🎬 Highlight</span></div><div class="slider-track">';hl.slice(0,20).forEach((a,i)=>{out+=`<div class="slider-item" onclick="openTikTok('highlights',${i})"><div class="slider-thumb">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${a.title}</div></div>`;});out+='</div></div>';}lazy.innerHTML=out;});}
38
+ function renderWallSlide(posts){let h='<div class="slider-wrap"><div class="slider-header"><span class="slider-label">🧱 Tường AI</span><span class="slider-note">Lưu trên server</span></div><div class="slider-track">';posts.slice(0,30).forEach(p=>{h+=`<div class="wall-item"><div class="wall-thumb">${p.img?`<img loading="lazy" src="${p.img}">`:''}</div><div class="wall-title">${escapeHtml(p.title)}</div><div class="wall-tone">Rewrite AI</div><div class="wall-text">${escapeHtml(p.text)}</div><div class="wall-actions"><button class="primary" onclick="readWallPost('${p.id}')">Xem</button>${p.url?`<button onclick="readArticle('${p.url.replace(/'/g,"\\'")}')">Gốc</button>`:''}</div></div>`;});h+='</div></div>';return h;}
39
+ function readWallPost(id){let p=_serverWall.find(x=>x.id===id);if(!p)return;showView('view-article');let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><span class="badge badge-ai">AI Rewrite</span><h1 class="article-title">${escapeHtml(p.title)}</h1>${p.img?`<img class="article-img" src="${p.img}">`:''}<div class="article-summary">Bài viết AI tóm tắt và viết lại</div><p class="article-p" style="white-space:pre-wrap">${escapeHtml(p.text)}</p><div class="article-actions"><button onclick="doShare('${escapeHtml(p.title)}','${p.url||location.href}','${p.img||''}')">📤 Chia sẻ</button>${p.url?`<button onclick="readArticle('${p.url.replace(/'/g,"\\'")}')">Đọc bài gốc</button>`:''}</div></div>`;document.getElementById('view-article').innerHTML=h;window.scrollTo(0,0);}
 
40
  async function loadCat(id){const el=document.getElementById('view-cat');el.innerHTML='<div class="loading">Đang tải...</div>';const arts=await fetchJsonTimeout('/api/category/'+id,9000).catch(()=>[]);if(!arts.length){el.innerHTML='<div class="loading">Không có tin</div>';return;}let h='<div class="grid">';arts.forEach(a=>{const bg=a.source==='bbc'?'badge-bbc':a.source==='dantri'?'badge-dt':a.source==='genk'?'badge-genk':'badge-vne';h+=`<div class="card" onclick="readArticle('${a.link.replace(/'/g,"\\'")}','${a.source}')"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}</div><div class="card-body"><span class="badge ${bg}">${a.source}</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';el.innerHTML=h;}
41
  async function readArticle(url,source){const supported=url.includes('vnexpress.net')||url.includes('bbc.com')||url.includes('dantri.com.vn')||url.includes('genk.vn')||url.includes('thethaovanhoa.vn');if(!supported){window.open(url,'_blank');return;}showView('view-article');const el=document.getElementById('view-article');el.innerHTML='<div class="loading">Đang tải...</div>';const data=await fetchJsonTimeout('/api/article?url='+encodeURIComponent(url),12000).catch(()=>null);if(!data||data.error||!data.body||!data.body.length){el.innerHTML=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="loading"><a href="${url}" target="_blank" style="color:#5cb87a">Mở link gốc</a></div>`;return;}_currentArticle={url,data};let h=`<button class="back-btn" onclick="switchCat('home')">← Quay lại</button><div class="article-view"><h1 class="article-title">${data.title}</h1>`;if(data.summary)h+=`<div class="article-summary">${data.summary}</div>`;let lastImg='';data.body.forEach(b=>{if(b.type==='p')h+=`<p class="article-p">${b.text}</p>`;else if(b.type==='img'&&b.src&&b.src!==lastImg){lastImg=b.src;h+=`<img loading="lazy" class="article-img" src="${b.src}">`;}else if(b.type==='heading')h+=`<h2 class="article-h2">${b.text}</h2>`;});h+=`<div class="article-actions"><select id="rewrite-tone"><option value="vui-ve">Vui vẻ</option><option value="nghiem-tuc">Nghiêm túc</option><option value="nghi-luan">Nghị luận</option><option value="hoi-dap">Hỏi đáp</option><option value="soi-noi">Sôi nổi</option><option value="thu-hut">Thu hút</option><option value="phan-tich">Phân tích</option><option value="chuyen-gia">Chuyên gia</option></select><button class="primary" onclick="rewriteCurrentArticle()">🤖 Rewrite AI</button><button onclick="doShare('${(data.title||'').replace(/'/g,"\\'")}','${url.replace(/'/g,"\\'")}','${(data.og_image||'').replace(/'/g,"\\'")}')">📤 Chia sẻ</button><button onclick="window.open('${url}','_blank')">🔗 Gốc</button></div><div id="rewrite-result"></div></div>`;el.innerHTML=h;window.scrollTo(0,0);}
42
+ function rewriteCurrentArticle(){if(!_currentArticle)return;let tone=document.getElementById('rewrite-tone')?.value||'nghiem-tuc';let btn=document.querySelector('.article-actions button.primary');if(btn){btn.textContent='Đang rewrite...';btn.disabled=true;}fetch('/api/rewrite_share',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:_currentArticle.url,tone})}).then(r=>r.json()).then(j=>{if(j&&j.post){document.getElementById('rewrite-result').innerHTML=`<div class="rewrite-box"><div class="rewrite-title">Đã rewrite và đăng lên Tường AI</div><div class="rewrite-text">${escapeHtml(j.post.text||'')}</div></div>`;alert('Đã đăng lên Tường AI trên server');loadServerWall();}else alert(j.error||'Không tạo được bài AI');}).catch(()=>alert('Lỗi tạo bài AI')).finally(()=>{if(btn){btn.textContent='🤖 Rewrite AI';btn.disabled=false;}});}
 
 
 
43
  async function loadVideos(){const el=document.getElementById('view-video');if(el.dataset.loaded)return;el.innerHTML='<div class="loading">Đang tải...</div>';const[hl,bdp]=await Promise.all([fetchJsonTimeout('/api/highlights',9000).catch(()=>[]),fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[])]);let h='<div class="section-title">🎬 Highlight</div><div class="grid">';hl.forEach((a,i)=>{h+=`<div class="card" onclick="openTikTok('highlights',${i})"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="card-body"><span class="badge badge-fpt">HL</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';if(bdp.length){h+='<div class="section-title">⚽ BDP</div><div class="grid">';bdp.forEach((a,i)=>{h+=`<div class="card" onclick="openTikTok('bdp',${i})"><div class="card-img">${a.img?`<img loading="lazy" src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="card-body"><span class="badge badge-fpt">BDP</span><div class="card-title">${a.title}</div></div></div>`;});h+='</div>';}el.innerHTML=h;el.dataset.loaded='1';}
44
  async function openTikTokByUrl(targetUrl,type){showView('view-tiktok');document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải video...</div>';let articles;if(type==='shorts')articles=await fetchJsonTimeout('/api/shorts',9000).catch(()=>[]);else if(type==='highlights')articles=await fetchJsonTimeout('/api/highlights',9000).catch(()=>[]);else articles=await fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[]);let startIdx=0;for(let i=0;i<articles.length;i++){if(articles[i].link===targetUrl){startIdx=i;break;}}await buildTikTokPlayer(articles,startIdx,type);}
45
  async function openTikTok(type,startIdx){showView('view-tiktok');document.querySelectorAll('.cat').forEach(x=>x.classList.remove('active'));const el=document.getElementById('view-tiktok');el.innerHTML='<div class="loading">Đang tải video...</div>';let articles;if(type==='shorts')articles=await fetchJsonTimeout('/api/shorts',9000).catch(()=>[]);else if(type==='highlights')articles=await fetchJsonTimeout('/api/highlights',9000).catch(()=>[]);else articles=await fetchJsonTimeout('/api/bdp_videos',9000).catch(()=>[]);await buildTikTokPlayer(articles,startIdx,type);}