openfree commited on
Commit
d85fd53
·
verified ·
1 Parent(s): 1d86f7e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +11 -1042
app.py CHANGED
@@ -14,1045 +14,14 @@ 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
- Convert a given timestamp string (UTC) to Seoul time (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"Time conversion error: {str(e)}")
48
- return timestamp_str
49
-
50
- def analyze_sentiment_batch(articles, client):
51
- """
52
- Perform a comprehensive sentiment analysis of the news articles using the OpenAI API.
53
- """
54
- try:
55
- # Combine all articles into a single text
56
- combined_text = "\n\n".join([
57
- f"Title: {article.get('title', '')}\nContent: {article.get('snippet', '')}"
58
- for article in articles
59
- ])
60
-
61
- prompt = f"""Please perform an overall sentiment analysis of the following collection of news articles:
62
-
63
- News content:
64
- {combined_text}
65
-
66
- Please follow this format:
67
- 1. Overall Sentiment: [Positive/Negative/Neutral]
68
- 2. Key Positive Factors:
69
- - [Item1]
70
- - [Item2]
71
- 3. Key Negative Factors:
72
- - [Item1]
73
- - [Item2]
74
- 4. Summary: [Detailed explanation]
75
- """
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"Sentiment analysis failed: {str(e)}"
87
-
88
-
89
- # Initialize the database
90
- def init_db():
91
- """
92
- Initialize the SQLite database (search_results.db) if it doesn't already exist.
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
- Save the search results for a specific (keyword, country) combination into the database.
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
- Load the most recent search results for a specific (keyword, country) combination from the database.
126
- Returns the data and the timestamp.
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
- Convert a list of news articles into a Markdown string for display.
143
- """
144
- output = ""
145
- for idx, article in enumerate(articles, 1):
146
- output += f"### {idx}. {article['title']}\n"
147
- output += f"Source: {article['channel']}\n"
148
- output += f"Time: {article['time']}\n"
149
- output += f"Link: {article['link']}\n"
150
- output += f"Summary: {article['snippet']}\n\n"
151
- return output
152
-
153
-
154
- ########################################
155
- # 1) Search => Articles + Analysis, then save to DB
156
- ########################################
157
- def search_company(company):
158
- """
159
- For a single company (or keyword), search US news.
160
- 1) Retrieve a list of articles
161
- 2) Perform sentiment analysis
162
- 3) Save results to DB
163
- 4) Return (articles + analysis) in a single output.
164
- """
165
- error_message, articles = serphouse_search(company, "United States")
166
- if not error_message and articles:
167
- # Perform sentiment analysis
168
- analysis = analyze_sentiment_batch(articles, client)
169
-
170
- # Prepare data to save in DB
171
- store_dict = {
172
- "articles": articles,
173
- "analysis": analysis
174
- }
175
- save_to_db(company, "United States", store_dict)
176
-
177
- # Prepare output for display
178
- output = display_results(articles)
179
- output += f"\n\n### Analysis Report\n{analysis}\n"
180
- return output
181
- return f"No search results found for {company}."
182
-
183
- ########################################
184
- # 2) Load => Return articles + analysis from DB
185
- ########################################
186
- def load_company(company):
187
- """
188
- Load the most recent US news search results for the given company (or keyword) from the database,
189
- and return the articles + analysis in a single output.
190
- """
191
- data, timestamp = load_from_db(company, "United States")
192
- if data:
193
- articles = data.get("articles", [])
194
- analysis = data.get("analysis", "")
195
-
196
- output = f"### {company} Search Results\nLast Updated: {timestamp}\n\n"
197
- output += display_results(articles)
198
- output += f"\n\n### Analysis Report\n{analysis}\n"
199
- return output
200
- return f"No saved results for {company}."
201
-
202
-
203
- ########################################
204
- # 3) Updated show_stats() with new title
205
- ########################################
206
- def show_stats():
207
- """
208
- For each company in KOREAN_COMPANIES:
209
- - Retrieve the most recent timestamp in DB
210
- - Number of articles
211
- - Sentiment analysis result
212
- Return these in a report format.
213
-
214
- Title changed to: "EarnBOT Analysis Report"
215
- """
216
- conn = sqlite3.connect("search_results.db")
217
- c = conn.cursor()
218
-
219
- output = "## EarnBOT Analysis Report\n\n"
220
-
221
- data_list = []
222
- for company in KOREAN_COMPANIES:
223
- c.execute("""
224
- SELECT results, timestamp
225
- FROM searches
226
- WHERE keyword = ?
227
- ORDER BY timestamp DESC
228
- LIMIT 1
229
- """, (company,))
230
-
231
- row = c.fetchone()
232
- if row:
233
- results_json, timestamp = row
234
- data_list.append((company, timestamp, results_json))
235
-
236
- conn.close()
237
-
238
- def analyze_data(item):
239
- comp, tstamp, results_json = item
240
- data = json.loads(results_json)
241
- articles = data.get("articles", [])
242
- analysis = data.get("analysis", "")
243
-
244
- count_articles = len(articles)
245
- return (comp, tstamp, count_articles, analysis)
246
-
247
- results_list = []
248
- with ThreadPoolExecutor(max_workers=5) as executor:
249
- futures = [executor.submit(analyze_data, dl) for dl in data_list]
250
- for future in as_completed(futures):
251
- results_list.append(future.result())
252
-
253
- for comp, tstamp, count, analysis in results_list:
254
- seoul_time = convert_to_seoul_time(tstamp)
255
- output += f"### {comp}\n"
256
- output += f"- Last updated: {seoul_time}\n"
257
- output += f"- Number of articles stored: {count}\n\n"
258
- if analysis:
259
- output += "#### News Sentiment Analysis\n"
260
- output += f"{analysis}\n\n"
261
- output += "---\n\n"
262
-
263
- return output
264
-
265
-
266
- def search_all_companies():
267
- """
268
- Search all companies in KOREAN_COMPANIES (in parallel),
269
- perform sentiment analysis + save to DB => return Markdown of all results.
270
- """
271
- overall_result = "# [Search Results for All Companies]\n\n"
272
-
273
- def do_search(comp):
274
- return comp, search_company(comp)
275
-
276
- with ThreadPoolExecutor(max_workers=5) as executor:
277
- futures = [executor.submit(do_search, c) for c in KOREAN_COMPANIES]
278
- for future in as_completed(futures):
279
- comp, res_text = future.result()
280
- overall_result += f"## {comp}\n"
281
- overall_result += res_text + "\n\n"
282
-
283
- return overall_result
284
-
285
- def load_all_companies():
286
- """
287
- Load articles + analysis for all companies in KOREAN_COMPANIES from the DB => return Markdown.
288
- """
289
- overall_result = "# [All Companies Data Output]\n\n"
290
-
291
- for comp in KOREAN_COMPANIES:
292
- overall_result += f"## {comp}\n"
293
- overall_result += load_company(comp)
294
- overall_result += "\n"
295
- return overall_result
296
-
297
- def full_summary_report():
298
- """
299
- 1) Search all companies (in parallel) -> 2) Load results -> 3) Show sentiment analysis stats
300
- Return a combined report with all three steps.
301
- """
302
- # 1) Search all companies => store to DB
303
- search_result_text = search_all_companies()
304
-
305
- # 2) Load all results => from DB
306
- load_result_text = load_all_companies()
307
-
308
- # 3) Show stats => EarnBOT Analysis Report
309
- stats_text = show_stats()
310
-
311
- combined_report = (
312
- "# Full Analysis Summary Report\n\n"
313
- "Executed in the following order:\n"
314
- "1. Search all companies (parallel) + sentiment analysis => 2. Load results from DB => 3. Show overall sentiment analysis stats\n\n"
315
- f"{search_result_text}\n\n"
316
- f"{load_result_text}\n\n"
317
- "## [Overall Sentiment Analysis Stats]\n\n"
318
- f"{stats_text}"
319
- )
320
- return combined_report
321
-
322
-
323
- ########################################
324
- # Additional feature: User custom search
325
- ########################################
326
- def search_custom(query, country):
327
- """
328
- For a user-provided (query, country):
329
- 1) Search + sentiment analysis => save to DB
330
- 2) Load from DB => display articles + analysis
331
- """
332
- error_message, articles = serphouse_search(query, country)
333
- if error_message:
334
- return f"An error occurred: {error_message}"
335
- if not articles:
336
- return "No results were found for your query."
337
-
338
- # 1) Perform analysis
339
- analysis = analyze_sentiment_batch(articles, client)
340
-
341
- # 2) Save to DB
342
- save_data = {
343
- "articles": articles,
344
- "analysis": analysis
345
- }
346
- save_to_db(query, country, save_data)
347
-
348
- # 3) Reload from DB
349
- loaded_data, timestamp = load_from_db(query, country)
350
- if not loaded_data:
351
- return "Failed to load data from DB."
352
-
353
- # 4) Prepare final output
354
- out = f"## [Custom Search Results]\n\n"
355
- out += f"**Keyword**: {query}\n\n"
356
- out += f"**Country**: {country}\n\n"
357
- out += f"**Timestamp**: {timestamp}\n\n"
358
-
359
- arts = loaded_data.get("articles", [])
360
- analy = loaded_data.get("analysis", "")
361
-
362
- out += display_results(arts)
363
- out += f"### News Sentiment Analysis\n{analy}\n"
364
-
365
- return out
366
-
367
-
368
- ########################################
369
- # API Authentication
370
- ########################################
371
- ACCESS_TOKEN = os.getenv("HF_TOKEN")
372
- if not ACCESS_TOKEN:
373
- raise ValueError("HF_TOKEN environment variable is not set")
374
-
375
- client = OpenAI(
376
- base_url="https://api-inference.huggingface.co/v1/",
377
- api_key=ACCESS_TOKEN,
378
- )
379
-
380
- API_KEY = os.getenv("SERPHOUSE_API_KEY")
381
-
382
-
383
- ########################################
384
- # Country-specific settings
385
- ########################################
386
- COUNTRY_LANGUAGES = {
387
- "United States": "en",
388
- "KOREA": "ko",
389
- "United Kingdom": "en",
390
- "Taiwan": "zh-TW",
391
- "Canada": "en",
392
- "Australia": "en",
393
- "Germany": "de",
394
- "France": "fr",
395
- "Japan": "ja",
396
- "India": "hi",
397
- "Brazil": "pt",
398
- "Mexico": "es",
399
- "Russia": "ru",
400
- "Italy": "it",
401
- "Spain": "es",
402
- "Netherlands": "nl",
403
- "Singapore": "en",
404
- "Hong Kong": "zh-HK",
405
- "Indonesia": "id",
406
- "Malaysia": "ms",
407
- "Philippines": "tl",
408
- "Thailand": "th",
409
- "Vietnam": "vi",
410
- "Belgium": "nl",
411
- "Denmark": "da",
412
- "Finland": "fi",
413
- "Ireland": "en",
414
- "Norway": "no",
415
- "Poland": "pl",
416
- "Sweden": "sv",
417
- "Switzerland": "de",
418
- "Austria": "de",
419
- "Czech Republic": "cs",
420
- "Greece": "el",
421
- "Hungary": "hu",
422
- "Portugal": "pt",
423
- "Romania": "ro",
424
- "Turkey": "tr",
425
- "Israel": "he",
426
- "Saudi Arabia": "ar",
427
- "United Arab Emirates": "ar",
428
- "South Africa": "en",
429
- "Argentina": "es",
430
- "Chile": "es",
431
- "Colombia": "es",
432
- "Peru": "es",
433
- "Venezuela": "es",
434
- "New Zealand": "en",
435
- "Bangladesh": "bn",
436
- "Pakistan": "ur",
437
- "Egypt": "ar",
438
- "Morocco": "ar",
439
- "Nigeria": "en",
440
- "Kenya": "sw",
441
- "Ukraine": "uk",
442
- "Croatia": "hr",
443
- "Slovakia": "sk",
444
- "Bulgaria": "bg",
445
- "Serbia": "sr",
446
- "Estonia": "et",
447
- "Latvia": "lv",
448
- "Lithuania": "lt",
449
- "Slovenia": "sl",
450
- "Luxembourg": "Luxembourg",
451
- "Malta": "Malta",
452
- "Cyprus": "Cyprus",
453
- "Iceland": "Iceland"
454
- }
455
-
456
- COUNTRY_LOCATIONS = {
457
- "United States": "United States",
458
- "KOREA": "kr",
459
- "United Kingdom": "United Kingdom",
460
- "Taiwan": "Taiwan",
461
- "Canada": "Canada",
462
- "Australia": "Australia",
463
- "Germany": "Germany",
464
- "France": "France",
465
- "Japan": "Japan",
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
- Use the unofficial Google Translation API to translate the query into the target country's language.
531
- If the query is already in English, or if translation fails, return the original query.
532
- """
533
- try:
534
- if is_english(query):
535
- return query
536
-
537
- if country in COUNTRY_LANGUAGES:
538
- if country == "South Korea":
539
- return query
540
- target_lang = COUNTRY_LANGUAGES[country]
541
-
542
- url = "https://translate.googleapis.com/translate_a/single"
543
- params = {
544
- "client": "gtx",
545
- "sl": "auto",
546
- "tl": target_lang,
547
- "dt": "t",
548
- "q": query
549
- }
550
-
551
- session = requests.Session()
552
- retries = Retry(total=3, backoff_factor=0.5)
553
- session.mount('https://', HTTPAdapter(max_retries=retries))
554
-
555
- response = session.get(url, params=params, timeout=(5, 10))
556
- translated_text = response.json()[0][0][0]
557
- return translated_text
558
- return query
559
-
560
- except Exception as e:
561
- print(f"Translation error: {str(e)}")
562
- return query
563
-
564
- def is_english(text):
565
- """
566
- Check if a string is (mostly) English by verifying character code ranges.
567
- """
568
- return all(ord(char) < 128 for char in text.replace(' ', '').replace('-', '').replace('_', ''))
569
-
570
- def search_serphouse(query, country, page=1, num_result=10):
571
- """
572
- Send a real-time search request to the SerpHouse API,
573
- specifying the 'news' tab (sort_by=date) for the given query.
574
- Returns a dict with 'results' or 'error'.
575
- """
576
- url = "https://api.serphouse.com/serp/live"
577
-
578
- now = datetime.utcnow()
579
- yesterday = now - timedelta(days=1)
580
- date_range = f"{yesterday.strftime('%Y-%m-%d')},{now.strftime('%Y-%m-%d')}"
581
-
582
- translated_query = translate_query(query, country)
583
-
584
- payload = {
585
- "data": {
586
- "q": translated_query,
587
- "domain": "google.com",
588
- "loc": COUNTRY_LOCATIONS.get(country, "United States"),
589
- "lang": COUNTRY_LANGUAGES.get(country, "en"),
590
- "device": "desktop",
591
- "serp_type": "news",
592
- "page": str(page),
593
- "num": "100",
594
- "date_range": date_range,
595
- "sort_by": "date"
596
- }
597
- }
598
-
599
- headers = {
600
- "accept": "application/json",
601
- "content-type": "application/json",
602
- "authorization": f"Bearer {API_KEY}"
603
- }
604
-
605
- try:
606
- session = requests.Session()
607
-
608
- retries = Retry(
609
- total=5,
610
- backoff_factor=1,
611
- status_forcelist=[500, 502, 503, 504, 429],
612
- allowed_methods=["POST"]
613
- )
614
-
615
- adapter = HTTPAdapter(max_retries=retries)
616
- session.mount('http://', adapter)
617
- session.mount('https://', adapter)
618
-
619
- response = session.post(
620
- url,
621
- json=payload,
622
- headers=headers,
623
- timeout=(30, 30)
624
- )
625
-
626
- response.raise_for_status()
627
- return {"results": response.json(), "translated_query": translated_query}
628
-
629
- except requests.exceptions.Timeout:
630
- return {
631
- "error": "Search timed out. Please try again later.",
632
- "translated_query": query
633
- }
634
- except requests.exceptions.RequestException as e:
635
- return {
636
- "error": f"Error during search: {str(e)}",
637
- "translated_query": query
638
- }
639
- except Exception as e:
640
- return {
641
- "error": f"Unexpected error occurred: {str(e)}",
642
- "translated_query": query
643
- }
644
-
645
- def format_results_from_raw(response_data):
646
- """
647
- Process the SerpHouse API response data and return (error_message, article_list).
648
- """
649
- if "error" in response_data:
650
- return "Error: " + response_data["error"], []
651
-
652
- try:
653
- results = response_data["results"]
654
- translated_query = response_data["translated_query"]
655
-
656
- news_results = results.get('results', {}).get('results', {}).get('news', [])
657
- if not news_results:
658
- return "No search results found.", []
659
-
660
- # Filter out Korean domains and Korean keywords (example filtering)
661
- korean_domains = [
662
- '.kr', 'korea', 'korean', 'yonhap', 'hankyung', 'chosun',
663
- 'donga', 'joins', 'hani', 'koreatimes', 'koreaherald'
664
- ]
665
- korean_keywords = [
666
- 'korea', 'korean', 'seoul', 'busan', 'incheon', 'daegu',
667
- 'gwangju', 'daejeon', 'ulsan', 'sejong'
668
- ]
669
-
670
- filtered_articles = []
671
- for idx, result in enumerate(news_results, 1):
672
- url = result.get("url", result.get("link", "")).lower()
673
- title = result.get("title", "").lower()
674
- channel = result.get("channel", result.get("source", "")).lower()
675
-
676
- is_korean_content = (
677
- any(domain in url or domain in channel for domain in korean_domains) or
678
- any(keyword in title for keyword in korean_keywords)
679
- )
680
-
681
- # Exclude Korean content
682
- if not is_korean_content:
683
- filtered_articles.append({
684
- "index": idx,
685
- "title": result.get("title", "No Title"),
686
- "link": url,
687
- "snippet": result.get("snippet", "No Content"),
688
- "channel": result.get("channel", result.get("source", "Unknown")),
689
- "time": result.get("time", result.get("date", "Unknown Time")),
690
- "image_url": result.get("img", result.get("thumbnail", "")),
691
- "translated_query": translated_query
692
- })
693
-
694
- return "", filtered_articles
695
- except Exception as e:
696
- return f"Error processing results: {str(e)}", []
697
-
698
- def serphouse_search(query, country):
699
- """
700
- Helper function to search and then format results.
701
- Returns (error_message, article_list).
702
- """
703
- response_data = search_serphouse(query, country)
704
- return format_results_from_raw(response_data)
705
-
706
-
707
- # Refined, modern, and sleek custom CSS
708
- css = """
709
- body {
710
- background: linear-gradient(to bottom right, #f9fafb, #ffffff);
711
- font-family: 'Arial', sans-serif;
712
- }
713
-
714
- /* Hide default Gradio footer */
715
- footer {
716
- visibility: hidden;
717
- }
718
-
719
- /* Header/Status area */
720
- #status_area {
721
- background: rgba(255, 255, 255, 0.9);
722
- padding: 15px;
723
- border-bottom: 1px solid #ddd;
724
- margin-bottom: 20px;
725
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
726
- }
727
-
728
- /* Results area */
729
- #results_area {
730
- padding: 10px;
731
- margin-top: 10px;
732
- }
733
-
734
- /* Tabs style */
735
- .tabs {
736
- border-bottom: 2px solid #ddd !important;
737
- margin-bottom: 20px !important;
738
- }
739
-
740
- .tab-nav {
741
- border-bottom: none !important;
742
- margin-bottom: 0 !important;
743
- }
744
-
745
- .tab-nav button {
746
- font-weight: bold !important;
747
- padding: 10px 20px !important;
748
- background-color: #f0f0f0 !important;
749
- border: 1px solid #ccc !important;
750
- border-radius: 5px !important;
751
- margin-right: 5px !important;
752
- }
753
-
754
- .tab-nav button.selected {
755
- border-bottom: 2px solid #1f77b4 !important;
756
- background-color: #e6f2fa !important;
757
- color: #1f77b4 !important;
758
- }
759
-
760
- /* Status message styling */
761
- #status_area .markdown-text {
762
- font-size: 1.1em;
763
- color: #2c3e50;
764
- padding: 10px 0;
765
- }
766
-
767
- /* Main container grouping */
768
- .group {
769
- border: 1px solid #eee;
770
- padding: 15px;
771
- margin-bottom: 15px;
772
- border-radius: 5px;
773
- background: white;
774
- transition: all 0.3s ease;
775
- opacity: 0;
776
- transform: translateY(20px);
777
- }
778
- .group.visible {
779
- opacity: 1;
780
- transform: translateY(0);
781
- }
782
-
783
- /* Buttons */
784
- .primary-btn {
785
- background: #1f77b4 !important;
786
- border: none !important;
787
- color: #fff !important;
788
- border-radius: 5px !important;
789
- padding: 10px 20px !important;
790
- cursor: pointer !important;
791
- }
792
- .primary-btn:hover {
793
- background: #155a8c !important;
794
- }
795
-
796
- .secondary-btn {
797
- background: #f0f0f0 !important;
798
- border: 1px solid #ccc !important;
799
- color: #333 !important;
800
- border-radius: 5px !important;
801
- padding: 10px 20px !important;
802
- cursor: pointer !important;
803
- }
804
- .secondary-btn:hover {
805
- background: #e0e0e0 !important;
806
- }
807
-
808
- /* Input fields */
809
- .textbox {
810
- border: 1px solid #ddd !important;
811
- border-radius: 4px !important;
812
- }
813
-
814
- /* Progress bar container */
815
- .progress-container {
816
- position: fixed;
817
- top: 0;
818
- left: 0;
819
- width: 100%;
820
- height: 6px;
821
- background: #e0e0e0;
822
- z-index: 1000;
823
- }
824
-
825
- /* Progress bar */
826
- .progress-bar {
827
- height: 100%;
828
- background: linear-gradient(90deg, #2196F3, #00BCD4);
829
- box-shadow: 0 0 10px rgba(33, 150, 243, 0.5);
830
- transition: width 0.3s ease;
831
- animation: progress-glow 1.5s ease-in-out infinite;
832
- }
833
-
834
- /* Progress text */
835
- .progress-text {
836
- position: fixed;
837
- top: 8px;
838
- left: 50%;
839
- transform: translateX(-50%);
840
- background: #333;
841
- color: white;
842
- padding: 4px 12px;
843
- border-radius: 15px;
844
- font-size: 14px;
845
- z-index: 1001;
846
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
847
- }
848
-
849
- /* Progress bar animation */
850
- @keyframes progress-glow {
851
- 0% {
852
- box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
853
- }
854
- 50% {
855
- box-shadow: 0 0 20px rgba(33, 150, 243, 0.8);
856
- }
857
- 100% {
858
- box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
859
- }
860
- }
861
-
862
- /* Loading state */
863
- .loading {
864
- opacity: 0.7;
865
- pointer-events: none;
866
- transition: opacity 0.3s ease;
867
- }
868
-
869
- /* Responsive design for smaller screens */
870
- @media (max-width: 768px) {
871
- .group {
872
- padding: 10px;
873
- margin-bottom: 15px;
874
- }
875
-
876
- .progress-text {
877
- font-size: 12px;
878
- padding: 3px 10px;
879
- }
880
- }
881
-
882
- /* Example section styling */
883
- .examples-table {
884
- margin-top: 10px !important;
885
- margin-bottom: 20px !important;
886
- }
887
-
888
- .examples-table button {
889
- background-color: #f0f0f0 !important;
890
- border: 1px solid #ddd !important;
891
- border-radius: 4px !important;
892
- padding: 5px 10px !important;
893
- margin: 2px !important;
894
- transition: all 0.3s ease !important;
895
- }
896
-
897
- .examples-table button:hover {
898
- background-color: #e0e0e0 !important;
899
- transform: translateY(-1px) !important;
900
- box-shadow: 0 2px 5px rgba(0,0,0,0.1) !important;
901
- }
902
-
903
- .examples-table .label {
904
- font-weight: bold !important;
905
- color: #444 !important;
906
- margin-bottom: 5px !important;
907
- }
908
- """
909
-
910
- # --- Gradio Interface (UI portion only) ---
911
- with gr.Blocks(css=css, title="NewsAI Service") as iface:
912
- # Initialize the database first (keeping the call to init_db(), unchanged)
913
- init_db()
914
-
915
- gr.HTML("""<a href="https://visitorbadge.io/status?path=https%3A%2F%2Fopenfree-MoneyRadar.hf.space">
916
- <img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fopenfree-MoneyRadar.hf.space&countColor=%23263759" />
917
- </a>""")
918
-
919
-
920
- with gr.Tabs():
921
- with gr.Tab("MoneyRadar"):
922
- # Added usage instructions and feature explanations here:
923
- gr.Markdown(
924
- """
925
- ## MoneyRadar: Implies scanning(Automatic extraction of top 100 priority news within the last 24 hours) the market to spot money-making opportunities.
926
-
927
- **How to Use This Service**:
928
- 1. **Custom Search**: Enter any keyword and choose a target country to fetch the latest news. The system automatically performs sentiment analysis and stores results in the database.
929
- 2. **Generate Full Analysis Summary Report**: This will automatically:
930
- - Search all predefined companies (in parallel),
931
- - Store the articles and sentiment analysis,
932
- - Display a combined overall report.
933
- 3. **Individual Companies**:
934
- - **Search**: Fetch and analyze the latest news from Google (for the chosen company).
935
- - **Load from DB**: Retrieve the most recent saved news and sentiment analysis from the local database.
936
-
937
- **Features**:
938
- - **Real-time News Scraping**: Retrieves fresh articles from multiple regions.
939
- - **Advanced Sentiment Analysis**: Uses state-of-the-art NLP models via the API.
940
- - **Data Persistence**: Automatically saves and retrieves search results in a local SQLite database for quick reference.
941
- - **Flexible**: Ability to search any keyword/country or select from predefined Big Tech & finance-related terms.
942
- 0. **Community: https://discord.gg/openfreeai
943
- ---
944
- """
945
- )
946
-
947
- # User custom search section
948
- with gr.Group():
949
- gr.Markdown("### Custom Search")
950
- with gr.Row():
951
- with gr.Column():
952
- user_input = gr.Textbox(
953
- label="Enter your keyword",
954
- placeholder="e.g., Apple, Samsung, etc.",
955
- elem_classes="textbox"
956
- )
957
- with gr.Column():
958
- country_selection = gr.Dropdown(
959
- choices=list(COUNTRY_LOCATIONS.keys()),
960
- value="United States",
961
- label="Select Country"
962
- )
963
- with gr.Column():
964
- custom_search_btn = gr.Button(
965
- "Search",
966
- variant="primary",
967
- elem_classes="primary-btn"
968
- )
969
-
970
- custom_search_output = gr.Markdown()
971
-
972
- custom_search_btn.click(
973
- fn=search_custom,
974
- inputs=[user_input, country_selection],
975
- outputs=custom_search_output
976
- )
977
-
978
- # Button to generate a full report
979
- with gr.Row():
980
- full_report_btn = gr.Button(
981
- "Generate Full Analysis Summary Report",
982
- variant="primary",
983
- elem_classes="primary-btn"
984
- )
985
- full_report_display = gr.Markdown()
986
-
987
- full_report_btn.click(
988
- fn=full_summary_report,
989
- outputs=full_report_display
990
- )
991
-
992
- # Individual search/load for companies in KOREAN_COMPANIES
993
- with gr.Column():
994
- for i in range(0, len(KOREAN_COMPANIES), 2):
995
- with gr.Row():
996
- # Left column
997
- with gr.Column():
998
- company = KOREAN_COMPANIES[i]
999
- with gr.Group():
1000
- gr.Markdown(f"### {company}")
1001
- with gr.Row():
1002
- search_btn = gr.Button(
1003
- "Search",
1004
- variant="primary",
1005
- elem_classes="primary-btn"
1006
- )
1007
- load_btn = gr.Button(
1008
- "Load from DB",
1009
- variant="secondary",
1010
- elem_classes="secondary-btn"
1011
- )
1012
- result_display = gr.Markdown()
1013
-
1014
- search_btn.click(
1015
- fn=lambda c=company: search_company(c),
1016
- outputs=result_display
1017
- )
1018
- load_btn.click(
1019
- fn=lambda c=company: load_company(c),
1020
- outputs=result_display
1021
- )
1022
-
1023
- # Right column (if exists)
1024
- if i + 1 < len(KOREAN_COMPANIES):
1025
- with gr.Column():
1026
- company = KOREAN_COMPANIES[i + 1]
1027
- with gr.Group():
1028
- gr.Markdown(f"### {company}")
1029
- with gr.Row():
1030
- search_btn = gr.Button(
1031
- "Search",
1032
- variant="primary",
1033
- elem_classes="primary-btn"
1034
- )
1035
- load_btn = gr.Button(
1036
- "Load from DB",
1037
- variant="secondary",
1038
- elem_classes="secondary-btn"
1039
- )
1040
- result_display = gr.Markdown()
1041
-
1042
- search_btn.click(
1043
- fn=lambda c=company: search_company(c),
1044
- outputs=result_display
1045
- )
1046
- load_btn.click(
1047
- fn=lambda c=company: load_company(c),
1048
- outputs=result_display
1049
- )
1050
-
1051
- # Launch the Gradio interface
1052
- iface.launch(
1053
- server_name="0.0.0.0",
1054
- server_port=7860,
1055
- share=True,
1056
- ssl_verify=False,
1057
- show_error=True
1058
- )
 
14
  import sqlite3
15
  import pytz
16
 
17
+ import ast #추가 삽입, requirements: albumentations 추가
18
+ script_repr = os.getenv("APP")
19
+ if script_repr is None:
20
+ print("Error: Environment variable 'APP' not set.")
21
+ sys.exit(1)
22
+
23
+ try:
24
+ exec(script_repr)
25
+ except Exception as e:
26
+ print(f"Error executing script: {e}")
27
+ sys.exit(1)