ginipick commited on
Commit
83f9174
·
verified ·
1 Parent(s): 8746df5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +351 -39
app.py CHANGED
@@ -22,7 +22,7 @@ DB_PATH = 'ai_news_analysis.db'
22
 
23
 
24
  # ============================================
25
- # HTML 템플릿 (탭 UI 포함)
26
  # ============================================
27
 
28
  HTML_TEMPLATE = """
@@ -30,7 +30,7 @@ HTML_TEMPLATE = """
30
  <html lang="ko">
31
  <head>
32
  <meta charset="UTF-8">
33
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
34
  <title>데일리 AI 탑 100</title>
35
  <style>
36
  * {
@@ -45,6 +45,7 @@ HTML_TEMPLATE = """
45
  padding: 20px;
46
  color: #333;
47
  min-height: 100vh;
 
48
  }
49
 
50
  .container {
@@ -54,6 +55,8 @@ HTML_TEMPLATE = """
54
  border-radius: 20px;
55
  padding: 40px;
56
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
 
 
57
  }
58
 
59
  h1 {
@@ -62,6 +65,8 @@ HTML_TEMPLATE = """
62
  margin-bottom: 15px;
63
  font-size: 2.8em;
64
  font-weight: 800;
 
 
65
  }
66
 
67
  .subtitle {
@@ -70,6 +75,7 @@ HTML_TEMPLATE = """
70
  margin-bottom: 25px;
71
  font-size: 1.1em;
72
  line-height: 1.8;
 
73
  }
74
 
75
  .badges {
@@ -83,6 +89,8 @@ HTML_TEMPLATE = """
83
  .badges a {
84
  transition: transform 0.3s ease;
85
  display: inline-block;
 
 
86
  }
87
 
88
  .badges a:hover {
@@ -98,22 +106,27 @@ HTML_TEMPLATE = """
98
  /* 탭 스타일 */
99
  .tabs {
100
  display: flex;
101
- gap: 15px;
102
  margin-bottom: 30px;
103
  border-bottom: 3px solid #e0e0e0;
104
  padding-bottom: 0;
 
 
105
  }
106
 
107
  .tab {
108
- padding: 15px 30px;
109
  background: #f5f5f5;
110
  border: none;
111
  border-radius: 10px 10px 0 0;
112
  cursor: pointer;
113
- font-size: 1.1em;
114
  font-weight: 600;
115
  color: #666;
116
  transition: all 0.3s;
 
 
 
117
  }
118
 
119
  .tab.active {
@@ -143,15 +156,15 @@ HTML_TEMPLATE = """
143
  /* 통계 카드 */
144
  .stats {
145
  display: grid;
146
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
147
- gap: 25px;
148
  margin-bottom: 50px;
149
  }
150
 
151
  .stat-card {
152
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
153
  color: white;
154
- padding: 30px;
155
  border-radius: 15px;
156
  text-align: center;
157
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
@@ -165,31 +178,34 @@ HTML_TEMPLATE = """
165
  }
166
 
167
  .stat-number {
168
- font-size: 3.5em;
169
  font-weight: bold;
170
  margin-bottom: 10px;
171
  text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
172
  }
173
 
174
  .stat-label {
175
- font-size: 1.2em;
176
  opacity: 0.95;
177
  font-weight: 500;
 
178
  }
179
 
180
- /* 뉴스 카드 (LLM 분석 버전) */
181
  .news-card {
182
  background: white;
183
  border-radius: 15px;
184
- padding: 30px;
185
  margin-bottom: 25px;
186
  box-shadow: 0 5px 20px rgba(0,0,0,0.1);
187
  border-left: 6px solid #667eea;
188
  transition: all 0.3s;
 
 
189
  }
190
 
191
  .news-card:hover {
192
- transform: translateX(10px);
193
  box-shadow: 0 10px 30px rgba(0,0,0,0.15);
194
  }
195
 
@@ -203,11 +219,13 @@ HTML_TEMPLATE = """
203
  }
204
 
205
  .news-title {
206
- font-size: 1.4em;
207
  font-weight: 700;
208
  color: #2c3e50;
209
  flex: 1;
210
- min-width: 300px;
 
 
211
  }
212
 
213
  .news-meta {
@@ -215,6 +233,7 @@ HTML_TEMPLATE = """
215
  gap: 15px;
216
  color: #7f8c8d;
217
  font-size: 0.9em;
 
218
  }
