Spaces:
Building
Building
Update app.py
Browse files
app.py
CHANGED
@@ -34,6 +34,11 @@ KOREAN_COMPANIES = [
|
|
34 |
"investing"
|
35 |
]
|
36 |
|
|
|
|
|
|
|
|
|
|
|
37 |
def convert_to_seoul_time(timestamp_str):
|
38 |
try:
|
39 |
dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
|
@@ -49,7 +54,6 @@ def analyze_sentiment_batch(articles, client):
|
|
49 |
OpenAI API๋ฅผ ํตํด ๋ด์ค ๊ธฐ์ฌ๋ค์ ์ข
ํฉ ๊ฐ์ฑ ๋ถ์์ ์ํ
|
50 |
"""
|
51 |
try:
|
52 |
-
# ๋ชจ๋ ๊ธฐ์ฌ์ ์ ๋ชฉ๊ณผ ๋ด์ฉ์ ํ๋์ ํ
์คํธ๋ก ๊ฒฐํฉ
|
53 |
combined_text = "\n\n".join([
|
54 |
f"์ ๋ชฉ: {article.get('title', '')}\n๋ด์ฉ: {article.get('snippet', '')}"
|
55 |
for article in articles
|
@@ -83,7 +87,10 @@ def analyze_sentiment_batch(articles, client):
|
|
83 |
return f"๊ฐ์ฑ ๋ถ์ ์คํจ: {str(e)}"
|
84 |
|
85 |
|
86 |
-
|
|
|
|
|
|
|
87 |
def init_db():
|
88 |
db_path = pathlib.Path("search_results.db")
|
89 |
conn = sqlite3.connect(db_path)
|
@@ -98,9 +105,6 @@ def init_db():
|
|
98 |
conn.close()
|
99 |
|
100 |
def save_to_db(keyword, country, results):
|
101 |
-
"""
|
102 |
-
ํน์ (keyword, country) ์กฐํฉ์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ DB์ ์ ์ฅ
|
103 |
-
"""
|
104 |
conn = sqlite3.connect("search_results.db")
|
105 |
c = conn.cursor()
|
106 |
seoul_tz = pytz.timezone('Asia/Seoul')
|
@@ -115,9 +119,6 @@ def save_to_db(keyword, country, results):
|
|
115 |
conn.close()
|
116 |
|
117 |
def load_from_db(keyword, country):
|
118 |
-
"""
|
119 |
-
ํน์ (keyword, country) ์กฐํฉ์ ๋ํ ๊ฐ์ฅ ์ต๊ทผ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ DB์์ ๋ถ๋ฌ์ค๊ธฐ
|
120 |
-
"""
|
121 |
conn = sqlite3.connect("search_results.db")
|
122 |
c = conn.cursor()
|
123 |
c.execute("SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
|
@@ -128,10 +129,31 @@ def load_from_db(keyword, country):
|
|
128 |
return json.loads(result[0]), convert_to_seoul_time(result[1])
|
129 |
return None, None
|
130 |
|
131 |
-
|
|
|
|
|
|
|
132 |
"""
|
133 |
-
|
134 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
135 |
output = ""
|
136 |
for idx, article in enumerate(articles, 1):
|
137 |
output += f"### {idx}. {article['title']}\n"
|
@@ -142,44 +164,29 @@ def display_results(articles):
|
|
142 |
return output
|
143 |
|
144 |
|
145 |
-
|
146 |
-
# 1) ๊ฒ์ ์ => ๊ธฐ์ฌ + ๋ถ์ ๋์ ์ถ๋ ฅ, DB ์ ์ฅ
|
147 |
-
|
148 |
def search_company(company):
|
149 |
-
"""
|
150 |
-
๋จ์ผ ๊ธฐ์
(๋๋ ํค์๋)์ ๋ํด ๋ฏธ๊ตญ ๋ด์ค ๊ฒ์ ํ,
|
151 |
-
1) ๊ธฐ์ฌ ๋ชฉ๋ก + 2) ๊ฐ์ฑ ๋ถ์ ๋ณด๊ณ ๋ฅผ ํจ๊ป ์ถ๋ ฅ
|
152 |
-
=> { "articles": [...], "analysis": ... } ํํ๋ก DB์ ์ ์ฅ
|
153 |
-
"""
|
154 |
error_message, articles = serphouse_search(company, "United States")
|
155 |
if not error_message and articles:
|
156 |
-
# ๊ฐ์ฑ ๋ถ์
|
157 |
analysis = analyze_sentiment_batch(articles, client)
|
158 |
-
|
159 |
-
# DB ์ ์ฅ์ฉ ๋ฐ์ดํฐ ๊ตฌ์ฑ
|
160 |
store_dict = {
|
161 |
"articles": articles,
|
162 |
"analysis": analysis
|
163 |
}
|
164 |
save_to_db(company, "United States", store_dict)
|
165 |
-
|
166 |
-
# ํ๋ฉด ์ถ๋ ฅ์ฉ
|
167 |
output = display_results(articles)
|
168 |
output += f"\n\n### ๋ถ์ ๋ณด๊ณ \n{analysis}\n"
|
169 |
return output
|
170 |
return f"{company}์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
171 |
|
172 |
-
|
173 |
-
# 2) ์ถ๋ ฅ ์ => DB์ ์ ์ฅ๋ ๊ธฐ์ฌ + ๋ถ์ ํจ๊ป ์ถ๋ ฅ
|
174 |
-
|
175 |
def load_company(company):
|
176 |
-
"""
|
177 |
-
DB์์ ๋จ์ผ ๊ธฐ์
(๋๋ ํค์๋)์ ๋ฏธ๊ตญ ๋ด์ค ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ถ๋ฌ์
|
178 |
-
๊ธฐ์ฌ ๋ชฉ๋ก + ๋ถ์ ๊ฒฐ๊ณผ๋ฅผ ํจ๊ป ์ถ๋ ฅ
|
179 |
-
"""
|
180 |
data, timestamp = load_from_db(company, "United States")
|
181 |
if data:
|
182 |
-
# data๋ { "articles": [...], "analysis": "..."} ํํ
|
183 |
articles = data.get("articles", [])
|
184 |
analysis = data.get("analysis", "")
|
185 |
|
@@ -190,25 +197,18 @@ def load_company(company):
|
|
190 |
return f"{company}์ ๋ํ ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
191 |
|
192 |
|
193 |
-
|
194 |
-
# 3)
|
195 |
-
|
196 |
def show_stats():
|
197 |
"""
|
198 |
-
|
199 |
-
- ๊ฐ์ฅ ์ต๊ทผ DB ์ ์ฅ ์ผ์
|
200 |
-
- ๊ธฐ์ฌ ์
|
201 |
-
- ๊ฐ์ฑ ๋ถ์ ๊ฒฐ๊ณผ
|
202 |
-
๋ฅผ ๋ณ๋ ฌ์ฒ๋ฆฌ๋ก ์กฐํํ์ฌ ๋ณด๊ณ ์ ํํ๋ก ๋ฐํ
|
203 |
-
|
204 |
-
(๋ฌธ๊ตฌ ๋ณ๊ฒฝ) "ํ๊ตญ ๊ธฐ์
๋ด์ค ๋ถ์ ๋ฆฌํฌํธ" -> "EarnBOT ๋ถ์ ๋ฆฌํฌํธ"
|
205 |
"""
|
206 |
conn = sqlite3.connect("search_results.db")
|
207 |
c = conn.cursor()
|
208 |
|
209 |
-
output = "## EarnBOT ๋ถ์ ๋ฆฌํฌํธ\n\n"
|
210 |
|
211 |
-
# ๋ชจ๋ ๊ธฐ์
์ ๋ํด DB์์ ์ฝ์ด์ฌ (company, timestamp, articles) ๋ชฉ๋ก ์์ง
|
212 |
data_list = []
|
213 |
for company in KOREAN_COMPANIES:
|
214 |
c.execute("""
|
@@ -221,23 +221,17 @@ def show_stats():
|
|
221 |
|
222 |
row = c.fetchone()
|
223 |
if row:
|
224 |
-
results_json,
|
225 |
-
data_list.append((company,
|
226 |
|
227 |
conn.close()
|
228 |
|
229 |
-
# ๊ฐ์ฑ ๋ถ์ ๋ณ๋ ฌ ์ฒ๋ฆฌ ํจ์
|
230 |
def analyze_data(item):
|
231 |
comp, tstamp, results_json = item
|
232 |
data = json.loads(results_json)
|
233 |
articles = data.get("articles", [])
|
234 |
analysis = data.get("analysis", "")
|
235 |
-
|
236 |
count_articles = len(articles)
|
237 |
-
# ์ฌ๊ธฐ์๋ ์ด๋ฏธ DB์ "analysis"๊ฐ ๋ค์ด ์์ผ๋ฏ๋ก,
|
238 |
-
# ๊ตณ์ด ์ฌ๋ถ์ํ ํ์๊ฐ ์์ผ๋ฉด ๊ทธ๋๋ก ์ฌ์ฉ
|
239 |
-
# (ํ์ ์ ์ฌ๋ถ์ ๊ฐ๋ฅ)
|
240 |
-
|
241 |
return (comp, tstamp, count_articles, analysis)
|
242 |
|
243 |
results_list = []
|
@@ -246,7 +240,6 @@ def show_stats():
|
|
246 |
for future in as_completed(futures):
|
247 |
results_list.append(future.result())
|
248 |
|
249 |
-
# ๊ฒฐ๊ณผ ์ถ๋ ฅ
|
250 |
for comp, tstamp, count, analysis in results_list:
|
251 |
seoul_time = convert_to_seoul_time(tstamp)
|
252 |
output += f"### {comp}\n"
|
@@ -260,11 +253,10 @@ def show_stats():
|
|
260 |
return output
|
261 |
|
262 |
|
|
|
|
|
|
|
263 |
def search_all_companies():
|
264 |
-
"""
|
265 |
-
KOREAN_COMPANIES ๋ฆฌ์คํธ ๋ด ๋ชจ๋ ๊ธฐ์
๊ฒ์ (๋ฉํฐ์ค๋ ๋ฉ) =>
|
266 |
-
=> ๋ถ์ + DB ์ ์ฅ => ๊ฒฐ๊ณผ Markdown ๋ฐํ
|
267 |
-
"""
|
268 |
overall_result = "# [์ ์ฒด ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
269 |
|
270 |
def do_search(comp):
|
@@ -280,12 +272,7 @@ def search_all_companies():
|
|
280 |
return overall_result
|
281 |
|
282 |
def load_all_companies():
|
283 |
-
"""
|
284 |
-
KOREAN_COMPANIES ๋ฆฌ์คํธ ๋ด ๋ชจ๋ ๊ธฐ์
DB ๋ถ๋ฌ์ค๊ธฐ =>
|
285 |
-
๊ธฐ์ฌ ๋ชฉ๋ก + ๋ถ์ ๋ณด๊ณ => ๊ฒฐ๊ณผ Markdown
|
286 |
-
"""
|
287 |
overall_result = "# [์ ์ฒด ์ถ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
|
288 |
-
|
289 |
for comp in KOREAN_COMPANIES:
|
290 |
overall_result += f"## {comp}\n"
|
291 |
overall_result += load_company(comp)
|
@@ -293,17 +280,8 @@ def load_all_companies():
|
|
293 |
return overall_result
|
294 |
|
295 |
def full_summary_report():
|
296 |
-
"""
|
297 |
-
(1) ๋ชจ๋ ๊ธฐ์
๊ฒ์(๋ณ๋ ฌ) -> (2) DB์์ ๋ถ๋ฌ์ค๊ธฐ -> (3) ๊ฐ์ฑ ๋ถ์ ํต๊ณ
|
298 |
-
์์๋๋ก ์คํํ์ฌ, ์ ์ฒด ๋ฆฌํฌํธ๋ฅผ ํฉ์ณ ๋ฐํ
|
299 |
-
"""
|
300 |
-
# 1) ์ ์ฒด ๊ฒ์(๋ณ๋ ฌ) => ๊ธฐ์ฌ + ๋ถ์ DB ์ ์ฅ
|
301 |
search_result_text = search_all_companies()
|
302 |
-
|
303 |
-
# 2) ์ ์ฒด ์ถ๋ ฅ => DB์ ์ ์ฅ๋ ๊ธฐ์ฌ + ๋ถ์ ๊ฒฐ๊ณผ
|
304 |
load_result_text = load_all_companies()
|
305 |
-
|
306 |
-
# 3) ์ ์ฒด ํต๊ณ(๊ฐ์ฑ ๋ถ์) - ๋ฆฌํฌํธ ์ ๋ชฉ ๋ณ๊ฒฝ๋จ(EarnBOT ๋ถ์ ๋ฆฌํฌํธ)
|
307 |
stats_text = show_stats()
|
308 |
|
309 |
combined_report = (
|
@@ -318,9 +296,9 @@ def full_summary_report():
|
|
318 |
return combined_report
|
319 |
|
320 |
|
321 |
-
|
322 |
-
# ์ฌ์ฉ์ ์์ ๊ฒ์ (
|
323 |
-
|
324 |
def search_custom(query, country):
|
325 |
"""
|
326 |
์ฌ์ฉ์๊ฐ ์
๋ ฅํ (query, country)์ ๋ํด
|
@@ -333,202 +311,103 @@ def search_custom(query, country):
|
|
333 |
if not articles:
|
334 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
335 |
|
336 |
-
# 1) ๋ถ์
|
337 |
analysis = analyze_sentiment_batch(articles, client)
|
338 |
-
|
339 |
-
# 2) DB ์ ์ฅ
|
340 |
save_data = {
|
341 |
"articles": articles,
|
342 |
"analysis": analysis
|
343 |
}
|
344 |
save_to_db(query, country, save_data)
|
345 |
|
346 |
-
# 3) DB ์ฌ๋ก๋
|
347 |
loaded_data, timestamp = load_from_db(query, country)
|
348 |
if not loaded_data:
|
349 |
return "DB์์ ๋ก๋ ์คํจ"
|
350 |
|
351 |
-
|
|
|
|
|
352 |
out = f"## [์ฌ์ฉ์ ์์ ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
353 |
out += f"**ํค์๋**: {query}\n\n"
|
354 |
out += f"**๊ตญ๊ฐ**: {country}\n\n"
|
355 |
out += f"**์ ์ฅ ์๊ฐ**: {timestamp}\n\n"
|
356 |
|
357 |
-
arts = loaded_data.get("articles", [])
|
358 |
-
analy = loaded_data.get("analysis", "")
|
359 |
-
|
360 |
out += display_results(arts)
|
361 |
out += f"### ๋ด์ค ๊ฐ์ฑ ๋ถ์\n{analy}\n"
|
362 |
|
363 |
return out
|
364 |
|
365 |
|
366 |
-
|
367 |
-
#
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
)
|
377 |
-
|
378 |
-
|
379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
380 |
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
"
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
"
|
397 |
-
|
398 |
-
|
399 |
-
"
|
400 |
-
|
401 |
-
"
|
402 |
-
"
|
403 |
-
|
404 |
-
"
|
405 |
-
"
|
406 |
-
|
407 |
-
"
|
408 |
-
|
409 |
-
|
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": "hr",
|
442 |
-
"Slovakia": "sk",
|
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 |
-
"China": "China",
|
466 |
-
"India": "India",
|
467 |
-
"Brazil": "Brazil",
|
468 |
-
"Mexico": "Mexico",
|
469 |
-
"Russia": "Russia",
|
470 |
-
"Italy": "Italy",
|
471 |
-
"Spain": "Spain",
|
472 |
-
"Netherlands": "Netherlands",
|
473 |
-
"Singapore": "Singapore",
|
474 |
-
"Hong Kong": "Hong Kong",
|
475 |
-
"Indonesia": "Indonesia",
|
476 |
-
"Malaysia": "Malaysia",
|
477 |
-
"Philippines": "Philippines",
|
478 |
-
"Thailand": "Thailand",
|
479 |
-
"Vietnam": "Vietnam",
|
480 |
-
"Belgium": "Belgium",
|
481 |
-
"Denmark": "Denmark",
|
482 |
-
"Finland": "Finland",
|
483 |
-
"Ireland": "Ireland",
|
484 |
-
"Norway": "Norway",
|
485 |
-
"Poland": "Poland",
|
486 |
-
"Sweden": "Sweden",
|
487 |
-
"Switzerland": "Switzerland",
|
488 |
-
"Austria": "Austria",
|
489 |
-
"Czech Republic": "Czech Republic",
|
490 |
-
"Greece": "Greece",
|
491 |
-
"Hungary": "Hungary",
|
492 |
-
"Portugal": "Portugal",
|
493 |
-
"Romania": "Romania",
|
494 |
-
"Turkey": "Turkey",
|
495 |
-
"Israel": "Israel",
|
496 |
-
"Saudi Arabia": "Saudi Arabia",
|
497 |
-
"United Arab Emirates": "United Arab Emirates",
|
498 |
-
"South Africa": "South Africa",
|
499 |
-
"Argentina": "Argentina",
|
500 |
-
"Chile": "Chile",
|
501 |
-
"Colombia": "Colombia",
|
502 |
-
"Peru": "Peru",
|
503 |
-
"Venezuela": "Venezuela",
|
504 |
-
"New Zealand": "New Zealand",
|
505 |
-
"Bangladesh": "Bangladesh",
|
506 |
-
"Pakistan": "Pakistan",
|
507 |
-
"Egypt": "Egypt",
|
508 |
-
"Morocco": "Morocco",
|
509 |
-
"Nigeria": "Nigeria",
|
510 |
-
"Kenya": "Kenya",
|
511 |
-
"Ukraine": "Ukraine",
|
512 |
-
"Croatia": "Croatia",
|
513 |
-
"Slovakia": "Slovakia",
|
514 |
-
"Bulgaria": "Bulgaria",
|
515 |
-
"Serbia": "Serbia",
|
516 |
-
"Estonia": "et",
|
517 |
-
"Latvia": "lv",
|
518 |
-
"Lithuania": "lt",
|
519 |
-
"Slovenia": "sl",
|
520 |
-
"Luxembourg": "Luxembourg",
|
521 |
-
"Malta": "Malta",
|
522 |
-
"Cyprus": "Cyprus",
|
523 |
-
"Iceland": "Iceland"
|
524 |
-
}
|
525 |
|
|
|
|
|
|
|
|
|
|
|
526 |
|
527 |
@lru_cache(maxsize=100)
|
528 |
def translate_query(query, country):
|
529 |
-
"""
|
530 |
-
Google Translation API(๋น๊ณต์) ์ฌ์ฉํ์ฌ ๊ฒ์์ด๋ฅผ ํด๋น ๊ตญ๊ฐ ์ธ์ด๋ก ๋ฒ์ญ
|
531 |
-
"""
|
532 |
try:
|
533 |
if is_english(query):
|
534 |
return query
|
@@ -561,14 +440,7 @@ def translate_query(query, country):
|
|
561 |
print(f"๋ฒ์ญ ์ค๋ฅ: {str(e)}")
|
562 |
return query
|
563 |
|
564 |
-
def is_english(text):
|
565 |
-
return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
|
566 |
-
|
567 |
def search_serphouse(query, country, page=1, num_result=10):
|
568 |
-
"""
|
569 |
-
SerpHouse API์ ์ค์๊ฐ ๊ฒ์ ์์ฒญ์ ๋ณด๋ด์ด,
|
570 |
-
'๋ด์ค' ํญ (sort_by=date)์์ ํด๋น query์ ๋ํ ๊ธฐ์ฌ ๋ชฉ๋ก์ ๊ฐ์ ธ์จ๋ค.
|
571 |
-
"""
|
572 |
url = "https://api.serphouse.com/serp/live"
|
573 |
|
574 |
now = datetime.utcnow()
|
@@ -639,10 +511,6 @@ def search_serphouse(query, country, page=1, num_result=10):
|
|
639 |
}
|
640 |
|
641 |
def format_results_from_raw(response_data):
|
642 |
-
"""
|
643 |
-
SerpHouse API์ ์๋ต ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ์ฌ,
|
644 |
-
(์๋ฌ๋ฉ์์ง, ๊ธฐ์ฌ๋ฆฌ์คํธ) ํํ๋ก ๋ฐํ.
|
645 |
-
"""
|
646 |
if "error" in response_data:
|
647 |
return "Error: " + response_data["error"], []
|
648 |
|
@@ -650,12 +518,10 @@ def format_results_from_raw(response_data):
|
|
650 |
results = response_data["results"]
|
651 |
translated_query = response_data["translated_query"]
|
652 |
|
653 |
-
# ์ค์ ๋ด์ค ๊ฒฐ๊ณผ
|
654 |
news_results = results.get('results', {}).get('results', {}).get('news', [])
|
655 |
if not news_results:
|
656 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.", []
|
657 |
|
658 |
-
# ํ๊ตญ ๋๋ฉ์ธ ๋ฐ ํ๊ตญ ๊ด๋ จ ํค์๋ ํฌํจ ๊ธฐ์ฌ ์ ์ธ
|
659 |
korean_domains = [
|
660 |
'.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
|
661 |
'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
|
@@ -676,7 +542,6 @@ def format_results_from_raw(response_data):
|
|
676 |
any(keyword in title for keyword in korean_keywords)
|
677 |
)
|
678 |
|
679 |
-
# ํ๊ตญ์ด ๋ด์ค(๋๋ ํ๊ตญ ๋๋ฉ์ธ) ์ ์ธ
|
680 |
if not is_korean_content:
|
681 |
filtered_articles.append({
|
682 |
"index": idx,
|
@@ -693,15 +558,163 @@ def format_results_from_raw(response_data):
|
|
693 |
except Exception as e:
|
694 |
return f"๊ฒฐ๊ณผ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}", []
|
695 |
|
696 |
-
def serphouse_search(query, country):
|
697 |
-
"""
|
698 |
-
๊ฒ์ ๋ฐ ๊ฒฐ๊ณผ ํฌ๋งคํ
๊น์ง ์ผ๊ด ์ฒ๋ฆฌ
|
699 |
-
"""
|
700 |
-
response_data = search_serphouse(query, country)
|
701 |
-
return format_results_from_raw(response_data)
|
702 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
703 |
|
704 |
-
# CSS (UI ์ปค์คํฐ๋ง์ด์ง)
|
705 |
css = """
|
706 |
/* ์ ์ญ ์คํ์ผ */
|
707 |
footer {visibility: hidden;}
|
@@ -886,35 +899,9 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น
|
|
886 |
# ์ฒซ ๋ฒ์งธ ํญ
|
887 |
with gr.Tab("Earnbot"):
|
888 |
gr.Markdown("## EarnBot: ๊ธ๋ก๋ฒ ๋น
ํ
ํฌ ๊ธฐ์
๋ฐ ํฌ์ ์ ๋ง AI ์๋ ๋ถ์")
|
889 |
-
gr.Markdown("
|
890 |
|
891 |
-
#
|
892 |
-
with gr.Group():
|
893 |
-
gr.Markdown("### ์ฌ์ฉ์ ์์ ๊ฒ์")
|
894 |
-
with gr.Row():
|
895 |
-
with gr.Column():
|
896 |
-
user_input = gr.Textbox(
|
897 |
-
label="๊ฒ์์ด ์
๋ ฅ",
|
898 |
-
placeholder="์) Apple, Samsung ๋ฑ ์์ ๋กญ๊ฒ"
|
899 |
-
)
|
900 |
-
with gr.Column():
|
901 |
-
country_selection = gr.Dropdown(
|
902 |
-
choices=list(COUNTRY_LOCATIONS.keys()),
|
903 |
-
value="United States",
|
904 |
-
label="๊ตญ๊ฐ ์ ํ"
|
905 |
-
)
|
906 |
-
with gr.Column():
|
907 |
-
custom_search_btn = gr.Button("์คํ", variant="primary")
|
908 |
-
|
909 |
-
custom_search_output = gr.Markdown()
|
910 |
-
|
911 |
-
custom_search_btn.click(
|
912 |
-
fn=search_custom,
|
913 |
-
inputs=[user_input, country_selection],
|
914 |
-
outputs=custom_search_output
|
915 |
-
)
|
916 |
-
|
917 |
-
# ์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ ๋ฒํผ
|
918 |
with gr.Row():
|
919 |
full_report_btn = gr.Button("์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ", variant="primary")
|
920 |
full_report_display = gr.Markdown()
|
@@ -924,7 +911,7 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น
|
|
924 |
outputs=full_report_display
|
925 |
)
|
926 |
|
927 |
-
# ์ง์ ๋
|
928 |
with gr.Column():
|
929 |
for i in range(0, len(KOREAN_COMPANIES), 2):
|
930 |
with gr.Row():
|
@@ -940,10 +927,12 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น
|
|
940 |
|
941 |
search_btn.click(
|
942 |
fn=lambda c=company: search_company(c),
|
|
|
943 |
outputs=result_display
|
944 |
)
|
945 |
load_btn.click(
|
946 |
fn=lambda c=company: load_company(c),
|
|
|
947 |
outputs=result_display
|
948 |
)
|
949 |
|
@@ -960,12 +949,105 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์๋น
|
|
960 |
|
961 |
search_btn.click(
|
962 |
fn=lambda c=company: search_company(c),
|
|
|
963 |
outputs=result_display
|
964 |
)
|
965 |
load_btn.click(
|
966 |
fn=lambda c=company: load_company(c),
|
|
|
967 |
outputs=result_display
|
968 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
969 |
|
970 |
iface.launch(
|
971 |
server_name="0.0.0.0",
|
|
|
34 |
"investing"
|
35 |
]
|
36 |
|
37 |
+
|
38 |
+
#########################################################
|
39 |
+
# ๊ณตํต ํจ์
|
40 |
+
#########################################################
|
41 |
+
|
42 |
def convert_to_seoul_time(timestamp_str):
|
43 |
try:
|
44 |
dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
|
|
|
54 |
OpenAI API๋ฅผ ํตํด ๋ด์ค ๊ธฐ์ฌ๋ค์ ์ข
ํฉ ๊ฐ์ฑ ๋ถ์์ ์ํ
|
55 |
"""
|
56 |
try:
|
|
|
57 |
combined_text = "\n\n".join([
|
58 |
f"์ ๋ชฉ: {article.get('title', '')}\n๋ด์ฉ: {article.get('snippet', '')}"
|
59 |
for article in articles
|
|
|
87 |
return f"๊ฐ์ฑ ๋ถ์ ์คํจ: {str(e)}"
|
88 |
|
89 |
|
90 |
+
#########################################################
|
91 |
+
# DB ๊ด๋ จ
|
92 |
+
#########################################################
|
93 |
+
|
94 |
def init_db():
|
95 |
db_path = pathlib.Path("search_results.db")
|
96 |
conn = sqlite3.connect(db_path)
|
|
|
105 |
conn.close()
|
106 |
|
107 |
def save_to_db(keyword, country, results):
|
|
|
|
|
|
|
108 |
conn = sqlite3.connect("search_results.db")
|
109 |
c = conn.cursor()
|
110 |
seoul_tz = pytz.timezone('Asia/Seoul')
|
|
|
119 |
conn.close()
|
120 |
|
121 |
def load_from_db(keyword, country):
|
|
|
|
|
|
|
122 |
conn = sqlite3.connect("search_results.db")
|
123 |
c = conn.cursor()
|
124 |
c.execute("SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
|
|
|
129 |
return json.loads(result[0]), convert_to_seoul_time(result[1])
|
130 |
return None, None
|
131 |
|
132 |
+
#########################################################
|
133 |
+
# "id"๋ก ์ง์ ๋ก๋ฉํ๊ธฐ (ํ์คํ ๋ฆฌ์์ ์ฌ์ฉ)
|
134 |
+
#########################################################
|
135 |
+
def load_by_id(search_id):
|
136 |
"""
|
137 |
+
DB์ PRIMARY KEY(id)๋ก ํน์ ๊ฒ์ ๊ธฐ๋ก์ ๋ก๋ฉ
|
138 |
"""
|
139 |
+
conn = sqlite3.connect("search_results.db")
|
140 |
+
c = conn.cursor()
|
141 |
+
c.execute("SELECT keyword, country, results, timestamp FROM searches WHERE id=?", (search_id,))
|
142 |
+
row = c.fetchone()
|
143 |
+
conn.close()
|
144 |
+
|
145 |
+
if row:
|
146 |
+
keyword, country, results_json, ts = row
|
147 |
+
data = json.loads(results_json)
|
148 |
+
return {
|
149 |
+
"keyword": keyword,
|
150 |
+
"country": country,
|
151 |
+
"data": data,
|
152 |
+
"timestamp": convert_to_seoul_time(ts)
|
153 |
+
}
|
154 |
+
return None
|
155 |
+
|
156 |
+
def display_results(articles):
|
157 |
output = ""
|
158 |
for idx, article in enumerate(articles, 1):
|
159 |
output += f"### {idx}. {article['title']}\n"
|
|
|
164 |
return output
|
165 |
|
166 |
|
167 |
+
#########################################################
|
168 |
+
# (1) ๊ฒ์ ์ => ๊ธฐ์ฌ + ๋ถ์ ๋์ ์ถ๋ ฅ, DB ์ ์ฅ
|
169 |
+
#########################################################
|
170 |
def search_company(company):
|
|
|
|
|
|
|
|
|
|
|
171 |
error_message, articles = serphouse_search(company, "United States")
|
172 |
if not error_message and articles:
|
|
|
173 |
analysis = analyze_sentiment_batch(articles, client)
|
|
|
|
|
174 |
store_dict = {
|
175 |
"articles": articles,
|
176 |
"analysis": analysis
|
177 |
}
|
178 |
save_to_db(company, "United States", store_dict)
|
|
|
|
|
179 |
output = display_results(articles)
|
180 |
output += f"\n\n### ๋ถ์ ๋ณด๊ณ \n{analysis}\n"
|
181 |
return output
|
182 |
return f"{company}์ ๋ํ ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
183 |
|
184 |
+
#########################################################
|
185 |
+
# (2) ์ถ๋ ฅ ์ => DB์ ์ ์ฅ๋ ๊ธฐ์ฌ + ๋ถ์ ํจ๊ป ์ถ๋ ฅ
|
186 |
+
#########################################################
|
187 |
def load_company(company):
|
|
|
|
|
|
|
|
|
188 |
data, timestamp = load_from_db(company, "United States")
|
189 |
if data:
|
|
|
190 |
articles = data.get("articles", [])
|
191 |
analysis = data.get("analysis", "")
|
192 |
|
|
|
197 |
return f"{company}์ ๋ํ ์ ์ฅ๋ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
198 |
|
199 |
|
200 |
+
#########################################################
|
201 |
+
# (3) EarnBOT ๋ถ์ ๋ฆฌํฌํธ
|
202 |
+
#########################################################
|
203 |
def show_stats():
|
204 |
"""
|
205 |
+
๊ธฐ์กด "ํ๊ตญ ๊ธฐ์
๋ด์ค ๋ถ์ ๋ฆฌํฌํธ" -> "EarnBOT ๋ถ์ ๋ฆฌํฌํธ"
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
"""
|
207 |
conn = sqlite3.connect("search_results.db")
|
208 |
c = conn.cursor()
|
209 |
|
210 |
+
output = "## EarnBOT ๋ถ์ ๋ฆฌํฌํธ\n\n"
|
211 |
|
|
|
212 |
data_list = []
|
213 |
for company in KOREAN_COMPANIES:
|
214 |
c.execute("""
|
|
|
221 |
|
222 |
row = c.fetchone()
|
223 |
if row:
|
224 |
+
results_json, tstamp = row
|
225 |
+
data_list.append((company, tstamp, results_json))
|
226 |
|
227 |
conn.close()
|
228 |
|
|
|
229 |
def analyze_data(item):
|
230 |
comp, tstamp, results_json = item
|
231 |
data = json.loads(results_json)
|
232 |
articles = data.get("articles", [])
|
233 |
analysis = data.get("analysis", "")
|
|
|
234 |
count_articles = len(articles)
|
|
|
|
|
|
|
|
|
235 |
return (comp, tstamp, count_articles, analysis)
|
236 |
|
237 |
results_list = []
|
|
|
240 |
for future in as_completed(futures):
|
241 |
results_list.append(future.result())
|
242 |
|
|
|
243 |
for comp, tstamp, count, analysis in results_list:
|
244 |
seoul_time = convert_to_seoul_time(tstamp)
|
245 |
output += f"### {comp}\n"
|
|
|
253 |
return output
|
254 |
|
255 |
|
256 |
+
#########################################################
|
257 |
+
# ์ ์ฒด ๊ฒ์ (๋ณ๋ ฌ) / ์ ์ฒด ์ถ๋ ฅ / ์ ์ฒด ๋ฆฌํฌํธ
|
258 |
+
#########################################################
|
259 |
def search_all_companies():
|
|
|
|
|
|
|
|
|
260 |
overall_result = "# [์ ์ฒด ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
261 |
|
262 |
def do_search(comp):
|
|
|
272 |
return overall_result
|
273 |
|
274 |
def load_all_companies():
|
|
|
|
|
|
|
|
|
275 |
overall_result = "# [์ ์ฒด ์ถ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
|
|
|
276 |
for comp in KOREAN_COMPANIES:
|
277 |
overall_result += f"## {comp}\n"
|
278 |
overall_result += load_company(comp)
|
|
|
280 |
return overall_result
|
281 |
|
282 |
def full_summary_report():
|
|
|
|
|
|
|
|
|
|
|
283 |
search_result_text = search_all_companies()
|
|
|
|
|
284 |
load_result_text = load_all_companies()
|
|
|
|
|
285 |
stats_text = show_stats()
|
286 |
|
287 |
combined_report = (
|
|
|
296 |
return combined_report
|
297 |
|
298 |
|
299 |
+
#########################################################
|
300 |
+
# (์ถ๊ฐ) ์ฌ์ฉ์ ์์ ๊ฒ์ ํจ์ (๋ ๋ฒ์งธ ํญ)
|
301 |
+
#########################################################
|
302 |
def search_custom(query, country):
|
303 |
"""
|
304 |
์ฌ์ฉ์๊ฐ ์
๋ ฅํ (query, country)์ ๋ํด
|
|
|
311 |
if not articles:
|
312 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค."
|
313 |
|
|
|
314 |
analysis = analyze_sentiment_batch(articles, client)
|
|
|
|
|
315 |
save_data = {
|
316 |
"articles": articles,
|
317 |
"analysis": analysis
|
318 |
}
|
319 |
save_to_db(query, country, save_data)
|
320 |
|
|
|
321 |
loaded_data, timestamp = load_from_db(query, country)
|
322 |
if not loaded_data:
|
323 |
return "DB์์ ๋ก๋ ์คํจ"
|
324 |
|
325 |
+
arts = loaded_data.get("articles", [])
|
326 |
+
analy = loaded_data.get("analysis", "")
|
327 |
+
|
328 |
out = f"## [์ฌ์ฉ์ ์์ ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
329 |
out += f"**ํค์๋**: {query}\n\n"
|
330 |
out += f"**๊ตญ๊ฐ**: {country}\n\n"
|
331 |
out += f"**์ ์ฅ ์๊ฐ**: {timestamp}\n\n"
|
332 |
|
|
|
|
|
|
|
333 |
out += display_results(arts)
|
334 |
out += f"### ๋ด์ค ๊ฐ์ฑ ๋ถ์\n{analy}\n"
|
335 |
|
336 |
return out
|
337 |
|
338 |
|
339 |
+
#########################################################
|
340 |
+
# (์ถ๊ฐ) ํ์คํ ๋ฆฌ ํจ์
|
341 |
+
#########################################################
|
342 |
+
def get_custom_search_history():
|
343 |
+
"""
|
344 |
+
KOREAN_COMPANIES์ ์๋ keyword๋ก ๊ฒ์๋ ๊ธฐ๋ก๋ง
|
345 |
+
(id, timestamp, keyword, country) ํํ๋ก ๋ฐํ
|
346 |
+
์ต์ ์ ์ ๋ ฌ
|
347 |
+
"""
|
348 |
+
# set์ผ๋ก ๋ง๋ค์ด์ ๋น ๋ฅด๊ฒ ๊ฒ์
|
349 |
+
company_set = set(k.lower() for k in KOREAN_COMPANIES)
|
350 |
+
|
351 |
+
conn = sqlite3.connect("search_results.db")
|
352 |
+
c = conn.cursor()
|
353 |
+
c.execute("""SELECT id, keyword, country, timestamp
|
354 |
+
FROM searches
|
355 |
+
ORDER BY timestamp DESC""")
|
356 |
+
rows = c.fetchall()
|
357 |
+
conn.close()
|
358 |
+
|
359 |
+
history_list = []
|
360 |
+
for (sid, kw, cty, ts) in rows:
|
361 |
+
# KOREAN_COMPANIES ์ ์๋ ๊ฒฝ์ฐ -> ์ฌ์ฉ์ ์์ ๊ฒ์
|
362 |
+
if kw.lower() not in company_set:
|
363 |
+
# "id, ์๊ฐ, ํค์๋(๊ตญ๊ฐ)" ๋ก ํ์ํ ํ
์คํธ ๊ตฌ์ฑ
|
364 |
+
# ์: "12 | 2025-01-22 10:23:00 KST | Apple (United States)"
|
365 |
+
# ์ค์ dropdown value ๋ sid (id) ๋ง ์ ์ฅ
|
366 |
+
display_time = convert_to_seoul_time(ts)
|
367 |
+
label = f"{sid} | {display_time} | {kw} ({cty})"
|
368 |
+
history_list.append((str(sid), label))
|
369 |
+
|
370 |
+
return history_list
|
371 |
|
372 |
+
def view_history_record(record_id):
|
373 |
+
"""
|
374 |
+
Dropdown ์์ ์ ํ๋ record_id (๋ฌธ์์ด)๋ก๋ถํฐ
|
375 |
+
ํด๋น ๊ฒ์ ๊ฒฐ๊ณผ(๊ธฐ์ฌ+๋ถ์)์ Markdown ํํ๋ก ๋ฐํ
|
376 |
+
"""
|
377 |
+
if not record_id:
|
378 |
+
return "๊ธฐ๋ก์ด ์์ต๋๋ค."
|
379 |
+
|
380 |
+
data = load_by_id(int(record_id))
|
381 |
+
if not data:
|
382 |
+
return "ํด๋น ID์ ๊ธฐ๋ก์ด ์์ต๋๋ค."
|
383 |
+
|
384 |
+
keyword = data["keyword"]
|
385 |
+
country = data["country"]
|
386 |
+
timestamp = data["timestamp"]
|
387 |
+
stored = data["data"] # { articles, analysis }
|
388 |
+
|
389 |
+
articles = stored.get("articles", [])
|
390 |
+
analysis = stored.get("analysis", "")
|
391 |
+
|
392 |
+
out = f"### [ํ์คํ ๋ฆฌ ๊ฒ์ ๊ฒฐ๊ณผ]\n\n"
|
393 |
+
out += f"- ID: {record_id}\n"
|
394 |
+
out += f"- ํค์๋: {keyword}\n"
|
395 |
+
out += f"- ๊ตญ๊ฐ: {country}\n"
|
396 |
+
out += f"- ์ ์ฅ ์๊ฐ: {timestamp}\n\n"
|
397 |
+
out += display_results(articles)
|
398 |
+
out += f"\n\n### ๋ถ์ ๋ณด๊ณ \n{analysis}\n"
|
399 |
+
|
400 |
+
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
401 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
402 |
|
403 |
+
#########################################################
|
404 |
+
# SerpHouse API
|
405 |
+
#########################################################
|
406 |
+
def is_english(text):
|
407 |
+
return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
|
408 |
|
409 |
@lru_cache(maxsize=100)
|
410 |
def translate_query(query, country):
|
|
|
|
|
|
|
411 |
try:
|
412 |
if is_english(query):
|
413 |
return query
|
|
|
440 |
print(f"๋ฒ์ญ ์ค๋ฅ: {str(e)}")
|
441 |
return query
|
442 |
|
|
|
|
|
|
|
443 |
def search_serphouse(query, country, page=1, num_result=10):
|
|
|
|
|
|
|
|
|
444 |
url = "https://api.serphouse.com/serp/live"
|
445 |
|
446 |
now = datetime.utcnow()
|
|
|
511 |
}
|
512 |
|
513 |
def format_results_from_raw(response_data):
|
|
|
|
|
|
|
|
|
514 |
if "error" in response_data:
|
515 |
return "Error: " + response_data["error"], []
|
516 |
|
|
|
518 |
results = response_data["results"]
|
519 |
translated_query = response_data["translated_query"]
|
520 |
|
|
|
521 |
news_results = results.get('results', {}).get('results', {}).get('news', [])
|
522 |
if not news_results:
|
523 |
return "๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์์ต๋๋ค.", []
|
524 |
|
|
|
525 |
korean_domains = [
|
526 |
'.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
|
527 |
'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
|
|
|
542 |
any(keyword in title for keyword in korean_keywords)
|
543 |
)
|
544 |
|
|
|
545 |
if not is_korean_content:
|
546 |
filtered_articles.append({
|
547 |
"index": idx,
|
|
|
558 |
except Exception as e:
|
559 |
return f"๊ฒฐ๊ณผ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: {str(e)}", []
|
560 |
|
|
|
|
|
|
|
|
|
|
|
|
|
561 |
|
562 |
+
###################################
|
563 |
+
# ํ๊ฒฝ ๋ณ์ ๋ฐ CSS
|
564 |
+
###################################
|
565 |
+
ACCESS_TOKEN = os.getenv("HF_TOKEN")
|
566 |
+
if not ACCESS_TOKEN:
|
567 |
+
raise ValueError("HF_TOKEN environment variable is not set")
|
568 |
+
|
569 |
+
client = OpenAI(
|
570 |
+
base_url="https://api-inference.huggingface.co/v1/",
|
571 |
+
api_key=ACCESS_TOKEN,
|
572 |
+
)
|
573 |
+
|
574 |
+
API_KEY = os.getenv("SERPHOUSE_API_KEY")
|
575 |
+
|
576 |
+
COUNTRY_LANGUAGES = {
|
577 |
+
"United States": "en",
|
578 |
+
"KOREA": "ko",
|
579 |
+
"United Kingdom": "en",
|
580 |
+
"Taiwan": "zh-TW",
|
581 |
+
"Canada": "en",
|
582 |
+
"Australia": "en",
|
583 |
+
"Germany": "de",
|
584 |
+
"France": "fr",
|
585 |
+
"Japan": "ja",
|
586 |
+
"China": "zh",
|
587 |
+
"India": "hi",
|
588 |
+
"Brazil": "pt",
|
589 |
+
"Mexico": "es",
|
590 |
+
"Russia": "ru",
|
591 |
+
"Italy": "it",
|
592 |
+
"Spain": "es",
|
593 |
+
"Netherlands": "nl",
|
594 |
+
"Singapore": "en",
|
595 |
+
"Hong Kong": "zh-HK",
|
596 |
+
"Indonesia": "id",
|
597 |
+
"Malaysia": "ms",
|
598 |
+
"Philippines": "tl",
|
599 |
+
"Thailand": "th",
|
600 |
+
"Vietnam": "vi",
|
601 |
+
"Belgium": "nl",
|
602 |
+
"Denmark": "da",
|
603 |
+
"Finland": "fi",
|
604 |
+
"Ireland": "en",
|
605 |
+
"Norway": "no",
|
606 |
+
"Poland": "pl",
|
607 |
+
"Sweden": "sv",
|
608 |
+
"Switzerland": "de",
|
609 |
+
"Austria": "de",
|
610 |
+
"Czech Republic": "cs",
|
611 |
+
"Greece": "el",
|
612 |
+
"Hungary": "hu",
|
613 |
+
"Portugal": "pt",
|
614 |
+
"Romania": "ro",
|
615 |
+
"Turkey": "tr",
|
616 |
+
"Israel": "he",
|
617 |
+
"Saudi Arabia": "ar",
|
618 |
+
"United Arab Emirates": "ar",
|
619 |
+
"South Africa": "en",
|
620 |
+
"Argentina": "es",
|
621 |
+
"Chile": "es",
|
622 |
+
"Colombia": "es",
|
623 |
+
"Peru": "es",
|
624 |
+
"Venezuela": "es",
|
625 |
+
"New Zealand": "en",
|
626 |
+
"Bangladesh": "bn",
|
627 |
+
"Pakistan": "ur",
|
628 |
+
"Egypt": "ar",
|
629 |
+
"Morocco": "ar",
|
630 |
+
"Nigeria": "en",
|
631 |
+
"Kenya": "sw",
|
632 |
+
"Ukraine": "uk",
|
633 |
+
"Croatia": "hr",
|
634 |
+
"Slovakia": "sk",
|
635 |
+
"Bulgaria": "bg",
|
636 |
+
"Serbia": "sr",
|
637 |
+
"Estonia": "et",
|
638 |
+
"Latvia": "lv",
|
639 |
+
"Lithuania": "lt",
|
640 |
+
"Slovenia": "sl",
|
641 |
+
"Luxembourg": "Luxembourg",
|
642 |
+
"Malta": "Malta",
|
643 |
+
"Cyprus": "Cyprus",
|
644 |
+
"Iceland": "Iceland"
|
645 |
+
}
|
646 |
+
|
647 |
+
COUNTRY_LOCATIONS = {
|
648 |
+
"United States": "United States",
|
649 |
+
"KOREA": "kr",
|
650 |
+
"United Kingdom": "United Kingdom",
|
651 |
+
"Taiwan": "Taiwan",
|
652 |
+
"Canada": "Canada",
|
653 |
+
"Australia": "Australia",
|
654 |
+
"Germany": "Germany",
|
655 |
+
"France": "France",
|
656 |
+
"Japan": "Japan",
|
657 |
+
"China": "China",
|
658 |
+
"India": "India",
|
659 |
+
"Brazil": "Brazil",
|
660 |
+
"Mexico": "Mexico",
|
661 |
+
"Russia": "Russia",
|
662 |
+
"Italy": "Italy",
|
663 |
+
"Spain": "Spain",
|
664 |
+
"Netherlands": "Netherlands",
|
665 |
+
"Singapore": "Singapore",
|
666 |
+
"Hong Kong": "Hong Kong",
|
667 |
+
"Indonesia": "Indonesia",
|
668 |
+
"Malaysia": "Malaysia",
|
669 |
+
"Philippines": "Philippines",
|
670 |
+
"Thailand": "Thailand",
|
671 |
+
"Vietnam": "Vietnam",
|
672 |
+
"Belgium": "Belgium",
|
673 |
+
"Denmark": "Denmark",
|
674 |
+
"Finland": "Finland",
|
675 |
+
"Ireland": "Ireland",
|
676 |
+
"Norway": "Norway",
|
677 |
+
"Poland": "Poland",
|
678 |
+
"Sweden": "Sweden",
|
679 |
+
"Switzerland": "Switzerland",
|
680 |
+
"Austria": "Austria",
|
681 |
+
"Czech Republic": "Czech Republic",
|
682 |
+
"Greece": "Greece",
|
683 |
+
"Hungary": "Hungary",
|
684 |
+
"Portugal": "Portugal",
|
685 |
+
"Romania": "Romania",
|
686 |
+
"Turkey": "Turkey",
|
687 |
+
"Israel": "Israel",
|
688 |
+
"Saudi Arabia": "Saudi Arabia",
|
689 |
+
"United Arab Emirates": "United Arab Emirates",
|
690 |
+
"South Africa": "South Africa",
|
691 |
+
"Argentina": "Argentina",
|
692 |
+
"Chile": "Chile",
|
693 |
+
"Colombia": "Colombia",
|
694 |
+
"Peru": "Peru",
|
695 |
+
"Venezuela": "Venezuela",
|
696 |
+
"New Zealand": "New Zealand",
|
697 |
+
"Bangladesh": "Bangladesh",
|
698 |
+
"Pakistan": "Pakistan",
|
699 |
+
"Egypt": "Egypt",
|
700 |
+
"Morocco": "Morocco",
|
701 |
+
"Nigeria": "Nigeria",
|
702 |
+
"Kenya": "Kenya",
|
703 |
+
"Ukraine": "Ukraine",
|
704 |
+
"Croatia": "Croatia",
|
705 |
+
"Slovakia": "Slovakia",
|
706 |
+
"Bulgaria": "Bulgaria",
|
707 |
+
"Serbia": "Serbia",
|
708 |
+
"Estonia": "et",
|
709 |
+
"Latvia": "lv",
|
710 |
+
"Lithuania": "lt",
|
711 |
+
"Slovenia": "sl",
|
712 |
+
"Luxembourg": "Luxembourg",
|
713 |
+
"Malta": "Malta",
|
714 |
+
"Cyprus": "Cyprus",
|
715 |
+
"Iceland": "Iceland"
|
716 |
+
}
|
717 |
|
|
|
718 |
css = """
|
719 |
/* ์ ์ญ ์คํ์ผ */
|
720 |
footer {visibility: hidden;}
|
|
|
899 |
# ์ฒซ ๋ฒ์งธ ํญ
|
900 |
with gr.Tab("Earnbot"):
|
901 |
gr.Markdown("## EarnBot: ๊ธ๋ก๋ฒ ๋น
ํ
ํฌ ๊ธฐ์
๋ฐ ํฌ์ ์ ๋ง AI ์๋ ๋ถ์")
|
902 |
+
gr.Markdown(" - '์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ' ํด๋ฆญ ์ ์ ์ฒด ์๋ ๋ณด๊ณ ์์ฑ.\n - ์๋ ๊ฐ๋ณ ์ข
๋ชฉ์ '๊ฒ์(DB ์๋ ์ ์ฅ)'๊ณผ '์ถ๋ ฅ(DB ์๋ ํธ์ถ)'๋ ๊ฐ๋ฅ.\n - ํ๋จ '์๋ ๊ฒ์ ํ์คํ ๋ฆฌ'์์ ์ด์ ์ ์๋ ์
๋ ฅํ ๊ฒ์์ด ๊ธฐ๋ก ํ์ธ ๊ฐ๋ฅ.")
|
903 |
|
904 |
+
# ์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
905 |
with gr.Row():
|
906 |
full_report_btn = gr.Button("์ ์ฒด ๋ถ์ ๋ณด๊ณ ์์ฝ", variant="primary")
|
907 |
full_report_display = gr.Markdown()
|
|
|
911 |
outputs=full_report_display
|
912 |
)
|
913 |
|
914 |
+
# ์ง์ ๋ (KOREAN_COMPANIES) ๊ธฐ์
๊ฒ์/์ถ๋ ฅ
|
915 |
with gr.Column():
|
916 |
for i in range(0, len(KOREAN_COMPANIES), 2):
|
917 |
with gr.Row():
|
|
|
927 |
|
928 |
search_btn.click(
|
929 |
fn=lambda c=company: search_company(c),
|
930 |
+
inputs=[],
|
931 |
outputs=result_display
|
932 |
)
|
933 |
load_btn.click(
|
934 |
fn=lambda c=company: load_company(c),
|
935 |
+
inputs=[],
|
936 |
outputs=result_display
|
937 |
)
|
938 |
|
|
|
949 |
|
950 |
search_btn.click(
|
951 |
fn=lambda c=company: search_company(c),
|
952 |
+
inputs=[],
|
953 |
outputs=result_display
|
954 |
)
|
955 |
load_btn.click(
|
956 |
fn=lambda c=company: load_company(c),
|
957 |
+
inputs=[],
|
958 |
outputs=result_display
|
959 |
)
|
960 |
+
|
961 |
+
# (์ถ๊ฐ) ์๋ ๊ฒ์ ํ์คํ ๋ฆฌ
|
962 |
+
gr.Markdown("---")
|
963 |
+
gr.Markdown("### ์๋ ๊ฒ์ ํ์คํ ๋ฆฌ")
|
964 |
+
with gr.Row():
|
965 |
+
refresh_hist_btn = gr.Button("ํ์คํ ๋ฆฌ ๊ฐฑ์ ", variant="secondary")
|
966 |
+
|
967 |
+
history_dropdown = gr.Dropdown(
|
968 |
+
label="๊ฒ์ ๊ธฐ๋ก ๋ชฉ๋ก",
|
969 |
+
choices=[], # ์ด ๋ถ๋ถ์ click ์ด๋ฒคํธ์์ ๋์ ์ผ๋ก ์
๋ฐ์ดํธ
|
970 |
+
value=None
|
971 |
+
)
|
972 |
+
hist_view_btn = gr.Button("๋ณด๊ธฐ", variant="primary")
|
973 |
+
hist_result_display = gr.Markdown()
|
974 |
+
|
975 |
+
# 1) ํ์คํ ๋ฆฌ ๊ฐฑ์ ํจ์
|
976 |
+
def update_history_dropdown():
|
977 |
+
history_list = get_custom_search_history() # [(id, label), ...]
|
978 |
+
labels = [lbl for (id_val, lbl) in history_list]
|
979 |
+
values = [id_val for (id_val, lbl) in history_list]
|
980 |
+
|
981 |
+
# gr.Dropdown ์ choices=[..., ...] ํํ๋ก ์ ๋ฌํด์ผ ํ๋ฏ๋ก
|
982 |
+
# zip ํ์ฌ (value, label) ์์ ๋ง๋ฆ
|
983 |
+
# Gradio 3.x์์๋ choices๋ฅผ list๋ก ์ฃผ๊ณ , label/value ๋ถ๋ฆฌ ๋ถ๊ฐ.
|
984 |
+
# -> workaround: "value|label" ์ or just store label in "choices" and store value separate
|
985 |
+
# ์ฌ๊ธฐ์๋ choices ์ label๋ง ๋ฃ๊ณ , id๋ label ํ์ฑ ๋ฑ ํด์ผํจ.
|
986 |
+
# ์ข ๋ ํธํ๊ฒ: return a dictionary for "choices"
|
987 |
+
|
988 |
+
# ๋ฐฉ๋ฒ1) label๋ง ์ ๋ฌ -> user picks label -> then parse.
|
989 |
+
# ๊ฐ๋จํ "value=label"์ด ๋๋๋ก:
|
990 |
+
choice_list = []
|
991 |
+
for (id_val, label) in history_list:
|
992 |
+
choice_list.append(label)
|
993 |
+
return gr.update(choices=choice_list, value=None)
|
994 |
+
|
995 |
+
refresh_hist_btn.click(
|
996 |
+
fn=update_history_dropdown,
|
997 |
+
inputs=[],
|
998 |
+
outputs=history_dropdown
|
999 |
+
)
|
1000 |
+
|
1001 |
+
# 2) ํ์คํ ๋ฆฌ ๋ณด๊ธฐ ํจ์
|
1002 |
+
def show_history_record(selected_label):
|
1003 |
+
# selected_label ์ด "id | time | keyword (country)" ํ์
|
1004 |
+
if not selected_label:
|
1005 |
+
return "ํ์คํ ๋ฆฌ๊ฐ ์ ํ๋์ง ์์์ต๋๋ค."
|
1006 |
+
|
1007 |
+
# id๋ ๊ฐ์ฅ ์๋ถ๋ถ์ ์ซ์
|
1008 |
+
# ์: "12 | 2025-01-22 10:23:00 KST | Apple (United States)"
|
1009 |
+
# -> id=12
|
1010 |
+
splitted = selected_label.split("|")
|
1011 |
+
if len(splitted) < 2:
|
1012 |
+
return "ํ์ ์ค๋ฅ"
|
1013 |
+
record_id = splitted[0].strip() # "12"
|
1014 |
+
return view_history_record(record_id)
|
1015 |
+
|
1016 |
+
hist_view_btn.click(
|
1017 |
+
fn=show_history_record,
|
1018 |
+
inputs=[history_dropdown],
|
1019 |
+
outputs=hist_result_display
|
1020 |
+
)
|
1021 |
+
|
1022 |
+
|
1023 |
+
# ๋ ๋ฒ์งธ ํญ: "์ง์ ์๋ ๊ฒ์/๋ถ์"
|
1024 |
+
with gr.Tab("์ง์ ์๋ ๊ฒ์/๋ถ์"):
|
1025 |
+
gr.Markdown("## ์ฌ์ฉ์ ์์ ํค์๋ + ๊ตญ๊ฐ ๊ฒ์/๋ถ์")
|
1026 |
+
gr.Markdown("๊ฒ์ ๊ฒฐ๊ณผ๊ฐ DB์ ์ ์ฅ๋๋ฉฐ, ์ฒซ ๋ฒ์งธ ํญ์ '์๋ ๊ฒ์ ํ์คํ ๋ฆฌ'์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.")
|
1027 |
+
|
1028 |
+
with gr.Row():
|
1029 |
+
with gr.Column():
|
1030 |
+
user_input = gr.Textbox(
|
1031 |
+
label="๊ฒ์์ด ์
๋ ฅ",
|
1032 |
+
placeholder="์) Apple, Samsung ๋ฑ ์์ ๋กญ๊ฒ"
|
1033 |
+
)
|
1034 |
+
with gr.Column():
|
1035 |
+
country_selection = gr.Dropdown(
|
1036 |
+
choices=list(COUNTRY_LOCATIONS.keys()),
|
1037 |
+
value="United States",
|
1038 |
+
label="๊ตญ๊ฐ ์ ํ"
|
1039 |
+
)
|
1040 |
+
with gr.Column():
|
1041 |
+
custom_search_btn = gr.Button("์คํ", variant="primary")
|
1042 |
+
|
1043 |
+
custom_search_output = gr.Markdown()
|
1044 |
+
|
1045 |
+
custom_search_btn.click(
|
1046 |
+
fn=search_custom,
|
1047 |
+
inputs=[user_input, country_selection],
|
1048 |
+
outputs=custom_search_output
|
1049 |
+
)
|
1050 |
+
|
1051 |
|
1052 |
iface.launch(
|
1053 |
server_name="0.0.0.0",
|