bep40 commited on
Commit
259d6f1
·
verified ·
1 Parent(s): 04502e0

Upload opinion_v3_patch.py

Browse files
Files changed (1) hide show
  1. opinion_v3_patch.py +146 -0
opinion_v3_patch.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PERSONAL OPINION POST v3 - AI synthesis from opinion + hot sources
3
+ Patched as standalone module imported by app_v2_entry.py
4
+ """
5
+ import os, re, json, time, uuid, threading
6
+ from fastapi import Request, Query
7
+ from fastapi.responses import JSONResponse
8
+ from urllib.parse import quote_plus, quote
9
+
10
+ try:
11
+ from main import app
12
+ except:
13
+ from fastapi import FastAPI
14
+ app = FastAPI()
15
+
16
+ # --- reuse functions from app_v2_entry ---
17
+ try:
18
+ from app_v2_entry import _clean, _has_kw, _search_all, _get_hot_topics, _load_wall_posts, _save_wall_posts, qwen_generate
19
+ except:
20
+ # fallback imports
21
+ from ai_ext import qwen_generate, _clean
22
+ from app_v2_entry import _has_kw, _search_all, _get_hot_topics, _load_wall_posts, _save_wall_posts
23
+
24
+
25
+ async def _ai_synthesize(topic, opinion_text, hot_sources, max_tokens=1200):
26
+ """Generate an article using AI by combining user opinion + news context."""
27
+ sources_str = "\n".join([f"- {s.get('title','')} ({s.get('via','')})" for s in hot_sources[:8]])
28
+
29
+ prompt = f'''Dưới đây là quan điểm của người dùng và các tin tức liên quan.
30
+
31
+ QUAN ĐIỂM CÁ NHÂN:
32
+ {opinion_text}
33
+
34
+ TIN TỨC LIÊN QUAN (tham khảo):
35
+ {sources_str}
36
+
37
+ YÊU CẦU: Hãy viết một bài viết tổng hợp dựa trên quan điểm trên và các nguồn tin liên quan.
38
+ Bài viết cần:
39
+ 1. Có tiêu đề hấp dẫn (bắt đầu bằng "## ")
40
+ 2. Trình bày quan điểm của người dùng làm nòng cốt
41
+ 3. Lồng ghép thông tin từ các nguồn tin để hỗ trợ/đối chiếu
42
+ 4. Kết luận ở cuối
43
+ 5. Viết bằng tiếng Việt tự nhiên, dài 300-500 từ
44
+ 6. Định dạng Markdown rõ ràng với heading, bullet points nếu cần'''
45
+
46
+ try:
47
+ result = await qwen_generate(prompt, max_tokens=max_tokens)
48
+ if result:
49
+ return result
50
+ except Exception as e:
51
+ pass
52
+ return f"**{topic}**\n\n{opinion_text}\n\n*Bài viết đang được cập nhật...*"
53
+
54
+
55
+ @app.post('/api/opinion/post')
56
+ async def api_opinion_post(request: Request):
57
+ """POST /api/opinion/post
58
+ Body: {"topic", "opinion", "sources": [{"title","url","via"}]}
59
+ Returns: {"article": "markdown", "title": "...", "post": {...}, "ok": true}
60
+ """
61
+ try:
62
+ body = await request.json()
63
+ except Exception:
64
+ return JSONResponse({"error": "Invalid JSON"}, status_code=400)
65
+
66
+ topic = (body.get('topic') or '').strip()
67
+ opinion = (body.get('opinion') or '').strip()
68
+ sources = body.get('sources', [])
69
+
70
+ if not opinion:
71
+ return JSONResponse({"error": "Vui lòng nhập quan điểm cá nhân"}, status_code=400)
72
+ if not topic:
73
+ words = [w for w in re.findall(r'[A-Za-zÀ-ỹ0-9]+', opinion) if len(w) > 3]
74
+ topic = ' '.join(words[:5]) if words else 'Bài viết quan điểm'
75
+
76
+ # Step 1: Get hot topics as additional context if sources empty
77
+ if not sources or len(sources) < 2:
78
+ hot = _get_hot_topics() if '_get_hot_topics' in dir() else []
79
+ if isinstance(hot, dict):
80
+ hot = hot.get('topics', hot) if isinstance(hot, dict) else list(hot)
81
+ related = [t for t in (hot or []) if _has_kw(topic, t.get('topic',''))]
82
+ if related:
83
+ for rtopic in related[:3]:
84
+ more = _search_all(rtopic.get('topic',''), 6)
85
+ seen_urls = set(s.get('url') for s in sources)
86
+ for s in more:
87
+ if s.get('url') not in seen_urls:
88
+ seen_urls.add(s.get('url'))
89
+ sources.append(s)
90
+ if len(sources) >= 10:
91
+ break
92
+
93
+ # Step 2: Generate article via AI
94
+ article = await _ai_synthesize(topic, opinion, sources)
95
+
96
+ # Step 3: Extract title from article
97
+ title_match = re.search(r'^##\s+(.+)$', article, re.MULTILINE)
98
+ title = title_match.group(1).strip() if title_match else f'Quan điểm: {topic}'
99
+
100
+ # Step 4: Save to wall
101
+ post_id = str(uuid.uuid4())[:12]
102
+ post = {
103
+ "id": post_id,
104
+ "title": title[:200],
105
+ "text": article[:2000],
106
+ "source": "opinion_v3",
107
+ "opinion": opinion,
108
+ "topic": topic,
109
+ "sources": sources[:10],
110
+ "img": None,
111
+ "video": None,
112
+ "created": int(time.time()),
113
+ "created_str": time.strftime('%H:%M %d/%m/%Y', time.localtime()),
114
+ }
115
+ posts = _load_wall_posts()
116
+ if not isinstance(posts, list):
117
+ posts = []
118
+ posts.insert(0, post)
119
+ posts = posts[:200]
120
+ _save_wall_posts(posts)
121
+
122
+ return JSONResponse({
123
+ "post": post,
124
+ "article": article,
125
+ "title": title,
126
+ "ok": True
127
+ })
128
+
129
+
130
+ @app.get('/api/opinion/hot_context')
131
+ async def api_opinion_hot_context(topic: str = Query(...)):
132
+ """GET /api/opinion/hot_context?topic=X
133
+ Returns: {"sources": [...], "hot_topics": [...]}
134
+ """
135
+ sources = _search_all(topic, 12)
136
+ hot = _get_hot_topics() if '_get_hot_topics' in dir() else []
137
+ if isinstance(hot, dict):
138
+ hot = hot.get('topics', hot) if isinstance(hot, dict) else list(hot)
139
+ related_hot = [t for t in (hot or []) if _has_kw(topic, t.get('topic',''))]
140
+ return JSONResponse({
141
+ "sources": sources,
142
+ "hot_topics": related_hot[:5],
143
+ })
144
+
145
+
146
+ print("[opinion_v3_patch] PERSONAL OPINION POST v3 endpoints registered: POST /api/opinion/post, GET /api/opinion/hot_context")