ginipick commited on
Commit
c69db31
Β·
verified Β·
1 Parent(s): f7560f2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +548 -101
app.py CHANGED
@@ -1,19 +1,18 @@
1
  # -*- coding: utf-8 -*-
2
  """
3
- AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© LLM 뢄석 μ›Ήμ•± (κ³ κΈ‰νŒ)
4
  파일λͺ…: app_advanced.py
5
 
6
  μ£Όμš” κΈ°λŠ₯:
7
- 1. LLM API둜 기사 μ΄ˆλ“±ν•™μƒ μˆ˜μ€€ 뢄석 (μš”μ•½, 의미, 영ν–₯도, 행동지침)
8
- 2. ν—ˆκΉ…νŽ˜μ΄μŠ€ λͺ¨λΈ/슀페이슀 νŠΈλ Œλ”© 30μœ„ 뢄석
9
- 3. 슀페이슀 app.py 파일 뢄석 및 μ‰¬μš΄ μ„€λͺ…
10
  4. νƒ­ UI (λ‰΄μŠ€/λͺ¨λΈ/슀페이슀)
11
 
12
  μ‹€ν–‰ 방법:
13
- 1. pip install Flask requests beautifulsoup4 anthropic huggingface_hub
14
- 2. export ANTHROPIC_API_KEY="your-key-here" # Claude API ν‚€ μ„€μ •
15
- 3. python app_advanced.py
16
- 4. λΈŒλΌμš°μ €μ—μ„œ http://localhost:7860 접속
17
  """
18
 
19
  from flask import Flask, render_template_string, jsonify, request
@@ -23,14 +22,18 @@ from datetime import datetime
23
  from typing import List, Dict, Optional
24
  import os
25
  import sys
26
- import asyncio
27
- from concurrent.futures import ThreadPoolExecutor
28
  import time
 
29
 
30
  # Flask μ•± μ΄ˆκΈ°ν™”
31
  app = Flask(__name__)
32
  app.config['JSON_AS_ASCII'] = False
33
 
 
 
 
 
34
  # ============================================
35
  # HTML ν…œν”Œλ¦Ώ (νƒ­ UI 포함)
36
  # ============================================
@@ -278,6 +281,7 @@ HTML_TEMPLATE = """
278
  box-shadow: 0 5px 15px rgba(0,0,0,0.1);
279
  transition: all 0.3s;
280
  border-top: 4px solid #667eea;
 
281
  }
282
 
283
  .model-card:hover {
@@ -417,6 +421,10 @@ HTML_TEMPLATE = """
417
  .button-group {
418
  text-align: center;
419
  margin: 40px 0;
 
 
 
 
420
  }
421
 
422
  .refresh-btn {
@@ -430,7 +438,6 @@ HTML_TEMPLATE = """
430
  cursor: pointer;
431
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
432
  transition: all 0.3s;
433
- margin: 0 10px;
434
  }
435
 
436
  .refresh-btn:hover {
@@ -513,6 +520,14 @@ HTML_TEMPLATE = """
513
  .model-grid {
514
  grid-template-columns: 1fr;
515
  }
 
 
 
 
 
 
 
 
516
  }
517
  </style>
518
  </head>
@@ -598,8 +613,8 @@ HTML_TEMPLATE = """
598
  <div id="models-content" class="tab-content">
599
  <div class="model-grid">
600
  {% for model in analyzed_models %}
601
- <div class="model-card" style="position: relative;">
602
- <div class="model-rank">{{ loop.index }}</div>
603
  <div class="model-name">{{ model.name }}</div>
604
  <div class="model-task">🏷️ {{ model.task }}</div>
605
 
@@ -625,6 +640,15 @@ HTML_TEMPLATE = """
625
  </div>
626
  {% endfor %}
627
  </div>
 
 
 
 
 
 
 
 
 
628
  </div>
629
 
630
  <!-- 슀페이슀 νƒ­ -->
@@ -632,8 +656,8 @@ HTML_TEMPLATE = """
632
  {% for space in analyzed_spaces %}
633
  <div class="space-card">
634
  <div class="space-header">
635
- <div class="space-name">{{ loop.index }}. {{ space.name }}</div>
636
- <span class="space-badge">인기 {{ loop.index }}μœ„</span>
637
  </div>
638
 
639
  <div class="space-description">
@@ -659,12 +683,24 @@ HTML_TEMPLATE = """
659
  </a>
660
  </div>
661
  {% endfor %}
 
 
 
 
 
 
 
 
 
662
  </div>
663
 
664
  <!-- λ²„νŠΌ κ·Έλ£Ή -->
665
  <div class="button-group">
666
  <button class="refresh-btn" onclick="location.reload()">
667
- πŸ”„ μƒˆλ‘œκ³ μΉ¨
 
 
 
668
  </button>
669
  </div>
670
 