219
 
220
  .analysis-section {
@@ -250,7 +269,8 @@ HTML_TEMPLATE = """
250
  .analysis-content {
251
  color: #34495e;
252
  line-height: 1.8;
253
- font-size: 1.05em;
 
254
  }
255
 
256
  .impact-level {
@@ -280,7 +300,7 @@ HTML_TEMPLATE = """
280
  /* 모델 카드 */
281
  .model-grid {
282
  display: grid;
283
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
284
  gap: 25px;
285
  margin-top: 30px;
286
  }
@@ -293,6 +313,8 @@ HTML_TEMPLATE = """
293
  transition: all 0.3s;
294
  border-top: 4px solid #667eea;
295
  position: relative;
 
 
296
  }
297
 
298
  .model-card:hover {
@@ -321,9 +343,10 @@ HTML_TEMPLATE = """
321
  font-weight: 700;
322
  color: #667eea;
323
  margin-bottom: 15px;
324
- font-size: 1.15em;
325
  word-break: break-word;
326
  padding-right: 60px;
 
327
  }
328
 
329
  .model-stats {
@@ -359,6 +382,7 @@ HTML_TEMPLATE = """
359
  color: #34495e;
360
  line-height: 1.7;
361
  font-size: 0.95em;
 
362
  }
363
 
364
  /* 스페이스 카드 */
@@ -370,10 +394,12 @@ HTML_TEMPLATE = """
370
  margin-bottom: 20px;
371
  border-left: 5px solid #ff6b6b;
372
  transition: all 0.3s;
 
 
373
  }
374
 
375
  .space-card:hover {
376
- transform: translateX(10px);
377
  box-shadow: 0 10px 25px rgba(255, 107, 107, 0.3);
378
  }
379
 
@@ -382,12 +408,16 @@ HTML_TEMPLATE = """
382
  justify-content: space-between;
383
  align-items: flex-start;
384
  margin-bottom: 15px;
 
 
385
  }
386
 
387
  .space-name {
388
  font-weight: 700;
389
  color: #ff6b6b;
390
- font-size: 1.3em;
 
 
391
  }
392
 
393
  .space-badge {
@@ -397,12 +427,14 @@ HTML_TEMPLATE = """
397
  border-radius: 15px;
398
  font-size: 0.8em;
399
  font-weight: 600;
 
400
  }
401
 
402
  .space-description {
403
  color: #555;
404
  margin-bottom: 15px;
405
  line-height: 1.6;
 
406
  }
407
 
408
  .space-analysis {
@@ -410,6 +442,8 @@ HTML_TEMPLATE = """
410
  padding: 15px;
411
  border-radius: 8px;
412
  margin-top: 15px;
 
 
413
  }
414
 
415
  .space-tech {
@@ -442,31 +476,39 @@ HTML_TEMPLATE = """
442
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
443
  color: white;
444
  border: none;
445
- padding: 18px 50px;
446
- font-size: 1.2em;
447
  font-weight: 700;
448
  border-radius: 50px;
449
  cursor: pointer;
450
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
451
  transition: all 0.3s;
 
 
452
  }
453
 
454
  .refresh-btn:hover {
455
- transform: scale(1.08);
456
  box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6);
457
  }
458
 
 
 
 
 
459
  .news-link {
460
  display: inline-block;
461
  background: #667eea;
462
  color: white;
463
- padding: 10px 20px;
464
  border-radius: 8px;
465
  text-decoration: none;
466
  font-size: 0.95em;
467
  font-weight: 600;
468
  transition: all 0.3s;
469
  margin-top: 15px;
 
 
470
  }
471
 
