ginipick commited on
Commit
c6beee2
ยท
verified ยท
1 Parent(s): a1e836a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -28
app.py CHANGED
@@ -3,7 +3,7 @@ import requests
3
  import json
4
  import os
5
  from datetime import datetime, timedelta
6
- from concurrent.futures import ThreadPoolExecutor
7
  from functools import lru_cache
8
  from requests.adapters import HTTPAdapter
9
  from requests.packages.urllib3.util.retry import Retry
@@ -45,6 +45,9 @@ def convert_to_seoul_time(timestamp_str):
45
  return timestamp_str
46
 
47
  def analyze_sentiment_batch(articles, client):
 
 
 
48
  try:
49
  # ๋ชจ๋“  ๊ธฐ์‚ฌ์˜ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์„ ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ๋กœ ๊ฒฐํ•ฉ
50
  combined_text = "\n\n".join([
@@ -52,7 +55,6 @@ def analyze_sentiment_batch(articles, client):
52
  for article in articles
53
  ])
54
 
55
- # f""" ... """ ํ˜•ํƒœ๋กœ ์—ฌ๋Ÿฌ ์ค„ ๋ฌธ์ž์—ด์„ ์ •ํ™•ํžˆ ์‚ฌ์šฉ
56
  prompt = f"""๋‹ค์Œ ๋‰ด์Šค ๋ชจ์Œ์— ๋Œ€ํ•ด ์ „๋ฐ˜์ ์ธ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”:
57
 
58
  ๋‰ด์Šค ๋‚ด์šฉ:
@@ -96,6 +98,9 @@ def init_db():
96
  conn.close()
97
 
98
  def save_to_db(keyword, country, results):
 
 
 
99
  conn = sqlite3.connect("search_results.db")
100
  c = conn.cursor()
101
  seoul_tz = pytz.timezone('Asia/Seoul')
@@ -110,6 +115,9 @@ def save_to_db(keyword, country, results):
110
  conn.close()
111
 
112
  def load_from_db(keyword, country):
 
 
 
113
  conn = sqlite3.connect("search_results.db")
114
  c = conn.cursor()
115
  c.execute("SELECT results, timestamp FROM searches WHERE keyword=? AND country=? ORDER BY timestamp DESC LIMIT 1",
@@ -121,6 +129,9 @@ def load_from_db(keyword, country):
121
  return None, None
122
 
123
  def display_results(articles):
 
 
 
124
  output = ""
125
  for idx, article in enumerate(articles, 1):
126
  output += f"### {idx}. {article['title']}\n"
@@ -131,6 +142,9 @@ def display_results(articles):
131
  return output
132
 
133
  def search_company(company):
 
 
 
134
  error_message, articles = serphouse_search(company, "United States")
135
  if not error_message and articles:
136
  save_to_db(company, "United States", articles)
@@ -138,17 +152,29 @@ def search_company(company):
138
  return f"{company}์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
139
 
140
  def load_company(company):
 
 
 
141
  results, timestamp = load_from_db(company, "United States")
142
  if results:
143
  return f"### {company} ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ\n์ €์žฅ ์‹œ๊ฐ„: {timestamp}\n\n" + display_results(results)
144
  return f"{company}์— ๋Œ€ํ•œ ์ €์žฅ๋œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
145
 
146
  def show_stats():
 
 
 
 
 
 
 
147
  conn = sqlite3.connect("search_results.db")
148
  c = conn.cursor()
149
 
150
  output = "## ํ•œ๊ตญ ๊ธฐ์—… ๋‰ด์Šค ๋ถ„์„ ๋ฆฌํฌํŠธ\n\n"
151
 
 
 
152
  for company in KOREAN_COMPANIES:
153
  c.execute("""
154
  SELECT results, timestamp
@@ -158,36 +184,70 @@ def show_stats():
158
  LIMIT 1
