openfree commited on
Commit
5548293
ยท
verified ยท
1 Parent(s): 6ba1af9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1 -1060
app.py CHANGED
@@ -1,1061 +1,2 @@
1
- import gradio as gr
2
- import requests
3
- import json
4
  import os
5
- from datetime import datetime, timedelta
6
- from concurrent.futures import ThreadPoolExecutor, as_completed
7
- from functools import lru_cache
8
- from requests.adapters import HTTPAdapter
9
- from requests.packages.urllib3.util.retry import Retry
10
- from openai import OpenAI
11
- from bs4 import BeautifulSoup
12
- import re
13
- import pathlib
14
- import sqlite3
15
- import pytz
16
-
17
- # List of target companies/keywords (์˜๋ฌธ์œผ๋กœ ๊ฒ€์ƒ‰์–ด๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€)
18
- KOREAN_COMPANIES = [
19
- "NVIDIA",
20
- "ALPHABET",
21
- "APPLE",
22
- "TESLA",
23
- "AMAZON",
24
- "MICROSOFT",
25
- "META",
26
- "INTEL",
27
- "SAMSUNG",
28
- "HYNIX",
29
- "BITCOIN",
30
- "crypto",
31
- "stock",
32
- "Economics",
33
- "Finance",
34
- "investing"
35
- ]
36
-
37
- def convert_to_seoul_time(timestamp_str):
38
- """
39
- ์ฃผ์–ด์ง„ UTC ํƒ€์ž„์Šคํƒฌํ”„ ๋ฌธ์ž์—ด์„ ์„œ์šธ ์‹œ๊ฐ„(KST)์œผ๋กœ ๋ณ€ํ™˜.
40
- """
41
- try:
42
- dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
43
- seoul_tz = pytz.timezone('Asia/Seoul')
44
- seoul_time = seoul_tz.localize(dt)
45
- return seoul_time.strftime('%Y-%m-%d %H:%M:%S KST')
46
- except Exception as e:
47
- print(f"์‹œ๊ฐ„ ๋ณ€ํ™˜ ์˜ค๋ฅ˜: {str(e)}")
48
- return timestamp_str
49
-
50
- def analyze_sentiment_batch(articles, client):
51
- """
52
- OpenAI API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ชจ์Œ์— ๋Œ€ํ•œ ์ „๋ฐ˜์ ์ธ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
53
- ๋ถ„์„ ๊ฒฐ๊ณผ๋Š” ํ•œ๊ธ€๋กœ ๋ฐ˜ํ™˜๋œ๋‹ค.
54
- """
55
- try:
56
- # ๋ชจ๋“  ๊ธฐ์‚ฌ๋ฅผ ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ๋กœ ๊ฒฐํ•ฉ
57
- combined_text = "\n\n".join([
58
- f"์ œ๋ชฉ: {article.get('title', '')}\n๋‚ด์šฉ: {article.get('snippet', '')}"
59
- for article in articles
60
- ])
61
-
62
- prompt = f"""์•„๋ž˜ ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ชจ์Œ์— ๋Œ€ํ•œ ์ „๋ฐ˜์ ์ธ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”.
63
-
64
- ๋‰ด์Šค ๋‚ด์šฉ:
65
- {combined_text}
66
-
67
- ๋‹ค์Œ ํ˜•์‹์„ ๋”ฐ๋ผ ์ž‘์„ฑํ•ด ์ฃผ์„ธ์š”:
68
- 1. ์ „์ฒด ๊ฐ์„ฑ: [๊ธ์ •/๋ถ€์ •/์ค‘๋ฆฝ]
69
- 2. ์ฃผ์š” ๊ธ์ • ์š”์ธ:
70
- - [ํ•ญ๋ชฉ1]
71
- - [ํ•ญ๋ชฉ2]
72
- 3. ์ฃผ์š” ๋ถ€์ • ์š”์ธ:
73
- - [ํ•ญ๋ชฉ1]
74
- - [ํ•ญ๋ชฉ2]
75
- 4. ์š”์•ฝ: [์ž์„ธํ•œ ์„ค๋ช…]
76
- """
77
- response = client.chat.completions.create(
78
- model="CohereForAI/c4ai-command-r-plus-08-2024",
79
- messages=[{"role": "user", "content": prompt}],
80
- temperature=0.3,
81
- max_tokens=1000
82
- )
83
-
84
- return response.choices[0].message.content
85
- except Exception as e:
86
- return f"๊ฐ์„ฑ ๋ถ„์„ ์‹คํŒจ: {str(e)}"
87
-
88
-
89
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”
90
- def init_db():
91
- """
92
- SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(search_results.db)๋ฅผ ์ดˆ๊ธฐํ™” (์—†์œผ๋ฉด ์ƒ์„ฑ).
93
- """
94
- db_path = pathlib.Path("search_results.db")
95
- conn = sqlite3.connect(db_path)
96
- c = conn.cursor()
97
- c.execute('''CREATE TABLE IF NOT EXISTS searches
98
- (id INTEGER PRIMARY KEY AUTOINCREMENT,
99
- keyword TEXT,
100
- country TEXT,
101
- results TEXT,
102
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP)''')
103
- conn.commit()
104
- conn.close()
105
-
106
- def save_to_db(keyword, country, results):
107
- """
108
- ํŠน์ • (ํ‚ค์›Œ๋“œ, ๊ตญ๊ฐ€) ์กฐํ•ฉ์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ.
109
- """
110
- conn = sqlite3.connect("search_results.db")
111
- c = conn.cursor()
112
- seoul_tz = pytz.timezone('Asia/Seoul')
113
- now = datetime.now(seoul_tz)
114
- timestamp = now.strftime('%Y-%m-%d %H:%M:%S')
115
-
116
- c.execute("""INSERT INTO searches
117
- (keyword, country, results, timestamp)
118
- VALUES (?, ?, ?, ?)""",
119
- (keyword, country, json.dumps(results), timestamp))
120
- conn.commit()
121
- conn.close()
122
-
123
- def load_from_db(keyword, country):
124
- """
125
- ํŠน์ • (ํ‚ค์›Œ๋“œ, ๊ตญ๊ฐ€) ์กฐํ•ฉ์— ๋Œ€ํ•œ ๊ฐ€์žฅ ์ตœ๊ทผ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ถˆ๋Ÿฌ์˜ด.
126
- ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜.
127
- """
128
- conn = sqlite3.connect("search_results.db")
129
- c = conn.cursor()
130
- c.execute(
131
- "SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
132
- (keyword, country)
133
- )
134
- result = c.fetchone()
135
- conn.close()
136
- if result:
137
- return json.loads(result[0]), convert_to_seoul_time(result[1])
138
- return None, None
139
-
140
- def display_results(articles):
141
- """
142
- ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ชฉ๋ก์„ ๋งˆํฌ๋‹ค์šด ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜.
143
- """
144
- output = ""
145
- for idx, article in enumerate(articles, 1):
146
- output += f"### {idx}. {article['title']}\n"
147
- output += f"์ถœ์ฒ˜: {article['channel']}\n"
148
- output += f"์‹œ๊ฐ„: {article['time']}\n"
149
- output += f"๋งํฌ: {article['link']}\n"
150
- output += f"์š”์•ฝ: {article['snippet']}\n\n"
151
- return output
152
-
153
-
154
- ########################################
155
- # 1) ๊ฒ€์ƒ‰ => ๊ธฐ์‚ฌ + ๊ฐ์„ฑ ๋ถ„์„, DB ์ €์žฅ
156
- ########################################
157
- def search_company(company):
158
- """
159
- ๋‹จ์ผ ํšŒ์‚ฌ(๋˜๋Š” ํ‚ค์›Œ๋“œ)์— ๋Œ€ํ•ด ๋ฏธ๊ตญ ๋‰ด์Šค ๊ฒ€์ƒ‰์„ ์ง„ํ–‰:
160
- 1) ๊ธฐ์‚ฌ ๋ชฉ๋ก์„ ๊ฒ€์ƒ‰
161
- 2) ๊ฐ์„ฑ ๋ถ„์„ ์ˆ˜ํ–‰
162
- 3) ๊ฒฐ๊ณผ๋ฅผ DB์— ์ €์žฅ
163
- 4) ๊ธฐ์‚ฌ์™€ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์˜ ์ถœ๋ ฅ์œผ๋กœ ๋ฐ˜ํ™˜
164
- """
165
- error_message, articles = serphouse_search(company, "United States")
166
- if not error_message and articles:
167
- # ๊ฐ์„ฑ ๋ถ„์„ ์ˆ˜ํ–‰ (ํ•œ๊ธ€ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜)
168
- analysis = analyze_sentiment_batch(articles, client)
169
-
170
- # DB์— ์ €์žฅํ•  ๋ฐ์ดํ„ฐ ์ค€๋น„
171
- store_dict = {
172
- "articles": articles,
173
- "analysis": analysis
174
- }
175
- save_to_db(company, "United States", store_dict)
176
-
177
- # ์ถœ๋ ฅ์šฉ ๋ฐ์ดํ„ฐ ์ค€๋น„
178
- output = display_results(articles)
179
- output += f"\n\n### ๊ฐ์„ฑ ๋ถ„์„ ๋ณด๊ณ ์„œ\n{analysis}\n"
180
- return output
181
- return f"{company}์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
182
-
183
- ########################################
184
- # 2) ๋ถˆ๋Ÿฌ์˜ค๊ธฐ => DB์—์„œ ๊ธฐ์‚ฌ + ๋ถ„์„ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
185
- ########################################
186
- def load_company(company):
187
- """
188
- ์ฃผ์–ด์ง„ ํšŒ์‚ฌ(๋˜๋Š” ํ‚ค์›Œ๋“œ)์— ๋Œ€ํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๊ฐ€์žฅ ์ตœ๊ทผ ๋ฏธ๊ตญ ๋‰ด์Šค ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ(๊ธฐ์‚ฌ + ๊ฐ์„ฑ ๋ถ„์„)๋ฅผ ๋ถˆ๋Ÿฌ์˜ด.
189
- """
190
- data, timestamp = load_from_db(company, "United States")
191
- if data:
192
- articles = data.get("articles", [])
193
- analysis = data.get("analysis", "")
194
-
195
- output = f"### {company} ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ\n๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {timestamp}\n\n"
196
- output += display_results(articles)
197
- output += f"\n\n### ๊ฐ์„ฑ ๋ถ„์„ ๋ณด๊ณ ์„œ\n{analysis}\n"
198
- return output
199
- return f"{company}์— ๋Œ€ํ•œ ์ €์žฅ๋œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
200
-
201
-
202
- ########################################
203
- # 3) show_stats() โ€“ ๋ณด๊ณ ์„œ ์ œ๋ชฉ ๋ณ€๊ฒฝ
204
- ########################################
205
- def show_stats():
206
- """
207
- ๊ฐ ํšŒ์‚ฌ(KOREAN_COMPANIES)์— ๋Œ€ํ•ด:
208
- - DB์— ์ €์žฅ๋œ ์ตœ์‹  ํƒ€์ž„์Šคํƒฌํ”„
209
- - ์ €์žฅ๋œ ๊ธฐ์‚ฌ ๊ฐœ์ˆ˜
210
- - ๊ฐ์„ฑ ๋ถ„์„ ๊ฒฐ๊ณผ
211
- ์„ ๋ณด๊ณ ์„œ ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜.
212
-
213
- ์ œ๋ชฉ: "EarnBOT ๋ถ„์„ ๋ณด๊ณ ์„œ"
214
- """
215
- conn = sqlite3.connect("search_results.db")
216
- c = conn.cursor()
217
-
218
- output = "## EarnBOT ๋ถ„์„ ๋ณด๊ณ ์„œ\n\n"
219
-
220
- data_list = []
221
- for company in KOREAN_COMPANIES:
222
- c.execute("""
223
- SELECT results, timestamp
224
- FROM searches
225
- WHERE keyword = ?
226
- ORDER BY timestamp DESC
227
- LIMIT 1
228
- """, (company,))
229
-
230
- row = c.fetchone()
231
- if row:
232
- results_json, timestamp = row
233
- data_list.append((company, timestamp, results_json))
234
-
235
- conn.close()
236
-
237
- def analyze_data(item):
238
- comp, tstamp, results_json = item
239
- data = json.loads(results_json)
240
- articles = data.get("articles", [])
241
- analysis = data.get("analysis", "")
242
-
243
- count_articles = len(articles)
244
- return (comp, tstamp, count_articles, analysis)
245
-
246
- results_list = []
247
- with ThreadPoolExecutor(max_workers=5) as executor:
248
- futures = [executor.submit(analyze_data, dl) for dl in data_list]
249
- for future in as_completed(futures):
250
- results_list.append(future.result())
251
-
252
- for comp, tstamp, count, analysis in results_list:
253
- seoul_time = convert_to_seoul_time(tstamp)
254
- output += f"### {comp}\n"
255
- output += f"- ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {seoul_time}\n"
256
- output += f"- ์ €์žฅ๋œ ๊ธฐ์‚ฌ ์ˆ˜: {count}\n\n"
257
- if analysis:
258
- output += "#### ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„\n"
259
- output += f"{analysis}\n\n"
260
- output += "---\n\n"
261
-
262
- return output
263
-
264
-
265
- def search_all_companies():
266
- """
267
- KOREAN_COMPANIES์˜ ๋ชจ๋“  ํ•ญ๋ชฉ์— ๋Œ€ํ•ด(๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ) ๊ฒ€์ƒ‰ ํ›„,
268
- ๊ฐ์„ฑ ๋ถ„์„ ์ˆ˜ํ–‰ ๋ฐ DB ์ €์žฅ => ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ๋งˆํฌ๋‹ค์šด ๋ฌธ์ž์—ด๋กœ ๋ฐ˜ํ™˜.
269
- """
270
- overall_result = "# [์ „์ฒด ํšŒ์‚ฌ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ]\n\n"
271
-
272
- def do_search(comp):
273
- return comp, search_company(comp)
274
-
275
- with ThreadPoolExecutor(max_workers=5) as executor:
276
- futures = [executor.submit(do_search, c) for c in KOREAN_COMPANIES]
277
- for future in as_completed(futures):
278
- comp, res_text = future.result()
279
- overall_result += f"## {comp}\n"
280
- overall_result += res_text + "\n\n"
281
-
282
- return overall_result
283
-
284
- def load_all_companies():
285
- """
286
- DB์— ์ €์žฅ๋œ ๋ชจ๋“  ํšŒ์‚ฌ(KOREAN_COMPANIES)์˜ ๊ธฐ์‚ฌ์™€ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋Ÿฌ์™€ ๋งˆํฌ๋‹ค์šด์œผ๋กœ ๋ฐ˜ํ™˜.
287
- """
288
- overall_result = "# [์ „์ฒด ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ ์ถœ๋ ฅ]\n\n"
289
-
290
- for comp in KOREAN_COMPANIES:
291
- overall_result += f"## {comp}\n"
292
- overall_result += load_company(comp)
293
- overall_result += "\n"
294
- return overall_result
295
-
296
- def full_summary_report():
297
- """
298
- 1) ๋ชจ๋“  ํšŒ์‚ฌ๋ฅผ ๊ฒ€์ƒ‰(๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ) -> 2) DB์—์„œ ๊ฒฐ๊ณผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ -> 3) ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„ ํ‘œ์‹œ
299
- ์„ธ ๋‹จ๊ณ„์˜ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜์˜ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ๋กœ ๋ฐ˜ํ™˜.
300
- """
301
- # 1) ๋ชจ๋“  ํšŒ์‚ฌ ๊ฒ€์ƒ‰ ๋ฐ DB ์ €์žฅ
302
- search_result_text = search_all_companies()
303
-
304
- # 2) DB์—์„œ ๊ฒฐ๊ณผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
305
- load_result_text = load_all_companies()
306
-
307
- # 3) ํ†ต๊ณ„ ํ‘œ์‹œ โ€“ EarnBOT ๋ถ„์„ ๋ณด๊ณ ์„œ
308
- stats_text = show_stats()
309
-
310
- combined_report = (
311
- "# ์ „์ฒด ๋ถ„์„ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ\n\n"
312
- "์‹คํ–‰ ์ˆœ์„œ:\n"
313
- "1. ๋ชจ๋“  ํšŒ์‚ฌ ๊ฒ€์ƒ‰(๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ) ๋ฐ ๊ฐ์„ฑ ๋ถ„์„ โ†’ 2. DB์—์„œ ๊ฒฐ๊ณผ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ โ†’ 3. ์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„ ํ‘œ์‹œ\n\n"
314
- f"{search_result_text}\n\n"
315
- f"{load_result_text}\n\n"
316
- "## [์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„]\n\n"
317
- f"{stats_text}"
318
- )
319
- return combined_report
320
-
321
-
322
- ########################################
323
- # ์ถ”๊ฐ€ ๊ธฐ๋Šฅ: ์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ƒ‰
324
- ########################################
325
- def search_custom(query, country):
326
- """
327
- ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ (ํ‚ค์›Œ๋“œ, ๊ตญ๊ฐ€)์— ๋Œ€ํ•ด:
328
- 1) ๊ฒ€์ƒ‰ ๋ฐ ๊ฐ์„ฑ ๋ถ„์„ ์ˆ˜ํ–‰ ํ›„ DB ์ €์žฅ
329
- 2) DB์—์„œ ๋ถˆ๋Ÿฌ์™€ ๊ธฐ์‚ฌ์™€ ๋ถ„์„ ๊ฒฐ๊ณผ ํ‘œ์‹œ
330
- """
331
- error_message, articles = serphouse_search(query, country)
332
- if error_message:
333
- return f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {error_message}"
334
- if not articles:
335
- return "์ž…๋ ฅํ•˜์‹  ๊ฒ€์ƒ‰์–ด์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
336
-
337
- # 1) ๊ฐ์„ฑ ๋ถ„์„ ์ˆ˜ํ–‰ (ํ•œ๊ธ€)
338
- analysis = analyze_sentiment_batch(articles, client)
339
-
340
- # 2) DB์— ์ €์žฅ
341
- save_data = {
342
- "articles": articles,
343
- "analysis": analysis
344
- }
345
- save_to_db(query, country, save_data)
346
-
347
- # 3) DB์—์„œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
348
- loaded_data, timestamp = load_from_db(query, country)
349
- if not loaded_data:
350
- return "DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค."
351
-
352
- # 4) ์ตœ์ข… ์ถœ๋ ฅ ์ค€๋น„
353
- out = f"## [์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ]\n\n"
354
- out += f"**๊ฒ€์ƒ‰์–ด**: {query}\n\n"
355
- out += f"**๊ตญ๊ฐ€**: {country}\n\n"
356
- out += f"**ํƒ€์ž„์Šคํƒฌํ”„**: {timestamp}\n\n"
357
-
358
- arts = loaded_data.get("articles", [])
359
- analy = loaded_data.get("analysis", "")
360
-
361
- out += display_results(arts)
362
- out += f"### ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„\n{analy}\n"
363
-
364
- return out
365
-
366
-
367
- ########################################
368
- # API ์ธ์ฆ ์„ค์ •
369
- ########################################
370
- ACCESS_TOKEN = os.getenv("HF_TOKEN")
371
- if not ACCESS_TOKEN:
372
- raise ValueError("HF_TOKEN ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
373
-
374
- client = OpenAI(
375
- base_url="https://api-inference.huggingface.co/v1/",
376
- api_key=ACCESS_TOKEN,
377
- )
378
-
379
- API_KEY = os.getenv("SERPHOUSE_API_KEY")
380
-
381
-
382
- ########################################
383
- # ๊ตญ๊ฐ€๋ณ„ ์„ค์ •
384
- ########################################
385
- COUNTRY_LANGUAGES = {
386
- "United States": "en",
387
- "KOREA": "ko",
388
- "United Kingdom": "en",
389
- "Taiwan": "zh-TW",
390
- "Canada": "en",
391
- "Australia": "en",
392
- "Germany": "de",
393
- "France": "fr",
394
- "Japan": "ja",
395
- "India": "hi",
396
- "Brazil": "pt",
397
- "Mexico": "es",
398
- "Russia": "ru",
399
- "Italy": "it",
400
- "Spain": "es",
401
- "Netherlands": "nl",
402
- "Singapore": "en",
403
- "Hong Kong": "zh-HK",
404
- "Indonesia": "id",
405
- "Malaysia": "ms",
406
- "Philippines": "tl",
407
- "Thailand": "th",
408
- "Vietnam": "vi",
409
- "Belgium": "nl",
410
- "Denmark": "da",
411
- "Finland": "fi",
412
- "Ireland": "en",
413
- "Norway": "no",
414
- "Poland": "pl",
415
- "Sweden": "sv",
416
- "Switzerland": "de",
417
- "Austria": "de",
418
- "Czech Republic": "cs",
419
- "Greece": "el",
420
- "Hungary": "hu",
421
- "Portugal": "pt",
422
- "Romania": "ro",
423
- "Turkey": "tr",
424
- "Israel": "he",
425
- "Saudi Arabia": "ar",
426
- "United Arab Emirates": "ar",
427
- "South Africa": "en",
428
- "Argentina": "es",
429
- "Chile": "es",
430
- "Colombia": "es",
431
- "Peru": "es",
432
- "Venezuela": "es",
433
- "New Zealand": "en",
434
- "Bangladesh": "bn",
435
- "Pakistan": "ur",
436
- "Egypt": "ar",
437
- "Morocco": "ar",
438
- "Nigeria": "en",
439
- "Kenya": "sw",
440
- "Ukraine": "uk",
441
- "Croatia": "Croatia",
442
- "Slovakia": "Slovakia",
443
- "Bulgaria": "bg",
444
- "Serbia": "sr",
445
- "Estonia": "et",
446
- "Latvia": "lv",
447
- "Lithuania": "lt",
448
- "Slovenia": "sl",
449
- "Luxembourg": "Luxembourg",
450
- "Malta": "Malta",
451
- "Cyprus": "Cyprus",
452
- "Iceland": "Iceland"
453
- }
454
-
455
- COUNTRY_LOCATIONS = {
456
- "United States": "United States",
457
- "KOREA": "kr",
458
- "United Kingdom": "United Kingdom",
459
- "Taiwan": "Taiwan",
460
- "Canada": "Canada",
461
- "Australia": "Australia",
462
- "Germany": "Germany",
463
- "France": "France",
464
- "Japan": "Japan",
465
- "India": "India",
466
- "Brazil": "Brazil",
467
- "Mexico": "Mexico",
468
- "Russia": "Russia",
469
- "Italy": "Italy",
470
- "Spain": "Spain",
471
- "Netherlands": "Netherlands",
472
- "Singapore": "Singapore",
473
- "Hong Kong": "Hong Kong",
474
- "Indonesia": "Indonesia",
475
- "Malaysia": "Malaysia",
476
- "Philippines": "Philippines",
477
- "Thailand": "Thailand",
478
- "Vietnam": "Vietnam",
479
- "Belgium": "Belgium",
480
- "Denmark": "Denmark",
481
- "Finland": "Finland",
482
- "Ireland": "Ireland",
483
- "Norway": "Norway",
484
- "Poland": "Poland",
485
- "Sweden": "Sweden",
486
- "Switzerland": "Switzerland",
487
- "Austria": "Austria",
488
- "Czech Republic": "Czech Republic",
489
- "Greece": "Greece",
490
- "Hungary": "Hungary",
491
- "Portugal": "Portugal",
492
- "Romania": "Romania",
493
- "Turkey": "Turkey",
494
- "Israel": "Israel",
495
- "Saudi Arabia": "Saudi Arabia",
496
- "United Arab Emirates": "United Arab Emirates",
497
- "South Africa": "South Africa",
498
- "Argentina": "Argentina",
499
- "Chile": "Chile",
500
- "Colombia": "Colombia",
501
- "Peru": "Peru",
502
- "Venezuela": "Venezuela",
503
- "New Zealand": "New Zealand",
504
- "Bangladesh": "Bangladesh",
505
- "Pakistan": "Pakistan",
506
- "Egypt": "Egypt",
507
- "Morocco": "Morocco",
508
- "Nigeria": "Nigeria",
509
- "Kenya": "Kenya",
510
- "Ukraine": "Ukraine",
511
- "Croatia": "Croatia",
512
- "Slovakia": "Slovakia",
513
- "Bulgaria": "Bulgaria",
514
- "Serbia": "Serbia",
515
- "Estonia": "et",
516
- "Latvia": "lv",
517
- "Lithuania": "lt",
518
- "Slovenia": "sl",
519
- "Luxembourg": "Luxembourg",
520
- "Malta": "Malta",
521
- "Cyprus": "Cyprus",
522
- "Iceland": "Iceland"
523
- }
524
-
525
-
526
- @lru_cache(maxsize=100)
527
- def translate_query(query, country):
528
- """
529
- ๋น„๊ณต์‹ Google Translation API๋ฅผ ์‚ฌ์šฉํ•ด ๋Œ€์ƒ ๊ตญ๊ฐ€์˜ ์–ธ์–ด๋กœ ๊ฒ€์ƒ‰์–ด ๋ฒˆ์—ญ.
530
- ๋ฒˆ์—ญ์— ์‹คํŒจํ•˜๊ฑฐ๋‚˜ ์˜์–ด์ผ ๊ฒฝ์šฐ ์›๋ณธ ๊ฒ€์ƒ‰์–ด ๋ฐ˜ํ™˜.
531
- """
532
- try:
533
- if is_english(query):
534
- return query
535
-
536
- if country in COUNTRY_LANGUAGES:
537
- if country == "South Korea":
538
- return query
539
- target_lang = COUNTRY_LANGUAGES[country]
540
-
541
- url = "https://translate.googleapis.com/translate_a/single"
542
- params = {
543
- "client": "gtx",
544
- "sl": "auto",
545
- "tl": target_lang,
546
- "dt": "t",
547
- "q": query
548
- }
549
-
550
- session = requests.Session()
551
- retries = Retry(total=3, backoff_factor=0.5)
552
- session.mount('https://', HTTPAdapter(max_retries=retries))
553
-
554
- response = session.get(url, params=params, timeout=(5, 10))
555
- translated_text = response.json()[0][0][0]
556
- return translated_text
557
- return query
558
-
559
- except Exception as e:
560
- print(f"๋ฒˆ์—ญ ์˜ค๋ฅ˜: {str(e)}")
561
- return query
562
-
563
- def is_english(text):
564
- """
565
- ๋ฌธ์ž์—ด์ด ์ฃผ๋กœ ์˜์–ด์ธ์ง€ ํ™•์ธ (๋ฌธ์ž ์ฝ”๋“œ ๋ฒ”์œ„ ํ™•์ธ).
566
- """
567
- return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
568
-
569
- def search_serphouse(query, country, page=1, num_result=10):
570
- """
571
- SerpHouse API์— ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉฐ,
572
- 'news' ํƒญ(๋‚ ์งœ์ˆœ ์ •๋ ฌ)๋กœ ๊ฒ€์ƒ‰.
573
- ๋ฐ˜ํ™˜๊ฐ’์€ 'results' ๋˜๋Š” 'error'๊ฐ€ ํฌํ•จ๋œ dict.
574
- """
575
- url = "https://api.serphouse.com/serp/live"
576
-
577
- now = datetime.utcnow()
578
- yesterday = now - timedelta(days=1)
579
- date_range = f"{yesterday.strftime('%Y-%m-%d')},{now.strftime('%Y-%m-%d')}"
580
-
581
- translated_query = translate_query(query, country)
582
-
583
- payload = {
584
- "data": {
585
- "q": translated_query,
586
- "domain": "google.com",
587
- "loc": COUNTRY_LOCATIONS.get(country, "United States"),
588
- "lang": COUNTRY_LANGUAGES.get(country, "en"),
589
- "device": "desktop",
590
- "serp_type": "news",
591
- "page": str(page),
592
- "num": "100",
593
- "date_range": date_range,
594
- "sort_by": "date"
595
- }
596
- }
597
-
598
- headers = {
599
- "accept": "application/json",
600
- "content-type": "application/json",
601
- "authorization": f"Bearer {API_KEY}"
602
- }
603
-
604
- try:
605
- session = requests.Session()
606
-
607
- retries = Retry(
608
- total=5,
609
- backoff_factor=1,
610
- status_forcelist=[500, 502, 503, 504, 429],
611
- allowed_methods=["POST"]
612
- )
613
-
614
- adapter = HTTPAdapter(max_retries=retries)
615
- session.mount('http://', adapter)
616
- session.mount('https://', adapter)
617
-
618
- response = session.post(
619
- url,
620
- json=payload,
621
- headers=headers,
622
- timeout=(30, 30)
623
- )
624
-
625
- response.raise_for_status()
626
- return {"results": response.json(), "translated_query": translated_query}
627
-
628
- except requests.exceptions.Timeout:
629
- return {
630
- "error": "๊ฒ€์ƒ‰ ์‹œ๊ฐ„์ด ์ดˆ๊ณผ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด ์ฃผ์„ธ์š”.",
631
- "translated_query": query
632
- }
633
- except requests.exceptions.RequestException as e:
634
- return {
635
- "error": f"๊ฒ€์ƒ‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}",
636
- "translated_query": query
637
- }
638
- except Exception as e:
639
- return {
640
- "error": f"์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}",
641
- "translated_query": query
642
- }
643
-
644
- def format_results_from_raw(response_data):
645
- """
646
- SerpHouse API์˜ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜์—ฌ (์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€, ๊ธฐ์‚ฌ ๋ชฉ๋ก)์„ ๋ฐ˜ํ™˜.
647
- """
648
- if "error" in response_data:
649
- return "์˜ค๋ฅ˜: " + response_data["error"], []
650
-
651
- try:
652
- results = response_data["results"]
653
- translated_query = response_data["translated_query"]
654
-
655
- news_results = results.get('results', {}).get('results', {}).get('news', [])
656
- if not news_results:
657
- return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", []
658
-
659
- # ํ•œ๊ตญ ๋„๋ฉ”์ธ ๋ฐ ํ‚ค์›Œ๋“œ ํ•„ํ„ฐ๋ง (์˜ˆ์‹œ)
660
- korean_domains = [
661
- '.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
662
- 'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
663
- ]
664
- korean_keywords = [
665
- 'korea', 'korean', 'seoul', 'busan', 'incheon', 'daegu',
666
- 'gwangju', 'daejeon', 'ulsan', 'sejong'
667
- ]
668
-
669
- filtered_articles = []
670
- for idx, result in enumerate(news_results, 1):
671
- url = result.get("url", result.get("link", "")).lower()
672
- title = result.get("title", "").lower()
673
- channel = result.get("channel", result.get("source", "")).lower()
674
-
675
- is_korean_content = (
676
- any(domain in url or domain in channel for domain in korean_domains) or
677
- any(keyword in title for keyword in korean_keywords)
678
- )
679
-
680
- # ํ•œ๊ตญ ๊ด€๋ จ ์ฝ˜ํ…์ธ  ์ œ์™ธ
681
- if not is_korean_content:
682
- filtered_articles.append({
683
- "index": idx,
684
- "title": result.get("title", "์ œ๋ชฉ ์—†์Œ"),
685
- "link": url,
686
- "snippet": result.get("snippet", "๋‚ด์šฉ ์—†์Œ"),
687
- "channel": result.get("channel", result.get("source", "์•Œ ์ˆ˜ ์—†์Œ")),
688
- "time": result.get("time", result.get("date", "์‹œ๊ฐ„ ์ •๋ณด ์—†์Œ")),
689
- "image_url": result.get("img", result.get("thumbnail", "")),
690
- "translated_query": translated_query
691
- })
692
-
693
- return "", filtered_articles
694
- except Exception as e:
695
- return f"๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", []
696
-
697
- def serphouse_search(query, country):
698
- """
699
- ๊ฒ€์ƒ‰ ๋ฐ ๊ฒฐ๊ณผ ํฌ๋งทํŒ…์„ ์œ„ํ•œ ํ—ฌํผ ํ•จ์ˆ˜.
700
- (์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€, ๊ธฐ์‚ฌ ๋ชฉ๋ก)์„ ๋ฐ˜ํ™˜.
701
- """
702
- response_data = search_serphouse(query, country)
703
- return format_results_from_raw(response_data)
704
-
705
-
706
- # Refined, modern, and sleek custom CSS
707
- css = """
708
- body {
709
- background: linear-gradient(to bottom right, #f9fafb, #ffffff);
710
- font-family: 'Arial', sans-serif;
711
- }
712
-
713
- /* Hide default Gradio footer */
714
- footer {
715
- visibility: hidden;
716
- }
717
-
718
- /* Header/Status area */
719
- #status_area {
720
- background: rgba(255, 255, 255, 0.9);
721
- padding: 15px;
722
- border-bottom: 1px solid #ddd;
723
- margin-bottom: 20px;
724
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
725
- }
726
-
727
- /* Results area */
728
- #results_area {
729
- padding: 10px;
730
- margin-top: 10px;
731
- }
732
-
733
- /* Tabs style */
734
- .tabs {
735
- border-bottom: 2px solid #ddd !important;
736
- margin-bottom: 20px !important;
737
- }
738
-
739
- .tab-nav {
740
- border-bottom: none !important;
741
- margin-bottom: 0 !important;
742
- }
743
-
744
- .tab-nav button {
745
- font-weight: bold !important;
746
- padding: 10px 20px !important;
747
- background-color: #f0f0f0 !important;
748
- border: 1px solid #ccc !important;
749
- border-radius: 5px !important;
750
- margin-right: 5px !important;
751
- }
752
-
753
- .tab-nav button.selected {
754
- border-bottom: 2px solid #1f77b4 !important;
755
- background-color: #e6f2fa !important;
756
- color: #1f77b4 !important;
757
- }
758
-
759
- /* Status message styling */
760
- #status_area .markdown-text {
761
- font-size: 1.1em;
762
- color: #2c3e50;
763
- padding: 10px 0;
764
- }
765
-
766
- /* Main container grouping */
767
- .group {
768
- border: 1px solid #eee;
769
- padding: 15px;
770
- margin-bottom: 15px;
771
- border-radius: 5px;
772
- background: white;
773
- transition: all 0.3s ease;
774
- opacity: 0;
775
- transform: translateY(20px);
776
- }
777
- .group.visible {
778
- opacity: 1;
779
- transform: translateY(0);
780
- }
781
-
782
- /* Buttons */
783
- .primary-btn {
784
- background: #1f77b4 !important;
785
- border: none !important;
786
- color: #fff !important;
787
- border-radius: 5px !important;
788
- padding: 10px 20px !important;
789
- cursor: pointer !important;
790
- }
791
- .primary-btn:hover {
792
- background: #155a8c !important;
793
- }
794
-
795
- .secondary-btn {
796
- background: #f0f0f0 !important;
797
- border: 1px solid #ccc !important;
798
- color: #333 !important;
799
- border-radius: 5px !important;
800
- padding: 10px 20px !important;
801
- cursor: pointer !important;
802
- }
803
- .secondary-btn:hover {
804
- background: #e0e0e0 !important;
805
- }
806
-
807
- /* Input fields */
808
- .textbox {
809
- border: 1px solid #ddd !important;
810
- border-radius: 4px !important;
811
- }
812
-
813
- /* Progress bar container */
814
- .progress-container {
815
- position: fixed;
816
- top: 0;
817
- left: 0;
818
- width: 100%;
819
- height: 6px;
820
- background: #e0e0e0;
821
- z-index: 1000;
822
- }
823
-
824
- /* Progress bar */
825
- .progress-bar {
826
- height: 100%;
827
- background: linear-gradient(90deg, #2196F3, #00BCD4);
828
- box-shadow: 0 0 10px rgba(33, 150, 243, 0.5);
829
- transition: width 0.3s ease;
830
- animation: progress-glow 1.5s ease-in-out infinite;
831
- }
832
-
833
- /* Progress text */
834
- .progress-text {
835
- position: fixed;
836
- top: 8px;
837
- left: 50%;
838
- transform: translateX(-50%);
839
- background: #333;
840
- color: white;
841
- padding: 4px 12px;
842
- border-radius: 15px;
843
- font-size: 14px;
844
- z-index: 1001;
845
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
846
- }
847
-
848
- /* Progress bar animation */
849
- @keyframes progress-glow {
850
- 0% {
851
- box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
852
- }
853
- 50% {
854
- box-shadow: 0 0 20px rgba(33, 150, 243, 0.8);
855
- }
856
- 100% {
857
- box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
858
- }
859
- }
860
-
861
- /* Loading state */
862
- .loading {
863
- opacity: 0.7;
864
- pointer-events: none;
865
- transition: opacity 0.3s ease;
866
- }
867
-
868
- /* Responsive design for smaller screens */
869
- @media (max-width: 768px) {
870
- .group {
871
- padding: 10px;
872
- margin-bottom: 15px;
873
- }
874
-
875
- .progress-text {
876
- font-size: 12px;
877
- padding: 3px 10px;
878
- }
879
- }
880
-
881
- /* Example section styling */
882
- .examples-table {
883
- margin-top: 10px !important;
884
- margin-bottom: 20px !important;
885
- }
886
-
887
- .examples-table button {
888
- background-color: #f0f0f0 !important;
889
- border: 1px solid #ddd !important;
890
- border-radius: 4px !important;
891
- padding: 5px 10px !important;
892
- margin: 2px !important;
893
- transition: all 0.3s ease !important;
894
- }
895
-
896
- .examples-table button:hover {
897
- background-color: #e0e0e0 !important;
898
- transform: translateY(-1px) !important;
899
- box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
900
- }
901
-
902
- .examples-table .label {
903
- font-weight: bold !important;
904
- color: #444 !important;
905
- margin-bottom: 5px !important;
906
- }
907
- """
908
-
909
- # --- Gradio ์ธํ„ฐํŽ˜์ด์Šค (UI ๋ถ€๋ถ„) ---
910
- with gr.Blocks(css=css, title="NewsAI ์„œ๋น„์Šค") as iface:
911
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™” (init_db() ํ˜ธ์ถœ)
912
- init_db()
913
-
914
- gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fopenfree-MoneyRadar.hf.space">
915
- <img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fopenfree-MoneyRadar.hf.space&countColor=%23263759" />
916
- </a>""")
917
-
918
-
919
- with gr.Tabs():
920
- with gr.Tab("MoneyRadar"):
921
- # ์‚ฌ์šฉ ๋ฐฉ๋ฒ• ๋ฐ ๊ธฐ๋Šฅ ์„ค๋ช… (ํ•œ๊ธ€)
922
- gr.Markdown(
923
- """
924
- ## MoneyRadar
925
- ์ตœ์‹  24์‹œ๊ฐ„ ๋‚ด ์ƒ์œ„ 100๊ฐœ์˜ ์šฐ์„ ์ˆœ์œ„ ๋‰ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ถ”์ถœํ•˜์—ฌ
926
- ์ˆ˜์ต ๊ธฐํšŒ๋ฅผ ํฌ์ฐฉํ•ฉ๋‹ˆ๋‹ค.
927
-
928
- **์„œ๋น„์Šค ์‚ฌ์šฉ ๋ฐฉ๋ฒ•**:
929
- 1. **์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ƒ‰**: ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ๋Œ€์ƒ ๊ตญ๊ฐ€๋ฅผ ์„ ํƒํ•˜์—ฌ ์ตœ์‹  ๋‰ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
930
- ์‹œ์Šคํ…œ์ด ์ž๋™์œผ๋กœ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
931
- 2. **์ „์ฒด ๋ถ„์„ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ ์ƒ์„ฑ**: ์•„๋ž˜ ์ž‘์—…์„ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
932
- - ์‚ฌ์ „ ์ •์˜๋œ ๋ชจ๋“  ํšŒ์‚ฌ๋ฅผ ๋ณ‘๋ ฌ๋กœ ๊ฒ€์ƒ‰
933
- - ๊ธฐ์‚ฌ์™€ ๊ฐ์„ฑ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ
934
- - ์ „์ฒด ๊ฒฐ๊ณผ๋ฅผ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ๋กœ ํ‘œ์‹œ
935
- 3. **๊ฐœ๋ณ„ ํšŒ์‚ฌ ๊ฒ€์ƒ‰/๋ถˆ๋Ÿฌ์˜ค๊ธฐ**:
936
- - **๊ฒ€์ƒ‰**: ์„ ํƒํ•œ ํšŒ์‚ฌ์— ๋Œ€ํ•ด Google์—์„œ ์ตœ์‹  ๋‰ด์Šค๋ฅผ ๊ฒ€์ƒ‰ ๋ฐ ๋ถ„์„
937
- - **DB ๋ถˆ๋Ÿฌ์˜ค๊ธฐ**: ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์ €์žฅ๋œ ์ตœ์‹  ๋‰ด์Šค์™€ ๊ฐ์„ฑ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค.
938
-
939
- **์ฃผ์š” ๊ธฐ๋Šฅ**:
940
- - **์‹ค์‹œ๊ฐ„ ๋‰ด์Šค ์Šคํฌ๋ž˜ํ•‘**: ์—ฌ๋Ÿฌ ์ง€์—ญ์—์„œ ์ตœ์‹  ๊ธฐ์‚ฌ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
941
- - **๊ณ ๊ธ‰ ๊ฐ์„ฑ ๋ถ„์„**: ์ตœ์‹  NLP ๋ชจ๋ธ์„ ํ™œ์šฉํ•œ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
942
- - **๋ฐ์ดํ„ฐ ์˜์†์„ฑ**: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ž๋™ ์ €์žฅ ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ.
943
- - **์œ ์—ฐ์„ฑ**: ์‚ฌ์ „ ์ •์˜๋œ ํ‚ค์›Œ๋“œ๋ฟ ์•„๋‹ˆ๋ผ ์›ํ•˜๋Š” ํ‚ค์›Œ๋“œ/๊ตญ๊ฐ€ ๊ฒ€์ƒ‰์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
944
-
945
- **์ปค๋ฎค๋‹ˆํ‹ฐ**: https://discord.gg/openfreeai
946
- ---
947
- """
948
- )
949
-
950
- # ์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ƒ‰ ์„น์…˜
951
- with gr.Group():
952
- gr.Markdown("### ์‚ฌ์šฉ์ž ์ •์˜ ๊ฒ€์ƒ‰")
953
- with gr.Row():
954
- with gr.Column():
955
- user_input = gr.Textbox(
956
- label="ํ‚ค์›Œ๋“œ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”",
957
- placeholder="์˜ˆ: Apple, Samsung ๋“ฑ",
958
- elem_classes="textbox"
959
- )
960
- with gr.Column():
961
- country_selection = gr.Dropdown(
962
- choices=list(COUNTRY_LOCATIONS.keys()),
963
- value="United States",
964
- label="๊ตญ๊ฐ€ ์„ ํƒ"
965
- )
966
- with gr.Column():
967
- custom_search_btn = gr.Button(
968
- "๊ฒ€์ƒ‰",
969
- variant="primary",
970
- elem_classes="primary-btn"
971
- )
972
-
973
- custom_search_output = gr.Markdown()
974
-
975
- custom_search_btn.click(
976
- fn=search_custom,
977
- inputs=[user_input, country_selection],
978
- outputs=custom_search_output
979
- )
980
-
981
- # ์ „์ฒด ๋ณด๊ณ ์„œ ์ƒ์„ฑ์„ ์œ„ํ•œ ๋ฒ„ํŠผ
982
- with gr.Row():
983
- full_report_btn = gr.Button(
984
- "์ „์ฒด ๋ถ„์„ ์ข…ํ•ฉ ๋ณด๊ณ ์„œ ์ƒ์„ฑ",
985
- variant="primary",
986
- elem_classes="primary-btn"
987
- )
988
- full_report_display = gr.Markdown()
989
-
990
- full_report_btn.click(
991
- fn=full_summary_report,
992
- outputs=full_report_display
993
- )
994
-
995
- # ๊ฐœ๋ณ„ ํšŒ์‚ฌ์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰/๋ถˆ๋Ÿฌ์˜ค๊ธฐ
996
- with gr.Column():
997
- for i in range(0, len(KOREAN_COMPANIES), 2):
998
- with gr.Row():
999
- # ์™ผ์ชฝ ์ปฌ๋Ÿผ
1000
- with gr.Column():
1001
- company = KOREAN_COMPANIES[i]
1002
- with gr.Group():
1003
- gr.Markdown(f"### {company}")
1004
- with gr.Row():
1005
- search_btn = gr.Button(
1006
- "๊ฒ€์ƒ‰",
1007
- variant="primary",
1008
- elem_classes="primary-btn"
1009
- )
1010
- load_btn = gr.Button(
1011
- "DB ๋ถˆ๋Ÿฌ์˜ค๊ธฐ",
1012
- variant="secondary",
1013
- elem_classes="secondary-btn"
1014
- )
1015
- result_display = gr.Markdown()
1016
-
1017
- search_btn.click(
1018
- fn=lambda c=company: search_company(c),
1019
- outputs=result_display
1020
- )
1021
- load_btn.click(
1022
- fn=lambda c=company: load_company(c),
1023
- outputs=result_display
1024
- )
1025
-
1026
- # ์˜ค๋ฅธ์ชฝ ์ปฌ๋Ÿผ (์กด์žฌํ•  ๊ฒฝ์šฐ)
1027
- if i + 1 < len(KOREAN_COMPANIES):
1028
- with gr.Column():
1029
- company = KOREAN_COMPANIES[i + 1]
1030
- with gr.Group():
1031
- gr.Markdown(f"### {company}")
1032
- with gr.Row():
1033
- search_btn = gr.Button(
1034
- "๊ฒ€์ƒ‰",
1035
- variant="primary",
1036
- elem_classes="primary-btn"
1037
- )
1038
- load_btn = gr.Button(
1039
- "DB ๋ถˆ๋Ÿฌ์˜ค๊ธฐ",
1040
- variant="secondary",
1041
- elem_classes="secondary-btn"
1042
- )
1043
- result_display = gr.Markdown()
1044
-
1045
- search_btn.click(
1046
- fn=lambda c=company: search_company(c),
1047
- outputs=result_display
1048
- )
1049
- load_btn.click(
1050
- fn=lambda c=company: load_company(c),
1051
- outputs=result_display
1052
- )
1053
-
1054
- # Gradio ์ธํ„ฐํŽ˜์ด์Šค ์‹คํ–‰
1055
- iface.launch(
1056
- server_name="0.0.0.0",
1057
- server_port=7860,
1058
- share=True,
1059
- ssl_verify=False,
1060
- show_error=True
1061
- )
 
 
 
 
1
  import os
2
+ exec(os.environ.get('APP'))