ginipick commited on
Commit
9ad2f29
ยท
verified ยท
1 Parent(s): 8a9b412

Delete app-backup2.py

Browse files
Files changed (1) hide show
  1. app-backup2.py +0 -1800
app-backup2.py DELETED
@@ -1,1800 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from flask import Flask, render_template_string, jsonify, request
4
- import requests
5
- import json
6
- from datetime import datetime
7
- from typing import List, Dict, Optional
8
- import os
9
- import sys
10
- import sqlite3
11
- import time
12
- from huggingface_hub import HfApi
13
- from bs4 import BeautifulSoup
14
- import re
15
-
16
- # Flask ์•ฑ ์ดˆ๊ธฐํ™”
17
- app = Flask(__name__)
18
- app.config['JSON_AS_ASCII'] = False
19
-
20
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ ๊ฒฝ๋กœ
21
- DB_PATH = 'ai_news_analysis.db'
22
-
23
-
24
- # ============================================
25
- # HTML ํ…œํ”Œ๋ฆฟ (ํƒญ UI ํฌํ•จ)
26
- # ============================================
27
-
28
- HTML_TEMPLATE = """
29
- <!DOCTYPE html>
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 ๋‰ด์Šค & ํ—ˆ๊น…ํŽ˜์ด์Šค LLM ๋ถ„์„ ์‹œ์Šคํ…œ</title>
35
- <style>
36
- * {
37
- margin: 0;
38
- padding: 0;
39
- box-sizing: border-box;
40
- }
41
-
42
- body {
43
- font-family: 'Segoe UI', 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
44
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
45
- padding: 20px;
46
- color: #333;
47
- min-height: 100vh;
48
- }
49
-
50
- .container {
51
- max-width: 1400px;
52
- margin: 0 auto;
53
- background: white;
54
- border-radius: 20px;
55
- padding: 40px;
56
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
57
- }
58
-
59
- h1 {
60
- text-align: center;
61
- color: #667eea;
62
- margin-bottom: 10px;
63
- font-size: 2.8em;
64
- font-weight: 800;
65
- }
66
-
67
- .subtitle {
68
- text-align: center;
69
- color: #666;
70
- margin-bottom: 40px;
71
- font-size: 1.2em;
72
- }
73
-
74
- /* ํƒญ ์Šคํƒ€์ผ */
75
- .tabs {
76
- display: flex;
77
- gap: 15px;
78
- margin-bottom: 30px;
79
- border-bottom: 3px solid #e0e0e0;
80
- padding-bottom: 0;
81
- }
82
-
83
- .tab {
84
- padding: 15px 30px;
85
- background: #f5f5f5;
86
- border: none;
87
- border-radius: 10px 10px 0 0;
88
- cursor: pointer;
89
- font-size: 1.1em;
90
- font-weight: 600;
91
- color: #666;
92
- transition: all 0.3s;
93
- }
94
-
95
- .tab.active {
96
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
97
- color: white;
98
- transform: translateY(-3px);
99
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
100
- }
101
-
102
- .tab:hover {
103
- background: #e0e0e0;
104
- }
105
-
106
- .tab.active:hover {
107
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
108
- }
109
-
110
- .tab-content {
111
- display: none;
112
- }
113
-
114
- .tab-content.active {
115
- display: block;
116
- animation: fadeIn 0.5s ease-out;
117
- }
118
-
119
- /* ํ†ต๊ณ„ ์นด๋“œ */
120
- .stats {
121
- display: grid;
122
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
123
- gap: 25px;
124
- margin-bottom: 50px;
125
- }
126
-
127
- .stat-card {
128
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
129
- color: white;
130
- padding: 30px;
131
- border-radius: 15px;
132
- text-align: center;
133
- box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
134
- transform: translateY(0);
135
- transition: transform 0.3s, box-shadow 0.3s;
136
- }
137
-
138
- .stat-card:hover {
139
- transform: translateY(-5px);
140
- box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6);
141
- }
142
-
143
- .stat-number {
144
- font-size: 3.5em;
145
- font-weight: bold;
146
- margin-bottom: 10px;
147
- text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
148
- }
149
-
150
- .stat-label {
151
- font-size: 1.2em;
152
- opacity: 0.95;
153
- font-weight: 500;
154
- }
155
-
156
- /* ๋‰ด์Šค ์นด๋“œ (LLM ๋ถ„์„ ๋ฒ„์ „) */
157
- .news-card {
158
- background: white;
159
- border-radius: 15px;
160
- padding: 30px;
161
- margin-bottom: 25px;
162
- box-shadow: 0 5px 20px rgba(0,0,0,0.1);
163
- border-left: 6px solid #667eea;
164
- transition: all 0.3s;
165
- }
166
-
167
- .news-card:hover {
168
- transform: translateX(10px);
169
- box-shadow: 0 10px 30px rgba(0,0,0,0.15);
170
- }
171
-
172
- .news-header {
173
- display: flex;
174
- justify-content: space-between;
175
- align-items: flex-start;
176
- margin-bottom: 20px;
177
- flex-wrap: wrap;
178
- gap: 15px;
179
- }
180
-
181
- .news-title {
182
- font-size: 1.4em;
183
- font-weight: 700;
184
- color: #2c3e50;
185
- flex: 1;
186
- min-width: 300px;
187
- }
188
-
189
- .news-meta {
190
- display: flex;
191
- gap: 15px;
192
- color: #7f8c8d;
193
- font-size: 0.9em;
194
- }
195
-
196
- .analysis-section {
197
- background: #f8f9fa;
198
- padding: 20px;
199
- border-radius: 10px;
200
- margin-top: 15px;
201
- }
202
-
203
- .analysis-item {
204
- margin-bottom: 20px;
205
- padding-bottom: 20px;
206
- border-bottom: 1px solid #e0e0e0;
207
- }
208
-
209
- .analysis-item:last-child {
210
- border-bottom: none;
211
- margin-bottom: 0;
212
- padding-bottom: 0;
213
- }
214
-
215
- .analysis-label {
216
- display: inline-block;
217
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
218
- color: white;
219
- padding: 8px 15px;
220
- border-radius: 20px;
221
- font-size: 0.9em;
222
- font-weight: 600;
223
- margin-bottom: 10px;
224
- }
225
-
226
- .analysis-content {
227
- color: #34495e;
228
- line-height: 1.8;
229
- font-size: 1.05em;
230
- }
231
-
232
- .impact-level {
233
- display: inline-block;
234
- padding: 5px 12px;
235
- border-radius: 15px;
236
- font-size: 0.85em;
237
- font-weight: 600;
238
- margin-left: 10px;
239
- }
240
-
241
- .impact-high {
242
- background: #ff6b6b;
243
- color: white;
244
- }
245
-
246
- .impact-medium {
247
- background: #ffa502;
248
- color: white;
249
- }
250
-
251
- .impact-low {
252
- background: #26de81;
253
- color: white;
254
- }
255
-
256
- /* ๋ชจ๋ธ ์นด๋“œ */
257
- .model-grid {
258
- display: grid;
259
- grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
260
- gap: 25px;
261
- margin-top: 30px;
262
- }
263
-
264
- .model-card {
265
- background: white;
266
- padding: 25px;
267
- border-radius: 12px;
268
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
269
- transition: all 0.3s;
270
- border-top: 4px solid #667eea;
271
- position: relative;
272
- }
273
-
274
- .model-card:hover {
275
- transform: translateY(-5px);
276
- box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
277
- }
278
-
279
- .model-rank {
280
- position: absolute;
281
- top: -15px;
282
- right: 20px;
283
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
284
- color: white;
285
- width: 50px;
286
- height: 50px;
287
- border-radius: 50%;
288
- display: flex;
289
- align-items: center;
290
- justify-content: center;
291
- font-weight: 700;
292
- font-size: 1.2em;
293
- box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
294
- }
295
-
296
- .model-name {
297
- font-weight: 700;
298
- color: #667eea;
299
- margin-bottom: 15px;
300
- font-size: 1.15em;
301
- word-break: break-word;
302
- padding-right: 60px;
303
- }
304
-
305
- .model-stats {
306
- display: grid;
307
- grid-template-columns: repeat(2, 1fr);
308
- gap: 10px;
309
- margin: 15px 0;
310
- padding: 15px;
311
- background: #f8f9fa;
312
- border-radius: 8px;
313
- }
314
-
315
- .model-stat-item {
316
- font-size: 0.9em;
317
- }
318
-
319
- .model-task {
320
- background: #e8f0fe;
321
- color: #667eea;
322
- padding: 6px 12px;
323
- border-radius: 20px;
324
- font-size: 0.85em;
325
- display: inline-block;
326
- margin-bottom: 15px;
327
- font-weight: 600;
328
- }
329
-
330
- .model-analysis {
331
- background: #f0f4ff;
332
- padding: 15px;
333
- border-radius: 8px;
334
- margin-top: 15px;
335
- color: #34495e;
336
- line-height: 1.7;
337
- font-size: 0.95em;
338
- }
339
-
340
- /* ์ŠคํŽ˜์ด์Šค ์นด๋“œ */
341
- .space-card {
342
- background: white;
343
- padding: 25px;
344
- border-radius: 12px;
345
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
346
- margin-bottom: 20px;
347
- border-left: 5px solid #ff6b6b;
348
- transition: all 0.3s;
349
- }
350
-
351
- .space-card:hover {
352
- transform: translateX(10px);
353
- box-shadow: 0 10px 25px rgba(255, 107, 107, 0.3);
354
- }
355
-
356
- .space-header {
357
- display: flex;
358
- justify-content: space-between;
359
- align-items: flex-start;
360
- margin-bottom: 15px;
361
- }
362
-
363
- .space-name {
364
- font-weight: 700;
365
- color: #ff6b6b;
366
- font-size: 1.3em;
367
- }
368
-
369
- .space-badge {
370
- background: #ff6b6b;
371
- color: white;
372
- padding: 5px 12px;
373
- border-radius: 15px;
374
- font-size: 0.8em;
375
- font-weight: 600;
376
- }
377
-
378
- .space-description {
379
- color: #555;
380
- margin-bottom: 15px;
381
- line-height: 1.6;
382
- }
383
-
384
- .space-analysis {
385
- background: #fff5f5;
386
- padding: 15px;
387
- border-radius: 8px;
388
- margin-top: 15px;
389
- }
390
-
391
- .space-tech {
392
- display: flex;
393
- flex-wrap: wrap;
394
- gap: 8px;
395
- margin-top: 15px;
396
- }
397
-
398
- .tech-tag {
399
- background: #ffe5e5;
400
- color: #ff6b6b;
401
- padding: 5px 10px;
402
- border-radius: 12px;
403
- font-size: 0.8em;
404
- font-weight: 600;
405
- }
406
-
407
- /* ๋ฒ„ํŠผ */
408
- .button-group {
409
- text-align: center;
410
- margin: 40px 0;
411
- display: flex;
412
- justify-content: center;
413
- gap: 15px;
414
- flex-wrap: wrap;
415
- }
416
-
417
- .refresh-btn {
418
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
419
- color: white;
420
- border: none;
421
- padding: 18px 50px;
422
- font-size: 1.2em;
423
- font-weight: 700;
424
- border-radius: 50px;
425
- cursor: pointer;
426
- box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
427
- transition: all 0.3s;
428
- }
429
-
430
- .refresh-btn:hover {
431
- transform: scale(1.08);
432
- box-shadow: 0 12px 30px rgba(102, 126, 234, 0.6);
433
- }
434
-
435
- .news-link {
436
- display: inline-block;
437
- background: #667eea;
438
- color: white;
439
- padding: 10px 20px;
440
- border-radius: 8px;
441
- text-decoration: none;
442
- font-size: 0.95em;
443
- font-weight: 600;
444
- transition: all 0.3s;
445
- margin-top: 15px;
446
- }
447
-
448
- .news-link:hover {
449
- background: #764ba2;
450
- transform: scale(1.05);
451
- }
452
-
453
- .loading {
454
- text-align: center;
455
- padding: 60px;
456
- font-size: 1.8em;
457
- color: #667eea;
458
- font-weight: 600;
459
- }
460
-
461
- .timestamp {
462
- text-align: center;
463
- color: #999;
464
- margin-top: 40px;
465
- font-size: 1em;
466
- padding: 20px;
467
- background: #f8f9fa;
468
- border-radius: 10px;
469
- }
470
-
471
- .footer {
472
- text-align: center;
473
- margin-top: 50px;
474
- padding-top: 30px;
475
- border-top: 2px solid #e0e0e0;
476
- color: #666;
477
- }
478
-
479
- @keyframes fadeIn {
480
- from {
481
- opacity: 0;
482
- transform: translateY(20px);
483
- }
484
- to {
485
- opacity: 1;
486
- transform: translateY(0);
487
- }
488
- }
489
-
490
- @media (max-width: 768px) {
491
- .container {
492
- padding: 20px;
493
- }
494
-
495
- h1 {
496
- font-size: 2em;
497
- }
498
-
499
- .tabs {
500
- flex-direction: column;
501
- }
502
-
503
- .tab {
504
- width: 100%;
505
- }
506
-
507
- .model-grid {
508
- grid-template-columns: 1fr;
509
- }
510
-
511
- .button-group {
512
- flex-direction: column;
513
- }
514
-
515
- .refresh-btn {
516
- width: 100%;
517
- }
518
- }
519
- </style>
520
- </head>
521
- <body>
522
- <div class="container">
523
- <h1>๐Ÿค– AI ๋‰ด์Šค & ํ—ˆ๊น…ํŽ˜์ด์Šค LLM ๋ถ„์„</h1>
524
- <p class="subtitle">AI ํŠธ๋ Œ๋“œ ๋ถ„์„ ์‹œ์Šคํ…œ ๐ŸŽ“</p>
525
-
526
- <!-- ํ†ต๊ณ„ ์นด๋“œ -->
527
- <div class="stats">
528
- <div class="stat-card">
529
- <div class="stat-number">{{ stats.total_news }}</div>
530
- <div class="stat-label">๐Ÿ“ฐ ๋ถ„์„๋œ ๋‰ด์Šค</div>
531
- </div>
532
- <div class="stat-card">
533
- <div class="stat-number">{{ stats.hf_models }}</div>
534
- <div class="stat-label">๐Ÿค— ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ</div>
535
- </div>
536
- <div class="stat-card">
537
- <div class="stat-number">{{ stats.hf_spaces }}</div>
538
- <div class="stat-label">๐Ÿš€ ์ธ๊ธฐ ์ŠคํŽ˜์ด์Šค</div>
539
- </div>
540
- <div class="stat-card">
541
- <div class="stat-number">{{ stats.llm_analyses }}</div>
542
- <div class="stat-label">๐Ÿง  LLM ๋ถ„์„</div>
543
- </div>
544
- </div>
545
-
546
- <!-- ํƒญ ๋ฉ”๋‰ด -->
547
- <div class="tabs">
548
- <button class="tab active" onclick="switchTab('news')">๐Ÿ“ฐ AI ๋‰ด์Šค ๋ถ„์„</button>
549
- <button class="tab" onclick="switchTab('models')">๐Ÿค— ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ</button>
550
- <button class="tab" onclick="switchTab('spaces')">๐Ÿš€ ์ธ๊ธฐ ์ŠคํŽ˜์ด์Šค</button>
551
- </div>
552
-
553
- <!-- ๋‰ด์Šค ํƒญ -->
554
- <div id="news-content" class="tab-content active">
555
- {% for article in analyzed_news %}
556
- <div class="news-card">
557
- <div class="news-header">
558
- <div class="news-title">{{ loop.index }}. {{ article.title }}</div>
559
- <div class="news-meta">
560
- <span>๐Ÿ“… {{ article.date }}</span>
561
- <span>๐Ÿ“ฐ {{ article.source }}</span>
562
- </div>
563
- </div>
564
-
565
- <div class="analysis-section">
566
- <div class="analysis-item">
567
- <span class="analysis-label">๐ŸŽฏ ์‰ฌ์šด ์š”์•ฝ</span>
568
- <div class="analysis-content">{{ article.analysis.summary }}</div>
569
- </div>
570
-
571
- <div class="analysis-item">
572
- <span class="analysis-label">๐Ÿ’ก ์™œ ์ค‘์š”ํ• ๊นŒ?</span>
573
- <div class="analysis-content">{{ article.analysis.significance }}</div>
574
- </div>
575
-
576
- <div class="analysis-item">
577
- <span class="analysis-label">๐Ÿ“Š ์˜ํ–ฅ๋„</span>
578
- <span class="impact-level impact-{{ article.analysis.impact_level }}">
579
- {{ article.analysis.impact_text }}
580
- </span>
581
- <div class="analysis-content" style="margin-top: 10px;">
582
- {{ article.analysis.impact_description }}
583
- </div>
584
- </div>
585
-
586
- <div class="analysis-item">
587
- <span class="analysis-label">โœ… ์šฐ๋ฆฌ๊ฐ€ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ</span>
588
- <div class="analysis-content">{{ article.analysis.action }}</div>
589
- </div>
590
- </div>
591
-
592
- <a href="{{ article.url }}" target="_blank" class="news-link">
593
- ๐Ÿ”— ์ „์ฒด ๊ธฐ์‚ฌ ์ฝ์–ด๋ณด๊ธฐ
594
- </a>
595
- </div>
596
- {% endfor %}
597
- </div>
598
-
599
- <!-- ๋ชจ๋ธ ํƒญ -->
600
- <div id="models-content" class="tab-content">
601
- <div class="model-grid">
602
- {% for model in analyzed_models %}
603
- <div class="model-card">
604
- <div class="model-rank">{{ model.rank }}</div>
605
- <div class="model-name">{{ model.name }}</div>
606
- <div class="model-task">๐Ÿท๏ธ {{ model.task }}</div>
607
-
608
- <div class="model-stats">
609
- <div class="model-stat-item">
610
- <strong>๐Ÿ“ฅ ๋‹ค์šด๋กœ๋“œ</strong><br>
611
- {{ "{:,}".format(model.downloads) }}
612
- </div>
613
- <div class="model-stat-item">
614
- <strong>โค๏ธ ์ข‹์•„์š”</strong><br>
615
- {{ "{:,}".format(model.likes) }}
616
- </div>
617
- </div>
618
-
619
- <div class="model-analysis">
620
- <strong>๐Ÿง  AI ๋ถ„์„:</strong><br>
621
- {{ model.analysis }}
622
- </div>
623
-
624
- <a href="{{ model.url }}" target="_blank" class="news-link">
625
- ๐Ÿ”— ๋ชจ๋ธ ํŽ˜์ด์ง€ ๋ฐฉ๋ฌธ
626
- </a>
627
- </div>
628
- {% endfor %}
629
- </div>
630
-
631
- {% if analyzed_models|length == 0 %}
632
- <div class="loading">
633
- โš ๏ธ ๋ชจ๋ธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...<br>
634
- <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;">
635
- ๐Ÿ”ฅ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ํ•˜๊ธฐ
636
- </button>
637
- </div>
638
- {% endif %}
639
- </div>
640
-
641
- <!-- ์ŠคํŽ˜์ด์Šค ํƒญ -->
642
- <div id="spaces-content" class="tab-content">
643
- {% for space in analyzed_spaces %}
644
- <div class="space-card">
645
- <div class="space-header">
646
- <div class="space-name">{{ space.rank }}. {{ space.name }}</div>
647
- <span class="space-badge">ํŠธ๋ Œ๋”ฉ {{ space.rank }}์œ„</span>
648
- </div>
649
-
650
- <div class="space-description">
651
- <strong>๐Ÿ“ ์„ค๋ช…:</strong> {{ space.description }}
652
- </div>
653
-
654
- <div class="space-analysis">
655
- <strong>๐ŸŽ“ ์‰ฌ์šด ์„ค๋ช…:</strong><br>
656
- {{ space.simple_explanation }}
657
- </div>
658
-
659
- {% if space.tech_stack %}
660
- <div class="space-tech">
661
- <strong style="width: 100%; margin-bottom: 5px;">๐Ÿ› ๏ธ ์‚ฌ์šฉ ๊ธฐ์ˆ :</strong>
662
- {% for tech in space.tech_stack %}
663
- <span class="tech-tag">{{ tech }}</span>
664
- {% endfor %}
665
- </div>
666
- {% endif %}
667
-
668
- <a href="{{ space.url }}" target="_blank" class="news-link">
669
- ๐Ÿ”— ์ŠคํŽ˜์ด์Šค ์ฒดํ—˜ํ•˜๊ธฐ
670
- </a>
671
- </div>
672
- {% endfor %}
673
-
674
- {% if analyzed_spaces|length == 0 %}
675
- <div class="loading">
676
- โš ๏ธ ์ŠคํŽ˜์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...<br>
677
- <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;">
678
- ๐Ÿ”ฅ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ํ•˜๊ธฐ
679
- </button>
680
- </div>
681
- {% endif %}
682
- </div>
683
-
684
- <!-- ๋ฒ„ํŠผ ๊ทธ๋ฃน -->
685
- <div class="button-group">
686
- <button class="refresh-btn" onclick="location.reload()">
687
- ๐Ÿ”„ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ
688
- </button>
689
- <button class="refresh-btn" onclick="location.href='/?refresh=true'" style="background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);">
690
- ๐Ÿ”ฅ ๋ฐ์ดํ„ฐ ๊ฐ•์ œ ๊ฐฑ์‹ 
691
- </button>
692
- </div>
693
-
694
- <!-- ํƒ€์ž„์Šคํƒฌํ”„ -->
695
- <div class="timestamp">
696
- โฐ ๋งˆ์ง€๋ง‰ ์—…๋ฐ์ดํŠธ: {{ timestamp }}
697
- </div>
698
-
699
- <!-- ํ‘ธํ„ฐ -->
700
- <div class="footer">
701
- <p>๐Ÿค– AI ๋‰ด์Šค LLM ๋ถ„์„ ์‹œ์Šคํ…œ v3.2</p>
702
- <p style="margin-top: 10px; font-size: 0.9em;">
703
- ๐Ÿ’พ SQLite DB ์˜๊ตฌ ์ €์žฅ | ๐ŸŒ AI Times ์‹ค์‹œ๊ฐ„ ํฌ๋กค๋ง | ๐Ÿค— Hugging Face Trending API | ๐Ÿง  Powered by Fireworks AI (Qwen3-235B)
704
- </p>
705
- <p style="margin-top: 10px; font-size: 0.85em; color: #999;">
706
- ๋ฐ์ดํ„ฐ ์ถœ์ฒ˜: AI Times (์‹ค์‹œ๊ฐ„ ํฌ๋กค๋ง), Hugging Face | ์‹ค์‹œ๊ฐ„ ๋ถ„์„: Fireworks AI
707
- </p>
708
- </div>
709
- </div>
710
-
711
- <script>
712
- function switchTab(tabName) {
713
- // ๋ชจ๋“  ํƒญ ๋น„ํ™œ์„ฑํ™”
714
- document.querySelectorAll('.tab').forEach(tab => {
715
- tab.classList.remove('active');
716
- });
717
- document.querySelectorAll('.tab-content').forEach(content => {
718
- content.classList.remove('active');
719
- });
720
-
721
- // ์„ ํƒ๋œ ํƒญ ํ™œ์„ฑํ™”
722
- event.target.classList.add('active');
723
- document.getElementById(tabName + '-content').classList.add('active');
724
- }
725
-
726
- console.log('โœ… AI ๋‰ด์Šค LLM ๋ถ„์„ ์‹œ์Šคํ…œ ๋กœ๋“œ ์™„๋ฃŒ');
727
- </script>
728
- </body>
729
- </html>
730
- """
731
-
732
-
733
- # ============================================
734
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”
735
- # ============================================
736
-
737
- def init_database():
738
- """SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”"""
739
- conn = sqlite3.connect(DB_PATH)
740
- cursor = conn.cursor()
741
-
742
- # ๋‰ด์Šค ํ…Œ์ด๋ธ”
743
- cursor.execute('''
744
- CREATE TABLE IF NOT EXISTS news (
745
- id INTEGER PRIMARY KEY AUTOINCREMENT,
746
- title TEXT NOT NULL,
747
- url TEXT NOT NULL UNIQUE,
748
- date TEXT,
749
- source TEXT,
750
- category TEXT,
751
- summary TEXT,
752
- significance TEXT,
753
- impact_level TEXT,
754
- impact_text TEXT,
755
- impact_description TEXT,
756
- action TEXT,
757
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
758
- )
759
- ''')
760
-
761
- # ๋ชจ๋ธ ํ…Œ์ด๋ธ”
762
- cursor.execute('''
763
- CREATE TABLE IF NOT EXISTS models (
764
- id INTEGER PRIMARY KEY AUTOINCREMENT,
765
- name TEXT NOT NULL UNIQUE,
766
- downloads INTEGER,
767
- likes INTEGER,
768
- task TEXT,
769
- url TEXT,
770
- analysis TEXT,
771
- rank INTEGER,
772
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
773
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
774
- )
775
- ''')
776
-
777
- # ์ŠคํŽ˜์ด์Šค ํ…Œ์ด๋ธ”
778
- cursor.execute('''
779
- CREATE TABLE IF NOT EXISTS spaces (
780
- id INTEGER PRIMARY KEY AUTOINCREMENT,
781
- space_id TEXT NOT NULL UNIQUE,
782
- name TEXT NOT NULL,
783
- author TEXT,
784
- title TEXT,
785
- likes INTEGER,
786
- url TEXT,
787
- sdk TEXT,
788
- simple_explanation TEXT,
789
- tech_stack TEXT,
790
- rank INTEGER,
791
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
792
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
793
- )
794
- ''')
795
-
796
- conn.commit()
797
- conn.close()
798
- print("โœ… ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™” ์™„๋ฃŒ")
799
-
800
-
801
- def save_news_to_db(news_list: List[Dict]):
802
- """๋‰ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅ"""
803
- conn = sqlite3.connect(DB_PATH)
804
- cursor = conn.cursor()
805
-
806
- saved_count = 0
807
- for news in news_list:
808
- try:
809
- cursor.execute('''
810
- INSERT OR REPLACE INTO news
811
- (title, url, date, source, category, summary, significance,
812
- impact_level, impact_text, impact_description, action)
813
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
814
- ''', (
815
- news['title'],
816
- news['url'],
817
- news.get('date', ''),
818
- news.get('source', ''),
819
- news.get('category', ''),
820
- news['analysis']['summary'],
821
- news['analysis']['significance'],
822
- news['analysis']['impact_level'],
823
- news['analysis']['impact_text'],
824
- news['analysis']['impact_description'],
825
- news['analysis']['action']
826
- ))
827
- saved_count += 1
828
- except sqlite3.IntegrityError:
829
- pass # ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๋‰ด์Šค
830
-
831
- conn.commit()
832
- conn.close()
833
- print(f"โœ… {saved_count}๊ฐœ ๋‰ด์Šค DB ์ €์žฅ ์™„๋ฃŒ")
834
-
835
-
836
- def save_models_to_db(models_list: List[Dict]):
837
- """๋ชจ๋ธ ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅ"""
838
- conn = sqlite3.connect(DB_PATH)
839
- cursor = conn.cursor()
840
-
841
- saved_count = 0
842
- for model in models_list:
843
- try:
844
- cursor.execute('''
845
- INSERT OR REPLACE INTO models
846
- (name, downloads, likes, task, url, analysis, rank, updated_at)
847
- VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
848
- ''', (
849
- model['name'],
850
- model['downloads'],
851
- model['likes'],
852
- model['task'],
853
- model['url'],
854
- model['analysis'],
855
- model['rank']
856
- ))
857
- saved_count += 1
858
- except Exception as e:
859
- print(f"โš ๏ธ ๋ชจ๋ธ ์ €์žฅ ์˜ค๋ฅ˜: {e}")
860
-
861
- conn.commit()
862
- conn.close()
863
- print(f"โœ… {saved_count}๊ฐœ ๋ชจ๋ธ DB ์ €์žฅ ์™„๋ฃŒ")
864
-
865
-
866
- def save_spaces_to_db(spaces_list: List[Dict]):
867
- """์ŠคํŽ˜์ด์Šค ๋ฐ์ดํ„ฐ๋ฅผ DB์— ์ €์žฅ"""
868
- conn = sqlite3.connect(DB_PATH)
869
- cursor = conn.cursor()
870
-
871
- saved_count = 0
872
- for space in spaces_list:
873
- try:
874
- cursor.execute('''
875
- INSERT OR REPLACE INTO spaces
876
- (space_id, name, author, title, likes, url, sdk,
877
- simple_explanation, tech_stack, rank, updated_at)
878
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
879
- ''', (
880
- space['space_id'],
881
- space['name'],
882
- space.get('author', ''),
883
- space.get('title', ''),
884
- space.get('likes', 0),
885
- space['url'],
886
- space.get('sdk', ''),
887
- space['simple_explanation'],
888
- json.dumps(space.get('tech_stack', [])),
889
- space['rank']
890
- ))
891
- saved_count += 1
892
- except Exception as e:
893
- print(f"โš ๏ธ ์ŠคํŽ˜์ด์Šค ์ €์žฅ ์˜ค๋ฅ˜: {e}")
894
-
895
- conn.commit()
896
- conn.close()
897
- print(f"โœ… {saved_count}๊ฐœ ์ŠคํŽ˜์ด์Šค DB ์ €์žฅ ์™„๋ฃŒ")
898
-
899
-
900
- def load_news_from_db() -> List[Dict]:
901
- """DB์—์„œ ๋‰ด์Šค ๋กœ๋“œ"""
902
- conn = sqlite3.connect(DB_PATH)
903
- cursor = conn.cursor()
904
-
905
- cursor.execute('''
906
- SELECT title, url, date, source, category, summary, significance,
907
- impact_level, impact_text, impact_description, action
908
- FROM news ORDER BY created_at DESC LIMIT 50
909
- ''')
910
-
911
- news_list = []
912
- for row in cursor.fetchall():
913
- news_list.append({
914
- 'title': row[0],
915
- 'url': row[1],
916
- 'date': row[2],
917
- 'source': row[3],
918
- 'category': row[4],
919
- 'analysis': {
920
- 'summary': row[5],
921
- 'significance': row[6],
922
- 'impact_level': row[7],
923
- 'impact_text': row[8],
924
- 'impact_description': row[9],
925
- 'action': row[10]
926
- }
927
- })
928
-
929
- conn.close()
930
- return news_list
931
-
932
-
933
- def load_models_from_db() -> List[Dict]:
934
- """DB์—์„œ ๋ชจ๋ธ ๋กœ๋“œ"""
935
- conn = sqlite3.connect(DB_PATH)
936
- cursor = conn.cursor()
937
-
938
- cursor.execute('''
939
- SELECT name, downloads, likes, task, url, analysis, rank
940
- FROM models ORDER BY rank ASC LIMIT 30
941
- ''')
942
-
943
- models_list = []
944
- for row in cursor.fetchall():
945
- models_list.append({
946
- 'name': row[0],
947
- 'downloads': row[1],
948
- 'likes': row[2],
949
- 'task': row[3],
950
- 'url': row[4],
951
- 'analysis': row[5],
952
- 'rank': row[6]
953
- })
954
-
955
- conn.close()
956
- return models_list
957
-
958
-
959
- def load_spaces_from_db() -> List[Dict]:
960
- """DB์—์„œ ์ŠคํŽ˜์ด์Šค ๋กœ๋“œ"""
961
- conn = sqlite3.connect(DB_PATH)
962
- cursor = conn.cursor()
963
-
964
- cursor.execute('''
965
- SELECT space_id, name, author, title, likes, url, sdk,
966
- simple_explanation, tech_stack, rank
967
- FROM spaces ORDER BY rank ASC LIMIT 30
968
- ''')
969
-
970
- spaces_list = []
971
- for row in cursor.fetchall():
972
- spaces_list.append({
973
- 'space_id': row[0],
974
- 'name': row[1],
975
- 'author': row[2],
976
- 'title': row[3],
977
- 'likes': row[4],
978
- 'url': row[5],
979
- 'sdk': row[6],
980
- 'simple_explanation': row[7],
981
- 'tech_stack': json.loads(row[8]) if row[8] else [],
982
- 'rank': row[9],
983
- 'description': row[3] # title์„ description์œผ๋กœ ์‚ฌ์šฉ
984
- })
985
-
986
- conn.close()
987
- return spaces_list
988
-
989
-
990
- # ============================================
991
- # LLM ๋ถ„์„๊ธฐ ํด๋ž˜์Šค
992
- # ============================================
993
-
994
- class LLMAnalyzer:
995
- """Fireworks AI (Qwen3) ๊ธฐ๋ฐ˜ LLM ๋ถ„์„๊ธฐ"""
996
-
997
- def __init__(self):
998
- self.api_key = os.environ.get('FIREWORKS_API_KEY', '')
999
- self.api_url = "https://api.fireworks.ai/inference/v1/chat/completions"
1000
- self.api_available = bool(self.api_key)
1001
-
1002
- if not self.api_available:
1003
- print("โš ๏ธ FIREWORKS_API_KEY ํ™˜๊ฒฝ๋ณ€์ˆ˜๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ํ…œํ”Œ๋ฆฟ ๋ชจ๋“œ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.")
1004
-
1005
- def call_llm(self, messages: List[Dict], max_tokens: int = 2000) -> str:
1006
- """Fireworks AI API ํ˜ธ์ถœ"""
1007
- if not self.api_available:
1008
- return None
1009
-
1010
- try:
1011
- payload = {
1012
- "model": "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507",
1013
- "max_tokens": max_tokens,
1014
- "top_p": 1,
1015
- "top_k": 40,
1016
- "presence_penalty": 0,
1017
- "frequency_penalty": 0,
1018
- "temperature": 0.6,
1019
- "messages": messages
1020
- }
1021
-
1022
- headers = {
1023
- "Accept": "application/json",
1024
- "Content-Type": "application/json",
1025
- "Authorization": f"Bearer {self.api_key}"
1026
- }
1027
-
1028
- response = requests.post(self.api_url, headers=headers, json=payload, timeout=30)
1029
- response.raise_for_status()
1030
-
1031
- result = response.json()
1032
- return result['choices'][0]['message']['content']
1033
-
1034
- except Exception as e:
1035
- print(f" โš ๏ธ LLM API ํ˜ธ์ถœ ์˜ค๋ฅ˜: {e}")
1036
- return None
1037
-
1038
- def fetch_model_card(self, model_id: str) -> str:
1039
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ๋ชจ๋ธ ์นด๋“œ(README.md) ๊ฐ€์ ธ์˜ค๊ธฐ"""
1040
- try:
1041
- url = f"https://huggingface.co/{model_id}/raw/main/README.md"
1042
- response = requests.get(url, timeout=10)
1043
-
1044
- if response.status_code == 200:
1045
- content = response.text
1046
- # ๋„ˆ๋ฌด ๊ธด ๊ฒฝ์šฐ ์•ž๋ถ€๋ถ„๋งŒ (์•ฝ 3000์ž)
1047
- if len(content) > 3000:
1048
- content = content[:3000] + "\n...(ํ›„๋žต)"
1049
- return content
1050
- else:
1051
- return None
1052
- except Exception as e:
1053
- print(f" โš ๏ธ ๋ชจ๋ธ ์นด๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜: {e}")
1054
- return None
1055
-
1056
- def fetch_space_code(self, space_id: str) -> str:
1057
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค app.py ๊ฐ€์ ธ์˜ค๊ธฐ"""
1058
- try:
1059
- url = f"https://huggingface.co/spaces/{space_id}/raw/main/app.py"
1060
- response = requests.get(url, timeout=10)
1061
-
1062
- if response.status_code == 200:
1063
- content = response.text
1064
- # ๋„ˆ๋ฌด ๊ธด ๊ฒฝ์šฐ ์•ž๋ถ€๋ถ„๋งŒ (์•ฝ 2000์ž)
1065
- if len(content) > 2000:
1066
- content = content[:2000] + "\n...(ํ›„๋žต)"
1067
- return content
1068
- else:
1069
- return None
1070
- except Exception as e:
1071
- print(f" โš ๏ธ ์ŠคํŽ˜์ด์Šค ์ฝ”๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜: {e}")
1072
- return None
1073
-
1074
- def analyze_news_simple(self, title: str, content: str = "") -> Dict:
1075
- """๋‰ด์Šค ๊ธฐ์‚ฌ๋ฅผ ์ค‘๊ณ ๋“ฑํ•™์ƒ ์ˆ˜์ค€์œผ๋กœ ๋ถ„์„"""
1076
-
1077
- analysis_templates = {
1078
- "์ฑ—GPT": {
1079
- "summary": "๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ(MS)๋Š” ์ฑ—GPT์˜ ํญ๋ฐœ์ ์ธ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€๋กœ ์ธํ•ด ๋ฐ์ดํ„ฐ์„ผํ„ฐ ์šฉ๋Ÿ‰์ด ๋ถ€์กฑํ•œ ์ƒํ™ฉ์— ์ง๋ฉดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฏธ๊ตญ ๋‚ด ์—ฌ๋Ÿฌ ์ง€์—ญ์—์„œ ๋ฌผ๋ฆฌ์  ๊ณต๊ฐ„๊ณผ ์„œ๋ฒ„๊ฐ€ ๋ชจ๋‘ ๋ถ€์กฑํ•œ ์ƒํƒœ์ด๋ฉฐ, ์ด๋กœ ์ธํ•ด ๋ฒ„์ง€๋‹ˆ์•„์™€ ํ…์‚ฌ์Šค ๋“ฑ ํ•ต์‹ฌ ์ง€์—ญ์—์„œ๋Š” 2026๋…„ ์ƒ๋ฐ˜๊ธฐ๊นŒ์ง€ ์‹ ๊ทœ Azure ํด๋ผ์šฐ๋“œ ๊ตฌ๋…์ด ์ œํ•œ๋  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ƒ์„ฑํ˜• AI ์„œ๋น„์Šค์˜ ๊ธ‰๊ฒฉํ•œ ์„ฑ์žฅ์ด ๊ฐ€์ ธ์˜จ ์ธํ”„๋ผ ๊ณต๊ธ‰ ๋ฌธ์ œ๋ฅผ ์—ฌ์‹คํžˆ ๋ณด์—ฌ์ฃผ๋Š” ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค.",
1080
- "significance": "์ด ๋‰ด์Šค๋Š” AI ๊ธฐ์ˆ ์˜ ๋Œ€์ค‘ํ™” ์†๋„๊ฐ€ ๊ธฐ์—…๋“ค์˜ ์˜ˆ์ƒ์„ ํ›จ์”ฌ ๋›ฐ์–ด๋„˜๊ณ  ์žˆ์Œ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. MS ๊ฐ™์€ ๊ธ€๋กœ๋ฒŒ IT ๊ธฐ์—…๋„ AI ์ˆ˜์š”๋ฅผ ๋”ฐ๋ผ์žก๊ธฐ ์œ„ํ•ด ๊ณ ๊ตฐ๋ถ„ํˆฌํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋Š” AI๊ฐ€ ๋‹จ์ˆœํ•œ ์œ ํ–‰์ด ์•„๋‹Œ ์‚ฐ์—… ์ „๋ฐ˜์„ ๋ณ€ํ™”์‹œํ‚ค๋Š” ํ•ต์‹ฌ ๊ธฐ์ˆ ์ž„์„ ์ฆ๋ช…ํ•ฉ๋‹ˆ๋‹ค.",
1081
- "impact_level": "high",
1082
- "impact_text": "๋†’์Œ",
1083
- "impact_description": "ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ ๋ถ€์กฑ์€ AI ์„œ๋น„์Šค ํ™•์žฅ์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น˜๋ฉฐ, ํ–ฅํ›„ AI ๊ธฐ์ˆ  ์ ‘๊ทผ์„ฑ๊ณผ ๋น„์šฉ ๊ตฌ์กฐ๋ฅผ ๋ณ€ํ™”์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
1084
- "action": "์ฑ—GPT๋‚˜ Claude ๊ฐ™์€ AI ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•œ ํ•™์Šต ๋ฐฉ๋ฒ•์„ ์ตํžˆ์„ธ์š”. ๋ณด๊ณ ์„œ ์ž‘์„ฑ, ์ฝ”๋”ฉ ํ•™์Šต, ์™ธ๊ตญ์–ด ๊ณต๋ถ€ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ AI๋ฅผ ํ•™์Šต ๋ณด์กฐ ๋„๊ตฌ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
1085
- },
1086
- "GPU": {
1087
- "summary": "๋ฏธ๊ตญ ์ •๋ถ€๊ฐ€ ์•„๋ž์—๋ฏธ๋ฆฌํŠธ(UAE)์— ์ตœ์ฒจ๋‹จ AI ์นฉ(GPU) ์ˆ˜์ถœ์„ ์Šน์ธํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์Šน์ธ์€ UAE ๋‚ด ๋ฏธ๊ตญ ๊ธฐ์—…์ด ์šด์˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ์„ผํ„ฐ์— ํ•œ์ •๋˜๋ฉฐ, ์˜คํ”ˆAI ์ „์šฉ 5GW ๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์„ผํ„ฐ ๊ตฌ์ถ•์— ์‚ฌ์šฉ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. GPU๋Š” AI ๋ชจ๋ธ ํ•™์Šต์— ํ•„์ˆ˜์ ์ธ ํ•˜๋“œ์›จ์–ด๋กœ, ์—”๋น„๋””์•„๊ฐ€ ์‹œ์žฅ์„ ์ฃผ๋„ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ์ด๋ฒˆ ๊ฒฐ์ •์œผ๋กœ ์—”๋น„๋””์•„์˜ ์‹œ๊ฐ€์ด์•ก์ด 5์กฐ ๋‹ฌ๋Ÿฌ์— ๊ทผ์ ‘ํ•  ๊ฒƒ์œผ๋กœ ์ „๋ง๋ฉ๋‹ˆ๋‹ค.",
1088
- "significance": "์ด๋Š” ๋ฏธ๊ตญ์˜ AI ๊ธฐ์ˆ  ์ˆ˜์ถœ ์ •์ฑ… ๋ณ€ํ™”๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์ค‘์š”ํ•œ ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค. ๊ธฐ์ˆ  ํŒจ๊ถŒ ๊ฒฝ์Ÿ ์†์—์„œ๋„ ์ „๋žต์  ๋™๋งน๊ตญ๊ณผ์˜ ํ˜‘๋ ฅ์„ ํ†ตํ•ด AI ์ƒํƒœ๊ณ„๋ฅผ ํ™•์žฅํ•˜๋ ค๋Š” ๋ฏธ๊ตญ์˜ ์˜๋„๋ฅผ ์—ฟ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.",
1089
- "impact_level": "medium",
1090
- "impact_text": "์ค‘๊ฐ„",
1091
- "impact_description": "AI ํ•˜๋“œ์›จ์–ด ๊ณต๊ธ‰๋ง์˜ ์ง€์ •ํ•™์  ๋ณ€ํ™”๋Š” ๊ธ€๋กœ๋ฒŒ AI ์‚ฐ์—… ์ง€ํ˜•๋„์— ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠนํžˆ ๋ฐ˜๋„์ฒด ์‚ฐ์—…๊ณผ ๊ตญ์ œ ๊ด€๊ณ„์— ์ค‘์š”ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.",
1092
- "action": "์ปดํ“จํ„ฐ ํ•˜๋“œ์›จ์–ด, ํŠนํžˆ GPU์˜ ์ž‘๋™ ์›๋ฆฌ์™€ AI ํ•™์Šต์—์„œ์˜ ์—ญํ• ์„ ๊ณต๋ถ€ํ•ด๋ณด์„ธ์š”. ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ, ํ–‰๋ ฌ ์—ฐ์‚ฐ ๋“ฑ์˜ ๊ฐœ๋…์„ ์ดํ•ดํ•˜๋ฉด AI ๊ธฐ์ˆ ์˜ ๊ทผ๊ฐ„์„ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
1093
- },
1094
- "์†Œ๋ผ": {
1095
- "summary": "์˜คํ”ˆAI์˜ AI ๋™์˜์ƒ ์ƒ์„ฑ ์•ฑ '์†Œ๋ผ(Sora)'๊ฐ€ ์ถœ์‹œ 5์ผ ๋งŒ์— 100๋งŒ ๋‹ค์šด๋กœ๋“œ๋ฅผ ๋ŒํŒŒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ฑ—GPT๋ณด๋‹ค ๋น ๋ฅธ ์„ฑ์žฅ ์†๋„์ด๋ฉฐ, ์ดˆ๋Œ€ ์ „์šฉ(invite-only) ์•ฑ์ž„์„ ๊ณ ๋ คํ•˜๋ฉด ๋”์šฑ ๋†€๋ผ์šด ๊ธฐ๋ก์ž…๋‹ˆ๋‹ค. ์†Œ๋ผ๋Š” ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋งŒ์œผ๋กœ ๊ณ ํ’ˆ์งˆ ๋™์˜์ƒ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์ƒ์„ฑํ˜• AI ๋„๊ตฌ๋กœ, ๋ฏธ๊ตญ๊ณผ ์บ๋‚˜๋‹ค์—์„œ iOS ์ „์šฉ์œผ๋กœ ์ถœ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
1096
- "significance": "ํ…์ŠคํŠธ๋ฅผ ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ธฐ์ˆ ์—์„œ ๋” ๋‚˜์•„๊ฐ€ ๋™์˜์ƒ ์ƒ์„ฑ๊นŒ์ง€ ๊ฐ€๋Šฅํ•ด์ง„ ๊ฒƒ์€ AI ๊ธฐ์ˆ ์˜ ์ง„ํ™”๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ฝ˜ํ…์ธ  ์ œ์ž‘์˜ ๋ฏผ์ฃผํ™”๊ฐ€ ๊ฐ€์†ํ™”๋˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ๊ณ ํ’ˆ์งˆ ์˜์ƒ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์‹œ๋Œ€๊ฐ€ ์—ด๋ฆฌ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.",
1097
- "impact_level": "high",
1098
- "impact_text": "๋†’์Œ",
1099
- "impact_description": "์˜์ƒ ์ œ์ž‘ ์‚ฐ์—…์˜ ํŒจ๋Ÿฌ๋‹ค์ž„์ด ๋ณ€ํ™”ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๊ต์œก, ๋งˆ์ผ€ํŒ…, ์—”ํ„ฐํ…Œ์ธ๋จผํŠธ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ AI ๋™์˜์ƒ ์ƒ์„ฑ ๊ธฐ์ˆ ์˜ ํ™œ์šฉ์ด ์ฆ๊ฐ€ํ•  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.",
1100
- "action": "AI ๋™์˜์ƒ ์ƒ์„ฑ ๋„๊ตฌ์˜ ๊ฐ€๋Šฅ์„ฑ๊ณผ ํ•œ๊ณ„๋ฅผ ํƒ๊ตฌํ•ด๋ณด์„ธ์š”. ์ฐฝ์˜์ ์ธ ์•„์ด๋””์–ด๋ฅผ ์‹œ๊ฐํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ฐฐ์šฐ๊ณ , ๋™์‹œ์— ๋”ฅํŽ˜์ดํฌ ๊ฐ™์€ ์•…์šฉ ์‚ฌ๋ก€์— ๋Œ€ํ•œ ๋น„ํŒ์  ์‚ฌ๊ณ ๋„ ํ•จ์–‘ํ•˜์„ธ์š”."
1101
- }
1102
- }
1103
-
1104
- # ํ‚ค์›Œ๋“œ ๋งค์นญ์œผ๋กœ ํ…œํ”Œ๋ฆฟ ์„ ํƒ
1105
- for keyword, template in analysis_templates.items():
1106
- if keyword.lower() in title.lower():
1107
- return template
1108
-
1109
- # ๊ธฐ๋ณธ ๋ถ„์„ (์ค‘๊ณ ๋“ฑํ•™์ƒ ์ˆ˜์ค€)
1110
- return {
1111
- "summary": f"'{title}'์™€ ๊ด€๋ จ๋œ ์ตœ์‹  AI ๊ธฐ์ˆ  ๋™ํ–ฅ์ž…๋‹ˆ๋‹ค. ์ธ๊ณต์ง€๋Šฅ ๋ถ„์•ผ๋Š” ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ  ๋ณ€ํ™”๋Š” ์šฐ๋ฆฌ์˜ ์ผ์ƒ์ƒํ™œ๊ณผ ๋ฏธ๋ž˜ ์ง์—… ์„ธ๊ณ„์— ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น  ๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ ๊ธฐ์ˆ ์˜ ์›๋ฆฌ์™€ ์‚ฌํšŒ์  ํŒŒ๊ธ‰ํšจ๊ณผ๋ฅผ ํ•จ๊ป˜ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.",
1112
- "significance": "AI ๊ธฐ์ˆ ์˜ ๋ฐœ์ „์€ ๋‹จ์ˆœํ•œ ๊ธฐ์ˆ  ํ˜์‹ ์„ ๋„˜์–ด ์‚ฌํšŒ, ๊ฒฝ์ œ, ์œค๋ฆฌ์  ์ธก๋ฉด์—์„œ ๋‹ค์–‘ํ•œ ๋…ผ์˜๋ฅผ ๋ถˆ๋Ÿฌ์ผ์œผํ‚ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ณ€ํ™”๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋Œ€๋น„ํ•˜๋Š” ๊ฒƒ์ด ๋ฏธ๋ž˜ ์„ธ๋Œ€์—๊ฒŒ ์ค‘์š”ํ•œ ์—ญ๋Ÿ‰์ž…๋‹ˆ๋‹ค.",
1113
- "impact_level": "medium",
1114
- "impact_text": "์ค‘๊ฐ„",
1115
- "impact_description": "AI ๊ธฐ์ˆ ์˜ ๋ฐœ์ „์€ ๊ต์œก, ์ทจ์—…, ์‚ฐ์—… ์ „๋ฐ˜์— ๊ฑธ์ณ ๊ตฌ์กฐ์  ๋ณ€ํ™”๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ด๋ฉฐ, ์ด์— ๋Œ€ํ•œ ์ดํ•ด์™€ ์ค€๋น„๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.",
1116
- "action": "AI ๊ธฐ์ˆ ์˜ ๊ธฐ๋ณธ ์›๋ฆฌ๋ฅผ ํ•™์Šตํ•˜๊ณ , ๊ด€๋ จ ํ”„๋กœ๊ทธ๋ž˜๋ฐ(Python ๋“ฑ)์ด๋‚˜ ๋ฐ์ดํ„ฐ ๊ณผํ•™ ๊ธฐ์ดˆ๋ฅผ ๊ณต๋ถ€ํ•ด๋ณด์„ธ์š”. ๋˜ํ•œ AI ์œค๋ฆฌ์™€ ์‚ฌํšŒ์  ์˜ํ–ฅ์— ๋Œ€ํ•ด์„œ๋„ ๋น„ํŒ์ ์œผ๋กœ ์‚ฌ๊ณ ํ•˜๋Š” ์Šต๊ด€์„ ๊ธฐ๋ฅด์„ธ์š”."
1117
- }
1118
-
1119
- def analyze_model(self, model_name: str, task: str, downloads: int) -> str:
1120
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ๋ชจ๋ธ ๋ถ„์„ - ๋ชจ๋ธ ์นด๋“œ๋ฅผ LLM์œผ๋กœ ๋ถ„์„"""
1121
-
1122
- # 1. ๋ชจ๋ธ ์นด๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ
1123
- model_card = self.fetch_model_card(model_name)
1124
-
1125
- # 2. LLM์œผ๋กœ ๋ถ„์„
1126
- if model_card and self.api_available:
1127
- try:
1128
- messages = [
1129
- {
1130
- "role": "system",
1131
- "content": "๋‹น์‹ ์€ ์ค‘๊ณ ๋“ฑํ•™์ƒ๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ AI ๋ชจ๋ธ์„ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
1132
- },
1133
- {
1134
- "role": "user",
1135
- "content": f"""๋‹ค์Œ์€ ํ—ˆ๊น…ํŽ˜์ด์Šค ๋ชจ๋ธ '{model_name}'์˜ ๋ชจ๋ธ ์นด๋“œ์ž…๋‹ˆ๋‹ค:
1136
-
1137
- {model_card}
1138
-
1139
- ์ด ๋ชจ๋ธ์„ ์ค‘๊ณ ๋“ฑํ•™์ƒ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก 3-4๋ฌธ์žฅ์œผ๋กœ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”. ๋‹ค์Œ ๋‚ด์šฉ์„ ํฌํ•จํ•˜์„ธ์š”:
1140
- 1. ์ด ๋ชจ๋ธ์ด ๋ฌด์—‡์„ ํ•˜๋Š”์ง€
1141
- 2. ์–ด๋–ค ํŠน์ง•์ด ์žˆ๋Š”์ง€
1142
- 3. ๋ˆ„๊ฐ€ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์€์ง€
1143
-
1144
- ๋‹ต๋ณ€์€ ๋ฐ˜๋“œ์‹œ 3-4๋ฌธ์žฅ์˜ ํ•œ๊ตญ์–ด๋กœ๋งŒ ์ž‘์„ฑํ•˜์„ธ์š”."""
1145
- }
1146
- ]
1147
-
1148
- result = self.call_llm(messages, max_tokens=500)
1149
-
1150
- if result:
1151
- return result.strip()
1152
-
1153
- except Exception as e:
1154
- print(f" โš ๏ธ ๋ชจ๋ธ ๋ถ„์„ LLM ์˜ค๋ฅ˜: {e}")
1155
-
1156
- # 3. Fallback: ํ…œํ”Œ๋ฆฟ ๊ธฐ๋ฐ˜ ์„ค๋ช…
1157
- task_explanations = {
1158
- "text-generation": "๊ธ€์„ ์ž๋™์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š”",
1159
- "image-to-text": "์‚ฌ์ง„์„ ๋ณด๊ณ  ์„ค๋ช…์„ ์จ์ฃผ๋Š”",
1160
- "text-to-image": "๊ธ€์„ ์ฝ๊ณ  ๊ทธ๋ฆผ์„ ๊ทธ๋ ค์ฃผ๋Š”",
1161
- "translation": "๋‹ค๋ฅธ ์–ธ์–ด๋กœ ๋ฒˆ์—ญํ•ด์ฃผ๋Š”",
1162
- "question-answering": "์งˆ๋ฌธ์— ๋‹ตํ•ด์ฃผ๋Š”",
1163
- "summarization": "๊ธด ๊ธ€์„ ์งง๊ฒŒ ์š”์•ฝํ•ด์ฃผ๋Š”",
1164
- "text-classification": "๊ธ€์„ ๋ถ„๋ฅ˜ํ•ด์ฃผ๋Š”",
1165
- "token-classification": "๋‹จ์–ด๋ฅผ ๋ถ„์„ํ•ด์ฃผ๋Š”",
1166
- "fill-mask": "๋นˆ์นธ์„ ์ฑ„์›Œ์ฃผ๋Š”"
1167
- }
1168
-
1169
- task_desc = task_explanations.get(task, "ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์„ ํ•˜๋Š”")
1170
-
1171
- if downloads > 10000000:
1172
- popularity = "์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์€"
1173
- elif downloads > 1000000:
1174
- popularity = "์•„์ฃผ ๋งŽ์€"
1175
- elif downloads > 100000:
1176
- popularity = "๋งŽ์€"
1177
- else:
1178
- popularity = "์–ด๋А ์ •๋„"
1179
-
1180
- return f"์ด ๋ชจ๋ธ์€ {task_desc} AI์˜ˆ์š”. {popularity} ์‚ฌ๋žŒ๋“ค์ด ๋‹ค์šด๋กœ๋“œํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์–ด์š”. {model_name.split('/')[-1]}๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์œ ๋ช…ํ•ด์š”!"
1181
-
1182
- def analyze_space(self, space_name: str, space_id: str, description: str) -> Dict:
1183
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ๋ถ„์„ - app.py๋ฅผ LLM์œผ๋กœ ๋ถ„์„"""
1184
-
1185
- # 1. app.py ์ฝ”๋“œ ๊ฐ€์ ธ์˜ค๊ธฐ
1186
- app_code = self.fetch_space_code(space_id)
1187
-
1188
- # 2. LLM์œผ๋กœ ๋ถ„์„
1189
- if app_code and self.api_available:
1190
- try:
1191
- messages = [
1192
- {
1193
- "role": "system",
1194
- "content": "๋‹น์‹ ์€ ์ค‘๊ณ ๋“ฑํ•™์ƒ๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•˜๋Š” ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. ํ•œ๊ตญ์–ด๋กœ ๋‹ต๋ณ€ํ•˜์„ธ์š”."
1195
- },
1196
- {
1197
- "role": "user",
1198
- "content": f"""๋‹ค์Œ์€ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค '{space_name}'์˜ app.py ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค:
1199
-
1200
- {app_code}
1201
-
1202
- ์ด ์•ฑ์„ ์ค‘๊ณ ๋“ฑํ•™์ƒ์ด ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก 3-4๋ฌธ์žฅ์œผ๋กœ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”. ๋‹ค์Œ ๋‚ด์šฉ์„ ํฌํ•จํ•˜์„ธ์š”:
1203
- 1. ์ด ์•ฑ์ด ๋ฌด์—‡์„ ํ•˜๋Š”์ง€
1204
- 2. ์–ด๋–ค ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜๋Š”์ง€
1205
- 3. ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€
1206
-
1207
- ๋‹ต๋ณ€์€ ๋ฐ˜๋“œ์‹œ 3-4๋ฌธ์žฅ์˜ ํ•œ๊ตญ์–ด๋กœ๋งŒ ์ž‘์„ฑํ•˜์„ธ์š”."""
1208
- }
1209
- ]
1210
-
1211
- result = self.call_llm(messages, max_tokens=500)
1212
-
1213
- if result:
1214
- # ๊ธฐ์ˆ  ์Šคํƒ ์ถ”์ถœ ์‹œ๋„
1215
- tech_stack = []
1216
- if 'gradio' in app_code.lower():
1217
- tech_stack.append('Gradio')
1218
- if 'streamlit' in app_code.lower():
1219
- tech_stack.append('Streamlit')
1220
- if 'transformers' in app_code.lower():
1221
- tech_stack.append('Transformers')
1222
- if 'torch' in app_code.lower() or 'pytorch' in app_code.lower():
1223
- tech_stack.append('PyTorch')
1224
- if 'tensorflow' in app_code.lower():
1225
- tech_stack.append('TensorFlow')
1226
- if 'diffusers' in app_code.lower():
1227
- tech_stack.append('Diffusers')
1228
-
1229
- if not tech_stack:
1230
- tech_stack = ['Python', 'AI']
1231
-
1232
- return {
1233
- "simple_explanation": result.strip(),
1234
- "tech_stack": tech_stack
1235
- }
1236
-
1237
- except Exception as e:
1238
- print(f" โš ๏ธ ์ŠคํŽ˜์ด์Šค ๋ถ„์„ LLM ์˜ค๋ฅ˜: {e}")
1239
-
1240
- # 3. Fallback: ํ…œํ”Œ๋ฆฟ ๊ธฐ๋ฐ˜ ์„ค๋ช…
1241
- return {
1242
- "simple_explanation": f"{space_name}๋Š” ์›น๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ”๋กœ AI๋ฅผ ์ฒดํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ณณ์ด์—์š”. ์„ค์น˜ ์—†์ด๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด์„œ ํŽธ๋ฆฌํ•ด์š”! ๋งˆ์น˜ ์˜จ๋ผ์ธ ๊ฒŒ์ž„์ฒ˜๋Ÿผ ๋ฐ”๋กœ ์ ‘์†ํ•ด์„œ AI๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.",
1243
- "tech_stack": ["Python", "Gradio", "Transformers", "PyTorch"]
1244
- }
1245
-
1246
-
1247
- # ============================================
1248
- # ๊ณ ๊ธ‰ ๋ถ„์„๊ธฐ ํด๋ž˜์Šค
1249
- # ============================================
1250
-
1251
- class AdvancedAIAnalyzer:
1252
- """LLM ๊ธฐ๋ฐ˜ ๊ณ ๊ธ‰ AI ๋‰ด์Šค ๋ถ„์„๊ธฐ"""
1253
-
1254
- def __init__(self):
1255
- self.llm_analyzer = LLMAnalyzer()
1256
- self.huggingface_data = {
1257
- "models": [],
1258
- "spaces": []
1259
- }
1260
- self.news_data = []
1261
-
1262
- def fetch_aitimes_news(self) -> List[Dict]:
1263
- """AI Times์—์„œ ์˜ค๋Š˜ ๋‚ ์งœ ๋‰ด์Šค ํฌ๋กค๋ง"""
1264
- print("๐Ÿ“ฐ AI Times ๋‰ด์Šค ์ˆ˜์ง‘ ์ค‘...")
1265
-
1266
- # ์ˆ˜์ง‘ํ•  URL ๋ชฉ๋ก
1267
- urls = [
1268
- 'https://www.aitimes.com/news/articleList.html?sc_multi_code=S2&view_type=sm',
1269
- 'https://www.aitimes.com/news/articleList.html?sc_section_code=S1N24&view_type=sm'
1270
- ]
1271
-
1272
- all_news = []
1273
- today = datetime.now().strftime('%m-%d') # ์˜ˆ: '10-10'
1274
-
1275
- for url_idx, url in enumerate(urls, 1):
1276
- try:
1277
- print(f" ๐Ÿ” [{url_idx}/2] ์ˆ˜์ง‘ ์ค‘: {url}")
1278
- response = requests.get(url, timeout=15, headers={
1279
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
1280
- })
1281
- response.raise_for_status()
1282
- response.encoding = 'utf-8'
1283
-
1284
- soup = BeautifulSoup(response.text, 'html.parser')
1285
-
1286
- # ๋ชจ๋“  ๋งํฌ ์ฐพ๊ธฐ
1287
- articles = soup.find_all('a', href=re.compile(r'/news/articleView\.html\?idxno=\d+'))
1288
-
1289
- print(f" โ†’ {len(articles)}๊ฐœ ๋งํฌ ๋ฐœ๊ฒฌ")
1290
-
1291
- articles_found = 0
1292
- for article_tag in articles:
1293
- try:
1294
- # ์ œ๋ชฉ๊ณผ ๋งํฌ
1295
- title = article_tag.get_text(strip=True)
1296
- link = article_tag.get('href', '')
1297
-
1298
- # ๋งํฌ ์ •๊ทœํ™”
1299
- if link and not link.startswith('http'):
1300
- if link.startswith('/'):
1301
- link = 'https://www.aitimes.com' + link
1302
- else:
1303
- link = 'https://www.aitimes.com/' + link
1304
-
1305
- # ์ œ๋ชฉ์ด ๋„ˆ๋ฌด ์งง์œผ๋ฉด ์Šคํ‚ต
1306
- if not title or len(title) < 10:
1307
- continue
1308
-
1309
- # ๋ถ€๋ชจ ์š”์†Œ์—์„œ ๋‚ ์งœ ์ฐพ๊ธฐ
1310
- parent = article_tag.parent
1311
- date_text = ''
1312
-
1313
- # ๋ถ€๋ชจ์˜ ๋ชจ๋“  ํ…์ŠคํŠธ์—์„œ ๋‚ ์งœ ํŒจํ„ด ์ฐพ๊ธฐ
1314
- if parent:
1315
- parent_text = parent.get_text()
1316
- date_match = re.search(r'(\d{2}-\d{2}\s+\d{2}:\d{2})', parent_text)
1317
- if date_match:
1318
- date_text = date_match.group(1)
1319
-
1320
- # ๋‚ ์งœ๊ฐ€ ์—†์œผ๋ฉด ๋‹ค์Œ ํ˜•์ œ ์š”์†Œ๋“ค ํ™•์ธ
1321
- if not date_text:
1322
- for sibling in article_tag.find_next_siblings():
1323
- sibling_text = sibling.get_text()
1324
- date_match = re.search(r'(\d{2}-\d{2}\s+\d{2}:\d{2})', sibling_text)
1325
- if date_match:
1326
- date_text = date_match.group(1)
1327
- break
1328
-
1329
- # ๋‚ ์งœ๊ฐ€ ์—ฌ์ „ํžˆ ์—†์œผ๋ฉด ์˜ค๋Š˜ ๋‚ ์งœ ์‚ฌ์šฉ
1330
- if not date_text:
1331
- date_text = today
1332
-
1333
- # ์˜ค๋Š˜ ๋‚ ์งœ๋งŒ ํ•„ํ„ฐ๋ง
1334
- if today not in date_text:
1335
- continue
1336
-
1337
- news_item = {
1338
- 'title': title,
1339
- 'url': link,
1340
- 'date': date_text,
1341
- 'source': 'AI Times',
1342
- 'category': 'AI'
1343
- }
1344
-
1345
- all_news.append(news_item)
1346
- articles_found += 1
1347
-
1348
- print(f" โœ“ ์ถ”๊ฐ€: {title[:60]}... ({date_text})")
1349
-
1350
- except Exception as e:
1351
- continue
1352
-
1353
- print(f" โ†’ {articles_found}๊ฐœ ์˜ค๋Š˜์ž ๊ธฐ๏ฟฝ๏ฟฝ ์ˆ˜์ง‘\n")
1354
- time.sleep(1) # ์„œ๋ฒ„ ๋ถ€ํ•˜ ๋ฐฉ์ง€
1355
-
1356
- except Exception as e:
1357
- print(f" โš ๏ธ URL ์ˆ˜์ง‘ ์˜ค๋ฅ˜: {e}\n")
1358
- continue
1359
-
1360
- # ์ค‘๋ณต ์ œ๊ฑฐ (URL ๊ธฐ์ค€)
1361
- unique_news = []
1362
- seen_urls = set()
1363
- for news in all_news:
1364
- if news['url'] not in seen_urls:
1365
- unique_news.append(news)
1366
- seen_urls.add(news['url'])
1367
-
1368
- print(f"โœ… ์ด {len(unique_news)}๊ฐœ ์ค‘๋ณต ์ œ๊ฑฐ๋œ ์˜ค๋Š˜์ž ๋‰ด์Šค\n")
1369
-
1370
- # ์ตœ์†Œ 3๊ฐœ๋Š” ๋ณด์žฅ (์—†์œผ๋ฉด ์ƒ˜ํ”Œ ์ถ”๊ฐ€)
1371
- if len(unique_news) < 3:
1372
- print("โš ๏ธ ๋‰ด์Šค๊ฐ€ ๋ถ€์กฑํ•˜์—ฌ ์ตœ๊ทผ ์ƒ˜ํ”Œ ์ถ”๊ฐ€\n")
1373
- sample_news = [
1374
- {
1375
- 'title': 'MS "์ฑ—GPT ์ˆ˜์š” ํญ์ฆ์œผ๋กœ ๋ฐ์ดํ„ฐ์„ผํ„ฐ ๋ถ€์กฑ...2026๋…„๊นŒ์ง€ ์ง€์†"',
1376
- 'url': 'https://www.aitimes.com/news/articleView.html?idxno=203055',
1377
- 'date': '10-10 15:10',
1378
- 'source': 'AI Times',
1379
- 'category': 'AI'
1380
- },
1381
- {
1382
- 'title': '๋ฏธ๊ตญ, UAE์— GPU ํŒ๋งค ์ผ๋ถ€ ์Šน์ธ...์—”๋น„๋””์•„ ์‹œ์ด 5์กฐ๋‹ฌ๋Ÿฌ ๋ˆˆ์•ž',
1383
- 'url': 'https://www.aitimes.com/news/articleView.html?idxno=203053',
1384
- 'date': '10-10 14:46',
1385
- 'source': 'AI Times',
1386
- 'category': 'AI'
1387
- },
1388
- {
1389
- 'title': '์†Œ๋ผ, ์ฑ—GPT๋ณด๋‹ค ๋นจ๋ฆฌ 100๋งŒ ๋‹ค์šด๋กœ๋“œ ๋ŒํŒŒ',
1390
- 'url': 'https://www.aitimes.com/news/articleView.html?idxno=203045',
1391
- 'date': '10-10 12:55',
1392
- 'source': 'AI Times',
1393
- 'category': 'AI'
1394
- }
1395
- ]
1396
- for sample in sample_news:
1397
- if sample['url'] not in seen_urls:
1398
- unique_news.append(sample)
1399
-
1400
- return unique_news[:20] # ์ตœ๋Œ€ 20๊ฐœ
1401
-
1402
- def fetch_huggingface_models(self, limit: int = 30) -> List[Dict]:
1403
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ 30๊ฐœ ์ˆ˜์ง‘ (์‹ค์ œ API)"""
1404
- print(f"๐Ÿค— ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ {limit}๊ฐœ ์ˆ˜์ง‘ ์ค‘...")
1405
-
1406
- models_list = []
1407
-
1408
- try:
1409
- # Hugging Face API ์‚ฌ์šฉ
1410
- api = HfApi()
1411
-
1412
- # trending ์ˆœ์œ„๋กœ ๋ชจ๋ธ ๊ฐ€์ ธ์˜ค๊ธฐ
1413
- models = list(api.list_models(
1414
- sort="trending_score",
1415
- direction=-1,
1416
- limit=limit
1417
- ))
1418
-
1419
- print(f"๐Ÿ“Š API์—์„œ {len(models)}๊ฐœ ๋ชจ๋ธ ๋ฐ›์Œ")
1420
-
1421
- for idx, model in enumerate(models[:limit], 1):
1422
- try:
1423
- model_info = {
1424
- 'name': model.id,
1425
- 'downloads': getattr(model, 'downloads', 0) or 0,
1426
- 'likes': getattr(model, 'likes', 0) or 0,
1427
- 'task': getattr(model, 'pipeline_tag', 'N/A') or 'N/A',
1428
- 'url': f"https://huggingface.co/{model.id}",
1429
- 'rank': idx
1430
- }
1431
-
1432
- # LLM ๋ถ„์„ ์ถ”๊ฐ€ (๋ชจ๋ธ ์นด๋“œ ๋ถ„์„)
1433
- print(f" ๐Ÿ” {idx}. {model.id} ๋ถ„์„ ์ค‘...")
1434
- model_info['analysis'] = self.llm_analyzer.analyze_model(
1435
- model_info['name'],
1436
- model_info['task'],
1437
- model_info['downloads']
1438
- )
1439
-
1440
- models_list.append(model_info)
1441
-
1442
- # API rate limit ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์งง์€ ๋Œ€๊ธฐ
1443
- time.sleep(0.5)
1444
-
1445
- # ์ง„ํ–‰์ƒํ™ฉ ํ‘œ์‹œ
1446
- if idx % 5 == 0:
1447
- print(f" โœ“ {idx}๊ฐœ ๋ชจ๋ธ ์ฒ˜๋ฆฌ ์™„๋ฃŒ...")
1448
-
1449
- except Exception as e:
1450
- print(f" โš ๏ธ ๋ชจ๋ธ {idx} ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")
1451
- continue
1452
-
1453
- print(f"โœ… {len(models_list)}๊ฐœ ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ ์ˆ˜์ง‘ ์™„๋ฃŒ")
1454
-
1455
- # DB์— ์ €์žฅ
1456
- if models_list:
1457
- save_models_to_db(models_list)
1458
-
1459
- return models_list
1460
-
1461
- except Exception as e:
1462
- print(f"โŒ ๋ชจ๋ธ ์ˆ˜์ง‘ ์˜ค๋ฅ˜: {e}")
1463
- print("๐Ÿ’พ DB์—์„œ ์ด์ „ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ๋„...")
1464
- return load_models_from_db()
1465
-
1466
- def fetch_huggingface_spaces(self, limit: int = 30) -> List[Dict]:
1467
- """ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค 30๊ฐœ ์ˆ˜์ง‘ (์‹ค์ œ API)"""
1468
- print(f"๐Ÿš€ ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค {limit}๊ฐœ ์ˆ˜์ง‘ ์ค‘...")
1469
-
1470
- spaces_list = []
1471
-
1472
- try:
1473
- # Hugging Face API ์‚ฌ์šฉ
1474
- api = HfApi()
1475
-
1476
- # trending ์ˆœ์œ„๋กœ ์ŠคํŽ˜์ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
1477
- spaces = list(api.list_spaces(
1478
- sort="trending_score",
1479
- direction=-1,
1480
- limit=limit
1481
- ))
1482
-
1483
- print(f"๐Ÿ“Š API์—์„œ {len(spaces)}๊ฐœ ์ŠคํŽ˜์ด์Šค ๋ฐ›์Œ")
1484
-
1485
- for idx, space in enumerate(spaces[:limit], 1):
1486
- try:
1487
- space_info = {
1488
- 'space_id': space.id,
1489
- 'name': space.id.split('/')[-1] if '/' in space.id else space.id,
1490
- 'author': space.author,
1491
- 'title': getattr(space, 'title', space.id) or space.id,
1492
- 'likes': getattr(space, 'likes', 0) or 0,
1493
- 'url': f"https://huggingface.co/spaces/{space.id}",
1494
- 'sdk': getattr(space, 'sdk', 'gradio') or 'gradio',
1495
- 'rank': idx
1496
- }
1497
-
1498
- # LLM ๋ถ„์„ ์ถ”๊ฐ€ (app.py ๋ถ„์„)
1499
- print(f" ๐Ÿ” {idx}. {space.id} ๋ถ„์„ ์ค‘...")
1500
- space_analysis = self.llm_analyzer.analyze_space(
1501
- space_info['name'],
1502
- space_info['space_id'],
1503
- space_info['title']
1504
- )
1505
-
1506
- space_info['simple_explanation'] = space_analysis['simple_explanation']
1507
- space_info['tech_stack'] = space_analysis['tech_stack']
1508
- space_info['description'] = space_info['title']
1509
-
1510
- spaces_list.append(space_info)
1511
-
1512
- # API rate limit ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์งง์€ ๋Œ€๊ธฐ
1513
- time.sleep(0.5)
1514
-
1515
- # ์ง„ํ–‰์ƒํ™ฉ ํ‘œ์‹œ
1516
- if idx % 5 == 0:
1517
- print(f" โœ“ {idx}๊ฐœ ์ŠคํŽ˜์ด์Šค ์ฒ˜๋ฆฌ ์™„๋ฃŒ...")
1518
-
1519
- except Exception as e:
1520
- print(f" โš ๏ธ ์ŠคํŽ˜์ด์Šค {idx} ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")
1521
- continue
1522
-
1523
- print(f"โœ… {len(spaces_list)}๊ฐœ ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค ์ˆ˜์ง‘ ์™„๋ฃŒ")
1524
-
1525
- # DB์— ์ €์žฅ
1526
- if spaces_list:
1527
- save_spaces_to_db(spaces_list)
1528
-
1529
- return spaces_list
1530
-
1531
- except Exception as e:
1532
- print(f"โŒ ์ŠคํŽ˜์ด์Šค ์ˆ˜์ง‘ ์˜ค๋ฅ˜: {e}")
1533
- print("๐Ÿ’พ DB์—์„œ ์ด์ „ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์‹œ๋„...")
1534
- return load_spaces_from_db()
1535
-
1536
- def analyze_all_news(self) -> List[Dict]:
1537
- """๋ชจ๋“  ๋‰ด์Šค์— LLM ๋ถ„์„ ์ถ”๊ฐ€"""
1538
- print("๐Ÿ“ฐ ๋‰ด์Šค LLM ๋ถ„์„ ์‹œ์ž‘...")
1539
-
1540
- # ์‹ค์ œ ์›น์‚ฌ์ดํŠธ์—์„œ ๋‰ด์Šค ์ˆ˜์ง‘
1541
- news = self.fetch_aitimes_news()
1542
-
1543
- if not news:
1544
- print("โš ๏ธ ์ˆ˜์ง‘๋œ ๋‰ด์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
1545
- return []
1546
-
1547
- analyzed_news = []
1548
-
1549
- for idx, article in enumerate(news, 1):
1550
- print(f" ๐Ÿง  {idx}/{len(news)}: {article['title'][:50]}... ๋ถ„์„ ์ค‘")
1551
-
1552
- analysis = self.llm_analyzer.analyze_news_simple(
1553
- article['title'],
1554
- ""
1555
- )
1556
-
1557
- article['analysis'] = analysis
1558
- analyzed_news.append(article)
1559
-
1560
- print(f"โœ… {len(analyzed_news)}๊ฐœ ๋‰ด์Šค ๋ถ„์„ ์™„๋ฃŒ")
1561
-
1562
- # DB์— ์ €์žฅ
1563
- if analyzed_news:
1564
- save_news_to_db(analyzed_news)
1565
-
1566
- return analyzed_news
1567
-
1568
- def get_all_data(self, force_refresh: bool = False) -> Dict:
1569
- """๋ชจ๋“  ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„
1570
-
1571
- Args:
1572
- force_refresh: True๋ฉด ์ƒˆ๋กœ ์ˆ˜์ง‘, False๋ฉด DB์—์„œ ๋กœ๋“œ ํ›„ ์—†์œผ๋ฉด ์ˆ˜์ง‘
1573
- """
1574
- print("\n" + "="*60)
1575
- print("๐Ÿš€ AI ๋‰ด์Šค & ํ—ˆ๊น…ํŽ˜์ด์Šค LLM ๋ถ„์„ ์‹œ์ž‘")
1576
- print("="*60 + "\n")
1577
-
1578
- if force_refresh:
1579
- print("๐Ÿ”„ ๊ฐ•์ œ ์ƒˆ๋กœ๊ณ ์นจ ๋ชจ๋“œ: ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์ƒˆ๋กœ ์ˆ˜์ง‘")
1580
- analyzed_news = self.analyze_all_news()
1581
- analyzed_models = self.fetch_huggingface_models(30)
1582
- analyzed_spaces = self.fetch_huggingface_spaces(30)
1583
- else:
1584
- print("๐Ÿ’พ DB ์šฐ์„  ๋กœ๋“œ ๋ชจ๋“œ")
1585
-
1586
- # DB์—์„œ ๋จผ์ € ๋กœ๋“œ
1587
- analyzed_news = load_news_from_db()
1588
- if not analyzed_news:
1589
- print("๐Ÿ“ฐ DB์— ๋‰ด์Šค ์—†์Œ โ†’ ์ƒˆ๋กœ ์ˆ˜์ง‘")
1590
- analyzed_news = self.analyze_all_news()
1591
- else:
1592
- print(f"โœ… DB์—์„œ {len(analyzed_news)}๊ฐœ ๋‰ด์Šค ๋กœ๋“œ")
1593
-
1594
- analyzed_models = load_models_from_db()
1595
- if not analyzed_models:
1596
- print("๐Ÿค— DB์— ๋ชจ๋ธ ์—†์Œ โ†’ ์ƒˆ๋กœ ์ˆ˜์ง‘")
1597
- analyzed_models = self.fetch_huggingface_models(30)
1598
- else:
1599
- print(f"โœ… DB์—์„œ {len(analyzed_models)}๊ฐœ ๋ชจ๋ธ ๋กœ๋“œ")
1600
-
1601
- analyzed_spaces = load_spaces_from_db()
1602
- if not analyzed_spaces:
1603
- print("๐Ÿš€ DB์— ์ŠคํŽ˜์ด์Šค ์—†์Œ โ†’ ์ƒˆ๋กœ ์ˆ˜์ง‘")
1604
- analyzed_spaces = self.fetch_huggingface_spaces(30)
1605
- else:
1606
- print(f"โœ… DB์—์„œ {len(analyzed_spaces)}๊ฐœ ์ŠคํŽ˜์ด์Šค ๋กœ๋“œ")
1607
-
1608
- # ํ†ต๊ณ„
1609
- stats = {
1610
- 'total_news': len(analyzed_news),
1611
- 'hf_models': len(analyzed_models),
1612
- 'hf_spaces': len(analyzed_spaces),
1613
- 'llm_analyses': len(analyzed_news) + len(analyzed_models) + len(analyzed_spaces)
1614
- }
1615
-
1616
- print(f"\nโœ… ์ „์ฒด ๋ถ„์„ ์™„๋ฃŒ: {stats['llm_analyses']}๊ฐœ ํ•ญ๋ชฉ")
1617
- print(f" ๐Ÿ“ฐ ๋‰ด์Šค: {stats['total_news']}๊ฐœ")
1618
- print(f" ๐Ÿค— ๋ชจ๋ธ: {stats['hf_models']}๊ฐœ")
1619
- print(f" ๐Ÿš€ ์ŠคํŽ˜์ด์Šค: {stats['hf_spaces']}๊ฐœ")
1620
-
1621
- return {
1622
- 'analyzed_news': analyzed_news,
1623
- 'analyzed_models': analyzed_models,
1624
- 'analyzed_spaces': analyzed_spaces,
1625
- 'stats': stats,
1626
- 'timestamp': datetime.now().strftime('%Y๋…„ %m์›” %d์ผ %H:%M:%S')
1627
- }
1628
-
1629
-
1630
- # ============================================
1631
- # Flask ๋ผ์šฐํŠธ
1632
- # ============================================
1633
-
1634
- @app.route('/')
1635
- def index():
1636
- """๋ฉ”์ธ ํŽ˜์ด์ง€"""
1637
- try:
1638
- # refresh ํŒŒ๋ผ๋ฏธํ„ฐ ํ™•์ธ
1639
- force_refresh = request.args.get('refresh', 'false').lower() == 'true'
1640
-
1641
- analyzer = AdvancedAIAnalyzer()
1642
- data = analyzer.get_all_data(force_refresh=force_refresh)
1643
- return render_template_string(HTML_TEMPLATE, **data)
1644
- except Exception as e:
1645
- import traceback
1646
- error_detail = traceback.format_exc()
1647
- return f"""
1648
- <html>
1649
- <body style="font-family: Arial; padding: 50px; text-align: center;">
1650
- <h1 style="color: #e74c3c;">โš ๏ธ ์˜ค๋ฅ˜ ๋ฐœ์ƒ</h1>
1651
- <p>{str(e)}</p>
1652
- <pre style="text-align: left; background: #f5f5f5; padding: 20px; border-radius: 5px;">
1653
- {error_detail}
1654
- </pre>
1655
- <button onclick="location.href='/'" style="padding: 10px 20px; margin: 10px;">
1656
- ๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ
1657
- </button>
1658
- <button onclick="location.href='/?refresh=true'" style="padding: 10px 20px; margin: 10px; background: #ff6b6b; color: white; border: none; border-radius: 5px;">
1659
- ๐Ÿ”ฅ ๊ฐ•์ œ ๊ฐฑ์‹ 
1660
- </button>
1661
- </body>
1662
- </html>
1663
- """, 500
1664
-
1665
-
1666
- @app.route('/api/data')
1667
- def api_data():
1668
- """JSON API"""
1669
- try:
1670
- force_refresh = request.args.get('refresh', 'false').lower() == 'true'
1671
- analyzer = AdvancedAIAnalyzer()
1672
- data = analyzer.get_all_data(force_refresh=force_refresh)
1673
- return jsonify({
1674
- 'success': True,
1675
- 'data': data
1676
- })
1677
- except Exception as e:
1678
- return jsonify({
1679
- 'success': False,
1680
- 'error': str(e)
1681
- }), 500
1682
-
1683
-
1684
- @app.route('/api/refresh')
1685
- def api_refresh():
1686
- """๊ฐ•์ œ ์ƒˆ๋กœ๊ณ ์นจ API"""
1687
- try:
1688
- analyzer = AdvancedAIAnalyzer()
1689
- data = analyzer.get_all_data(force_refresh=True)
1690
- return jsonify({
1691
- 'success': True,
1692
- 'message': '๋ฐ์ดํ„ฐ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐฑ์‹ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
1693
- 'stats': data['stats']
1694
- })
1695
- except Exception as e:
1696
- return jsonify({
1697
- 'success': False,
1698
- 'error': str(e)
1699
- }), 500
1700
-
1701
-
1702
- @app.route('/health')
1703
- def health():
1704
- """ํ—ฌ์Šค ์ฒดํฌ"""
1705
- try:
1706
- # DB ์—ฐ๊ฒฐ ํ™•์ธ
1707
- conn = sqlite3.connect(DB_PATH)
1708
- cursor = conn.cursor()
1709
- cursor.execute("SELECT COUNT(*) FROM news")
1710
- news_count = cursor.fetchone()[0]
1711
- cursor.execute("SELECT COUNT(*) FROM models")
1712
- models_count = cursor.fetchone()[0]
1713
- cursor.execute("SELECT COUNT(*) FROM spaces")
1714
- spaces_count = cursor.fetchone()[0]
1715
- conn.close()
1716
-
1717
- return jsonify({
1718
- "status": "healthy",
1719
- "service": "AI News LLM Analyzer",
1720
- "version": "3.2.0",
1721
- "database": {
1722
- "connected": True,
1723
- "news_count": news_count,
1724
- "models_count": models_count,
1725
- "spaces_count": spaces_count
1726
- },
1727
- "fireworks_api": {
1728
- "configured": bool(os.environ.get('FIREWORKS_API_KEY'))
1729
- },
1730
- "timestamp": datetime.now().isoformat()
1731
- })
1732
- except Exception as e:
1733
- return jsonify({
1734
- "status": "unhealthy",
1735
- "error": str(e)
1736
- }), 500
1737
-
1738
-
1739
- # ============================================
1740
- # ๋ฉ”์ธ ์‹คํ–‰
1741
- # ============================================
1742
-
1743
- if __name__ == '__main__':
1744
- port = int(os.environ.get('PORT', 7860))
1745
-
1746
- print(f"""
1747
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•๏ฟฝ๏ฟฝ๏ฟฝโ•โ•โ•โ•โ•โ•โ•โ•—
1748
- โ•‘ โ•‘
1749
- โ•‘ ๐Ÿค– AI ๋‰ด์Šค & ํ—ˆ๊น…ํŽ˜์ด์Šค LLM ๋ถ„์„ ์›น์•ฑ v3.2 โ•‘
1750
- โ•‘ โ•‘
1751
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
1752
-
1753
- โœจ ์ฃผ์š” ๊ธฐ๋Šฅ:
1754
- โ€ข ๐Ÿ’พ SQLite DB ์˜๊ตฌ ์Šคํ† ๋ฆฌ์ง€
1755
- โ€ข ๐ŸŒ AI Times ์‹ค์‹œ๊ฐ„ ๋‰ด์Šค ํฌ๋กค๋ง (2๊ฐœ ์„น์…˜)
1756
- โ€ข ๐Ÿ“ฐ ๋‰ด์Šค ์ค‘๊ณ ๋“ฑํ•™์ƒ ์ˆ˜์ค€ ๋ถ„์„
1757
- โ€ข ๐Ÿค— ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ๋ชจ๋ธ TOP 30 (๋ชจ๋ธ ์นด๋“œ ๋ถ„์„)
1758
- โ€ข ๐Ÿš€ ํ—ˆ๊น…ํŽ˜์ด์Šค ํŠธ๋ Œ๋”ฉ ์ŠคํŽ˜์ด์Šค TOP 30 (app.py ๋ถ„์„)
1759
- โ€ข ๐Ÿง  Fireworks AI (Qwen3-235B) ์‹ค์‹œ๊ฐ„ LLM ๋ถ„์„
1760
- โ€ข ๐ŸŽจ ํƒญ UI (๋‰ด์Šค/๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค)
1761
-
1762
- ๐Ÿ”‘ API ์„ค์ •:
1763
- FIREWORKS_API_KEY: {"โœ… ์„ค์ •๋จ" if os.environ.get('FIREWORKS_API_KEY') else "โŒ ๋ฏธ์„ค์ • (ํ…œํ”Œ๋ฆฟ ๋ชจ๋“œ)"}
1764
-
1765
- ๐Ÿš€ ์„œ๋ฒ„ ์ •๋ณด:
1766
- ๐Ÿ“ ๋ฉ”์ธ: http://localhost:{port}
1767
- ๐Ÿ”„ ๊ฐ•์ œ๊ฐฑ์‹ : http://localhost:{port}/?refresh=true
1768
- ๐Ÿ“Š API: http://localhost:{port}/api/data
1769
- ๐Ÿ”ฅ ์ƒˆ๋กœ๊ณ ์นจ API: http://localhost:{port}/api/refresh
1770
- ๐Ÿ’š Health: http://localhost:{port}/health
1771
-
1772
- ๐Ÿ’พ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: {DB_PATH}
1773
-
1774
- ์ดˆ๊ธฐํ™” ์ค‘...
1775
- """)
1776
-
1777
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”
1778
- try:
1779
- init_database()
1780
- except Exception as e:
1781
- print(f"โŒ DB ์ดˆ๊ธฐํ™” ์˜ค๋ฅ˜: {e}")
1782
- sys.exit(1)
1783
-
1784
- print("\nโœ… ์„œ๋ฒ„ ์ค€๋น„ ์™„๋ฃŒ!")
1785
- print("๋ธŒ๋ผ์šฐ์ €์—์„œ ์œ„ URL์„ ์—ด์–ด์ฃผ์„ธ์š”!")
1786
- print("์ข…๋ฃŒ: Ctrl+C\n")
1787
-
1788
- try:
1789
- app.run(
1790
- host='0.0.0.0',
1791
- port=port,
1792
- debug=False,
1793
- threaded=True
1794
- )
1795
- except KeyboardInterrupt:
1796
- print("\n\n๐Ÿ‘‹ ์„œ๋ฒ„ ์ข…๋ฃŒ!")
1797
- sys.exit(0)
1798
- except Exception as e:
1799
- print(f"\nโŒ์„œ๋ฒ„ ์˜ค๋ฅ˜: {e}")
1800
- sys.exit(1)