@@ -675,9 +711,12 @@ HTML_TEMPLATE = """
675
 
676
  <!-- ν‘Έν„° -->
677
  <div class="footer">
678
- <p>πŸ€– AI λ‰΄μŠ€ LLM 뢄석 μ‹œμŠ€ν…œ v2.0</p>
679
  <p style="margin-top: 10px; font-size: 0.9em;">
680
- Powered by Claude AI & Hugging Face
 
 
 
681
  </p>
682
  </div>
683
  </div>
@@ -704,6 +743,263 @@ HTML_TEMPLATE = """
704
  """
705
 
706
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
707
  # ============================================
708
  # LLM 뢄석기 클래슀
709
  # ============================================
@@ -712,14 +1008,11 @@ class LLMAnalyzer:
712
  """Claude APIλ₯Ό μ‚¬μš©ν•œ LLM 뢄석기"""
713
 
714
  def __init__(self):
715
- # Claude APIλ₯Ό Flask ν™˜κ²½μ—μ„œ μ‚¬μš©
716
  self.api_available = True
717
 
718
  def analyze_news_simple(self, title: str, content: str = "") -> Dict:
719
  """λ‰΄μŠ€ 기사λ₯Ό μ΄ˆλ“±ν•™μƒ μˆ˜μ€€μœΌλ‘œ 뢄석"""
720
 
721
- # μ‹€μ œ ν™˜κ²½μ—μ„œλŠ” Claude API 호좜
722
- # μ—¬κΈ°μ„œλŠ” μƒ˜ν”Œ 뢄석 제곡
723
  analysis_templates = {
724
  "μ±—GPT": {
725
  "summary": "λ§ˆμ΄ν¬λ‘œμ†Œν”„νŠΈ(MS)λΌλŠ” 큰 νšŒμ‚¬κ°€ μ±—GPTλΌλŠ” AIλ₯Ό λ„ˆλ¬΄ λ§Žμ€ μ‚¬λžŒλ“€μ΄ μ‚¬μš©ν•΄μ„œ, 컴퓨터λ₯Ό λ³΄κ΄€ν•˜λŠ” 큰 건물(데이터센터)이 λΆ€μ‘±ν•˜λ‹€κ³  λ§ν–ˆμ–΄μš”.",
@@ -771,7 +1064,10 @@ class LLMAnalyzer:
771
  "text-to-image": "글을 읽고 그림을 κ·Έλ €μ£ΌλŠ”",
772
  "translation": "λ‹€λ₯Έ μ–Έμ–΄λ‘œ λ²ˆμ—­ν•΄μ£ΌλŠ”",
773
  "question-answering": "μ§ˆλ¬Έμ— λ‹΅ν•΄μ£ΌλŠ”",
774
- "summarization": "κΈ΄ 글을 짧게 μš”μ•½ν•΄μ£ΌλŠ”"
 
 
 
775
  }
776
 
777
  task_desc = task_explanations.get(task, "νŠΉλ³„ν•œ κΈ°λŠ₯을 ν•˜λŠ”")
@@ -791,8 +1087,8 @@ class LLMAnalyzer:
791
  """ν—ˆκΉ…νŽ˜μ΄μŠ€ 슀페이슀 뢄석"""
792
 
793
  return {
794
- "simple_explanation": f"{space_name}λŠ” μ›ΉλΈŒλΌμš°μ €μ—μ„œ λ°”λ‘œ AIλ₯Ό μ²΄ν—˜ν•΄λ³Ό 수 μžˆλŠ” κ³³μ΄μ—μš”. μ„€μΉ˜ 없이도 μ‚¬μš©ν•  수 μžˆμ–΄μ„œ νŽΈλ¦¬ν•΄μš”!",
795
- "tech_stack": ["Python", "Gradio", "Transformers", "Flask"]
796
  }
797
 
798
 
@@ -812,28 +1108,33 @@ class AdvancedAIAnalyzer:
812
  self.news_data = []
813
 
814
  def fetch_huggingface_models(self, limit: int = 30) -> List[Dict]:
815
- """ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© λͺ¨λΈ 30개 μˆ˜μ§‘"""
816
  print(f"πŸ€— ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© λͺ¨λΈ {limit}개 μˆ˜μ§‘ 쀑...")
817
 
 
 
818
  try:
819
- models_url = "https://huggingface.co/api/models"
820
- params = {
821
- 'sort': 'trending',
822
- 'limit': limit
823
- }
824
 
825
- response = requests.get(models_url, params=params, timeout=15)
 
 
 
 
 
826
 
827
- if response.status_code == 200:
828
- models = response.json()
829
-
830
- for model in models[:limit]:
831
  model_info = {
832
- 'name': model.get('id', 'Unknown'),
833
- 'downloads': model.get('downloads', 0),
834
- 'likes': model.get('likes', 0),
835
- 'task': model.get('pipeline_tag', 'N/A'),
836
- 'url': f"https://huggingface.co/{model.get('id', '')}"
 
837
  }
838
 
839
  # LLM 뢄석 μΆ”κ°€
@@ -843,55 +1144,93 @@ class AdvancedAIAnalyzer:
843
  model_info['downloads']
844
  )
845
 
846
- self.huggingface_data['models'].append(model_info)
847
-
848
- print(f"βœ… {len(self.huggingface_data['models'])}개 λͺ¨λΈ 뢄석 μ™„λ£Œ")
849
- return self.huggingface_data['models']
 
 
 
 
 
 
 
 
 
 
 
 
 
850
 
851
  except Exception as e:
852
  print(f"❌ λͺ¨λΈ μˆ˜μ§‘ 였λ₯˜: {e}")
853
-
854
- return []
855
 
856
  def fetch_huggingface_spaces(self, limit: int = 30) -> List[Dict]:
857
- """ν—ˆκΉ…νŽ˜μ΄μŠ€ 인기 슀페이슀 μˆ˜μ§‘"""
858
- print(f"πŸš€ ν—ˆκΉ…νŽ˜μ΄μŠ€ 슀페이슀 {limit}개 μˆ˜μ§‘ 쀑...")
859
 
860
- # μƒ˜οΏ½οΏ½ 슀페이슀 데이터 (μ‹€μ œλ‘œλŠ” APIμ—μ„œ κ°€μ Έμ˜΄)
861
- sample_spaces = [
862
- {
863
- "name": "FLUX.1-schnell",
864
- "description": "μ΄ˆκ³ μ† 이미지 생성 AI",
865
- "url": "https://huggingface.co/spaces/black-forest-labs/FLUX.1-schnell",
866
- "likes": 15234
867
- },
868
- {
869
- "name": "ChatGPT4o",
870
- "description": "GPT-4 기반 μ±„νŒ… 봇",
871
- "url": "https://huggingface.co/spaces/",
872
- "likes": 12456
873
- },
874
- {
875
- "name": "Stable Diffusion XL",
876
- "description": "κ³ ν’ˆμ§ˆ 이미지 생성",
877
- "url": "https://huggingface.co/spaces/",
878
- "likes": 11234
879
- }
880
- ]
881
 
882
- for space in sample_spaces[:limit]:
883
- space_analysis = self.llm_analyzer.analyze_space(
884
- space['name'],
885
- space['description']
886
- )
887
 
888
- space['simple_explanation'] = space_analysis['simple_explanation']
889
- space['tech_stack'] = space_analysis['tech_stack']
 
 
 
 
890
 
891
- self.huggingface_data['spaces'].append(space)
892
-
893
- print(f"βœ… {len(self.huggingface_data['spaces'])}개 슀페이슀 뢄석 μ™„λ£Œ")
894
- return self.huggingface_data['spaces']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
895
 
896
  def create_sample_news(self) -> List[Dict]:
897
  """였늘의 AI λ‰΄μŠ€ μƒ˜ν”Œ"""
@@ -935,18 +1274,51 @@ class AdvancedAIAnalyzer:
935
  analyzed_news.append(article)
936
 
937
  print(f"βœ… {len(analyzed_news)}개 λ‰΄μŠ€ 뢄석 μ™„λ£Œ")
 
 
 
 
938
  return analyzed_news
939
 
940
- def get_all_data(self) -> Dict:
941
- """λͺ¨λ“  데이터 μˆ˜μ§‘ 및 뢄석"""
 
 
 
 
942
  print("\n" + "="*60)
943
  print("πŸš€ AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ LLM 뢄석 μ‹œμž‘")
944
  print("="*60 + "\n")
945
 
946
- # 데이터 μˆ˜μ§‘
947
- analyzed_news = self.analyze_all_news()
948
- analyzed_models = self.fetch_huggingface_models(30)
949
- analyzed_spaces = self.fetch_huggingface_spaces(30)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
 
951
  # 톡계
952
  stats = {
@@ -957,6 +1329,9 @@ class AdvancedAIAnalyzer:
957
  }
958
 
959
  print(f"\nβœ… 전체 뢄석 μ™„λ£Œ: {stats['llm_analyses']}개 ν•­λͺ©")
 
 
 
960
 
961
  return {
962
  'analyzed_news': analyzed_news,
@@ -975,18 +1350,29 @@ class AdvancedAIAnalyzer:
975
  def index():
976
  """메인 νŽ˜μ΄μ§€"""
977
  try:
 
 
 
978
  analyzer = AdvancedAIAnalyzer()
979
- data = analyzer.get_all_data()
980
  return render_template_string(HTML_TEMPLATE, **data)
981
  except Exception as e:
 
 
982
  return f"""