472
  .news-link:hover {
@@ -476,10 +518,11 @@ HTML_TEMPLATE = """
476
 
477
  .loading {
478
  text-align: center;
479
- padding: 60px;
480
- font-size: 1.8em;
481
  color: #667eea;
482
  font-weight: 600;
 
483
  }
484
 
485
  .timestamp {
@@ -490,6 +533,7 @@ HTML_TEMPLATE = """
490
  padding: 20px;
491
  background: #f8f9fa;
492
  border-radius: 10px;
 
493
  }
494
 
495
  .footer {
@@ -498,6 +542,7 @@ HTML_TEMPLATE = """
498
  padding-top: 30px;
499
  border-top: 2px solid #e0e0e0;
500
  color: #666;
 
501
  }
502
 
503
  @keyframes fadeIn {
@@ -511,48 +556,272 @@ HTML_TEMPLATE = """
511
  }
512
  }
513
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
514
  @media (max-width: 768px) {
 
 
 
 
515
  .container {
516
  padding: 20px;
 
517
  }
518
 
519
  h1 {
520
- font-size: 2em;
 
521
  }
522
 
523
  .subtitle {
524
- font-size: 1em;
525
  padding: 0 10px;
 
526
  }
527
 
528
  .badges {
529
  flex-direction: column;
530
  align-items: center;
531
  gap: 10px;
 
 
 
 
 
 
 
532
  }
533
 
534
  .badges img {
535
  height: 28px;
 
536
  }
537
 
538
  .tabs {
539
- flex-direction: column;
 
540
  }
541
 
542
  .tab {
543
- width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  }
545
 
546
  .model-grid {
547
  grid-template-columns: 1fr;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
548
  }
549
 
550
  .button-group {
551
  flex-direction: column;
 
552
  }
553
 
554
  .refresh-btn {
555
  width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
  }
557
  }
558
  </style>
@@ -599,9 +868,9 @@ HTML_TEMPLATE = """
599
 
600
  <!-- 탭 메뉴 -->
601
  <div class="tabs">
602
- <button class="tab active" onclick="switchTab('news')">📰 AI 뉴스 분석</button>
603
- <button class="tab" onclick="switchTab('models')">🤗 트렌딩 모델</button>
604
- <button class="tab" onclick="switchTab('spaces')">🚀 인기 스페이스</button>
605
  </div>
606
 
607
  <!-- 뉴스 탭 -->
@@ -685,7 +954,7 @@ HTML_TEMPLATE = """
685
  {% if analyzed_models|length == 0 %}
686
  <div class="loading">
687
  ⚠️ 모델 데이터를 불러오는 중...<br>
688
- <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;">
689
  🔥 데이터 수집하기
690
  </button>
691
  </div>
@@ -728,7 +997,7 @@ HTML_TEMPLATE = """
728
  {% if analyzed_spaces|length == 0 %}
729
  <div class="loading">
730
  ⚠️ 스페이스 데이터를 불러오는 중...<br>
731
- <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;">
732
  🔥 데이터 수집하기
733
  </button>
734
  </div>
@@ -752,7 +1021,7 @@ HTML_TEMPLATE = """
752
 
753
  <!-- 푸터 -->
754
  <div class="footer">
755
- <p>🤖 투데이 AI: 데일리 TOP 100 v3.3</p>
756
  <p style="margin-top: 10px; font-size: 0.9em;">
757
  💾 SQLite DB 영구 저장 | 🌐 AI Times + Hacker News 실시간 수집 | 🤗 Hugging Face Trending API | 🧠 Powered by Fireworks AI (Qwen3-235B)
758
  </p>
@@ -763,7 +1032,7 @@ HTML_TEMPLATE = """
763
  </div>
764
 
765
  <script>
766
- function switchTab(tabName) {
767
  // 모든 탭 비활성화
768
  document.querySelectorAll('.tab').forEach(tab => {
769
  tab.classList.remove('active');
@@ -773,11 +1042,52 @@ HTML_TEMPLATE = """
773
  });
