bep40 commited on
Commit
232ee22
·
verified ·
1 Parent(s): 3002f20

Restore b9b6ba4 and patch topic rewrite/shorts

Browse files
Files changed (1) hide show
  1. restore_b9b6ba4_patch.py +147 -0
restore_b9b6ba4_patch.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, sys, re, json, time, requests
2
+ from urllib.parse import quote_plus, urlparse
3
+ from huggingface_hub import snapshot_download
4
+ from fastapi import Request, Query
5
+ from fastapi.responses import JSONResponse, HTMLResponse
6
+
7
+ REVISION=os.environ.get('VNEWS_RESTORE_REVISION','b9b6ba4')
8
+ REPO_ID=os.environ.get('VNEWS_REPO_ID','bep40/vnews')
9
+ snapshot_dir=snapshot_download(repo_id=REPO_ID,repo_type='space',revision=REVISION,local_dir='/tmp/vnews_restore_b9b6ba4',local_dir_use_symlinks=False)
10
+ os.chdir(snapshot_dir);sys.path.insert(0,snapshot_dir)
11
+
12
+ try:
13
+ import ai_runtime_final6 as runtime
14
+ except Exception:
15
+ try:import ai_runtime_final5 as runtime
16
+ except Exception:import ai_patch as runtime
17
+ app=runtime.app
18
+ try:import ai_ext as base
19
+ except Exception:base=None
20
+ try:import main as main_mod
21
+ except Exception:main_mod=None
22
+
23
+ SPACE_URL='https://bep40-vnews.hf.space'
24
+
25
+ def clean(s):
26
+ import html as html_lib
27
+ return re.sub(r'\s+',' ',html_lib.unescape(s or '')).strip()
28
+
29
+ def _domain(u):
30
+ try:return urlparse(u or '').netloc.replace('www.','')
31
+ except Exception:return ''
32
+
33
+ def _image(topic):
34
+ if base and hasattr(base,'pollinations_image_url'):
35
+ try:return base.pollinations_image_url(topic)
36
+ except Exception:pass
37
+ return 'https://image.pollinations.ai/prompt/'+quote_plus('Vietnamese editorial article '+topic)+'?width=1024&height=576&nologo=true'
38
+
39
+ async def _llm_article(topic, context=''):
40
+ img=_image(topic)
41
+ # Important: use messages role separation and post-filter; never allow instruction echo.
42
+ prompt=f'''Viết một bài báo tiếng Việt hoàn chỉnh về chủ đề: {topic}
43
+
44
+ Hãy tổng hợp thành bài đọc tự nhiên, giàu thông tin, có bối cảnh, phân tích và ví dụ. Không nhắc lại nhiệm vụ, không liệt kê chỉ dẫn, không bắt đầu bằng "Nhiệm vụ" hay "Chủ đề".
45
+
46
+ Nếu có nguồn/bối cảnh bên dưới thì dùng để tham khảo và diễn đạt lại, không sao chép nguyên văn:
47
+ {context[:9000]}'''
48
+ out=''
49
+ if base and hasattr(base,'qwen_generate'):
50
+ try:out=await base.qwen_generate(prompt,image_url=img,max_tokens=1600) or ''
51
+ except Exception:out=''
52
+ token=os.environ.get('HF_TOKEN') or os.environ.get('HUGGINGFACEHUB_API_TOKEN')
53
+ if (not out or _looks_like_instruction_echo(out)) and token:
54
+ for model in ['Qwen/Qwen2.5-72B-Instruct','Qwen/Qwen2.5-32B-Instruct','Qwen/Qwen2.5-14B-Instruct','Qwen/Qwen2.5-7B-Instruct']:
55
+ try:
56
+ r=requests.post('https://router.huggingface.co/v1/chat/completions',headers={'Authorization':'Bearer '+token,'Content-Type':'application/json'},json={'model':model,'messages':[{'role':'system','content':'Bạn là nhà báo tiếng Việt. Chỉ trả về bài viết cuối cùng, không lặp lại prompt hay chỉ dẫn.'},{'role':'user','content':prompt}],'temperature':0.55,'top_p':0.9,'max_tokens':1800},timeout=90)
57
+ if r.status_code<300:
58
+ out=r.json().get('choices',[{}])[0].get('message',{}).get('content','').strip()
59
+ if out and not _looks_like_instruction_echo(out):break
60
+ except Exception:pass
61
+ if _looks_like_instruction_echo(out):
62
+ out=_strip_instruction_echo(out)
63
+ if not out:
64
+ out=f'''{topic}: những điều đáng chú ý\n\n{topic} là một chủ đề cần được nhìn từ bối cảnh rộng hơn thay vì chỉ qua một định nghĩa ngắn. Khi phân tích, cần chú ý đến bản chất vấn đề, các yếu tố thúc đẩy, tác động thực tế và những giới hạn cần hiểu đúng.\n\nĐiểm quan trọng là chủ đề này không đứng riêng lẻ: nó thường liên quan đến các xu hướng xã hội, công nghệ, thể thao hoặc đời sống tùy từng ngữ cảnh. Người đọc vì vậy cần một cách tiếp cận cân bằng, vừa nhìn thấy cơ hội, vừa nhận diện rủi ro và các hiểu lầm phổ biến.\n\nTừ góc độ thực tế, {topic} có thể tạo ra thay đổi trong cách con người suy nghĩ, làm việc hoặc đánh giá một sự kiện. Điều cần tránh là nhìn nhận vấn đề bằng khẩu hiệu hoặc thông tin rời rạc; thay vào đó, cần đặt nó trong mối liên hệ với con người, dữ liệu, bối cảnh và hệ quả lâu dài.\n\nNguồn tham khảo: Qwen / kiến thức tổng hợp.'''
65
+ if 'Nguồn tham khảo' not in out:out+='\n\nNguồn tham khảo: Qwen / kiến thức tổng hợp.'
66
+ return out,img
67
+
68
+ def _looks_like_instruction_echo(text):
69
+ low=(text or '').lower()
70
+ bad=['nhiệm vụ:','viết một bài báo','chỉ xuất bản bài viết','không nhắc lại yêu cầu','không liệt kê chỉ dẫn','bạn là biên tập viên','cấu trúc:']
71
+ return sum(1 for b in bad if b in low)>=2
72
+
73
+ def _strip_instruction_echo(text):
74
+ lines=[]
75
+ for ln in (text or '').splitlines():
76
+ low=ln.lower().strip()
77
+ if any(x in low for x in ['nhiệm vụ:','chỉ xuất bản','không nhắc lại','không liệt kê','bạn là biên tập viên','cấu trúc:','• không sao chép']):
78
+ continue
79
+ if low.startswith('•') and ('chủ đề:' in low or 'bài có' in low):continue
80
+ lines.append(ln)
81
+ return '\n'.join(lines).strip()
82
+
83
+ # topic endpoint
84
+ app.router.routes=[r for r in app.router.routes if not (getattr(r,'path',None)=='/api/topic_post' and 'POST' in getattr(r,'methods',set()))]
85
+ @app.post('/api/topic_post')
86
+ async def topic_post(request:Request):
87
+ body=await request.json();topic=clean(body.get('topic',''))
88
+ if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
89
+ text,img=await _llm_article(topic)
90
+ post={'id':str(int(time.time()*1000)),'title':topic,'text':text,'img':img,'url':'','kind':'topic_qwen','images':[img],'sources':[{'title':'Qwen / kiến thức tổng hợp','url':'','via':'Qwen'}],'ts':int(time.time())}
91
+ if base and hasattr(base,'_load_ai_wall'):
92
+ posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
93
+ return JSONResponse({'post':post})
94
+
95
+ @app.post('/api/topic_rewrite')
96
+ async def topic_rewrite(request:Request):
97
+ body=await request.json();post_id=str(body.get('id',''));topic=clean(body.get('topic',''));old=clean(body.get('text',''))
98
+ if not topic and not old:return JSONResponse({'error':'missing topic'},status_code=400)
99
+ text,img=await _llm_article(topic or 'Bài viết',old)
100
+ return JSONResponse({'text':text,'img':img})
101
+
102
+ # shorts endpoint restore
103
+ app.router.routes=[r for r in app.router.routes if not (getattr(r,'path',None)=='/api/shorts' and 'GET' in getattr(r,'methods',set()))]
104
+ def _fallback_shorts():
105
+ out=[];seen=set();c=[]
106
+ try:c+=(getattr(main_mod,'SHORTS_FALLBACK',[]) or [])
107
+ except Exception:pass
108
+ hard=[('Lu_iCQ5YwNM','Công an lập hồ sơ xử lý người phụ nữ chửi bới, tát tài xế ô tô | Dân trí','baodantri7941'),('CwWvijF8BOA','Chú rể bật khóc nhận món quà bí mật người cha quá cố gửi 26 năm trước | Dân trí','baodantri7941'),('tvPewsc2ph4','Tính năng ẩn trên iPhone giúp giảm mỏi mắt | Dân trí','baodantri7941'),('7Pd6vZ2Lz1M','Hành động ấm lòng trong tìm kiếm học sinh tử vong ở sông Lô | SKĐS','baosuckhoedoisongboyte'),('SlHLt_ZyPiE','Xử phạt người đàn ông xóa số điện thoại cứu hộ trên cao tốc Bắc - Nam | SKĐS','baosuckhoedoisongboyte'),('IUOprcJyYr4','Phụ nữ táo bón có phải do lười ăn rau? | SKĐS','baosuckhoedoisongboyte')]
109
+ for vid,title,ch in hard:c.append({'id':vid,'title':title,'channel':ch})
110
+ for v in c:
111
+ vid=v.get('id')
112
+ if vid and vid not in seen:
113
+ seen.add(vid);out.append({'id':vid,'title':v.get('title','YouTube Short'),'channel':v.get('channel',''),'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
114
+ return out
115
+ @app.get('/api/shorts')
116
+ def api_shorts(refresh:int=Query(default=0)):
117
+ # Prefer existing scraper from restored app if it works, then hard fallback.
118
+ try:
119
+ if hasattr(main_mod,'scrape_shorts'):
120
+ data=main_mod.scrape_shorts()
121
+ if data:return JSONResponse(data)
122
+ except Exception:pass
123
+ return JSONResponse(_fallback_shorts())
124
+
125
+ PATCH_JS=r'''
126
+ <script>
127
+ (function(){
128
+ function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
129
+ function dedupeShorts(){const blocks=[...document.querySelectorAll('.slider-wrap')].filter(w=>((w.querySelector('.slider-label')||{}).textContent||'').toLowerCase().includes('short'));let kept=false;blocks.forEach(b=>{if(!kept){kept=true;b.id='shorts-single-home'}else b.remove()});}
130
+ async function ensureShorts(){let home=document.getElementById('view-home');if(!home)return;dedupeShorts();if(document.getElementById('shorts-single-home'))return;let sh=await fetch('/api/shorts?refresh=1').then(r=>r.json()).catch(()=>[]);if(!sh.length)return;let wrap=document.createElement('div');wrap.id='shorts-single-home';wrap.className='slider-wrap';let h='<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS</span></div><div class="slider-track">';sh.forEach((a,i)=>{h+=`<div class="slider-item shorts-item" onclick="openTikTok('shorts',${i})"><div class="slider-thumb shorts-thumb"><img src="${a.img}"><div class="card-play">▶</div></div><div class="slider-title">${esc(a.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;(document.querySelector('.ai-compose')||home.firstChild).after(wrap);}
131
+ window.rewriteTopicPost=async function(id,title,text){let r=await fetch('/api/topic_rewrite',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({id,topic:title,text})});let j=await r.json();if(j.text){alert('Đã rewrite bài chủ đề');location.reload();}else alert(j.error||'Lỗi rewrite')};
132
+ function addRewriteButtons(){document.querySelectorAll('.wall-item').forEach(item=>{if(item.querySelector('.topic-rewrite-btn'))return;let title=item.querySelector('.wall-title')?.textContent||'';let txt=item.querySelector('.wall-text')?.textContent||'';let actions=item.querySelector('.wall-actions');if(actions){let b=document.createElement('button');b.className='topic-rewrite-btn';b.textContent='Rewrite';b.onclick=function(e){e.stopPropagation();rewriteTopicPost('',title,txt)};actions.appendChild(b);}})}
133
+ setInterval(()=>{dedupeShorts();ensureShorts();addRewriteButtons();},1500);setTimeout(()=>{dedupeShorts();ensureShorts();addRewriteButtons();},800);
134
+ })();
135
+ </script>
136
+ '''
137
+
138
+ old_root=None
139
+ for r in list(app.router.routes):
140
+ if getattr(r,'path',None)=='/' and 'GET' in getattr(r,'methods',set()):old_root=r.endpoint;app.router.routes.remove(r);break
141
+ @app.get('/')
142
+ async def root():
143
+ if old_root:
144
+ resp=await old_root();body=getattr(resp,'body',b'').decode('utf-8','ignore') if hasattr(resp,'body') else str(resp)
145
+ else:
146
+ with open('/tmp/vnews_restore_b9b6ba4/static/index.html','r',encoding='utf-8') as f:body=f.read()
147
+ return HTMLResponse(body.replace('</body>',PATCH_JS+'\n</body>') if '</body>' in body else body+PATCH_JS)