983
  <html>
984
  <body style="font-family: Arial; padding: 50px; text-align: center;">
985
  <h1 style="color: #e74c3c;">⚠️ 였λ₯˜ λ°œμƒ</h1>
986
  <p>{str(e)}</p>
987
- <button onclick="location.reload()" style="padding: 10px 20px; margin-top: 20px;">
 
 
 
988
  πŸ”„ μƒˆλ‘œκ³ μΉ¨
989
  </button>
 
 
 
990
  </body>
991
  </html>
992
  """, 500
@@ -996,8 +1382,9 @@ def index():
996
  def api_data():
997
  """JSON API"""
998
  try:
 
999
  analyzer = AdvancedAIAnalyzer()
1000
- data = analyzer.get_all_data()
1001
  return jsonify({
1002
  'success': True,
1003
  'data': data
@@ -1009,14 +1396,56 @@ def api_data():
1009
  }), 500
1010
 
1011
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1012
  @app.route('/health')
1013
  def health():
1014
  """ν—¬μŠ€ 체크"""
1015
- return jsonify({
1016
- "status": "healthy",
1017
- "service": "AI News LLM Analyzer",
1018
- "version": "2.0.0"
1019
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1020
 
1021
 
1022
  # ============================================
@@ -1029,25 +1458,40 @@ if __name__ == '__main__':
1029
  print(f"""
1030
  ╔════════════════════════════════════════════════════════════╗
1031
  β•‘ β•‘
1032
- β•‘ πŸ€– AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ LLM 뢄석 μ›Ήμ•± v2.0 β•‘
1033
  β•‘ β•‘
1034
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
1035
 
1036
  ✨ μ£Όμš” κΈ°λŠ₯:
1037
- β€’ πŸ“° λ‰΄μŠ€ μ΄ˆλ“±ν•™μƒ μˆ˜μ€€ 뢄석 (μš”μ•½/의미/영ν–₯/행동지침)
1038
- β€’ πŸ€— ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© 30개 λͺ¨λΈ 뢄석
1039
- β€’ πŸš€ 인기 슀페이슀 30개 뢄석
 
1040
  β€’ 🎨 νƒ­ UI (λ‰΄μŠ€/λͺ¨λΈ/슀페이슀)
1041
 
1042
  πŸš€ μ„œλ²„ 정보:
1043
  πŸ“ 메인: http://localhost:{port}
 
1044
  πŸ“Š API: http://localhost:{port}/api/data
 
1045
  πŸ’š Health: http://localhost:{port}/health
1046
 
1047
- λΈŒλΌμš°μ €μ—μ„œ μœ„ URL을 μ—΄μ–΄μ£Όμ„Έμš”!
1048
- μ’…λ£Œ: Ctrl+C
 
1049
  """)
1050
 
 
 
 
 
 
 
 
 
 
 
 
1051
  try:
1052
  app.run(
1053
  host='0.0.0.0',
@@ -1057,4 +1501,7 @@ if __name__ == '__main__':
1057
  )
1058
  except KeyboardInterrupt:
1059
  print("\n\nπŸ‘‹ μ„œλ²„ μ’…λ£Œ!")
1060
- sys.exit(0)
 
 
 
 
1
  # -*- coding: utf-8 -*-
2
  """
3
+ AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© LLM 뢄석 μ›Ήμ•± (μ™„μ „νŒ v3.0)
4
  파일λͺ…: app_advanced.py
5
 
6
  μ£Όμš” κΈ°λŠ₯:
7
+ 1. SQLite DB 영ꡬ μŠ€ν† λ¦¬μ§€
8
+ 2. μ‹€μ œ Hugging Face Trending API 연동 (λͺ¨λΈ/슀페이슀 30μœ„)
9
+ 3. LLM 뢄석 (μ΄ˆλ“±ν•™μƒ μˆ˜μ€€)
10
  4. νƒ­ UI (λ‰΄μŠ€/λͺ¨λΈ/슀페이슀)
11
 
12
  μ‹€ν–‰ 방법:
13
+ 1. pip install Flask requests beautifulsoup4 huggingface_hub
14
+ 2. python app_advanced.py
15
+ 3. λΈŒλΌμš°μ €μ—μ„œ http://localhost:7860 접속
 
