seawolf2357 commited on
Commit
1bba99b
ยท
verified ยท
1 Parent(s): 7ff41f1

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +828 -0
templates/index.html ADDED
@@ -0,0 +1,828 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py์˜ __main__ ๋ถ€๋ถ„ ์ˆ˜์ • (index.html ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ ๋ถ€๋ถ„)
2
+
3
+ if __name__ == '__main__':
4
+ os.makedirs('templates', exist_ok=True)
5
+
6
+ with open('templates/index.html', 'w', encoding='utf-8') as f:
7
+ f.write('''
8
+ <!DOCTYPE html>
9
+ <html lang="ko">
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
+ <title>Hugging Face URL ๊ทธ๋ฆฌ๋“œ</title>
14
+ <style>
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ line-height: 1.6;
18
+ margin: 0;
19
+ padding: 0;
20
+ color: #333;
21
+ background-color: #f4f5f7;
22
+ }
23
+
24
+ .container {
25
+ max-width: 1600px;
26
+ margin: 0 auto;
27
+ padding: 1rem;
28
+ }
29
+
30
+ .header {
31
+ background-color: #fff;
32
+ padding: 1rem;
33
+ border-radius: 8px;
34
+ margin-bottom: 1rem;
35
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
36
+ }
37
+
38
+ .user-controls {
39
+ display: flex;
40
+ justify-content: space-between;
41
+ align-items: center;
42
+ flex-wrap: wrap;
43
+ }
44
+
45
+ .filter-controls {
46
+ background-color: #fff;
47
+ padding: 1rem;
48
+ border-radius: 8px;
49
+ margin-bottom: 1rem;
50
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ }
55
+
56
+ input[type="password"],
57
+ input[type="text"] {
58
+ padding: 0.5rem;
59
+ border: 1px solid #ddd;
60
+ border-radius: 4px;
61
+ margin-right: 5px;
62
+ }
63
+
64
+ button {
65
+ padding: 0.5rem 1rem;
66
+ background-color: #4CAF50;
67
+ color: white;
68
+ border: none;
69
+ border-radius: 4px;
70
+ cursor: pointer;
71
+ transition: background-color 0.2s;
72
+ }
73
+
74
+ button:hover {
75
+ background-color: #45a049;
76
+ }
77
+
78
+ button.refresh {
79
+ background-color: #2196F3;
80
+ }
81
+
82
+ button.refresh:hover {
83
+ background-color: #0b7dda;
84
+ }
85
+
86
+ button.logout {
87
+ background-color: #f44336;
88
+ }
89
+
90
+ button.logout:hover {
91
+ background-color: #d32f2f;
92
+ }
93
+
94
+ .token-help {
95
+ margin-top: 0.5rem;
96
+ font-size: 0.8rem;
97
+ color: #666;
98
+ }
99
+
100
+ .token-help a {
101
+ color: #4CAF50;
102
+ text-decoration: none;
103
+ }
104
+
105
+ .token-help a:hover {
106
+ text-decoration: underline;
107
+ }
108
+
109
+ /* ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ ์Šคํƒ€์ผ */
110
+ .grid-container {
111
+ display: grid;
112
+ grid-template-columns: repeat(4, 1fr);
113
+ gap: 1rem;
114
+ }
115
+
116
+ .grid-item {
117
+ border: 1px solid #ddd;
118
+ border-radius: 8px;
119
+ background-color: #fff;
120
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
121
+ transition: all 0.3s ease;
122
+ position: relative;
123
+ display: flex;
124
+ flex-direction: column;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .grid-item:hover {
129
+ transform: translateY(-5px);
130
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
131
+ }
132
+
133
+ .grid-item.liked {
134
+ border-color: #ff4757;
135
+ background-color: #ffebee;
136
+ }
137
+
138
+ .grid-header {
139
+ padding: 0.5rem 1rem;
140
+ border-bottom: 1px solid #eee;
141
+ position: relative;
142
+ }
143
+
144
+ .grid-title {
145
+ font-size: 1rem;
146
+ margin: 0;
147
+ padding-right: 30px;
148
+ white-space: nowrap;
149
+ overflow: hidden;
150
+ text-overflow: ellipsis;
151
+ }
152
+
153
+ .grid-content {
154
+ flex: 1;
155
+ position: relative;
156
+ height: 300px;
157
+ }
158
+
159
+ .iframe-container {
160
+ position: absolute;
161
+ top: 0;
162
+ left: 0;
163
+ width: 100%;
164
+ height: 100%;
165
+ }
166
+
167
+ .iframe-container iframe {
168
+ width: 100%;
169
+ height: 100%;
170
+ border: none;
171
+ }
172
+
173
+ .like-button {
174
+ position: absolute;
175
+ top: 0.5rem;
176
+ right: 0.5rem;
177
+ width: 24px;
178
+ height: 24px;
179
+ display: flex;
180
+ align-items: center;
181
+ justify-content: center;
182
+ border-radius: 50%;
183
+ border: none;
184
+ background: transparent;
185
+ font-size: 1.2rem;
186
+ cursor: pointer;
187
+ transition: all 0.3s ease;
188
+ color: #ddd;
189
+ padding: 0;
190
+ }
191
+
192
+ .like-button:hover {
193
+ transform: scale(1.2);
194
+ }
195
+
196
+ .like-button.liked {
197
+ color: #ff4757;
198
+ }
199
+
200
+ .like-badge {
201
+ position: absolute;
202
+ top: -5px;
203
+ left: -5px;
204
+ background-color: #ff4757;
205
+ color: white;
206
+ padding: 0.2rem 0.5rem;
207
+ border-radius: 4px;
208
+ font-size: 0.7rem;
209
+ font-weight: bold;
210
+ z-index: 10;
211
+ }
212
+
213
+ .like-status {
214
+ background-color: #fff;
215
+ padding: 1rem;
216
+ border-radius: 8px;
217
+ margin-bottom: 1rem;
218
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
219
+ display: none;
220
+ }
221
+
222
+ .like-status strong {
223
+ color: #ff4757;
224
+ }
225
+
226
+ .status-message {
227
+ position: fixed;
228
+ bottom: 20px;
229
+ right: 20px;
230
+ padding: 1rem;
231
+ border-radius: 8px;
232
+ display: none;
233
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
234
+ z-index: 1000;
235
+ max-width: 300px;
236
+ }
237
+
238
+ .success {
239
+ background-color: #4CAF50;
240
+ color: white;
241
+ }
242
+
243
+ .error {
244
+ background-color: #f44336;
245
+ color: white;
246
+ }
247
+
248
+ .loading {
249
+ position: fixed;
250
+ top: 0;
251
+ left: 0;
252
+ right: 0;
253
+ bottom: 0;
254
+ background-color: rgba(255, 255, 255, 0.8);
255
+ display: none;
256
+ justify-content: center;
257
+ align-items: center;
258
+ z-index: 1000;
259
+ }
260
+
261
+ .spinner {
262
+ width: 40px;
263
+ height: 40px;
264
+ border: 4px solid #f3f3f3;
265
+ border-top: 4px solid #3498db;
266
+ border-radius: 50%;
267
+ animation: spin 1s linear infinite;
268
+ }
269
+
270
+ @keyframes spin {
271
+ 0% { transform: rotate(0deg); }
272
+ 100% { transform: rotate(360deg); }
273
+ }
274
+
275
+ .filter-toggle {
276
+ display: flex;
277
+ }
278
+
279
+ .filter-toggle button {
280
+ margin-right: 0.5rem;
281
+ background-color: #f0f0f0;
282
+ color: #333;
283
+ }
284
+
285
+ .filter-toggle button.active {
286
+ background-color: #4CAF50;
287
+ color: white;
288
+ }
289
+
290
+ .login-section {
291
+ margin-top: 1rem;
292
+ }
293
+
294
+ .logged-in-section {
295
+ display: none;
296
+ margin-top: 1rem;
297
+ }
298
+
299
+ .note {
300
+ padding: 0.5rem;
301
+ background-color: #fffde7;
302
+ border-left: 3px solid #ffd600;
303
+ margin-bottom: 1rem;
304
+ font-size: 0.9rem;
305
+ }
306
+
307
+ .view-toggle {
308
+ margin-top: 1rem;
309
+ display: flex;
310
+ justify-content: space-between;
311
+ align-items: center;
312
+ }
313
+
314
+ .view-toggle button {
315
+ margin-left: 0.5rem;
316
+ }
317
+
318
+ /* ๋ฐ˜์‘ํ˜• ์„ค์ • */
319
+ @media (max-width: 1400px) {
320
+ .grid-container {
321
+ grid-template-columns: repeat(3, 1fr);
322
+ }
323
+ }
324
+
325
+ @media (max-width: 1024px) {
326
+ .grid-container {
327
+ grid-template-columns: repeat(2, 1fr);
328
+ }
329
+ }
330
+
331
+ @media (max-width: 768px) {
332
+ .user-controls {
333
+ flex-direction: column;
334
+ align-items: flex-start;
335
+ }
336
+
337
+ .user-controls > div {
338
+ margin-bottom: 1rem;
339
+ }
340
+
341
+ .filter-controls {
342
+ flex-direction: column;
343
+ }
344
+
345
+ .filter-controls > div {
346
+ margin-bottom: 0.5rem;
347
+ }
348
+
349
+ .grid-container {
350
+ grid-template-columns: 1fr;
351
+ }
352
+ }
353
+ </style>
354
+ </head>
355
+ <body>
356
+ <div class="container">
357
+ <div class="header">
358
+ <div class="user-controls">
359
+ <div>
360
+ <span>ํ—ˆ๊น…ํŽ˜์ด์Šค ๊ณ„์ •: </span>
361
+ <span id="currentUser">๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ</span>
362
+ </div>
363
+
364
+ <div id="loginSection" class="login-section">
365
+ <input type="password" id="tokenInput" placeholder="ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ ์ž…๋ ฅ" />
366
+ <button id="loginButton">์ธ์ฆํ•˜๊ธฐ</button>
367
+ <div class="token-help">
368
+ API ํ† ํฐ์€ <a href="https://huggingface.co/settings/tokens" target="_blank">ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ํŽ˜์ด์ง€</a>์—์„œ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
369
+ </div>
370
+ </div>
371
+
372
+ <div id="loggedInSection" class="logged-in-section">
373
+ <button id="refreshButton" class="refresh">์ƒˆ๋กœ๊ณ ์นจ</button>
374
+ <button id="logoutButton" class="logout">๋กœ๊ทธ์•„์›ƒ</button>
375
+ </div>
376
+ </div>
377
+ </div>
378
+
379
+ <div class="note">
380
+ <p><strong>์ฐธ๊ณ :</strong> ์ด ํŽ˜์ด์ง€๋Š” ์›น ์Šคํฌ๋ž˜ํ•‘ ๋ฐฉ์‹์œผ๋กœ ์ข‹์•„์š” ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ข‹์•„์š” ์ƒํƒœ๊ฐ€ ์ •ํ™•ํ•˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ง€์—ฐ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. '์ƒˆ๋กœ๊ณ ์นจ' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ ์ตœ์‹  ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.</p>
381
+ </div>
382
+
383
+ <div id="likeStatus" class="like-status">
384
+ <div id="likeStatsText">์ด <span id="totalUrlCount">0</span>๊ฐœ ์ค‘ <strong><span id="likedUrlCount">0</span>๊ฐœ</strong>์˜ URL์„ ์ข‹์•„์š” ํ–ˆ์Šต๋‹ˆ๋‹ค.</div>
385
+ </div>
386
+
387
+ <div class="filter-controls">
388
+ <div>
389
+ <input type="text" id="searchInput" placeholder="URL ๋˜๋Š” ์ œ๋ชฉ์œผ๋กœ ๊ฒ€์ƒ‰" style="width: 300px;" />
390
+ </div>
391
+ <div class="filter-toggle">
392
+ <button id="allUrlsBtn" class="active">์ „์ฒด ๋ณด๊ธฐ</button>
393
+ <button id="likedUrlsBtn">์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ</button>
394
+ </div>
395
+ </div>
396
+
397
+ <div class="view-toggle">
398
+ <div>
399
+ <input type="checkbox" id="embedToggle" checked />
400
+ <label for="embedToggle">URL ์ž„๋ฒ ๋”ฉ ๋ณด๊ธฐ</label>
401
+ </div>
402
+ </div>
403
+
404
+ <div id="statusMessage" class="status-message"></div>
405
+
406
+ <div id="loadingIndicator" class="loading">
407
+ <div class="spinner"></div>
408
+ </div>
409
+
410
+ <div id="gridContainer" class="grid-container"></div>
411
+ </div>
412
+
413
+ <script>
414
+ // DOM ์š”์†Œ ์ฐธ์กฐ
415
+ const elements = {
416
+ tokenInput: document.getElementById('tokenInput'),
417
+ loginButton: document.getElementById('loginButton'),
418
+ logoutButton: document.getElementById('logoutButton'),
419
+ refreshButton: document.getElementById('refreshButton'),
420
+ currentUser: document.getElementById('currentUser'),
421
+ gridContainer: document.getElementById('gridContainer'),
422
+ loadingIndicator: document.getElementById('loadingIndicator'),
423
+ statusMessage: document.getElementById('statusMessage'),
424
+ searchInput: document.getElementById('searchInput'),
425
+ loginSection: document.getElementById('loginSection'),
426
+ loggedInSection: document.getElementById('loggedInSection'),
427
+ likeStatus: document.getElementById('likeStatus'),
428
+ totalUrlCount: document.getElementById('totalUrlCount'),
429
+ likedUrlCount: document.getElementById('likedUrlCount'),
430
+ allUrlsBtn: document.getElementById('allUrlsBtn'),
431
+ likedUrlsBtn: document.getElementById('likedUrlsBtn'),
432
+ embedToggle: document.getElementById('embedToggle')
433
+ };
434
+
435
+ // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ
436
+ const state = {
437
+ username: null,
438
+ allURLs: [],
439
+ isLoading: false,
440
+ viewMode: 'all', // 'all' ๋˜๋Š” 'liked'
441
+ embedView: true // iframe ์ž„๋ฒ ๋”ฉ ์—ฌ๋ถ€
442
+ };
443
+
444
+ // ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ ํ•จ์ˆ˜
445
+ function setLoading(isLoading) {
446
+ state.isLoading = isLoading;
447
+ elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
448
+ }
449
+
450
+ // ์ƒํƒœ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜
451
+ function showMessage(message, isError = false) {
452
+ elements.statusMessage.textContent = message;
453
+ elements.statusMessage.className = `status-message ${isError ? 'error' : 'success'}`;
454
+ elements.statusMessage.style.display = 'block';
455
+
456
+ // 3์ดˆ ํ›„ ๋ฉ”์‹œ์ง€ ์‚ฌ๋ผ์ง
457
+ setTimeout(() => {
458
+ elements.statusMessage.style.display = 'none';
459
+ }, 3000);
460
+ }
461
+
462
+ // API ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
463
+ async function handleApiResponse(response) {
464
+ if (!response.ok) {
465
+ const errorText = await response.text();
466
+ throw new Error(`API ์˜ค๋ฅ˜ (${response.status}): ${errorText}`);
467
+ }
468
+ return response.json();
469
+ }
470
+
471
+ // ์ข‹์•„์š” ํ†ต๊ณ„ ์—…๋ฐ์ดํŠธ
472
+ function updateLikeStats() {
473
+ const totalCount = state.allURLs.length;
474
+ const likedCount = state.allURLs.filter(item => item.is_liked).length;
475
+
476
+ elements.totalUrlCount.textContent = totalCount;
477
+ elements.likedUrlCount.textContent = likedCount;
478
+ }
479
+
480
+ // ์„ธ์…˜ ์ƒํƒœ ํ™•์ธ
481
+ async function checkSessionStatus() {
482
+ try {
483
+ const response = await fetch('/api/session-status');
484
+ const data = await handleApiResponse(response);
485
+
486
+ if (data.logged_in) {
487
+ state.username = data.username;
488
+ elements.currentUser.textContent = data.username;
489
+ elements.loginSection.style.display = 'none';
490
+ elements.loggedInSection.style.display = 'block';
491
+ elements.likeStatus.style.display = 'block';
492
+
493
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
494
+ loadUrls();
495
+ }
496
+ } catch (error) {
497
+ console.error('์„ธ์…˜ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
498
+ }
499
+ }
500
+
501
+ // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
502
+ async function login(token) {
503
+ if (!token.trim()) {
504
+ showMessage('ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', true);
505
+ return;
506
+ }
507
+
508
+ setLoading(true);
509
+
510
+ try {
511
+ const formData = new FormData();
512
+ formData.append('token', token);
513
+
514
+ const response = await fetch('/api/login', {
515
+ method: 'POST',
516
+ body: formData
517
+ });
518
+
519
+ const data = await handleApiResponse(response);
520
+
521
+ if (data.success) {
522
+ state.username = data.username;
523
+
524
+ elements.currentUser.textContent = state.username;
525
+ elements.loginSection.style.display = 'none';
526
+ elements.loggedInSection.style.display = 'block';
527
+ elements.likeStatus.style.display = 'block';
528
+
529
+ showMessage(`${state.username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`);
530
+
531
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
532
+ loadUrls();
533
+ } else {
534
+ showMessage(data.message || '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
535
+ }
536
+ } catch (error) {
537
+ console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error);
538
+ showMessage(`๋กœ๊ทธ์ธ ์˜ค๋ฅ˜: ${error.message}`, true);
539
+ } finally {
540
+ setLoading(false);
541
+ }
542
+ }
543
+
544
+ // ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
545
+ async function logout() {
546
+ setLoading(true);
547
+
548
+ try {
549
+ const response = await fetch('/api/logout', {
550
+ method: 'POST'
551
+ });
552
+
553
+ const data = await handleApiResponse(response);
554
+
555
+ if (data.success) {
556
+ state.username = null;
557
+ state.allURLs = [];
558
+
559
+ elements.currentUser.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
560
+ elements.tokenInput.value = '';
561
+ elements.loginSection.style.display = 'block';
562
+ elements.loggedInSection.style.display = 'none';
563
+ elements.likeStatus.style.display = 'none';
564
+
565
+ showMessage('๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
566
+
567
+ // ๊ทธ๋ฆฌ๋“œ ์ดˆ๊ธฐํ™”
568
+ elements.gridContainer.innerHTML = '';
569
+ }
570
+ } catch (error) {
571
+ console.error('๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜:', error);
572
+ showMessage(`๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜: ${error.message}`, true);
573
+ } finally {
574
+ setLoading(false);
575
+ }
576
+ }
577
+
578
+ // ์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ
579
+ async function refreshLikes() {
580
+ setLoading(true);
581
+
582
+ try {
583
+ const response = await fetch('/api/refresh-likes', {
584
+ method: 'POST'
585
+ });
586
+
587
+ const data = await handleApiResponse(response);
588
+
589
+ if (data.success) {
590
+ // URL ๋ชฉ๋ก ๋‹ค์‹œ ๋กœ๋“œ
591
+ loadUrls();
592
+ showMessage('์ข‹์•„์š” ์ƒํƒœ๊ฐ€ ์ƒˆ๋กœ๊ณ ์นจ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
593
+ } else {
594
+ showMessage(data.message || '์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
595
+ }
596
+ } catch (error) {
597
+ console.error('์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์˜ค๋ฅ˜:', error);
598
+ showMessage(`์ข‹์•„์š” ์ƒํƒœ ์ƒˆ๋กœ๊ณ ์นจ ์˜ค๋ฅ˜: ${error.message}`, true);
599
+ } finally {
600
+ setLoading(false);
601
+ }
602
+ }
603
+
604
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
605
+ async function loadUrls() {
606
+ setLoading(true);
607
+
608
+ try {
609
+ const response = await fetch('/api/urls');
610
+ const data = await handleApiResponse(response);
611
+
612
+ state.allURLs = data;
613
+ updateLikeStats();
614
+ renderGrid();
615
+ } catch (error) {
616
+ console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
617
+ showMessage(`URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, true);
618
+ } finally {
619
+ setLoading(false);
620
+ }
621
+ }
622
+
623
+ // ์ข‹์•„์š” ํ† ๊ธ€
624
+ async function toggleLike(url) {
625
+ try {
626
+ const response = await fetch('/api/toggle-like', {
627
+ method: 'POST',
628
+ headers: {
629
+ 'Content-Type': 'application/json'
630
+ },
631
+ body: JSON.stringify({ url })
632
+ });
633
+
634
+ const data = await handleApiResponse(response);
635
+
636
+ if (data.success) {
637
+ // URL ๊ฐ์ฒด ์ฐพ๊ธฐ
638
+ const urlObj = state.allURLs.find(item => item.url === url);
639
+ if (urlObj) {
640
+ urlObj.is_liked = data.is_liked;
641
+ updateLikeStats();
642
+ renderGrid();
643
+ }
644
+
645
+ showMessage(data.message);
646
+ } else {
647
+ showMessage(data.message || '์ข‹์•„์š” ์ƒํƒœ ๋ณ€๊ฒฝ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
648
+ }
649
+ } catch (error) {
650
+ console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error);
651
+ showMessage(`์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜: ${error.message}`, true);
652
+ }
653
+ }
654
+
655
+ // ๊ทธ๋ฆฌ๋“œ ๋ Œ๋”๋ง
656
+ function renderGrid() {
657
+ elements.gridContainer.innerHTML = '';
658
+
659
+ let urlsToShow = state.allURLs;
660
+
661
+ // ๊ฒ€์ƒ‰์–ด๋กœ ํ•„ํ„ฐ๋ง
662
+ const searchTerm = elements.searchInput.value.trim().toLowerCase();
663
+ if (searchTerm) {
664
+ urlsToShow = urlsToShow.filter(item =>
665
+ item.url.toLowerCase().includes(searchTerm) ||
666
+ item.title.toLowerCase().includes(searchTerm)
667
+ );
668
+ }
669
+
670
+ // ๋ณด๊ธฐ ๋ชจ๋“œ๋กœ ํ•„ํ„ฐ๋ง (์ „์ฒด ๋˜๋Š” ์ข‹์•„์š”๋งŒ)
671
+ if (state.viewMode === 'liked') {
672
+ urlsToShow = urlsToShow.filter(item => item.is_liked);
673
+ }
674
+
675
+ if (urlsToShow.length === 0) {
676
+ const emptyMessage = document.createElement('div');
677
+ emptyMessage.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
678
+ emptyMessage.style.padding = '1rem';
679
+ emptyMessage.style.width = '100%';
680
+ emptyMessage.style.textAlign = 'center';
681
+ elements.gridContainer.appendChild(emptyMessage);
682
+ return;
683
+ }
684
+
685
+ // ๊ทธ๋ฆฌ๋“œ ์•„์ดํ…œ ์ƒ์„ฑ
686
+ urlsToShow.forEach(item => {
687
+ const gridItem = document.createElement('div');
688
+ gridItem.className = `grid-item ${item.is_liked ? 'liked' : ''}`;
689
+
690
+ if (item.is_liked) {
691
+ const badge = document.createElement('div');
692
+ badge.className = 'like-badge';
693
+ badge.textContent = '์ข‹์•„์š”';
694
+ gridItem.appendChild(badge);
695
+ }
696
+
697
+ // ํ—ค๋” ๋ถ€๋ถ„
698
+ const header = document.createElement('div');
699
+ header.className = 'grid-header';
700
+
701
+ const title = document.createElement('h3');
702
+ title.className = 'grid-title';
703
+ title.textContent = item.title;
704
+
705
+ const likeButton = document.createElement('button');
706
+ likeButton.className = `like-button ${item.is_liked ? 'liked' : ''}`;
707
+ likeButton.innerHTML = 'โค';
708
+ likeButton.dataset.url = item.url;
709
+ likeButton.addEventListener('click', (e) => {
710
+ e.preventDefault();
711
+ toggleLike(item.url);
712
+ });
713
+
714
+ header.appendChild(title);
715
+ header.appendChild(likeButton);
716
+
717
+ // ์ปจํ…์ธ  ๋ถ€๋ถ„ (iframe ์ž„๋ฒ ๋”ฉ)
718
+ const content = document.createElement('div');
719
+ content.className = 'grid-content';
720
+
721
+ if (state.embedView) {
722
+ const iframeContainer = document.createElement('div');
723
+ iframeContainer.className = 'iframe-container';
724
+
725
+ const iframe = document.createElement('iframe');
726
+ iframe.src = item.url;
727
+ iframe.title = item.title;
728
+ iframe.sandbox = 'allow-scripts allow-same-origin allow-forms';
729
+ iframe.loading = 'lazy';
730
+
731
+ iframeContainer.appendChild(iframe);
732
+ content.appendChild(iframeContainer);
733
+ } else {
734
+ // ์ž„๋ฒ ๋”ฉ ์—†๋Š” ๊ฒฝ์šฐ ๋งํฌ๋งŒ ํ‘œ์‹œ
735
+ const linkContainer = document.createElement('div');
736
+ linkContainer.style.padding = '1rem';
737
+
738
+ const link = document.createElement('a');
739
+ link.href = item.url;
740
+ link.textContent = item.url;
741
+ link.target = '_blank';
742
+
743
+ const owner = document.createElement('div');
744
+ owner.textContent = `์†Œ์œ ์ž: ${item.model_info.owner}`;
745
+ owner.style.marginTop = '0.5rem';
746
+
747
+ const repo = document.createElement('div');
748
+ repo.textContent = `์ €์žฅ์†Œ: ${item.model_info.repo}`;
749
+
750
+ const type = document.createElement('div');
751
+ type.textContent = `์œ ํ˜•: ${item.model_info.type}`;
752
+
753
+ linkContainer.appendChild(link);
754
+ linkContainer.appendChild(owner);
755
+ linkContainer.appendChild(repo);
756
+ linkContainer.appendChild(type);
757
+
758
+ content.appendChild(linkContainer);
759
+ }
760
+
761
+ // ๊ทธ๋ฆฌ๋“œ ์•„์ดํ…œ์— ์ถ”๊ฐ€
762
+ gridItem.appendChild(header);
763
+ gridItem.appendChild(content);
764
+
765
+ elements.gridContainer.appendChild(gridItem);
766
+ });
767
+ }
768
+
769
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ๋“ฑ๋ก
770
+ function registerEventListeners() {
771
+ // ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ
772
+ elements.loginButton.addEventListener('click', () => {
773
+ login(elements.tokenInput.value);
774
+ });
775
+
776
+ // ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ
777
+ elements.tokenInput.addEventListener('keydown', (e) => {
778
+ if (e.key === 'Enter') {
779
+ login(elements.tokenInput.value);
780
+ }
781
+ });
782
+
783
+ // ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ
784
+ elements.logoutButton.addEventListener('click', logout);
785
+
786
+ // ์ƒˆ๋กœ๊ณ ์นจ ๋ฒ„ํŠผ
787
+ elements.refreshButton.addEventListener('click', refreshLikes);
788
+
789
+ // ๊ฒ€์ƒ‰ ์ž…๋ ฅ ํ•„๋“œ
790
+ elements.searchInput.addEventListener('input', renderGrid);
791
+
792
+ // ํ•„ํ„ฐ ๋ฒ„ํŠผ - ์ „์ฒด ๋ณด๊ธฐ
793
+ elements.allUrlsBtn.addEventListener('click', () => {
794
+ elements.allUrlsBtn.classList.add('active');
795
+ elements.likedUrlsBtn.classList.remove('active');
796
+ state.viewMode = 'all';
797
+ renderGrid();
798
+ });
799
+
800
+ // ํ•„ํ„ฐ ๋ฒ„ํŠผ - ์ข‹์•„์š”๋งŒ ๋ณด๊ธฐ
801
+ elements.likedUrlsBtn.addEventListener('click', () => {
802
+ elements.likedUrlsBtn.classList.add('active');
803
+ elements.allUrlsBtn.classList.remove('active');
804
+ state.viewMode = 'liked';
805
+ renderGrid();
806
+ });
807
+
808
+ // ์ž„๋ฒ ๋”ฉ ํ† ๊ธ€
809
+ elements.embedToggle.addEventListener('change', () => {
810
+ state.embedView = elements.embedToggle.checked;
811
+ renderGrid();
812
+ });
813
+ }
814
+
815
+ // ์ดˆ๊ธฐํ™” ํ•จ์ˆ˜
816
+ function init() {
817
+ registerEventListeners();
818
+ checkSessionStatus();
819
+ }
820
+
821
+ // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”
822
+ init();
823
+ </script>
824
+ </body>
825
+ </html>
826
+ ''')
827
+
828
+ app.run(debug=True, host='0.0.0.0', port=5000)