159
  """, (company,))
160
 
161
- result = c.fetchone()
162
- if result:
163
- results_json, timestamp = result
164
  articles = json.loads(results_json)
165
  seoul_time = convert_to_seoul_time(timestamp)
166
-
167
- output += f"### {company}\n"
168
- output += f"- ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {seoul_time}\n"
169
- output += f"- ์ €์žฅ๋œ ๊ธฐ์‚ฌ ์ˆ˜: {len(articles)}๊ฑด\n\n"
170
-
171
- if articles:
172
- sentiment_analysis = analyze_sentiment_batch(articles, client)
173
- output += "#### ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„\n"
174
- output += f"{sentiment_analysis}\n\n"
175
-
176
- output += "---\n\n"
177
 
178
  conn.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
  return output
180
 
 
 
181
  def search_all_companies():
 
 
 
 
182
  overall_result = "# [์ „์ฒด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ]\n\n"
183
- for comp in KOREAN_COMPANIES:
184
- overall_result += f"## {comp}\n"
185
- overall_result += search_company(comp)
186
- overall_result += "\n"
 
 
 
 
 
 
 
187
  return overall_result
188
 
189
  def load_all_companies():
 
 
 
 
190
  overall_result = "# [์ „์ฒด ์ถœ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
 
191
  for comp in KOREAN_COMPANIES:
192
  overall_result += f"## {comp}\n"
193
  overall_result += load_company(comp)
@@ -195,17 +255,23 @@ def load_all_companies():
195
  return overall_result
196
 
197
  def full_summary_report():
198
- # 1) ์ „์ฒด ๊ฒ€์ƒ‰
 
 
 
 
199
  search_result_text = search_all_companies()
200
- # 2) ์ „์ฒด ์ถœ๋ ฅ
 
201
  load_result_text = load_all_companies()
202
- # 3) ์ „์ฒด ํ†ต๊ณ„
 
203
  stats_text = show_stats()
204
 
205
  combined_report = (
206
  "# ์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ\n\n"
207
  "์•„๋ž˜ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค:\n"
208
- "1. ๋ชจ๋“  ์ข…๋ชฉ ๊ฒ€์ƒ‰ โ†’ 2. ๋ชจ๋“  ์ข…๋ชฉ DB ๊ฒฐ๊ณผ ์ถœ๋ ฅ โ†’ 3. ์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„\n\n"
209
  f"{search_result_text}\n\n"
210
  f"{load_result_text}\n\n"
211
  "## [์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„]\n\n"
@@ -213,6 +279,47 @@ def full_summary_report():
213
  )
214
  return combined_report
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  ACCESS_TOKEN = os.getenv("HF_TOKEN")
217
  if not ACCESS_TOKEN:
218
  raise ValueError("HF_TOKEN environment variable is not set")
@@ -224,6 +331,8 @@ client = OpenAI(
224
 
225
  API_KEY = os.getenv("SERPHOUSE_API_KEY")
226
 
 
 
227
  COUNTRY_LANGUAGES = {
228
  "United States": "en",
229
  "KOREA": "ko",
@@ -366,8 +475,12 @@ COUNTRY_LOCATIONS = {
366
  "Iceland": "Iceland"
367
  }
368
 
 
369
  @lru_cache(maxsize=100)
370
  def translate_query(query, country):
 
 
 
371
  try:
372
  if is_english(query):
373
  return query
@@ -404,6 +517,10 @@ def is_english(text):
404
  return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
405
 
406
  def search_serphouse(query, country, page=1, num_result=10):
 
 
 
 
407
  url = "https://api.serphouse.com/serp/live"
408
 
409
  now = datetime.utcnow()
@@ -420,7 +537,7 @@ def search_serphouse(query, country, page=1, num_result=10):
420
  "lang": COUNTRY_LANGUAGES.get(country, "en"),
421
  "device": "desktop",
422
  "serp_type": "news",
423
- "page": "1",
424
  "num": "100",
425
  "date_range": date_range,
426
  "sort_by": "date"
@@ -474,6 +591,10 @@ def search_serphouse(query, country, page=1, num_result=10):
474
  }
475
 
476
  def format_results_from_raw(response_data):
 
 
 
 
477
  if "error" in response_data:
478
  return "Error: " + response_data["error"], []
479
 
@@ -481,10 +602,12 @@ def format_results_from_raw(response_data):
481
  results = response_data["results"]
482
  translated_query = response_data["translated_query"]
483
 
 
484
  news_results = results.get('results', {}).get('results', {}).get('news', [])
485
  if not news_results:
486
  return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", []
487
 
 
488
  korean_domains = [
489
  '.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
490
  'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
@@ -502,9 +625,10 @@ def format_results_from_raw(response_data):
502
 
503
  is_korean_content = (
504
  any(domain in url or domain in channel for domain in korean_domains) or
505
- any(keyword in title.lower() for keyword in korean_keywords)
506
  )
507
 
 
508
  if not is_korean_content:
509
  filtered_articles.append({
510
  "index": idx,
@@ -522,9 +646,14 @@ def format_results_from_raw(response_data):
522
  return f"๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", []
523
 
524
  def serphouse_search(query, country):
 
 
 
525
  response_data = search_serphouse(query, country)
526
  return format_results_from_raw(response_data)
527
 
 
 
528
  css = """