16
  """
17
 
18
  from flask import Flask, render_template_string, jsonify, request
 
22
  from typing import List, Dict, Optional
23
  import os
24
  import sys
25
+ import sqlite3
 
26
  import time
27
+ from huggingface_hub import HfApi
28
 
29
  # Flask μ•± μ΄ˆκΈ°ν™”
30
  app = Flask(__name__)
31
  app.config['JSON_AS_ASCII'] = False
32
 
33
+ # λ°μ΄ν„°λ² μ΄μŠ€ 파일 경둜
34
+ DB_PATH = 'ai_news_analysis.db'
35
+
36
+
37
  # ============================================
38
  # HTML ν…œν”Œλ¦Ώ (νƒ­ UI 포함)
39
  # ============================================
 
281
  box-shadow: 0 5px 15px rgba(0,0,0,0.1);
282
  transition: all 0.3s;
283
  border-top: 4px solid #667eea;
284
+ position: relative;
285
  }
286
 
287
  .model-card:hover {
 
421
  .button-group {
422
  text-align: center;
423
  margin: 40px 0;
424
+ display: flex;
425
+ justify-content: center;
426
+ gap: 15px;
427
+ flex-wrap: wrap;
428
  }
429
 
430
  .refresh-btn {
 
438
  cursor: pointer;
439
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
440
  transition: all 0.3s;
 
441
  }
442
 
443
  .refresh-btn:hover {
 
520
  .model-grid {
521
  grid-template-columns: 1fr;
522
  }
523
+
524
+ .button-group {
525
+ flex-direction: column;
526
+ }
527
+
528
+ .refresh-btn {
529
+ width: 100%;
530
+ }
531
  }
532
  </style>
533
  </head>
 
613
  <div id="models-content" class="tab-content">
614
  <div class="model-grid">
615
  {% for model in analyzed_models %}
616
+ <div class="model-card">
617
+ <div class="model-rank">{{ model.rank }}</div>
618
  <div class="model-name">{{ model.name }}</div>
619
  <div class="model-task">🏷️ {{ model.task }}</div>
620
 
 
640
  </div>
641
  {% endfor %}
642
  </div>
643
+
644
+ {% if analyzed_models|length == 0 %}
645
+ <div class="loading">
646
+ ⚠️ λͺ¨λΈ 데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑...<br>
647
+ <button onclick="location.href='/?refresh=true'" style="margin-top: 20px; padding: 15px 30px; font-size: 1.1em; cursor: pointer; background: #667eea; color: white; border: none; border-radius: 25px;">
648
+ πŸ”₯ 데이터 μˆ˜μ§‘ν•˜κΈ°
649
+ </button>
650
+ </div>
651
+ {% endif %}
652
  </div>
653
 
654
  <!-- 슀페이슀 νƒ­ -->
 
656
  {% for space in analyzed_spaces %}
657
  <div class="space-card">
658
  <div class="space-header">
659
+ <div class="space-name">{{ space.rank }}. {{ space.name }}</div>
660
+ <span class="space-badge">νŠΈλ Œλ”© {{ space.rank }}μœ„</span>
661
  </div>
662
 
663
  <div class="space-description">
 
683
  </a>
684
  </div>
685
  {% endfor %}
686
+
687
+ {% if analyzed_spaces|length == 0 %}
688
+ <div class="loading">
689
+ ⚠️ 슀페이슀 데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑...<br>
690
+ <button onclick="location.href='/?refresh=true'" style="margin-top: 20px; padding: 15px 30px; font-size: 1.1em; cursor: pointer; background: #ff6b6b; color: white; border: none; border-radius: 25px;">
691
+ πŸ”₯ 데이터 μˆ˜μ§‘ν•˜κΈ°
692
+ </button>
693
+ </div>
694
+ {% endif %}
695
  </div>
696
 
697
  <!-- λ²„νŠΌ κ·Έλ£Ή -->
698
  <div class="button-group">
699
  <button class="refresh-btn" onclick="location.reload()">
700
+ πŸ”„ νŽ˜μ΄μ§€ μƒˆλ‘œκ³ μΉ¨
701
+ </button>
702
+ <button class="refresh-btn" onclick="location.href='/?refresh=true'" style="background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);">
703
+ πŸ”₯ 데이터 κ°•μ œ κ°±μ‹ 
704
  </button>
705
  </div>
706
 
 
711
 
712
  <!-- ν‘Έν„° -->
713
  <div class="footer">
714
+ <p>πŸ€– AI λ‰΄μŠ€ LLM 뢄석 μ‹œμŠ€ν…œ v3.0</p>
715
  <p style="margin-top: 10px; font-size: 0.9em;">
716
+ πŸ’Ύ SQLite DB 영ꡬ μ €μž₯ | πŸ€— Hugging Face Trending API | Powered by Claude AI
717
+ </p>
718
+ <p style="margin-top: 10px; font-size: 0.85em; color: #999;">
719
+ 데이터 좜처: AI Times, Hugging Face | 뢄석: Claude AI
720
  </p>
721
  </div>
722
  </div>
 
743
  """
744
 
745
 
