bep40 commited on
Commit
66605da
·
verified ·
1 Parent(s): 6e21faf

Patch b9b6ba4 topic prompt, fast shorts, and topic article actions

Browse files
Files changed (1) hide show
  1. patch_b9b6ba4.py +168 -0
patch_b9b6ba4.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Small patch on top of b9b6ba4 snapshot.
2
+ - Remove leaked instruction lines from topic AI posts.
3
+ - Make Dantri/SKDS shorts fast/latest with cache + fallback.
4
+ - Add Rewrite AI + source link buttons to AI/topic wall articles.
5
+ """
6
+ import os, re, time, json, requests
7
+ from urllib.parse import quote, urlparse
8
+ from fastapi import Request, Query
9
+ from fastapi.responses import JSONResponse, HTMLResponse
10
+
11
+ # This module is copied into the b9b6ba4 snapshot and imported by runner.
12
+ import ai_runtime_final6 as base_app
13
+ app = base_app.app
14
+ base = base_app.base
15
+ rt = base_app.rt
16
+
17
+ SHORT_CHANNELS=["baodantri7941","baosuckhoedoisongboyte"]
18
+ _SHORTS_CACHE={"t":0,"d":[]}
19
+
20
+
21
+ def clean(s):
22
+ import html as html_lib
23
+ return re.sub(r"\s+"," ",html_lib.unescape(s or "")).strip()
24
+
25
+
26
+ def _domain(u):
27
+ try:return urlparse(u or '').netloc.replace('www.','')
28
+ except Exception:return ''
29
+
30
+
31
+ def _clean_ai_text(text):
32
+ """Remove leaked prompt/instruction lines from topic posts."""
33
+ bad_patterns=[
34
+ r'Chỉ xuất bản bài viết cuối cùng.*',
35
+ r'không nhắc lại yêu cầu.*',
36
+ r'không liệt kê chỉ dẫn.*',
37
+ r'•\s*Không sao chép nguyên văn.*',
38
+ r'Không sao chép nguyên văn.*',
39
+ r'•\s*Bài có tiêu đề, sapo.*',
40
+ r'Bài có tiêu đề, sapo.*',
41
+ r'Nhiệm vụ:\s*viết một bài báo tiếng Việt hoàn chỉnh.*',
42
+ r'Yêu cầu:\s*$',
43
+ r'Bắt buộc:\s*$',
44
+ ]
45
+ lines=[]
46
+ for ln in (text or '').splitlines():
47
+ raw=ln.strip()
48
+ if not raw:lines.append(ln);continue
49
+ if any(re.search(p,raw,flags=re.I) for p in bad_patterns):
50
+ continue
51
+ lines.append(ln)
52
+ out='\n'.join(lines)
53
+ out=re.sub(r'\n{3,}','\n\n',out).strip()
54
+ return out
55
+
56
+
57
+ def _yt_html(handle,count=20):
58
+ try:
59
+ html=requests.get(f"https://www.youtube.com/@{handle}/shorts",headers=getattr(base,'HEADERS',{}),timeout=8).text
60
+ ids=[];out=[]
61
+ for m in re.finditer(r'"videoId":"([A-Za-z0-9_-]{11})"',html):
62
+ vid=m.group(1)
63
+ if vid in ids:continue
64
+ ids.append(vid)
65
+ snip=html[max(0,m.start()-900):m.start()+1600]
66
+ title='YouTube Short'
67
+ mt=re.search(r'"title":\{"runs":\[\{"text":"([^"]+)"',snip) or re.search(r'"accessibilityText":"([^"]+)"',snip)
68
+ if mt:title=clean(mt.group(1).replace('\\n',' '))
69
+ out.append({'id':vid,'title':title,'channel':handle,'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
70
+ if len(out)>=count:break
71
+ return out
72
+ except Exception:return []
73
+
74
+
75
+ def _yt_dlp(handle,count=20):
76
+ try:
77
+ import yt_dlp
78
+ opts={'quiet':True,'extract_flat':True,'skip_download':True,'playlistend':count,'ignoreerrors':True,'no_warnings':True,'socket_timeout':8}
79
+ with yt_dlp.YoutubeDL(opts) as ydl:
80
+ info=ydl.extract_info(f"https://www.youtube.com/@{handle}/shorts",download=False)
81
+ out=[]
82
+ for e in (info or {}).get('entries') or []:
83
+ vid=e.get('id') or ''
84
+ if re.match(r'^[A-Za-z0-9_-]{11}$',vid):
85
+ out.append({'id':vid,'title':e.get('title') or 'YouTube Short','channel':handle,'link':'https://www.youtube.com/watch?v='+vid,'img':'https://i.ytimg.com/vi/'+vid+'/hqdefault.jpg','source':'yt'})
86
+ return out
87
+ except Exception:return []
88
+
89
+
90
+ def _fallback():
91
+ 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')]
92
+ return [{'id':v,'title':t,'channel':c,'link':'https://www.youtube.com/watch?v='+v,'img':'https://i.ytimg.com/vi/'+v+'/hqdefault.jpg','source':'yt'} for v,t,c in hard]
93
+
94
+
95
+ def _fresh_shorts():
96
+ seen=set();out=[]
97
+ for ch in SHORT_CHANNELS:
98
+ # HTML first is much faster; yt-dlp as backup.
99
+ got=_yt_html(ch,24) or _yt_dlp(ch,24)
100
+ for v in got:
101
+ if v['id'] not in seen:
102
+ seen.add(v['id']);out.append(v)
103
+ for v in _fallback():
104
+ if v['id'] not in seen:
105
+ seen.add(v['id']);out.append(v)
106
+ return out[:50]
107
+
108
+ # Remove routes to override.
109
+ _PATCH={('/api/shorts','GET'),('/api/topic_post','POST'),('/','GET')}
110
+ 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)]
111
+
112
+ @app.get('/api/shorts')
113
+ def api_shorts_fast(refresh:int=Query(default=0)):
114
+ now=time.time()
115
+ if _SHORTS_CACHE['d'] and now-_SHORTS_CACHE['t']<300 and not refresh:
116
+ return JSONResponse(_SHORTS_CACHE['d'])
117
+ data=_fresh_shorts()
118
+ _SHORTS_CACHE.update({'t':now,'d':data})
119
+ return JSONResponse(data)
120
+
121
+ @app.post('/api/topic_post')
122
+ async def topic_post_clean(request:Request):
123
+ body=await request.json();topic=clean(body.get('topic',''))
124
+ if not topic:return JSONResponse({'error':'missing topic'},status_code=400)
125
+ try:img=base.pollinations_image_url(topic)
126
+ except Exception:img='https://image.pollinations.ai/prompt/'+quote('Vietnamese news editorial '+topic)+'?width=1024&height=576&nologo=true'
127
+ prompt=f"""Viết một bài báo/giải thích tiếng Việt hoàn chỉnh về chủ đề: {topic}
128
+
129
+ Hãy dùng kiến thức tổng hợp của bạn để tạo nội dung thật sự hữu ích, không liệt kê chỉ dẫn, không nhắc lại yêu cầu.
130
+
131
+ Đầu ra cần là bài viết hoàn chỉnh:
132
+ - Tiêu đề rõ và hấp dẫn.
133
+ - Sapo ngắn mở vấn đề.
134
+ - Các đoạn phân tích bối cảnh, ý nghĩa, tác động hoặc kiến thức cốt lõi.
135
+ - Nếu là thể thao, giải thích nhân vật/đội bóng/bối cảnh/lịch sử liên quan.
136
+ - Nếu là xã hội/công nghệ/giáo dục, giải thích bản chất, ví dụ, lợi ích/rủi ro.
137
+ - Không bịa số liệu thời sự mới; nếu không chắc hãy diễn đạt thận trọng.
138
+ - Cuối bài có mục Nguồn tham khảo ngắn.
139
+ """
140
+ text=await base.qwen_generate(prompt,image_url=img,max_tokens=1600)
141
+ if not text:text=f"{topic}\n\n{topic} là một chủ đề đáng quan tâm, cần được nhìn từ bối cảnh, bản chất vấn đề và tác động thực tế. Nội dung này cung cấp phần giải thích tổng hợp để người đọc hiểu rõ hơn về chủ đề.\n\nNguồn tham khảo: Qwen2.5-VL / kiến thức tổng hợp."
142
+ text=_clean_ai_text(text)
143
+ post=base.make_post(topic,text,img,'','topic_qwen',sources=[{'title':'Qwen2.5-VL / kiến thức tổng hợp','url':'','via':'Qwen2.5-VL'}])
144
+ post['images']=[img]
145
+ posts=base._load_ai_wall();posts.insert(0,post);base._save_ai_wall(posts)
146
+ return JSONResponse({'post':post})
147
+
148
+ PATCH_INJECT=r'''
149
+ <script>
150
+ (function(){
151
+ function esc(s){return String(s||'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
152
+ // clean leaked instruction text in already-rendered AI posts
153
+ function cleanInstructionText(root=document){const bad=['Chỉ xuất bản bài viết cuối cùng','Không sao chép nguyên văn','Bài có tiêu đề, sapo','Nhiệm vụ: viết một bài báo tiếng Việt hoàn chỉnh'];root.querySelectorAll('.wall-text,.article-p,.rewrite-text').forEach(el=>{let lines=(el.textContent||'').split('\n').filter(l=>!bad.some(b=>l.includes(b)));el.textContent=lines.join('\n').trim();});}
154
+ // add rewrite/link buttons to topic AI article view if missing
155
+ function addTopicActions(){let art=document.querySelector('#view-article .article-view');if(!art)return;let title=art.querySelector('.article-title')?.textContent||'';let isAI=!!art.querySelector('.badge-ai')||title;let actions=art.querySelector('.article-actions');if(!actions){actions=document.createElement('div');actions.className='article-actions';art.appendChild(actions)}if(isAI&&!document.getElementById('topic-rewrite-btn')){let b=document.createElement('button');b.id='topic-rewrite-btn';b.className='primary';b.textContent='🤖 Rewrite AI & đăng tường';b.onclick=function(){alert('Bài chủ đề đã là nội dung AI. Bạn có thể copy/chia sẻ hoặc tạo Short AI từ Tường AI.');};actions.appendChild(b)}if(isAI&&!document.getElementById('topic-link-btn')){let b=document.createElement('button');b.id='topic-link-btn';b.textContent='🔗 Link bài';b.onclick=function(){navigator.clipboard.writeText(location.href).then(()=>alert('Đã sao chép link!')).catch(()=>{})};actions.appendChild(b)}}
156
+ // faster shorts home refresh: replace or insert shorts slide
157
+ async function refreshShortsFast(){let home=document.getElementById('view-home');if(!home)return;let data=await fetch('/api/shorts').then(r=>r.json()).catch(()=>[]);if(!data.length)return;let old=document.getElementById('shorts-fast-patch');if(old)old.remove();let wrap=document.createElement('div');wrap.id='shorts-fast-patch';wrap.className='slider-wrap';let h='<div class="slider-header"><span class="slider-label">📱 Shorts Dân trí & SKĐS</span><span class="slider-note">Nhanh / mới</span></div><div class="slider-track">';data.slice(0,24).forEach((a,i)=>{h+=`<div class="slider-item shorts-item" onclick="openTikTok('shorts',${i})"><div class="slider-thumb shorts-thumb">${a.img?`<img src="${a.img}">`:''}<div class="card-play">▶</div></div><div class="slider-title">${esc(a.title)}</div></div>`});h+='</div>';wrap.innerHTML=h;let ai=document.querySelector('.ai-compose');if(ai)ai.after(wrap);else home.prepend(wrap)}
158
+ setTimeout(()=>{cleanInstructionText();addTopicActions();refreshShortsFast();},1200);setInterval(()=>{cleanInstructionText();addTopicActions();},2000);
159
+ })();
160
+ </script>
161
+ '''
162
+
163
+ @app.get('/')
164
+ async def index_patch():
165
+ # Let b9b6ba4 index route build from restored non-placeholder HTML, then append patch.
166
+ with open('/app/static/index.html','r',encoding='utf-8') as f:html=f.read()
167
+ extra=getattr(base_app.old,'PATCH_INJECT','')+PATCH_INJECT if hasattr(base_app,'old') else PATCH_INJECT
168
+ return HTMLResponse(html.replace('</body>',extra+'\n</body>') if '</body>' in html else html+extra)