529
  /* ์ „์—ญ ์Šคํƒ€์ผ */
530
  footer {visibility: hidden;}
@@ -700,15 +829,44 @@ footer {visibility: hidden;}
700
  }
701
  """
702
 
 
 
703
  with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์„œ๋น„์Šค") as iface:
704
  init_db()
705
 
706
  with gr.Tabs():
707
- # ์ฒซ ๋ฒˆ์งธ ํƒญ (DB ๊ฒ€์ƒ‰)
708
  with gr.Tab("Earnbot"):
709
  gr.Markdown("## EarnBot: ๊ธ€๋กœ๋ฒŒ ๋น…ํ…Œํฌ ๊ธฐ์—… ๋ฐ ํˆฌ์ž ์ „๋ง AI ์ž๋™ ๋ถ„์„")
710
- gr.Markdown(" '์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ' ํด๋ฆญ์‹œ ์ „์ฒด ์ž๋™ ๋ณด๊ณ  ์ƒ์„ฑ, ๊ฐœ๋ณ„์€ '๊ฒ€์ƒ‰(DB ์ž๋™ ์ €์žฅ)'๊ณผ '์ถœ๋ ฅ(DB์ž๋™ ํ˜ธ์ถœ)' ์‚ฌ์šฉ")
711
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  with gr.Row():
713
  full_report_btn = gr.Button("์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ", variant="primary")
714
  full_report_display = gr.Markdown()
@@ -718,6 +876,7 @@ with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์„œ๋น„
718
  outputs=full_report_display
719
  )
720
 
 
721
  with gr.Column():
722
  for i in range(0, len(KOREAN_COMPANIES), 2):
723
  with gr.Row():
 
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
 
45
  return timestamp_str
46
 
47
  def analyze_sentiment_batch(articles, client):
48
+ """
49
+ OpenAI API๋ฅผ ํ†ตํ•ด ๋‰ด์Šค ๊ธฐ์‚ฌ๋“ค์˜ ์ข…ํ•ฉ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰
50
+ """
51
  try:
52
  # ๋ชจ๋“  ๊ธฐ์‚ฌ์˜ ์ œ๋ชฉ๊ณผ ๋‚ด์šฉ์„ ํ•˜๋‚˜์˜ ํ…์ŠคํŠธ๋กœ ๊ฒฐํ•ฉ
53
  combined_text = "\n\n".join([
 
55
  for article in articles
56
  ])
57
 
 
58
  prompt = f"""๋‹ค์Œ ๋‰ด์Šค ๋ชจ์Œ์— ๋Œ€ํ•ด ์ „๋ฐ˜์ ์ธ ๊ฐ์„ฑ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”:
59
 
60
  ๋‰ด์Šค ๋‚ด์šฉ:
 
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
  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",
 
129
  return None, None
130
 
131
  def display_results(articles):
132
+ """
133
+ ๋‰ด์Šค ๊ธฐ์‚ฌ ๋ชฉ๋ก์„ Markdown ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜
134
+ """
135
  output = ""
136
  for idx, article in enumerate(articles, 1):
137
  output += f"### {idx}. {article['title']}\n"
 
142
  return output
143
 
144
  def search_company(company):
145
+ """
146
+ ๋‹จ์ผ ๊ธฐ์—…(๋˜๋Š” ํ‚ค์›Œ๋“œ)์— ๋Œ€ํ•ด ๋ฏธ๊ตญ ๋‰ด์Šค ๊ฒ€์ƒ‰, DB ์ €์žฅ ํ›„ ๊ฒฐ๊ณผ Markdown ๋ฐ˜ํ™˜
147
+ """
148
  error_message, articles = serphouse_search(company, "United States")
149
  if not error_message and articles:
150
  save_to_db(company, "United States", articles)
 
152
  return f"{company}์— ๋Œ€ํ•œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
153
 
154
  def load_company(company):
155
+ """
156
+ DB์—์„œ ๋‹จ์ผ ๊ธฐ์—…(๋˜๋Š” ํ‚ค์›Œ๋“œ)์˜ ๋ฏธ๊ตญ ๋‰ด์Šค ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๋ถˆ๋Ÿฌ์™€ Markdown ๋ฐ˜ํ™˜
157
+ """
158
  results, timestamp = load_from_db(company, "United States")
159
  if results:
160
  return f"### {company} ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ\n์ €์žฅ ์‹œ๊ฐ„: {timestamp}\n\n" + display_results(results)
161
  return f"{company}์— ๋Œ€ํ•œ ์ €์žฅ๋œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
162
 
163
  def show_stats():
164
+ """
165
+ KOREAN_COMPANIES ๋ชฉ๋ก ๋‚ด ๋ชจ๋“  ๊ธฐ์—…์— ๋Œ€ํ•ด:
166
+ - ๊ฐ€์žฅ ์ตœ๊ทผ DB ์ €์žฅ ์ผ์ž
167
+ - ๊ธฐ์‚ฌ ์ˆ˜
168
+ - ๊ฐ์„ฑ ๋ถ„์„ ๊ฒฐ๊ณผ
169
+ ๋ฅผ ์ˆœ์ฐจ(๋˜๋Š” ๋ณ‘๋ ฌ)๋กœ ์กฐํšŒํ•˜์—ฌ ๋ณด๊ณ ์„œ ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
170
+ """
171
  conn = sqlite3.connect("search_results.db")
172
  c = conn.cursor()
173
 
174
  output = "## ํ•œ๊ตญ ๊ธฐ์—… ๋‰ด์Šค ๋ถ„์„ ๋ฆฌํฌํŠธ\n\n"
175
 
176
+ # ๋ชจ๋“  ๊ธฐ์—…์— ๋Œ€ํ•ด DB์—์„œ ์ฝ์–ด์˜ฌ (company, timestamp, articles) ๋ชฉ๋ก ์ˆ˜์ง‘
177
+ data_list = []
178
  for company in KOREAN_COMPANIES:
179
  c.execute("""
180
  SELECT results, timestamp
 
184
  LIMIT 1
185
  """, (company,))
186
 
187
+ row = c.fetchone()
188
+ if row:
189
+ results_json, timestamp = row
190
  articles = json.loads(results_json)
191
  seoul_time = convert_to_seoul_time(timestamp)
192
+ data_list.append((company, seoul_time, articles))
 
 
 
 
 
 
 
 
 
 
193
 
194
  conn.close()