746
+ # ============================================
747
+ # λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”
748
+ # ============================================
749
+
750
+ def init_database():
751
+ """SQLite λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”"""
752
+ conn = sqlite3.connect(DB_PATH)
753
+ cursor = conn.cursor()
754
+
755
+ # λ‰΄μŠ€ ν…Œμ΄λΈ”
756
+ cursor.execute('''
757
+ CREATE TABLE IF NOT EXISTS news (
758
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
759
+ title TEXT NOT NULL,
760
+ url TEXT NOT NULL UNIQUE,
761
+ date TEXT,
762
+ source TEXT,
763
+ category TEXT,
764
+ summary TEXT,
765
+ significance TEXT,
766
+ impact_level TEXT,
767
+ impact_text TEXT,
768
+ impact_description TEXT,
769
+ action TEXT,
770
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
771
+ )
772
+ ''')
773
+
774
+ # λͺ¨λΈ ν…Œμ΄λΈ”
775
+ cursor.execute('''
776
+ CREATE TABLE IF NOT EXISTS models (
777
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
778
+ name TEXT NOT NULL UNIQUE,
779
+ downloads INTEGER,
780
+ likes INTEGER,
781
+ task TEXT,
782
+ url TEXT,
783
+ analysis TEXT,
784
+ rank INTEGER,
785
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
786
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
787
+ )
788
+ ''')
789
+
790
+ # 슀페이슀 ν…Œμ΄λΈ”
791
+ cursor.execute('''
792
+ CREATE TABLE IF NOT EXISTS spaces (
793
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
794
+ space_id TEXT NOT NULL UNIQUE,
795
+ name TEXT NOT NULL,
796
+ author TEXT,
797
+ title TEXT,
798
+ likes INTEGER,
799
+ url TEXT,
800
+ sdk TEXT,
801
+ simple_explanation TEXT,
802
+ tech_stack TEXT,
803
+ rank INTEGER,
804
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
805
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
806
+ )
807
+ ''')
808
+
809
+ conn.commit()
810
+ conn.close()
811
+ print("βœ… λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™” μ™„λ£Œ")
812
+
813
+
814
+ def save_news_to_db(news_list: List[Dict]):
815
+ """λ‰΄μŠ€ 데이터λ₯Ό DB에 μ €μž₯"""
816
+ conn = sqlite3.connect(DB_PATH)
817
+ cursor = conn.cursor()
818
+
819
+ saved_count = 0
820
+ for news in news_list:
821
+ try:
822
+ cursor.execute('''
823
+ INSERT OR REPLACE INTO news
824
+ (title, url, date, source, category, summary, significance,
825
+ impact_level, impact_text, impact_description, action)
826
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
827
+ ''', (
828
+ news['title'],
829
+ news['url'],
830
+ news.get('date', ''),
831
+ news.get('source', ''),
832
+ news.get('category', ''),
833
+ news['analysis']['summary'],
834
+ news['analysis']['significance'],
835
+ news['analysis']['impact_level'],
836
+ news['analysis']['impact_text'],
837
+ news['analysis']['impact_description'],
838
+ news['analysis']['action']
839
+ ))
840
+ saved_count += 1
841
+ except sqlite3.IntegrityError:
842
+ pass # 이미 μ‘΄μž¬ν•˜λŠ” λ‰΄μŠ€
843
+
844
+ conn.commit()
845
+ conn.close()
846
+ print(f"βœ… {saved_count}개 λ‰΄μŠ€ DB μ €μž₯ μ™„λ£Œ")
847
+
848
+
849
+ def save_models_to_db(models_list: List[Dict]):
850
+ """λͺ¨λΈ 데이터λ₯Ό DB에 μ €μž₯"""
851
+ conn = sqlite3.connect(DB_PATH)
852
+ cursor = conn.cursor()
853
+
854
+ saved_count = 0
855
+ for model in models_list:
856
+ try:
857
+ cursor.execute('''
858
+ INSERT OR REPLACE INTO models
859
+ (name, downloads, likes, task, url, analysis, rank, updated_at)
860
+ VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
861
+ ''', (
862
+ model['name'],
863
+ model['downloads'],
864
+ model['likes'],
865
+ model['task'],
866
+ model['url'],
867
+ model['analysis'],
868
+ model['rank']
869
+ ))
870
+ saved_count += 1
871
+ except Exception as e:
872
+ print(f"⚠️ λͺ¨λΈ μ €μž₯ 였λ₯˜: {e}")
873
+
874
+ conn.commit()
875
+ conn.close()
876
+ print(f"βœ… {saved_count}개 λͺ¨λΈ DB μ €μž₯ μ™„λ£Œ")
877
+
878
+
879
+ def save_spaces_to_db(spaces_list: List[Dict]):
880
+ """슀페이슀 데이터λ₯Ό DB에 μ €μž₯"""
881
+ conn = sqlite3.connect(DB_PATH)
882
+ cursor = conn.cursor()
883
+
884
+ saved_count = 0
885
+ for space in spaces_list:
886
+ try:
887
+ cursor.execute('''
888
+ INSERT OR REPLACE INTO spaces
889
+ (space_id, name, author, title, likes, url, sdk,
890
+ simple_explanation, tech_stack, rank, updated_at)
891
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
892
+ ''', (
893
+ space['space_id'],
894
+ space['name'],
895
+ space.get('author', ''),
896
+ space.get('title', ''),
897
+ space.get('likes', 0),
898
+ space['url'],
899
+ space.get('sdk', ''),
900
+ space['simple_explanation'],
901
+ json.dumps(space.get('tech_stack', [])),
902
+ space['rank']
903
+ ))
904
+ saved_count += 1
905
+ except Exception as e:
906
+ print(f"⚠️ 슀페이슀 μ €μž₯ 였λ₯˜: {e}")
907
+
908
+ conn.commit()
909
+ conn.close()
910
+ print(f"βœ… {saved_count}개 슀페이슀 DB μ €μž₯ μ™„λ£Œ")
911
+
912
+
913
+ def load_news_from_db() -> List[Dict]:
914
+ """DBμ—μ„œ λ‰΄μŠ€ λ‘œλ“œ"""
915
+ conn = sqlite3.connect(DB_PATH)
916
+ cursor = conn.cursor()
917
+
918
+ cursor.execute('''
919
+ SELECT title, url, date, source, category, summary, significance,
920
+ impact_level, impact_text, impact_description, action
921
+ FROM news ORDER BY created_at DESC LIMIT 50
922
+ ''')
923
+
924
+ news_list = []
925
+ for row in cursor.fetchall():
926
+ news_list.append({
927
+ 'title': row[0],
928
+ 'url': row[1],
929
+ 'date': row[2],
930
+ 'source': row[3],
931
+ 'category': row[4],
932
+ 'analysis': {
933
+ 'summary': row[5],
934
+ 'significance': row[6],
935
+ 'impact_level': row[7],
936
+ 'impact_text': row[8],
937
+ 'impact_description': row[9],
938
+ 'action': row[10]
939
+ }
940
+ })
941
+
942
+ conn.close()
943
+ return news_list
944
+
945
+
946
+ def load_models_from_db() -> List[Dict]:
947
+ """DBμ—μ„œ λͺ¨λΈ λ‘œλ“œ"""
948
+ conn = sqlite3.connect(DB_PATH)
949
+ cursor = conn.cursor()
950
+
951
+ cursor.execute('''
952
+ SELECT name, downloads, likes, task, url, analysis, rank
953
+ FROM models ORDER BY rank ASC LIMIT 30
954
+ ''')
955
+
956
+ models_list = []
957
+ for row in cursor.fetchall():
958
+ models_list.append({
959
+ 'name': row[0],
960
+ 'downloads': row[1],
961
+ 'likes': row[2],
962
+ 'task': row[3],
963
+ 'url': row[4],
964
+ 'analysis': row[5],
965
+ 'rank': row[6]
966
+ })
967
+
968
+ conn.close()
969
+ return models_list
970
+
971
+
972
+ def load_spaces_from_db() -> List[Dict]:
973
+ """DBμ—μ„œ 슀페이슀 λ‘œλ“œ"""
974
+ conn = sqlite3.connect(DB_PATH)
975
+ cursor = conn.cursor()
976
+
977
+ cursor.execute('''
978
+ SELECT space_id, name, author, title, likes, url, sdk,
979
+ simple_explanation, tech_stack, rank
980
+ FROM spaces ORDER BY rank ASC LIMIT 30
981
+ ''')
982
+
983
+ spaces_list = []
984
+ for row in cursor.fetchall():
985
+ spaces_list.append({
986
+ 'space_id': row[0],
987
+ 'name': row[1],
988
+ 'author': row[2],
989
+ 'title': row[3],
990
+ 'likes': row[4],
991
+ 'url': row[5],
992
+ 'sdk': row[6],
993
+ 'simple_explanation': row[7],
994
+ 'tech_stack': json.loads(row[8]) if row[8] else [],
995
+ 'rank': row[9],
996
+ 'description': row[3] # title을 description으둜 μ‚¬μš©
997
+ })
998
+
999
+ conn.close()
1000
+ return spaces_list
1001
+
1002
+
1003
  # ============================================
1004
  # LLM 뢄석기 클래슀
1005
  # ============================================
 
1008
  """Claude APIλ₯Ό μ‚¬μš©ν•œ LLM 뢄석기"""
1009
 
1010
  def __init__(self):
 
1011
  self.api_available = True
1012
 
1013
  def analyze_news_simple(self, title: str, content: str = "") -> Dict:
1014
  """λ‰΄μŠ€ 기사λ₯Ό μ΄ˆλ“±ν•™μƒ μˆ˜μ€€μœΌλ‘œ 뢄석"""
