bep40 commited on
Commit
32fe6b4
·
verified ·
1 Parent(s): 35c2603

Upload app_v2_entry_patch.py

Browse files
Files changed (1) hide show
  1. app_v2_entry_patch.py +194 -0
app_v2_entry_patch.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Patch to add rewrite endpoints to app_v2_entry.py - append to bottom of file."""
2
+ # This file contains the code that should be appended to app_v2_entry.py
3
+ # to enable the Rewrite functionality on Tường AI
4
+
5
+ # ===== REWRITE / ARTICLE-TO-WALL ENDPOINTS =====
6
+ import random as _random2
7
+ from urllib.parse import quote as _quote2
8
+
9
+ _UA_RW = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept-Language': 'vi-VN,vi;q=0.9'}
10
+
11
+ def _scrape_article_for_rewrite(url):
12
+ """Scrape article: extract title, paragraphs, images, OG image."""
13
+ try:
14
+ r = req.get(url, headers=_UA_RW, timeout=15, allow_redirects=True)
15
+ r.encoding = 'utf-8'
16
+ soup = BeautifulSoup(r.text, 'lxml')
17
+ for tag in soup.find_all(['script', 'style', 'nav', 'footer', 'aside', 'form']):
18
+ tag.decompose()
19
+ h1 = soup.find('h1')
20
+ ogt = soup.find('meta', property='og:title')
21
+ title = (h1.get_text(strip=True) if h1 else '') or (ogt.get('content', '') if ogt else '')
22
+ ogi = soup.find('meta', property='og:image')
23
+ og_img = ogi.get('content', '') if ogi else ''
24
+ if og_img and og_img.startswith('//'):
25
+ og_img = 'https:' + og_img
26
+ block = None
27
+ for sel in ['article', '.singular-content', '.detail-content', '.fck_detail', '.content-detail', '.knc-content', 'main', '.cms-body', '.article__body']:
28
+ el = soup.select_one(sel)
29
+ if el and len(el.find_all('p')) >= 2:
30
+ block = el
31
+ break
32
+ if not block:
33
+ block = soup.body or soup
34
+ paragraphs = []
35
+ images = []
36
+ seen_imgs = set()
37
+ if og_img and og_img not in seen_imgs:
38
+ images.append(og_img)
39
+ seen_imgs.add(og_img)
40
+ for el in block.find_all(['p', 'h2', 'h3', 'figure', 'img'], recursive=True):
41
+ if el.name == 'p':
42
+ t = _clean(el.get_text(strip=True))
43
+ if t and len(t) > 40:
44
+ paragraphs.append(t)
45
+ elif el.name in ('figure', 'img'):
46
+ im = el if el.name == 'img' else el.find('img')
47
+ if im:
48
+ src = im.get('data-src') or im.get('src') or im.get('data-original') or ''
49
+ if src and 'base64' not in src:
50
+ if src.startswith('//'):
51
+ src = 'https:' + src
52
+ if src not in seen_imgs:
53
+ images.append(src)
54
+ seen_imgs.add(src)
55
+ return {'title': _clean(title), 'paragraphs': paragraphs, 'images': images, 'og_img': og_img}
56
+ except Exception:
57
+ return None
58
+
59
+
60
+ def _extract_key_points_rw(paragraphs, max_points=5):
61
+ """Extract key points from paragraphs."""
62
+ points = []
63
+ for p in paragraphs:
64
+ if len(points) >= max_points:
65
+ break
66
+ m = re.match(r'^(.+?[.!?])\s', p)
67
+ if m:
68
+ sentence = m.group(1)
69
+ else:
70
+ sentence = p[:150] + ('.' if not p.endswith('.') else '')
71
+ if len(sentence) < 30:
72
+ continue
73
+ if any(sentence[:50] in existing for existing in points):
74
+ continue
75
+ points.append(sentence)
76
+ return points
77
+
78
+
79
+ @app.post("/api/rewrite_slide")
80
+ async def api_rewrite_slide(request: Request):
81
+ """Fast rewrite as SLIDES - no AI needed, instant response."""
82
+ body = await request.json()
83
+ url = _clean(body.get("url", ""))
84
+ context = body.get("context", "")
85
+ if not url and not context:
86
+ return JSONResponse({"error": "Cần URL hoặc nội dung"}, status_code=400)
87
+ data = None
88
+ if url and url.startswith("http"):
89
+ data = _scrape_article_for_rewrite(url)
90
+ if not data and context:
91
+ paragraphs = [_clean(p) for p in context.split('\n') if len(_clean(p)) > 40]
92
+ data = {'title': paragraphs[0][:80] if paragraphs else 'Bài viết', 'paragraphs': paragraphs, 'images': [], 'og_img': ''}
93
+ if not data or not data.get('paragraphs'):
94
+ return JSONResponse({"error": "Không đọc được bài viết"}, status_code=422)
95
+ points = _extract_key_points_rw(data['paragraphs'], max_points=6)
96
+ if not points:
97
+ return JSONResponse({"error": "Không tìm được ý chính"}, status_code=422)
98
+ images = data.get('images', [])
99
+ slides = []
100
+ for i, point in enumerate(points):
101
+ img = images[i] if i < len(images) else (images[-1] if images else '')
102
+ if img and 'cdnphoto.dantri' in img:
103
+ img = '/api/proxy/img?url=' + _quote2(img, safe='')
104
+ slides.append({'text': point, 'image': img, 'index': i + 1})
105
+ summary_text = '\n\n'.join([f"• {s['text']}" for s in slides])
106
+ post = {
107
+ "id": str(int(time.time() * 1000)) + str(_random2.randint(100, 999)),
108
+ "title": data['title'],
109
+ "text": summary_text,
110
+ "img": images[0] if images else '',
111
+ "url": url,
112
+ "kind": "slide_summary",
113
+ "slides": slides,
114
+ "images": images[:10],
115
+ "video": "",
116
+ "voice": "hoaimy",
117
+ "emotion": "trung_tinh",
118
+ "ts": int(time.time())
119
+ }
120
+ posts = _load_wall_posts()
121
+ posts.insert(0, post)
122
+ _save_wall_posts(posts)
123
+ return JSONResponse({"post": post, "slides": slides})
124
+
125
+
126
+ @app.post("/api/rewrite_share")
127
+ async def api_rewrite_share(request: Request):
128
+ """Rewrite article and post to Tường AI (with AI fallback to extractive)."""
129
+ body = await request.json()
130
+ url = _clean(body.get("url", ""))
131
+ ctx = _clean(body.get("context", ""))
132
+ if not url and not ctx:
133
+ return JSONResponse({"error": "Cần URL hoặc nội dung"}, status_code=400)
134
+ data = None
135
+ if url and url.startswith("http"):
136
+ data = _scrape_article_for_rewrite(url)
137
+ if not data and ctx:
138
+ paragraphs = [_clean(p) for p in ctx.split('\n') if len(_clean(p)) > 40]
139
+ data = {'title': paragraphs[0][:80] if paragraphs else 'Bài viết', 'paragraphs': paragraphs, 'images': [], 'og_img': ''}
140
+ if not data or not data.get('paragraphs'):
141
+ return JSONResponse({"error": "Không đọc được bài viết"}, status_code=422)
142
+ raw_text = '\n'.join(data['paragraphs'])
143
+ if len(raw_text) < 50:
144
+ raw_text = ctx[:14000]
145
+ if len(raw_text) < 50:
146
+ return JSONResponse({"error": "Bài viết quá ngắn"}, status_code=422)
147
+ domain = ''
148
+ try:
149
+ from urllib.parse import urlparse
150
+ domain = urlparse(url).netloc.replace('www.', '')
151
+ except:
152
+ pass
153
+ ai_text = None
154
+ try:
155
+ import ai_ext
156
+ if hasattr(ai_ext, 'qwen_generate'):
157
+ prompt = f'Tóm tắt đăng Tường AI:\nTiêu đề: {data["title"]}\n{raw_text[:14000]}\n\n4-6 ý chính. Cuối ghi nguồn.'
158
+ ai_text = await ai_ext.qwen_generate(prompt, max_tokens=1000)
159
+ except Exception:
160
+ pass
161
+ if not ai_text or len(ai_text) < 80:
162
+ key_pts = _extract_key_points_rw(data['paragraphs'], max_points=6)
163
+ if key_pts:
164
+ ai_text = '\n\n'.join([f"• {p}" for p in key_pts])
165
+ else:
166
+ ai_text = f"Tóm tắt: {data['title']}\n\n{raw_text[:1200]}\n\nNguồn: {domain}"
167
+ images = data.get('images', [])
168
+ post = {
169
+ "id": str(int(time.time() * 1000)) + str(_random2.randint(100, 999)),
170
+ "title": data['title'],
171
+ "text": ai_text,
172
+ "img": images[0] if images else '',
173
+ "url": url,
174
+ "kind": "rewrite",
175
+ "images": images[:10],
176
+ "video": "",
177
+ "voice": "hoaimy",
178
+ "emotion": "trung_tinh",
179
+ "ts": int(time.time())
180
+ }
181
+ posts = _load_wall_posts()
182
+ posts.insert(0, post)
183
+ _save_wall_posts(posts)
184
+ return JSONResponse({"post": post})
185
+
186
+
187
+ @app.post("/api/url_wall")
188
+ async def api_url_wall(request: Request):
189
+ """Submit URL to add to Tường AI."""
190
+ body = await request.json()
191
+ url = _clean(body.get("url", ""))
192
+ if not url or not url.startswith('http'):
193
+ return JSONResponse({"error": "URL không hợp lệ"}, status_code=400)
194
+ return await api_rewrite_share(request)