195
+
196
+ # (์˜ต์…˜) ๊ฐ ๊ธฐ์—… ๊ฐ์„ฑ ๋ถ„์„์„ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
197
+ def analyze_data(item):
198
+ comp, tstamp, arts = item
199
+ sentiment = ""
200
+ if arts:
201
+ sentiment = analyze_sentiment_batch(arts, client)
202
+ return (comp, tstamp, len(arts), sentiment)
203
+
204
+ # ThreadPoolExecutor๋กœ ๋ณ‘๋ ฌ ๊ฐ์„ฑ ๋ถ„์„
205
+ results_list = []
206
+ with ThreadPoolExecutor(max_workers=5) as executor:
207
+ futures = [executor.submit(analyze_data, dl) for dl in data_list]
208
+ for future in as_completed(futures):
209
+ results_list.append(future.result())
210
+
211
+ # ๊ฒฐ๊ณผ ์ •๋ ฌ(์›ํ•˜๋Š” ์ˆœ์„œ๋Œ€๋กœ) - ์—ฌ๊ธฐ์„œ๋Š” ๊ธฐ์—…๋ช… ๊ธฐ์ค€ or ๊ทธ๋ƒฅ ์ˆœ์„œ ์—†์Œ
212
+ for comp, tstamp, count, sentiment in results_list:
213
+ output += f"### {comp}\n"
214
+ output += f"- ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {tstamp}\n"
215
+ output += f"- ์ €์žฅ๋œ ๊ธฐ์‚ฌ ์ˆ˜: {count}๊ฑด\n\n"
216
+ if sentiment:
217
+ output += "#### ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„\n"
218
+ output += f"{sentiment}\n\n"
219
+ output += "---\n\n"
220
+
221
  return output
222
 
223
+
224
+ ### (1) ์ „์ฒด ๊ฒ€์ƒ‰: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์ ์šฉ
225
  def search_all_companies():
226
+ """
227
+ KOREAN_COMPANIES ๋ฆฌ์ŠคํŠธ ๋‚ด ๋ชจ๋“  ๊ธฐ์—…์— ๋Œ€ํ•ด,
228
+ ๊ฒ€์ƒ‰์„ ๋ณ‘๋ ฌ(์“ฐ๋ ˆ๋“œ)๋กœ ์ˆ˜ํ–‰ ํ›„ ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์ณ Markdown ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
229
+ """
230
  overall_result = "# [์ „์ฒด ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ]\n\n"
231
+
232
+ def do_search(comp):
233
+ return comp, search_company(comp)
234
+
235
+ with ThreadPoolExecutor(max_workers=5) as executor:
236
+ futures = [executor.submit(do_search, c) for c in KOREAN_COMPANIES]
237
+ for future in as_completed(futures):
238
+ comp, res_text = future.result()
239
+ overall_result += f"## {comp}\n"
240
+ overall_result += res_text + "\n\n"
241
+
242
  return overall_result
243
 
244
  def load_all_companies():
245
+ """
246
+ KOREAN_COMPANIES ๋ฆฌ์ŠคํŠธ ๋‚ด ๋ชจ๋“  ๊ธฐ์—…์— ๋Œ€ํ•ด,
247
+ DB์—์„œ ๋ถˆ๋Ÿฌ์˜จ ๊ฒฐ๊ณผ๋ฅผ ์ˆœ์ฐจ(๋˜๋Š” ๋ณ‘๋ ฌ)๋กœ ํ•ฉ์ณ์„œ Markdown ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
248
+ """
249
  overall_result = "# [์ „์ฒด ์ถœ๋ ฅ ๊ฒฐ๊ณผ]\n\n"
250
+
251
  for comp in KOREAN_COMPANIES:
252
  overall_result += f"## {comp}\n"
253
  overall_result += load_company(comp)
 
255
  return overall_result
256
 
257
  def full_summary_report():