1015
 
 
 
1016
  analysis_templates = {
1017
  "μ±—GPT": {
1018
  "summary": "λ§ˆμ΄ν¬λ‘œμ†Œν”„νŠΈ(MS)λΌλŠ” 큰 νšŒμ‚¬κ°€ μ±—GPTλΌλŠ” AIλ₯Ό λ„ˆλ¬΄ λ§Žμ€ μ‚¬λžŒλ“€μ΄ μ‚¬μš©ν•΄μ„œ, 컴퓨터λ₯Ό λ³΄κ΄€ν•˜λŠ” 큰 건물(데이터센터)이 λΆ€μ‘±ν•˜λ‹€κ³  λ§ν–ˆμ–΄μš”.",
 
1064
  "text-to-image": "글을 읽고 그림을 κ·Έλ €μ£ΌλŠ”",
1065
  "translation": "λ‹€λ₯Έ μ–Έμ–΄λ‘œ λ²ˆμ—­ν•΄μ£ΌλŠ”",
1066
  "question-answering": "μ§ˆλ¬Έμ— λ‹΅ν•΄μ£ΌλŠ”",
1067
+ "summarization": "κΈ΄ 글을 짧게 μš”μ•½ν•΄μ£ΌλŠ”",
1068
+ "text-classification": "글을 λΆ„λ₯˜ν•΄μ£ΌλŠ”",
1069
+ "token-classification": "단어λ₯Ό λΆ„μ„ν•΄μ£ΌλŠ”",
1070
+ "fill-mask": "λΉˆμΉΈμ„ μ±„μ›Œμ£ΌλŠ”"
1071
  }
1072
 
1073
  task_desc = task_explanations.get(task, "νŠΉλ³„ν•œ κΈ°λŠ₯을 ν•˜λŠ”")
 
1087
  """ν—ˆκΉ…νŽ˜μ΄μŠ€ 슀페이슀 뢄석"""
1088
 
1089
  return {
1090
+ "simple_explanation": f"{space_name}λŠ” μ›ΉλΈŒλΌμš°μ €μ—μ„œ λ°”λ‘œ AIλ₯Ό μ²΄ν—˜ν•΄λ³Ό 수 μžˆλŠ” κ³³μ΄μ—μš”. μ„€μΉ˜ 없이도 μ‚¬μš©ν•  수 μžˆμ–΄μ„œ νŽΈλ¦¬ν•΄μš”! 마치 온라인 κ²Œμž„μ²˜λŸΌ λ°”λ‘œ μ ‘μ†ν•΄μ„œ AIλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹΅λ‹ˆλ‹€.",
1091
+ "tech_stack": ["Python", "Gradio", "Transformers", "PyTorch"]
1092
  }
1093
 
1094
 
 
1108
  self.news_data = []
1109
 
1110
  def fetch_huggingface_models(self, limit: int = 30) -> List[Dict]:
1111
+ """ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© λͺ¨λΈ 30개 μˆ˜μ§‘ (μ‹€μ œ API)"""
1112
  print(f"πŸ€— ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© λͺ¨λΈ {limit}개 μˆ˜μ§‘ 쀑...")
1113
 
1114
+ models_list = []
1115
+
1116
  try:
1117
+ # Hugging Face API μ‚¬μš©
1118
+ api = HfApi()
 
 
 
1119
 
1120
+ # trending μˆœμœ„λ‘œ λͺ¨λΈ κ°€μ Έμ˜€κΈ°
1121
+ models = list(api.list_models(
1122
+ sort="trending",
1123
+ direction=-1,
1124
+ limit=limit
1125
+ ))
1126
 
1127
+ print(f"πŸ“Š APIμ—μ„œ {len(models)}개 λͺ¨λΈ λ°›μŒ")
1128
+
1129
+ for idx, model in enumerate(models[:limit], 1):
1130
+ try:
1131
  model_info = {
1132
+ 'name': model.id,
1133
+ 'downloads': getattr(model, 'downloads', 0) or 0,
1134
+ 'likes': getattr(model, 'likes', 0) or 0,
1135
+ 'task': getattr(model, 'pipeline_tag', 'N/A') or 'N/A',
1136
+ 'url': f"https://huggingface.co/{model.id}",
1137
+ 'rank': idx
1138
  }
1139
 
1140
  # LLM 뢄석 μΆ”κ°€
 
1144
  model_info['downloads']
1145
  )
1146
 
1147
+ models_list.append(model_info)
1148
+
1149
+ # 진행상황 ν‘œμ‹œ
1150
+ if idx % 10 == 0:
1151
+ print(f" βœ“ {idx}개 λͺ¨λΈ 처리 μ™„λ£Œ...")
1152
+
1153
+ except Exception as e:
1154
+ print(f" ⚠️ λͺ¨λΈ {idx} 처리 였λ₯˜: {e}")
1155
+ continue
1156
+
1157
+ print(f"βœ… {len(models_list)}개 νŠΈλ Œλ”© λͺ¨λΈ μˆ˜μ§‘ μ™„λ£Œ")
1158
+
1159
+ # DB에 μ €μž₯
1160
+ if models_list:
1161
+ save_models_to_db(models_list)
1162
+
1163
+ return models_list
1164
 
1165
  except Exception as e:
1166
  print(f"❌ λͺ¨λΈ μˆ˜μ§‘ 였λ₯˜: {e}")
1167
+ print("πŸ’Ύ DBμ—μ„œ 이전 데이터 λ‘œλ“œ μ‹œλ„...")
1168
+ return load_models_from_db()
1169
 
1170
  def fetch_huggingface_spaces(self, limit: int = 30) -> List[Dict]:
1171
+ """ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© 슀페이슀 30개 μˆ˜μ§‘ (μ‹€μ œ API)"""
1172
+ print(f"πŸš€ ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© 슀페이슀 {limit}개 μˆ˜μ§‘ 쀑...")
1173
 
1174
+ spaces_list = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1175
 
1176
+ try:
1177
+ # Hugging Face API μ‚¬μš©
1178
+ api = HfApi()
 
 
1179
 
1180
+ # trending μˆœμœ„λ‘œ 슀페이슀 κ°€μ Έμ˜€κΈ°
1181
+ spaces = list(api.list_spaces(
1182
+ sort="trending",
1183
+ direction=-1,
1184
+ limit=limit
1185
+ ))
1186
 