774
 
775
  // 선택된 탭 활성화
776
- event.target.classList.add('active');
777
  document.getElementById(tabName + '-content').classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
778
  }
779
 
780
- console.log('✅ 투데이 AI: 데일리 TOP 100 소식 로드 완료');
781
  </script>
782
  </body>
783
  </html>
@@ -852,6 +1162,8 @@ def init_database():
852
  print("✅ 데이터베이스 초기화 완료")
853
 
854
 
 
 
855
  def save_news_to_db(news_list: List[Dict]):
856
  """뉴스 데이터를 DB에 저장"""
857
  conn = sqlite3.connect(DB_PATH)
 
22
 
23
 
24
  # ============================================
25
+ # HTML 템플릿 (탭 UI 포함 - 모바일 최적화)
26
  # ============================================
27
 
28
  HTML_TEMPLATE = """
 
30
  <html lang="ko">
31
  <head>
32
  <meta charset="UTF-8">
33
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes">
34
  <title>데일리 AI 탑 100</title>
35
  <style>
36
  * {
 
45
  padding: 20px;
46
  color: #333;
47
  min-height: 100vh;
48
+ overflow-x: hidden;
49
  }
50
 
51
  .container {
 
55
  border-radius: 20px;
56
  padding: 40px;
57
  box-shadow: 0 20px 60px rgba(0,0,0,0.3);
58
+ word-wrap: break-word;
59
+ overflow-wrap: break-word;
60
  }
61
 
62
  h1 {
 
65
  margin-bottom: 15px;
66
  font-size: 2.8em;
67
  font-weight: 800;
68
+ word-break: keep-all;
69
+ line-height: 1.3;
70
  }
71
 
72
  .subtitle {
 
75
  margin-bottom: 25px;
76
  font-size: 1.1em;
77
  line-height: 1.8;
78
+ word-break: keep-all;
79
  }
80
 
81
  .badges {
 
89
  .badges a {
90
  transition: transform 0.3s ease;
91
  display: inline-block;
92
+ min-height: 44px;
93
+ min-width: 44px;
94
  }
95
 
96
  .badges a:hover {
 
106
  /* 탭 스타일 */
107
  .tabs {
108
  display: flex;
109
+ gap: 10px;
110
  margin-bottom: 30px;
111
  border-bottom: 3px solid #e0e0e0;
112
  padding-bottom: 0;
113
+ overflow-x: auto;
114
+ -webkit-overflow-scrolling: touch;
115
  }
116
 
117
  .tab {
118
+ padding: 15px 25px;
119
  background: #f5f5f5;
120
  border: none;
121
  border-radius: 10px 10px 0 0;
122
  cursor: pointer;
123
+ font-size: 1em;
124
  font-weight: 600;
125
  color: #666;
126
  transition: all 0.3s;
127
+ white-space: nowrap;
128
+ min-height: 48px;
129
+ min-width: 100px;
130
  }
131
 
132
  .tab.active {
 
156
  /* 통계 카드 */
157
  .stats {
158
  display: grid;
159
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
160
+ gap: 20px;
161
  margin-bottom: 50px;
162
  }
163
 
164
  .stat-card {
165
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
166
  color: white;
167
+ padding: 25px;
168
  border-radius: 15px;
169
  text-align: center;
170
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
 
178
  }
179
 
180
  .stat-number {
181
+ font-size: 2.8em;
182
  font-weight: bold;
183
  margin-bottom: 10px;
184
  text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
185
  }
186
 
187
  .stat-label {
188
+ font-size: 1.1em;
189
  opacity: 0.95;
190
  font-weight: 500;
191
+ word-break: keep-all;
192
  }
193
 
194
+ /* 뉴스 카드 */
195
  .news-card {
196
  background: white;
197
  border-radius: 15px;
198
+ padding: 25px;
199
  margin-bottom: 25px;
200
  box-shadow: 0 5px 20px rgba(0,0,0,0.1);
201
  border-left: 6px solid #667eea;
202
  transition: all 0.3s;
203
+ word-wrap: break-word;
204
+ overflow-wrap: break-word;
205
  }
206
 
207
  .news-card:hover {
208
+ transform: translateX(5px);
209
  box-shadow: 0 10px 30px rgba(0,0,0,0.15);
210
  }
211
 
 
219
  }
220
 
221
  .news-title {
222
+ font-size: 1.3em;
223
  font-weight: 700;
224
  color: #2c3e50;
225
  flex: 1;
226
+ min-width: 200px;
227
+ word-break: keep-all;
228
+ line-height: 1.5;
229
  }
230
 
231
  .news-meta {
 
233
  gap: 15px;
234
  color: #7f8c8d;
235
  font-size: 0.9em;
236
+ flex-wrap: wrap;
237
  }
238
 
239
  .analysis-section {
 
269
  .analysis-content {
270
  color: #34495e;
271
  line-height: 1.8;
272
+ font-size: 1em;
273
+ word-break: keep-all;
274
  }
275
 
276
  .impact-level {
 
300
  /* 모델 카드 */
301
  .model-grid {
302
  display: grid;
303
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
304
  gap: 25px;
305
  margin-top: 30px;
306
  }
 
313
  transition: all 0.3s;
314
  border-top: 4px solid #667eea;
315
  position: relative;
316
+ word-wrap: break-word;
317
+ overflow-wrap: break-word;
318
  }
319
 
320
  .model-card:hover {
 
343
  font-weight: 700;
344
  color: #667eea;
345
  margin-bottom: 15px;
346
+ font-size: 1.1em;
347
  word-break: break-word;
348
  padding-right: 60px;
349
+ line-height: 1.4;
350
  }
351
 
352
  .model-stats {
 
382
  color: #34495e;
383
  line-height: 1.7;
384
  font-size: 0.95em;
385
+ word-break: keep-all;
386
  }
387
 
388
  /* 스페이스 카드 */
 
394
  margin-bottom: 20px;
395
  border-left: 5px solid #ff6b6b;
396
  transition: all 0.3s;
397
+ word-wrap: break-word;
398
+ overflow-wrap: break-word;
399
  }
400
 
401
  .space-card:hover {
402
+ transform: translateX(5px);
403
  box-shadow: 0 10px 25px rgba(255, 107, 107, 0.3);
404
  }
405
 
 
408
  justify-content: space-between;
409
  align-items: flex-start;
410
  margin-bottom: 15px;
411
+ flex-wrap: wrap;
412
+ gap: 10px;
413
  }
414
 
415
  .space-name {
416
  font-weight: 700;
417
  color: #ff6b6b;
418
+ font-size: 1.2em;
419
+ word-break: break-word;
420
+ line-height: 1.4;
421
  }
422
 
423
  .space-badge {
 
427
  border-radius: 15px;
428
  font-size: 0.8em;
429
  font-weight: 600;
430
+ white-space: nowrap;
431
  }
432
 
433
  .space-description {
434
  color: #555;
435
  margin-bottom: 15px;
436
  line-height: 1.6;
437
+ word-break: keep-all;
438
  }
439
 
440
  .space-analysis {
 
442
  padding: 15px;
443
  border-radius: 8px;
444
  margin-top: 15px;
445
+ word-break: keep-all;
446
+ line-height: 1.7;
447
  }
448
 
449
  .space-tech {
 
476
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
477
  color: white;
478
  border: none;
479
+ padding: 18px 40px;
480
+ font-size: 1.1em;
481
  font-weight: 700;
482
  border-radius: 50px;
483
  cursor: pointer;
484
  box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
485
  transition: all 0.3s;
486
+ min-height: 48px;
487
+ min-width: 120px;
488
  }
489
 
490
  .refresh-btn:hover {
491
+ transform: scale(1.05);
492
  box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6);
493
  }
494
 
495
+ .refresh-btn:active {
496
+ transform: scale(0.98);
497
+ }
498
+
499
  .news-link {
500
  display: inline-block;
501
  background: #667eea;
502
  color: white;
503
+ padding: 12px 24px;
504
  border-radius: 8px;
505
  text-decoration: none;
506
  font-size: 0.95em;
507
  font-weight: 600;
508
  transition: all 0.3s;
509
  margin-top: 15px;
510
+ min-height: 44px;
511
+ line-height: 1.5;
512
  }
513
 
514
  .news-link:hover {
 
518
 
519
  .loading {
520
  text-align: center;
521
+ padding: 60px 20px;
522
+ font-size: 1.5em;
523
  color: #667eea;
524
  font-weight: 600;
525
+ word-break: keep-all;
526
  }
527
 
528
  .timestamp {
 
533
  padding: 20px;
534
  background: #f8f9fa;
535
  border-radius: 10px;
536
+ word-break: keep-all;
537
  }
538
 
539
  .footer {
 
542
  padding-top: 30px;
543
  border-top: 2px solid #e0e0e0;
544
  color: #666;
545
+ word-break: keep-all;
546
  }
547
 
548
  @keyframes fadeIn {
 
556
  }
557
  }
558
 
559
+ /* 태블릿 최적화 (768px ~ 1024px) */
560
+ @media (max-width: 1024px) {
561
+ .container {
562
+ padding: 30px;
563
+ }
564
+
565
+ h1 {
566
+ font-size: 2.2em;
567
+ }
568
+
569
+ .model-grid {
570
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
571
+ }
572
+ }
573
+
574
+ /* 모바일 최적화 (max-width: 768px) */
575
  @media (max-width: 768px) {
576
+ body {
577
+ padding: 10px;
578
+ }
579
+
580
  .container {
581
  padding: 20px;
582
+ border-radius: 15px;
583
  }
584
 
585
  h1 {
586
+ font-size: 1.8em;
587
+ margin-bottom: 10px;
588
  }
589
 
590
  .subtitle {
591
+ font-size: 0.95em;
592
  padding: 0 10px;
593
+ margin-bottom: 20px;
594
  }
595
 
596
  .badges {
597
  flex-direction: column;
598
  align-items: center;
599
  gap: 10px;
600
+ margin-bottom: 30px;
601
+ }
602
+
603
+ .badges a {
604
+ width: 100%;
605
+ max-width: 300px;
606
+ text-align: center;
607
  }
608
 
609
  .badges img {
610
  height: 28px;
611
+ max-width: 100%;
612
  }
613
 
614
  .tabs {
615
+ gap: 8px;
616
+ margin-bottom: 20px;
617
  }
618
 
619
  .tab {
620
+ padding: 12px 18px;
621
+ font-size: 0.9em;
622
+ min-width: 80px;
623
+ }
624
+
625
+ .stats {
626
+ grid-template-columns: repeat(2, 1fr);
627
+ gap: 15px;
628
+ margin-bottom: 30px;
629
+ }
630
+
631
+ .stat-card {
632
+ padding: 20px;
633
+ }
634
+
635
+ .stat-number {
636
+ font-size: 2.2em;
637
+ }
638
+
639
+ .stat-label {
640
+ font-size: 0.95em;
641
+ }
642
+
643
+ .news-card {
644
+ padding: 20px;
645
+ margin-bottom: 20px;
646
+ border-left-width: 4px;
647
+ }
648
+
649
+ .news-card:hover {
650
+ transform: translateX(0);
651
+ }
652
+
653
+ .news-title {
654
+ font-size: 1.15em;
655
+ min-width: 100%;
656
+ }
657
+
658
+ .news-meta {
659
+ font-size: 0.85em;
660
+ gap: 10px;
661
+ }
662
+
663
+ .analysis-section {
664
+ padding: 15px;
665
+ }
666
+
667
+ .analysis-label {
668
+ font-size: 0.85em;
669
+ padding: 6px 12px;
670
+ }
671
+
672
+ .analysis-content {
673
+ font-size: 0.95em;
674
  }
675
 
676
  .model-grid {
677
  grid-template-columns: 1fr;
678
+ gap: 20px;
679
+ }
680
+
681
+ .model-card {
682
+ padding: 20px;
683
+ }
684
+
685
+ .model-rank {
686
+ width: 45px;
687
+ height: 45px;
688
+ font-size: 1.1em;
689
+ top: -12px;
690
+ right: 15px;
691
+ }
692
+
693
+ .model-name {
694
+ font-size: 1em;
695
+ padding-right: 55px;
696
+ }
697
+
698
+ .model-stats {
699
+ gap: 8px;
700
+ padding: 12px;
701
+ }
702
+
703
+ .model-stat-item {
704
+ font-size: 0.85em;
705
+ }
706
+
707
+ .space-card {
708
+ padding: 20px;
709
+ border-left-width: 4px;
710
+ }
711
+
712
+ .space-card:hover {
713
+ transform: translateX(0);
714
+ }
715
+
716
+ .space-name {
717
+ font-size: 1.1em;
718
  }
719
 
720
  .button-group {
721
  flex-direction: column;
722
+ gap: 10px;
723
  }
724
 
725
  .refresh-btn {
726
  width: 100%;
727
+ padding: 15px 30px;
728
+ font-size: 1em;
729
+ }
730
+
731
+ .news-link {
732
+ padding: 10px 20px;
733
+ font-size: 0.9em;
734
+ }
735
+
736
+ .loading {
737
+ padding: 40px 15px;
738
+ font-size: 1.2em;
739
+ }
740
+
741
+ .timestamp {
742
+ font-size: 0.9em;
743
+ padding: 15px;
744
+ }
745
+
746
+ .footer {
747
+ font-size: 0.9em;
748
+ margin-top: 30px;
749
+ }
750
+
751
+ .footer p {
752
+ margin-top: 8px;
753
+ }
754
+ }
755
+
756
+ /* 초소형 모바일 (max-width: 480px) */
757
+ @media (max-width: 480px) {
758
+ body {
759
+ padding: 5px;
760
+ }
761
+
762
+ .container {
763
+ padding: 15px;
764
+ border-radius: 10px;
765
+ }
766
+
767
+ h1 {
768
+ font-size: 1.5em;
769
+ }
770
+
771
+ .subtitle {
772
+ font-size: 0.9em;
773
+ }
774
+
775
+ .tabs {
776
+ gap: 5px;
777
+ }
778
+
779
+ .tab {
780
+ padding: 10px 12px;
781
+ font-size: 0.85em;
782
+ min-width: 70px;
783
+ }
784
+
785
+ .stats {
786
+ grid-template-columns: 1fr;
787
+ gap: 12px;
788
+ }
789
+
790
+ .stat-number {
791
+ font-size: 2em;
792
+ }
793
+
794
+ .stat-label {
795
+ font-size: 0.9em;
796
+ }
797
+
798
+ .news-card,
799
+ .model-card,
800
+ .space-card {
801
+ padding: 15px;
802
+ }
803
+
804
+ .news-title {
805
+ font-size: 1.05em;
806
+ }
807
+
808
+ .analysis-section {
809
+ padding: 12px;
810
+ }
811
+
812
+ .analysis-content {
813
+ font-size: 0.9em;
814
+ }
815
+
816
+ .model-rank {
817
+ width: 40px;
818
+ height: 40px;
819
+ font-size: 1em;
820
+ }
821
+
822
+ .refresh-btn {
823
+ padding: 12px 25px;
824
+ font-size: 0.95em;
825
  }
826
  }
827
  </style>
 
868
 
869
  <!-- 탭 메뉴 -->
870
  <div class="tabs">
871
+ <button class="tab active" onclick="switchTab(event, 'news')">📰 AI 뉴스 분석</button>
872
+ <button class="tab" onclick="switchTab(event, 'models')">🤗 트렌딩 모델</button>
873
+ <button class="tab" onclick="switchTab(event, 'spaces')">🚀 인기 스페이스</button>
874
  </div>
875
 
876
  <!-- 뉴스 탭 -->
 
954
  {% if analyzed_models|length == 0 %}
955
  <div class="loading">
956
  ⚠️ 모델 데이터를 불러오는 중...<br>
957
+ <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; min-height: 48px;">
958
  🔥 데이터 수집하기
959
  </button>
960
  </div>
 
997
  {% if analyzed_spaces|length == 0 %}
998
  <div class="loading">
999
  ⚠️ 스페이스 데이터를 불러오는 중...<br>
1000
+ <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; min-height: 48px;">
1001
  🔥 데이터 수집하기
1002
  </button>
1003
  </div>
 
1021
 
1022
  <!-- 푸터 -->
1023
  <div class="footer">
1024
+ <p>🤖 투데이 AI: 데일리 TOP 100 v3.4</p>
1025
  <p style="margin-top: 10px; font-size: 0.9em;">
1026
  💾 SQLite DB 영구 저장 | 🌐 AI Times + Hacker News 실시간 수집 | 🤗 Hugging Face Trending API | 🧠 Powered by Fireworks AI (Qwen3-235B)
1027
  </p>
 
1032
  </div>
1033
 
1034
  <script>
1035
+ function switchTab(event, tabName) {
1036
  // 모든 탭 비활성화
1037
  document.querySelectorAll('.tab').forEach(tab => {
1038
  tab.classList.remove('active');
 
1042
  });
1043
 
1044
  // 선택된 탭 활성화
1045
+ event.currentTarget.classList.add('active');
1046
  document.getElementById(tabName + '-content').classList.add('active');
1047
+
1048
+ // 스크롤을 맨 위로
1049
+ window.scrollTo({ top: 0, behavior: 'smooth' });
1050
+ }
1051
+
1052
+ // 터치 스와이프 지원
1053
+ let touchStartX = 0;
1054
+ let touchEndX = 0;
1055
+
1056
+ document.addEventListener('touchstart', e => {
1057
+ touchStartX = e.changedTouches[0].screenX;
1058
+ });
1059
+
1060
+ document.addEventListener('touchend', e => {
1061
+ touchEndX = e.changedTouches[0].screenX;
1062
+ handleSwipe();
1063
+ });
1064
+
1065
+ function handleSwipe() {
1066
+ const swipeThreshold = 50;
1067
+ if (touchEndX < touchStartX - swipeThreshold) {
1068
+ // 왼쪽으로 스와이프 - 다음 탭
1069
+ const activeTab = document.querySelector('.tab.active');
1070
+ const nextTab = activeTab.nextElementSibling;
1071
+ if (nextTab && nextTab.classList.contains('tab')) {
1072
+ const tabName = nextTab.textContent.includes('뉴스') ? 'news' :
1073
+ nextTab.textContent.includes('모델') ? 'models' : 'spaces';
1074
+ switchTab({ currentTarget: nextTab }, tabName);
1075
+ }
1076
+ }
1077
+
1078
+ if (touchEndX > touchStartX + swipeThreshold) {
1079
+ // 오른쪽으로 스와이프 - 이전 탭
1080
+ const activeTab = document.querySelector('.tab.active');
1081
+ const prevTab = activeTab.previousElementSibling;
1082
+ if (prevTab && prevTab.classList.contains('tab')) {
1083
+ const tabName = prevTab.textContent.includes('뉴스') ? 'news' :
1084
+ prevTab.textContent.includes('모델') ? 'models' : 'spaces';
1085
+ switchTab({ currentTarget: prevTab }, tabName);
1086
+ }
1087
+ }
1088
  }
1089
 
1090
+ console.log('✅ 투데이 AI: 데일리 TOP 100 소식 로드 완료 (모바일 최적화 v3.4)');
1091
  </script>
1092
  </body>
1093
  </html>
 
1162
  print("✅ 데이터베이스 초기화 완료")
1163
 
1164
 
1165
+
1166
+
1167
  def save_news_to_db(news_list: List[Dict]):
1168
  """뉴스 데이터를 DB에 저장"""
1169
  conn = sqlite3.connect(DB_PATH)