258
+ """
259
+ (1) ๋ชจ๋“  ๊ธฐ์—… ๊ฒ€์ƒ‰ -> (2) DB์—์„œ ๋ชจ๋“  ๊ธฐ์—… ๋ถˆ๋Ÿฌ์˜ค๊ธฐ -> (3) ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„
260
+ ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰ํ•˜์—ฌ, ์ „์ฒด ๋ฆฌํฌํŠธ๋ฅผ ํ•ฉ์ณ ๋ฐ˜ํ™˜
261
+ """
262
+ # 1) ์ „์ฒด ๊ฒ€์ƒ‰(๋ณ‘๋ ฌ)
263
  search_result_text = search_all_companies()
264
+
265
+ # 2) ์ „์ฒด ์ถœ๋ ฅ(์ˆœ์ฐจ)
266
  load_result_text = load_all_companies()
267
+
268
+ # 3) ์ „์ฒด ํ†ต๊ณ„(๊ฐ์„ฑ ๋ถ„์„)
269
  stats_text = show_stats()
270
 
271
  combined_report = (
272
  "# ์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ\n\n"
273
  "์•„๋ž˜ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค:\n"
274
+ "1. ๋ชจ๋“  ์ข…๋ชฉ ๊ฒ€์ƒ‰(๋ณ‘๋ ฌ) โ†’ 2. ๋ชจ๋“  ์ข…๋ชฉ DB ๊ฒฐ๊ณผ ์ถœ๋ ฅ โ†’ 3. ์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„\n\n"
275
  f"{search_result_text}\n\n"
276
  f"{load_result_text}\n\n"
277
  "## [์ „์ฒด ๊ฐ์„ฑ ๋ถ„์„ ํ†ต๊ณ„]\n\n"
 
279
  )
280
  return combined_report
281
 
282
+
283
+ ### (2) ์‚ฌ์šฉ์ž ์ž„์˜ ๊ฒ€์ƒ‰ + ๊ตญ๊ฐ€ ์„ ํƒ ๊ธฐ๋Šฅ
284
+ def search_custom(query, country):
285
+ """
286
+ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ (query, country)๋ฅผ ๋Œ€์ƒ์œผ๋กœ
287
+ - ๊ฒ€์ƒ‰ (API ์š”์ฒญ)
288
+ - DB ์ €์žฅ
289
+ - DB ๋กœ๋“œ ํ›„ ๊ฐ์„ฑ ๋ถ„์„
290
+ - ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ Markdown ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜
291
+ """
292
+ # 1) ๊ฒ€์ƒ‰
293
+ error_message, articles = serphouse_search(query, country)
294
+ if error_message:
295
+ return f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {error_message}"
296
+ if not articles:
297
+ return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
298
+
299
+ # 2) DB ์ €์žฅ
300
+ save_to_db(query, country, articles)
301
+
302
+ # 3) DB์—์„œ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
303
+ results, timestamp = load_from_db(query, country)
304
+ if not results:
305
+ return f"DB ๋กœ๋“œ ์‹คํŒจ: ์ €์žฅ๋œ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค."
306
+
307
+ # 4) ๊ฐ์„ฑ ๋ถ„์„
308
+ sentiment_analysis = analyze_sentiment_batch(results, client)
309
+
310
+ # 5) ์ตœ์ข… ๋ฆฌํฌํŠธ(๊ธฐ์‚ฌ ๋ชฉ๋ก + ๊ฐ์„ฑ ๋ถ„์„)
311
+ output = f"## [์‚ฌ์šฉ์ž ์ž„์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ]\n\n"
312
+ output += f"**ํ‚ค์›Œ๋“œ**: {query}\n\n"
313
+ output += f"**๊ตญ๊ฐ€**: {country}\n\n"
314
+ output += f"**์ €์žฅ ์‹œ๊ฐ„**: {timestamp}\n\n"
315
+ output += display_results(results)
316
+
317
+ output += "### ๋‰ด์Šค ๊ฐ์„ฑ ๋ถ„์„\n"
318
+ output += f"{sentiment_analysis}\n"
319
+ return output
320
+
321
+
322
+ ### (ํ•„์ˆ˜) API ์ธ์ฆ
323
  ACCESS_TOKEN = os.getenv("HF_TOKEN")