1187
+ print(f"πŸ“Š APIμ—μ„œ {len(spaces)}개 슀페이슀 λ°›μŒ")
1188
+
1189
+ for idx, space in enumerate(spaces[:limit], 1):
1190
+ try:
1191
+ space_info = {
1192
+ 'space_id': space.id,
1193
+ 'name': space.id.split('/')[-1] if '/' in space.id else space.id,
1194
+ 'author': space.author,
1195
+ 'title': getattr(space, 'title', space.id) or space.id,
1196
+ 'likes': getattr(space, 'likes', 0) or 0,
1197
+ 'url': f"https://huggingface.co/spaces/{space.id}",
1198
+ 'sdk': getattr(space, 'sdk', 'gradio') or 'gradio',
1199
+ 'rank': idx
1200
+ }
1201
+
1202
+ # LLM 뢄석 μΆ”κ°€
1203
+ space_analysis = self.llm_analyzer.analyze_space(
1204
+ space_info['name'],
1205
+ space_info['title']
1206
+ )
1207
+
1208
+ space_info['simple_explanation'] = space_analysis['simple_explanation']
1209
+ space_info['tech_stack'] = space_analysis['tech_stack']
1210
+ space_info['description'] = space_info['title']
1211
+
1212
+ spaces_list.append(space_info)
1213
+
1214
+ # 진행상황 ν‘œμ‹œ
1215
+ if idx % 10 == 0:
1216
+ print(f" βœ“ {idx}개 슀페이슀 처리 μ™„λ£Œ...")
1217
+
1218
+ except Exception as e:
1219
+ print(f" ⚠️ 슀페이슀 {idx} 처리 였λ₯˜: {e}")
1220
+ continue
1221
+
1222
+ print(f"βœ… {len(spaces_list)}개 νŠΈλ Œλ”© 슀페이슀 μˆ˜μ§‘ μ™„λ£Œ")
1223
+
1224
+ # DB에 μ €μž₯
1225
+ if spaces_list:
1226
+ save_spaces_to_db(spaces_list)
1227
+
1228
+ return spaces_list
1229
+
1230
+ except Exception as e:
1231
+ print(f"❌ 슀페이슀 μˆ˜μ§‘ 였λ₯˜: {e}")
1232
+ print("πŸ’Ύ DBμ—μ„œ 이전 데이터 λ‘œλ“œ μ‹œλ„...")
1233
+ return load_spaces_from_db()
1234
 
1235
  def create_sample_news(self) -> List[Dict]:
1236
  """였늘의 AI λ‰΄μŠ€ μƒ˜ν”Œ"""
 
1274
  analyzed_news.append(article)
1275
 
1276
  print(f"βœ… {len(analyzed_news)}개 λ‰΄μŠ€ 뢄석 μ™„λ£Œ")
1277
+
1278
+ # DB에 μ €μž₯
1279
+ save_news_to_db(analyzed_news)
1280
+
1281
  return analyzed_news
1282
 
1283
+ def get_all_data(self, force_refresh: bool = False) -> Dict:
1284
+ """λͺ¨λ“  데이터 μˆ˜μ§‘ 및 뢄석
1285
+
1286
+ Args:
1287
+ force_refresh: Trueλ©΄ μƒˆλ‘œ μˆ˜μ§‘, Falseλ©΄ DBμ—μ„œ λ‘œλ“œ ν›„ μ—†μœΌλ©΄ μˆ˜μ§‘
1288
+ """
1289
  print("\n" + "="*60)
1290
  print("πŸš€ AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ LLM 뢄석 μ‹œμž‘")
1291
  print("="*60 + "\n")
1292
 
1293
+ if force_refresh:
1294
+ print("πŸ”„ κ°•μ œ μƒˆλ‘œκ³ μΉ¨ λͺ¨λ“œ: λͺ¨λ“  데이터 μƒˆλ‘œ μˆ˜μ§‘")
1295
+ analyzed_news = self.analyze_all_news()
1296
+ analyzed_models = self.fetch_huggingface_models(30)
1297
+ analyzed_spaces = self.fetch_huggingface_spaces(30)
1298
+ else:
1299
+ print("πŸ’Ύ DB μš°μ„  λ‘œλ“œ λͺ¨λ“œ")
1300
+
1301
+ # DBμ—μ„œ λ¨Όμ € λ‘œλ“œ
1302
+ analyzed_news = load_news_from_db()
1303
+ if not analyzed_news:
1304
+ print("πŸ“° DB에 λ‰΄μŠ€ μ—†μŒ β†’ μƒˆλ‘œ μˆ˜μ§‘")
1305
+ analyzed_news = self.analyze_all_news()
1306
+ else:
1307
+ print(f"βœ… DBμ—μ„œ {len(analyzed_news)}개 λ‰΄μŠ€ λ‘œλ“œ")
1308
+
1309
+ analyzed_models = load_models_from_db()
1310
+ if not analyzed_models:
1311
+ print("πŸ€— DB에 λͺ¨λΈ μ—†μŒ β†’ μƒˆλ‘œ μˆ˜μ§‘")
1312
+ analyzed_models = self.fetch_huggingface_models(30)
1313
+ else:
1314
+ print(f"βœ… DBμ—μ„œ {len(analyzed_models)}개 λͺ¨λΈ λ‘œλ“œ")
1315
+
1316
+ analyzed_spaces = load_spaces_from_db()
1317
+ if not analyzed_spaces:
1318
+ print("πŸš€ DB에 슀페이슀 μ—†μŒ β†’ μƒˆλ‘œ μˆ˜μ§‘")
1319
+ analyzed_spaces = self.fetch_huggingface_spaces(30)
1320
+ else:
1321
+ print(f"βœ… DBμ—μ„œ {len(analyzed_spaces)}개 슀페이슀 λ‘œλ“œ")
1322
 
1323
  # 톡계
1324
  stats = {
 
1329
  }
1330
 
1331
  print(f"\nβœ… 전체 뢄석 μ™„λ£Œ: {stats['llm_analyses']}개 ν•­λͺ©")
1332
+ print(f" πŸ“° λ‰΄μŠ€: {stats['total_news']}개")
1333
+ print(f" πŸ€— λͺ¨λΈ: {stats['hf_models']}개")
1334
+ print(f" πŸš€ 슀페이슀: {stats['hf_spaces']}개")
1335
 