324
  if not ACCESS_TOKEN:
325
  raise ValueError("HF_TOKEN environment variable is not set")
 
331
 
332
  API_KEY = os.getenv("SERPHOUSE_API_KEY")
333
 
334
+
335
+ ### ๊ตญ๊ฐ€๋ณ„ ์„ค์ •
336
  COUNTRY_LANGUAGES = {
337
  "United States": "en",
338
  "KOREA": "ko",
 
475
  "Iceland": "Iceland"
476
  }
477
 
478
+
479
  @lru_cache(maxsize=100)
480
  def translate_query(query, country):
481
+ """
482
+ Google Translation API(๋น„๊ณต์‹) ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰์–ด๋ฅผ ํ•ด๋‹น ๊ตญ๊ฐ€ ์–ธ์–ด๋กœ ๋ฒˆ์—ญ
483
+ """
484
  try:
485
  if is_english(query):
486
  return query
 
517
  return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
518
 
519
  def search_serphouse(query, country, page=1, num_result=10):
520
+ """
521
+ SerpHouse API์— ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰ ์š”์ฒญ์„ ๋ณด๋‚ด์–ด,
522
+ '๋‰ด์Šค' ํƒญ (sort_by=date)์—์„œ ํ•ด๋‹น query์— ๋Œ€ํ•œ ๊ธฐ์‚ฌ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜จ๋‹ค.
523
+ """
524
  url = "https://api.serphouse.com/serp/live"
525
 
526
  now = datetime.utcnow()
 
537
  "lang": COUNTRY_LANGUAGES.get(country, "en"),
538
  "device": "desktop",
539
  "serp_type": "news",
540
+ "page": str(page),
541
  "num": "100",
542
  "date_range": date_range,
543
  "sort_by": "date"
 
591
  }
592
 
593
  def format_results_from_raw(response_data):
594
+ """
595
+ SerpHouse API์˜ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜์—ฌ,
596
+ (์—๋Ÿฌ๋ฉ”์‹œ์ง€, ๊ธฐ์‚ฌ๋ฆฌ์ŠคํŠธ) ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜.
597
+ """
598
  if "error" in response_data:
599
  return "Error: " + response_data["error"], []
600
 
 
602
  results = response_data["results"]
603
  translated_query = response_data["translated_query"]
604
 
605
+ # ์‹ค์ œ ๋‰ด์Šค ๊ฒฐ๊ณผ
606
  news_results = results.get('results', {}).get('results', {}).get('news', [])
607
  if not news_results:
608
  return "๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", []
609
 
610
+ # ํ•œ๊ตญ ๋„๋ฉ”์ธ ๋ฐ ํ•œ๊ตญ ๊ด€๋ จ ํ‚ค์›Œ๋“œ ํฌํ•จ ๊ธฐ์‚ฌ ์ œ์™ธ
611
  korean_domains = [
612
  '.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
613
  'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
 
625
 
626
  is_korean_content = (
627
  any(domain in url or domain in channel for domain in korean_domains) or
628
+ any(keyword in title for keyword in korean_keywords)
629
  )
630
 
631
+ # ํ•œ๊ตญ์–ด ๋‰ด์Šค(๋˜๋Š” ํ•œ๊ตญ ๋„๋ฉ”์ธ) ์ œ์™ธ
632
  if not is_korean_content:
633
  filtered_articles.append({
634
  "index": idx,
 
646
  return f"๊ฒฐ๊ณผ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}", []
647
 
648
  def serphouse_search(query, country):
649
+ """
650
+ ๊ฒ€์ƒ‰ ๋ฐ ๊ฒฐ๊ณผ ํฌ๋งคํŒ…๊นŒ์ง€ ์ผ๊ด„ ์ฒ˜๋ฆฌ
651
+ """
652
  response_data = search_serphouse(query, country)
653
  return format_results_from_raw(response_data)
654
 
655
+
656
+ # CSS (UI ์ปค์Šคํ„ฐ๋งˆ์ด์ง•)
657
  css = """
658
  /* ์ „์—ญ ์Šคํƒ€์ผ */
659
  footer {visibility: hidden;}
 
829
  }
830
  """
831
 
832
+ import gradio as gr
833
+
834
  with gr.Blocks(theme="Yntec/HaleyCH_Theme_Orange", css=css, title="NewsAI ์„œ๋น„์Šค") as iface:
835
  init_db()
836
 
837
  with gr.Tabs():
838
+ # ์ฒซ ๋ฒˆ์งธ ํƒญ
839
  with gr.Tab("Earnbot"):
840
  gr.Markdown("## EarnBot: ๊ธ€๋กœ๋ฒŒ ๋น…ํ…Œํฌ ๊ธฐ์—… ๋ฐ ํˆฌ์ž ์ „๋ง AI ์ž๋™ ๋ถ„์„")
841
+ gr.Markdown(" * '์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ' ํด๋ฆญ ์‹œ ์ „์ฒด ์ž๋™ ๋ณด๊ณ  ์ƒ์„ฑ.\n * ์•„๋ž˜ ๊ฐœ๋ณ„ ์ข…๋ชฉ์˜ '๊ฒ€์ƒ‰(DB ์ž๋™ ์ €์žฅ)'๊ณผ '์ถœ๋ ฅ(DB ์ž๋™ ํ˜ธ์ถœ)'๋„ ๊ฐ€๋Šฅ.\n * ์ถ”๊ฐ€๋กœ, ์›ํ•˜๋Š” ์ž„์˜ ํ‚ค์›Œ๋“œ ๋ฐ ๊ตญ๊ฐ€๋กœ ๊ฒ€์ƒ‰/๋ถ„์„ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.")
842
 
843
+ # (2) ์‚ฌ์šฉ์ž ์ž„์˜ ๊ฒ€์ƒ‰ ์„น์…˜
844
+ with gr.Group():
845
+ gr.Markdown("### ์‚ฌ์šฉ์ž ์ž„์˜ ๊ฒ€์ƒ‰")
846
+ with gr.Row():
847
+ with gr.Column():
848
+ user_input = gr.Textbox(
849
+ label="๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ",
850
+ placeholder="์˜ˆ) Apple, Samsung ๋“ฑ ์ž์œ ๋กญ๊ฒŒ"
851
+ )
852
+ with gr.Column():
853
+ country_selection = gr.Dropdown(
854
+ choices=list(COUNTRY_LOCATIONS.keys()),
855
+ value="United States",
856
+ label="๊ตญ๊ฐ€ ์„ ํƒ"
857
+ )
858
+ with gr.Column():
859
+ custom_search_btn = gr.Button("์‹คํ–‰", variant="primary")
860
+
861
+ custom_search_output = gr.Markdown()
862
+
863
+ custom_search_btn.click(
864
+ fn=search_custom,
865
+ inputs=[user_input, country_selection],
866
+ outputs=custom_search_output
867
+ )
868
+
869
+ # ์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ ๋ฒ„ํŠผ
870
  with gr.Row():
871
  full_report_btn = gr.Button("์ „์ฒด ๋ถ„์„ ๋ณด๊ณ  ์š”์•ฝ", variant="primary")
872
  full_report_display = gr.Markdown()
 
876
  outputs=full_report_display
877
  )
878
 
879
+ # ๊ธฐ์กด ๊ฐœ๋ณ„ ๊ธฐ์—… ๊ฒ€์ƒ‰/์ถœ๋ ฅ ์˜์—ญ
880
  with gr.Column():
881
  for i in range(0, len(KOREAN_COMPANIES), 2):
882
  with gr.Row():