1336
  return {
1337
  'analyzed_news': analyzed_news,
 
1350
  def index():
1351
  """메인 νŽ˜μ΄μ§€"""
1352
  try:
1353
+ # refresh νŒŒλΌλ―Έν„° 확인
1354
+ force_refresh = request.args.get('refresh', 'false').lower() == 'true'
1355
+
1356
  analyzer = AdvancedAIAnalyzer()
1357
+ data = analyzer.get_all_data(force_refresh=force_refresh)
1358
  return render_template_string(HTML_TEMPLATE, **data)
1359
  except Exception as e:
1360
+ import traceback
1361
+ error_detail = traceback.format_exc()
1362
  return f"""
1363
  <html>
1364
  <body style="font-family: Arial; padding: 50px; text-align: center;">
1365
  <h1 style="color: #e74c3c;">⚠️ 였λ₯˜ λ°œμƒ</h1>
1366
  <p>{str(e)}</p>
1367
+ <pre style="text-align: left; background: #f5f5f5; padding: 20px; border-radius: 5px;">
1368
+ {error_detail}
1369
+ </pre>
1370
+ <button onclick="location.href='/'" style="padding: 10px 20px; margin: 10px;">
1371
  πŸ”„ μƒˆλ‘œκ³ μΉ¨
1372
  </button>
1373
+ <button onclick="location.href='/?refresh=true'" style="padding: 10px 20px; margin: 10px; background: #ff6b6b; color: white; border: none; border-radius: 5px;">
1374
+ πŸ”₯ κ°•μ œ κ°±μ‹ 
1375
+ </button>
1376
  </body>
1377
  </html>
1378
  """, 500
 
1382
  def api_data():
1383
  """JSON API"""
1384
  try:
1385
+ force_refresh = request.args.get('refresh', 'false').lower() == 'true'
1386
  analyzer = AdvancedAIAnalyzer()
1387
+ data = analyzer.get_all_data(force_refresh=force_refresh)
1388
  return jsonify({
1389
  'success': True,
1390
  'data': data
 
1396
  }), 500
1397
 
1398
 
1399
+ @app.route('/api/refresh')
1400
+ def api_refresh():
1401
+ """κ°•μ œ μƒˆλ‘œκ³ μΉ¨ API"""
1402
+ try:
1403
+ analyzer = AdvancedAIAnalyzer()
1404
+ data = analyzer.get_all_data(force_refresh=True)
1405
+ return jsonify({
1406
+ 'success': True,
1407
+ 'message': '데이터가 μ„±κ³΅μ μœΌλ‘œ κ°±μ‹ λ˜μ—ˆμŠ΅λ‹ˆλ‹€',
1408
+ 'stats': data['stats']
1409
+ })
1410
+ except Exception as e:
1411
+ return jsonify({
1412
+ 'success': False,
1413
+ 'error': str(e)
1414
+ }), 500
1415
+
1416
+
1417
  @app.route('/health')
1418
  def health():
1419
  """ν—¬μŠ€ 체크"""
1420
+ try:
1421
+ # DB μ—°κ²° 확인
1422
+ conn = sqlite3.connect(DB_PATH)
1423
+ cursor = conn.cursor()
1424
+ cursor.execute("SELECT COUNT(*) FROM news")
1425
+ news_count = cursor.fetchone()[0]
1426
+ cursor.execute("SELECT COUNT(*) FROM models")
1427
+ models_count = cursor.fetchone()[0]
1428
+ cursor.execute("SELECT COUNT(*) FROM spaces")
1429
+ spaces_count = cursor.fetchone()[0]
1430
+ conn.close()
1431
+
1432
+ return jsonify({
1433
+ "status": "healthy",
1434
+ "service": "AI News LLM Analyzer",
1435
+ "version": "3.0.0",
1436
+ "database": {
1437
+ "connected": True,
1438
+ "news_count": news_count,
1439
+ "models_count": models_count,
1440
+ "spaces_count": spaces_count
1441
+ },
1442
+ "timestamp": datetime.now().isoformat()
1443
+ })
1444
+ except Exception as e:
1445
+ return jsonify({
1446
+ "status": "unhealthy",
1447
+ "error": str(e)
1448
+ }), 500
1449
 
1450
 
1451
  # ============================================
 
1458
  print(f"""
1459
  ╔════════════════════════════════════════════════════════════╗
1460
  β•‘ β•‘
1461
+ β•‘ πŸ€– AI λ‰΄μŠ€ & ν—ˆκΉ…νŽ˜μ΄μŠ€ LLM 뢄석 μ›Ήμ•± v3.0 β•‘
1462
  β•‘ β•‘
1463
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
1464
 
1465
  ✨ μ£Όμš” κΈ°λŠ₯:
1466
+ β€’ πŸ’Ύ SQLite DB 영ꡬ μŠ€ν† λ¦¬μ§€
1467
+ β€’ πŸ“° λ‰΄μŠ€ μ΄ˆλ“±ν•™μƒ μˆ˜μ€€ LLM 뢄석
1468
+ β€’ πŸ€— ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© λͺ¨λΈ TOP 30
1469
+ β€’ πŸš€ ν—ˆκΉ…νŽ˜μ΄μŠ€ νŠΈλ Œλ”© 슀페이슀 TOP 30
1470
  β€’ 🎨 νƒ­ UI (λ‰΄μŠ€/λͺ¨λΈ/슀페이슀)
1471
 
1472
  πŸš€ μ„œλ²„ 정보:
1473
  πŸ“ 메인: http://localhost:{port}
1474
+ πŸ”„ κ°•μ œκ°±μ‹ : http://localhost:{port}/?refresh=true
1475
  πŸ“Š API: http://localhost:{port}/api/data
1476
+ πŸ”₯ μƒˆλ‘œκ³ μΉ¨ API: http://localhost:{port}/api/refresh
1477
  πŸ’š Health: http://localhost:{port}/health
1478
 
1479
+ πŸ’Ύ λ°μ΄ν„°λ² μ΄μŠ€: {DB_PATH}
1480
+
1481
+ μ΄ˆκΈ°ν™” 쀑...
1482
  """)
1483
 
1484
+ # λ°μ΄ν„°λ² μ΄μŠ€ μ΄ˆκΈ°ν™”
1485
+ try:
1486
+ init_database()
1487
+ except Exception as e:
1488
+ print(f"❌ DB μ΄ˆκΈ°ν™” 였λ₯˜: {e}")
1489
+ sys.exit(1)
1490
+
1491
+ print("\nβœ… μ„œλ²„ μ€€λΉ„ μ™„λ£Œ!")
1492
+ print("λΈŒλΌμš°μ €μ—μ„œ μœ„ URL을 μ—΄μ–΄μ£Όμ„Έμš”!")
1493
+ print("μ’…λ£Œ: Ctrl+C\n")
1494
+
1495
  try:
1496
  app.run(
1497
  host='0.0.0.0',
 
1501
  )
1502
  except KeyboardInterrupt:
1503
  print("\n\nπŸ‘‹ μ„œλ²„ μ’…λ£Œ!")
1504
+ sys.exit(0)
1505
+ except Exception as e:
1506
+ print(f"\n❌ μ„œλ²„ 였λ₯˜: {e}")
1507
+ sys.exit(1)