seawolf2357 commited on
Commit
6bd9991
ยท
verified ยท
1 Parent(s): ec0e5a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +665 -159
app.py CHANGED
@@ -3,158 +3,339 @@ import requests
3
  import os
4
  import json
5
  from datetime import timedelta
6
- import logging
7
-
8
- # ๋กœ๊น… ์„ค์ •
9
- logging.basicConfig(level=logging.DEBUG,
10
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
11
- logger = logging.getLogger(__name__)
12
 
13
  app = Flask(__name__)
14
  app.secret_key = os.urandom(24) # ์„ธ์…˜ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ๋น„๋ฐ€ ํ‚ค
15
  app.permanent_session_lifetime = timedelta(days=7) # ์„ธ์…˜ ์œ ์ง€ ๊ธฐ๊ฐ„ ์„ค์ •
16
 
17
- # ํ—ˆ๊น…ํŽ˜์ด์Šค URL ๋ชฉ๋ก (๊ฐ„๋žตํ™”)
18
  HUGGINGFACE_URLS = [
19
  "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
20
  "https://huggingface.co/spaces/openfree/deepseek_r1_API",
21
- "https://huggingface.co/spaces/ginipick/open_Deep-Research"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  ]
23
 
24
- # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ธ์ฆ ํ™•์ธ (๋””๋ฒ„๊น… ๊ฐœ์„ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def validate_token(token):
26
- logger.debug(f"ํ† ํฐ ๊ฒ€์ฆ ์‹œ๋„ (ํ† ํฐ ์ผ๋ถ€: {token[:4]}...)")
27
  headers = {"Authorization": f"Bearer {token}"}
28
 
29
- # ์—ฌ๋Ÿฌ ์—”๋“œํฌ์ธํŠธ ์‹œ๋„
30
- api_endpoints = [
31
- "https://huggingface.co/api/whoami",
32
- "https://huggingface.co/api/whoami-v2"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  ]
34
 
35
- for endpoint in api_endpoints:
 
 
36
  try:
37
- logger.debug(f"API ์—”๋“œํฌ์ธํŠธ ์‹œ๋„: {endpoint}")
38
- response = requests.get(endpoint, headers=headers)
39
-
40
- # ์‘๋‹ต ๋กœ๊น…
41
- logger.debug(f"API ์‘๋‹ต ์ƒํƒœ ์ฝ”๋“œ: {response.status_code}")
42
- logger.debug(f"API ์‘๋‹ต ํ—ค๋”: {response.headers}")
43
-
44
  if response.ok:
45
- logger.debug("ํ† ํฐ ๊ฒ€์ฆ ์„ฑ๊ณต!")
46
- return True, response.json()
47
- else:
48
- logger.debug(f"ํ† ํฐ ๊ฒ€์ฆ ์‹คํŒจ - ์‘๋‹ต: {response.text}")
 
 
 
 
 
 
 
 
 
 
49
  except Exception as e:
50
- logger.error(f"API ํ˜ธ์ถœ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
51
 
52
- # ๋ชจ๋“  API ํ˜ธ์ถœ ์‹คํŒจ
53
- return False, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
- # ํ† ํฐ ๊ฒ€์ฆ ํ…Œ์ŠคํŠธ API
56
- @app.route('/api/test-token', methods=['POST'])
57
- def test_token():
 
 
 
 
 
58
  token = request.form.get('token', '')
59
 
60
  if not token:
61
  return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
62
 
63
- # ์ƒ์„ธ ๋””๋ฒ„๊น… ์ •๋ณด ์ˆ˜์ง‘
64
- debug_info = {}
65
 
66
- # ํ† ํฐ ํ˜•์‹ ํ™•์ธ
67
- is_valid_format = token.startswith('hf_')
68
- debug_info['token_format'] = 'hf_ ์ ‘๋‘์‚ฌ ์žˆ์Œ' if is_valid_format else '์ ‘๋‘์‚ฌ ์—†์Œ'
69
 
70
- # ์ง์ ‘ API ํ˜ธ์ถœ ํ…Œ์ŠคํŠธ
71
- test_endpoints = [
72
- "https://huggingface.co/api/whoami",
73
- "https://huggingface.co/api/whoami-v2",
74
- "https://huggingface.co/api/models/meta/llama"
75
- ]
 
 
 
 
76
 
77
- endpoint_results = {}
 
78
 
79
- for endpoint in test_endpoints:
80
- try:
81
- headers = {"Authorization": f"Bearer {token}"}
82
- response = requests.get(endpoint, headers=headers, timeout=5)
83
-
84
- endpoint_results[endpoint] = {
85
- 'status_code': response.status_code,
86
- 'success': response.ok,
87
- 'content_type': response.headers.get('Content-Type', 'unknown'),
88
- 'response_sample': str(response.text)[:100] + '...' if response.text else 'Empty response'
89
- }
90
- except Exception as e:
91
- endpoint_results[endpoint] = {'error': str(e)}
92
 
93
- debug_info['endpoint_tests'] = endpoint_results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
- # ์ตœ์ข… ํŒ๋‹จ
96
- is_valid = any(result.get('success', False) for result in endpoint_results.values())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  return jsonify({
99
- 'success': is_valid,
100
- 'debug_info': debug_info,
101
- 'message': 'ํ† ํฐ์ด ์œ ํšจํ•ฉ๋‹ˆ๋‹ค.' if is_valid else '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'
102
  })
103
 
104
- # ๊ธฐ๋ณธ ํ™ˆํŽ˜์ด์ง€ (๋””๋ฒ„๊น… ํ™”๋ฉด ํฌํ•จ)
105
- @app.route('/')
106
- def home():
107
- return render_template('debug_index.html')
108
-
109
  if __name__ == '__main__':
110
  # templates ํด๋” ์ƒ์„ฑ
111
  os.makedirs('templates', exist_ok=True)
112
 
113
- # ๋””๋ฒ„๊น…์šฉ HTML ์ƒ์„ฑ
114
- with open('templates/debug_index.html', 'w', encoding='utf-8') as f:
115
  f.write('''
116
  <!DOCTYPE html>
117
  <html lang="ko">
118
  <head>
119
  <meta charset="UTF-8">
120
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
121
- <title>ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ๋””๋ฒ„๊ฑฐ</title>
122
  <style>
123
  body {
124
  font-family: Arial, sans-serif;
125
  line-height: 1.6;
126
  margin: 0;
127
- padding: 20px;
128
- color: #333;
129
- }
130
-
131
- h1, h2 {
132
  color: #333;
133
  }
134
 
135
  .container {
136
- max-width: 800px;
137
  margin: 0 auto;
 
138
  }
139
 
140
- .card {
141
- border: 1px solid #ddd;
 
142
  border-radius: 5px;
143
- padding: 20px;
144
- margin-bottom: 20px;
145
- background-color: #f9f9f9;
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
 
148
- input[type="password"] {
149
- width: 100%;
150
- padding: 10px;
151
- margin: 10px 0;
152
  border: 1px solid #ccc;
153
  border-radius: 4px;
 
154
  }
155
 
156
  button {
157
- padding: 10px 15px;
158
  background-color: #4CAF50;
159
  color: white;
160
  border: none;
@@ -166,10 +347,75 @@ if __name__ == '__main__':
166
  background-color: #45a049;
167
  }
168
 
169
- .result {
170
- margin-top: 20px;
171
- padding: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  border-radius: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  display: none;
174
  }
175
 
@@ -183,116 +429,376 @@ if __name__ == '__main__':
183
  color: #a94442;
184
  }
185
 
186
- pre {
187
- background-color: #f5f5f5;
188
- padding: 10px;
189
- border-radius: 5px;
190
- overflow-x: auto;
191
- white-space: pre-wrap;
 
 
 
 
 
 
192
  }
193
 
194
- .loading {
 
 
 
 
195
  display: none;
196
- margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
  </style>
199
  </head>
200
  <body>
201
  <div class="container">
202
- <h1>ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ๋””๋ฒ„๊ฑฐ</h1>
203
-
204
- <div class="card">
205
- <h2>ํ† ํฐ ํ…Œ์ŠคํŠธ</h2>
206
- <p>ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ์„ ์ž…๋ ฅํ•˜์—ฌ ์œ ํšจ์„ฑ์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค.</p>
207
-
208
- <input type="password" id="tokenInput" placeholder="ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ ์ž…๋ ฅ (hf_๋กœ ์‹œ์ž‘)" />
209
- <button id="testButton">ํ† ํฐ ํ…Œ์ŠคํŠธ</button>
210
-
211
- <div id="loading" class="loading">ํ…Œ์ŠคํŠธ ์ค‘...</div>
212
-
213
- <div id="result" class="result">
214
- <h3>ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ</h3>
215
- <p id="resultMessage"></p>
216
- <div id="detailsContainer">
217
- <h4>์ƒ์„ธ ์ •๋ณด</h4>
218
- <pre id="resultDetails"></pre>
219
  </div>
220
  </div>
221
  </div>
222
 
223
- <div class="card">
224
- <h2>๋””๋ฒ„๊น… ํŒ</h2>
225
- <ul>
226
- <li>ํ† ํฐ์€ <code>hf_</code>๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</li>
227
- <li>ํ† ํฐ์€ ๊ณต๋ฐฑ ์—†์ด ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.</li>
228
- <li>ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์ง€ ์•Š์•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.</li>
229
- <li>ํ† ํฐ์— ์ ์ ˆํ•œ ๊ถŒํ•œ(์ตœ์†Œ READ)์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.</li>
230
- <li>๋„คํŠธ์›Œํฌ ์ œํ•œ์ด ์žˆ๋Š” ํ™˜๊ฒฝ์ธ์ง€ ํ™•์ธํ•˜์„ธ์š”.</li>
231
- </ul>
232
  </div>
 
 
 
 
 
 
233
  </div>
234
 
235
  <script>
236
- // ์š”์†Œ ์ฐธ์กฐ
237
- const tokenInput = document.getElementById('tokenInput');
238
- const testButton = document.getElementById('testButton');
239
- const loading = document.getElementById('loading');
240
- const result = document.getElementById('result');
241
- const resultMessage = document.getElementById('resultMessage');
242
- const resultDetails = document.getElementById('resultDetails');
243
-
244
- // ํ† ํฐ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜
245
- async function testToken() {
246
- const token = tokenInput.value.trim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
- if (!token) {
249
- alert('ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
  return;
251
  }
252
 
253
- // UI ์—…๋ฐ์ดํŠธ
254
- loading.style.display = 'block';
255
- result.style.display = 'none';
256
 
257
  try {
258
- // FormData ์ƒ์„ฑ
259
  const formData = new FormData();
260
  formData.append('token', token);
261
 
262
- // API ํ˜ธ์ถœ
263
- const response = await fetch('/api/test-token', {
264
  method: 'POST',
265
  body: formData
266
  });
267
 
268
- const data = await response.json();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
- // ๊ฒฐ๊ณผ ํ‘œ์‹œ
271
- result.className = `result ${data.success ? 'success' : 'error'}`;
272
- resultMessage.textContent = data.message;
273
- resultDetails.textContent = JSON.stringify(data.debug_info, null, 2);
274
- result.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  } catch (error) {
276
- result.className = 'result error';
277
- resultMessage.textContent = `์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`;
278
- resultDetails.textContent = error.stack || '์ƒ์„ธ ์ •๋ณด ์—†์Œ';
279
- result.style.display = 'block';
280
  } finally {
281
- loading.style.display = 'none';
282
  }
283
  }
284
 
285
- // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
286
- testButton.addEventListener('click', testToken);
287
- tokenInput.addEventListener('keypress', (event) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  if (event.key === 'Enter') {
289
- testToken();
290
  }
291
  });
 
 
 
 
 
 
 
 
 
 
 
292
  </script>
293
  </body>
294
  </html>
295
  ''')
296
 
297
  # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค์—์„œ๋Š” 7860 ํฌํŠธ ์‚ฌ์šฉ
298
- app.run(host='0.0.0.0', port=7860, debug=True)
 
3
  import os
4
  import json
5
  from datetime import timedelta
 
 
 
 
 
 
6
 
7
  app = Flask(__name__)
8
  app.secret_key = os.urandom(24) # ์„ธ์…˜ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ ๋น„๋ฐ€ ํ‚ค
9
  app.permanent_session_lifetime = timedelta(days=7) # ์„ธ์…˜ ์œ ์ง€ ๊ธฐ๊ฐ„ ์„ค์ •
10
 
11
+ # ํ—ˆ๊น…ํŽ˜์ด์Šค URL ๋ชฉ๋ก
12
  HUGGINGFACE_URLS = [
13
  "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
14
  "https://huggingface.co/spaces/openfree/deepseek_r1_API",
15
+ "https://huggingface.co/spaces/ginipick/open_Deep-Research",
16
+ "https://huggingface.co/spaces/aiqmaster/open-deep-research",
17
+ "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search",
18
+ "https://huggingface.co/spaces/ginigen/LLaDA",
19
+ "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
20
+ "https://huggingface.co/spaces/ginigen/Ovis2-8B",
21
+ "https://huggingface.co/spaces/ginigen/Graph-Mind",
22
+ "https://huggingface.co/spaces/ginigen/Workflow-Canvas",
23
+ "https://huggingface.co/spaces/ginigen/Design",
24
+ "https://huggingface.co/spaces/ginigen/Diagram",
25
+ "https://huggingface.co/spaces/ginigen/Mockup",
26
+ "https://huggingface.co/spaces/ginigen/Infographic",
27
+ "https://huggingface.co/spaces/ginigen/Flowchart",
28
+ "https://huggingface.co/spaces/aiqcamp/FLUX-Vision",
29
+ "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
30
+ "https://huggingface.co/spaces/openfree/Perceptron-Network",
31
+ "https://huggingface.co/spaces/openfree/Article-Generator",
32
  ]
33
 
34
+ # URL์—์„œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ์ •๋ณด ์ถ”์ถœ
35
+ def extract_model_info(url):
36
+ parts = url.split('/')
37
+ if len(parts) < 6:
38
+ return None
39
+
40
+ if parts[3] == 'spaces' or parts[3] == 'models':
41
+ return {
42
+ 'type': parts[3],
43
+ 'owner': parts[4],
44
+ 'repo': parts[5],
45
+ 'full_id': f"{parts[4]}/{parts[5]}"
46
+ }
47
+ elif len(parts) >= 5:
48
+ # ๋‹ค๋ฅธ ํ˜•์‹์˜ URL
49
+ return {
50
+ 'type': 'models', # ๊ธฐ๋ณธ๊ฐ’
51
+ 'owner': parts[3],
52
+ 'repo': parts[4],
53
+ 'full_id': f"{parts[3]}/{parts[4]}"
54
+ }
55
+
56
+ return None
57
+
58
+ # URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ
59
+ def extract_title(url):
60
+ parts = url.split("/")
61
+ title = parts[-1] if parts else ""
62
+ return title.replace("_", " ").replace("-", " ")
63
+
64
+ # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ธ์ฆ ํ™•์ธ - ์ˆ˜์ •๋œ ๋ฒ„์ „
65
  def validate_token(token):
 
66
  headers = {"Authorization": f"Bearer {token}"}
67
 
68
+ # ์ˆ˜์ •: ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ whoami-v2 ์—”๋“œํฌ์ธํŠธ ๋จผ์ € ์‹œ๋„
69
+ try:
70
+ response = requests.get("https://huggingface.co/api/whoami-v2", headers=headers)
71
+ if response.ok:
72
+ return True, response.json()
73
+ except Exception as e:
74
+ print(f"whoami-v2 ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {e}")
75
+
76
+ # ๊ธฐ์กด whoami ์—”๋“œํฌ์ธํŠธ๋„ ์‹œ๋„
77
+ try:
78
+ response = requests.get("https://huggingface.co/api/whoami", headers=headers)
79
+ if response.ok:
80
+ return True, response.json()
81
+ except Exception as e:
82
+ print(f"whoami ํ† ํฐ ๊ฒ€์ฆ ์˜ค๋ฅ˜: {e}")
83
+
84
+ return False, None
85
+
86
+ # ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
87
+ def get_liked_repos(token):
88
+ headers = {"Authorization": f"Bearer {token}"}
89
+ endpoints = [
90
+ "/api/me/likes",
91
+ "/api/me/liked-repos",
92
+ "/api/me/favorites"
93
  ]
94
 
95
+ liked_models = {}
96
+
97
+ for endpoint in endpoints:
98
  try:
99
+ response = requests.get(f"https://huggingface.co{endpoint}", headers=headers)
 
 
 
 
 
 
100
  if response.ok:
101
+ data = response.json()
102
+ if isinstance(data, list):
103
+ for model in data:
104
+ if isinstance(model, dict):
105
+ if 'owner' in model and 'name' in model:
106
+ liked_models[f"{model['owner']}/{model['name']}"] = True
107
+ elif 'id' in model:
108
+ liked_models[model['id']] = True
109
+ elif isinstance(data, dict):
110
+ for key in data:
111
+ liked_models[key] = True
112
+
113
+ print(f"์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์„ฑ๊ณต (์—”๋“œํฌ์ธํŠธ: {endpoint})")
114
+ return liked_models
115
  except Exception as e:
116
+ print(f"์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜ (์—”๋“œํฌ์ธํŠธ: {endpoint}): {e}")
117
 
118
+ return liked_models
119
+
120
+ # ์ข‹์•„์š” ํ† ๊ธ€ API ํ˜ธ์ถœ
121
+ def toggle_like(token, type_name, owner, repo, is_liked):
122
+ headers = {"Authorization": f"Bearer {token}"}
123
+ normalized_type = "spaces" if type_name == "spaces" else "models"
124
+ method = "DELETE" if is_liked else "POST"
125
+ url = f"https://huggingface.co/api/{normalized_type}/{owner}/{repo}/like"
126
+
127
+ try:
128
+ if method == "DELETE":
129
+ response = requests.delete(url, headers=headers)
130
+ else:
131
+ response = requests.post(url, headers=headers)
132
+
133
+ return response.ok, response.status_code
134
+ except Exception as e:
135
+ print(f"์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜: {e}")
136
+ return False, 500
137
 
138
+ # ํ™ˆํŽ˜์ด์ง€ ๋ผ์šฐํŠธ
139
+ @app.route('/')
140
+ def home():
141
+ return render_template('index.html')
142
+
143
+ # ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ API
144
+ @app.route('/api/login', methods=['POST'])
145
+ def login():
146
  token = request.form.get('token', '')
147
 
148
  if not token:
149
  return jsonify({'success': False, 'message': 'ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.'})
150
 
151
+ is_valid, user_info = validate_token(token)
 
152
 
153
+ if not is_valid or not user_info:
154
+ return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค.'})
 
155
 
156
+ # ์‚ฌ์šฉ์ž ์ด๋ฆ„ ์ฐพ๊ธฐ
157
+ username = None
158
+ if 'name' in user_info:
159
+ username = user_info['name']
160
+ elif 'user' in user_info and 'username' in user_info['user']:
161
+ username = user_info['user']['username']
162
+ elif 'username' in user_info:
163
+ username = user_info['username']
164
+ else:
165
+ username = '์ธ์ฆ๋œ ์‚ฌ์šฉ์ž'
166
 
167
+ # ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
168
+ liked_models = get_liked_repos(token)
169
 
170
+ # ์„ธ์…˜์— ์ €์žฅ
171
+ session['token'] = token
172
+ session['username'] = username
173
+ session['liked_models'] = liked_models
 
 
 
 
 
 
 
 
 
174
 
175
+ return jsonify({
176
+ 'success': True,
177
+ 'username': username,
178
+ 'liked_models': liked_models
179
+ })
180
+
181
+ # ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ API
182
+ @app.route('/api/logout', methods=['POST'])
183
+ def logout():
184
+ session.pop('token', None)
185
+ session.pop('username', None)
186
+ session.pop('liked_models', None)
187
+ return jsonify({'success': True})
188
+
189
+ # URL ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ API
190
+ @app.route('/api/urls', methods=['GET'])
191
+ def get_urls():
192
+ search_query = request.args.get('search', '').lower()
193
+ show_only_liked = request.args.get('liked', 'false').lower() == 'true'
194
+
195
+ liked_models = session.get('liked_models', {})
196
+
197
+ results = []
198
+ for url in HUGGINGFACE_URLS:
199
+ title = extract_title(url)
200
+ model_info = extract_model_info(url)
201
+
202
+ if not model_info:
203
+ continue
204
+
205
+ is_liked = model_info['full_id'] in liked_models
206
+
207
+ # ํ•„ํ„ฐ๋ง ์ ์šฉ
208
+ if show_only_liked and not is_liked:
209
+ continue
210
+
211
+ if search_query and search_query not in url.lower() and search_query not in title.lower():
212
+ continue
213
+
214
+ results.append({
215
+ 'url': url,
216
+ 'title': title,
217
+ 'model_info': model_info,
218
+ 'is_liked': is_liked
219
+ })
220
 
221
+ return jsonify(results)
222
+
223
+ # ์ข‹์•„์š” ํ† ๊ธ€ API
224
+ @app.route('/api/toggle-like', methods=['POST'])
225
+ def api_toggle_like():
226
+ if 'token' not in session:
227
+ return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
228
+
229
+ data = request.json
230
+ url = data.get('url')
231
+ current_liked = data.get('currentLiked', False)
232
+
233
+ if not url:
234
+ return jsonify({'success': False, 'message': 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.'})
235
+
236
+ model_info = extract_model_info(url)
237
+ if not model_info:
238
+ return jsonify({'success': False, 'message': '์œ ํšจํ•˜์ง€ ์•Š์€ URL์ž…๋‹ˆ๋‹ค.'})
239
+
240
+ success, status_code = toggle_like(
241
+ session['token'],
242
+ model_info['type'],
243
+ model_info['owner'],
244
+ model_info['repo'],
245
+ current_liked
246
+ )
247
+
248
+ liked_models = session.get('liked_models', {})
249
 
250
+ if success:
251
+ # ์„ธ์…˜์˜ ์ข‹์•„์š” ์ƒํƒœ ์—…๋ฐ์ดํŠธ
252
+ if current_liked:
253
+ if model_info['full_id'] in liked_models:
254
+ del liked_models[model_info['full_id']]
255
+ else:
256
+ liked_models[model_info['full_id']] = True
257
+
258
+ session['liked_models'] = liked_models
259
+
260
+ return jsonify({
261
+ 'success': True,
262
+ 'new_state': not current_liked,
263
+ 'message': f"{'์ข‹์•„์š” ์ทจ์†Œ' if current_liked else '์ข‹์•„์š”'} ์„ฑ๊ณต"
264
+ })
265
+ else:
266
+ return jsonify({
267
+ 'success': False,
268
+ 'message': f"API ์˜ค๋ฅ˜ (์ƒํƒœ ์ฝ”๋“œ: {status_code})"
269
+ })
270
+
271
+ # ์„ธ์…˜ ์ƒํƒœ ํ™•์ธ API
272
+ @app.route('/api/session-status', methods=['GET'])
273
+ def session_status():
274
  return jsonify({
275
+ 'logged_in': 'token' in session,
276
+ 'username': session.get('username'),
277
+ 'liked_count': len(session.get('liked_models', {}))
278
  })
279
 
 
 
 
 
 
280
  if __name__ == '__main__':
281
  # templates ํด๋” ์ƒ์„ฑ
282
  os.makedirs('templates', exist_ok=True)
283
 
284
+ # index.html ํŒŒ์ผ ์ƒ์„ฑ
285
+ with open('templates/index.html', 'w', encoding='utf-8') as f:
286
  f.write('''
287
  <!DOCTYPE html>
288
  <html lang="ko">
289
  <head>
290
  <meta charset="UTF-8">
291
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
292
+ <title>Hugging Face URL ์นด๋“œ ๋ฆฌ์ŠคํŠธ</title>
293
  <style>
294
  body {
295
  font-family: Arial, sans-serif;
296
  line-height: 1.6;
297
  margin: 0;
298
+ padding: 0;
 
 
 
 
299
  color: #333;
300
  }
301
 
302
  .container {
303
+ max-width: 1200px;
304
  margin: 0 auto;
305
+ padding: 1rem;
306
  }
307
 
308
+ .header {
309
+ background-color: #f0f0f0;
310
+ padding: 1rem;
311
  border-radius: 5px;
312
+ margin-bottom: 1rem;
313
+ }
314
+
315
+ .user-controls {
316
+ display: flex;
317
+ justify-content: space-between;
318
+ align-items: center;
319
+ flex-wrap: wrap;
320
+ }
321
+
322
+ .filter-controls {
323
+ background-color: #f8f9fa;
324
+ padding: 1rem;
325
+ border-radius: 5px;
326
+ margin-bottom: 1rem;
327
  }
328
 
329
+ input[type="password"],
330
+ input[type="text"] {
331
+ padding: 0.5rem;
 
332
  border: 1px solid #ccc;
333
  border-radius: 4px;
334
+ margin-right: 5px;
335
  }
336
 
337
  button {
338
+ padding: 0.5rem 1rem;
339
  background-color: #4CAF50;
340
  color: white;
341
  border: none;
 
347
  background-color: #45a049;
348
  }
349
 
350
+ button.logout {
351
+ background-color: #f44336;
352
+ }
353
+
354
+ button.logout:hover {
355
+ background-color: #d32f2f;
356
+ }
357
+
358
+ .token-help {
359
+ margin-top: 0.5rem;
360
+ font-size: 0.8rem;
361
+ color: #666;
362
+ }
363
+
364
+ .token-help a {
365
+ color: #4CAF50;
366
+ text-decoration: none;
367
+ }
368
+
369
+ .token-help a:hover {
370
+ text-decoration: underline;
371
+ }
372
+
373
+ .cards-container {
374
+ display: flex;
375
+ flex-wrap: wrap;
376
+ gap: 1rem;
377
+ }
378
+
379
+ .card {
380
+ border: 1px solid #ccc;
381
  border-radius: 5px;
382
+ padding: 1rem;
383
+ width: 300px;
384
+ box-shadow: 2px 2px 8px rgba(0,0,0,0.1);
385
+ position: relative;
386
+ background-color: #f9f9f9;
387
+ }
388
+
389
+ .card a {
390
+ text-decoration: none;
391
+ color: #333;
392
+ word-break: break-all;
393
+ }
394
+
395
+ .like-button {
396
+ position: absolute;
397
+ top: 1rem;
398
+ right: 1rem;
399
+ border: none;
400
+ background: transparent;
401
+ font-size: 1.5rem;
402
+ cursor: pointer;
403
+ transition: color 0.2s;
404
+ }
405
+
406
+ .like-button.liked {
407
+ color: red;
408
+ }
409
+
410
+ .like-button.not-liked {
411
+ color: white;
412
+ -webkit-text-stroke: 1px #333;
413
+ }
414
+
415
+ .status-message {
416
+ padding: 1rem;
417
+ border-radius: 4px;
418
+ margin-bottom: 1rem;
419
  display: none;
420
  }
421
 
 
429
  color: #a94442;
430
  }
431
 
432
+ .loading {
433
+ position: fixed;
434
+ top: 0;
435
+ left: 0;
436
+ right: 0;
437
+ bottom: 0;
438
+ background-color: rgba(255, 255, 255, 0.7);
439
+ display: none;
440
+ justify-content: center;
441
+ align-items: center;
442
+ z-index: 1000;
443
+ font-size: 1.5rem;
444
  }
445
 
446
+ .login-section {
447
+ margin-top: 1rem;
448
+ }
449
+
450
+ .logged-in-section {
451
  display: none;
452
+ margin-top: 1rem;
453
+ }
454
+
455
+ @media (max-width: 768px) {
456
+ .user-controls {
457
+ flex-direction: column;
458
+ align-items: flex-start;
459
+ }
460
+
461
+ .user-controls > div {
462
+ margin-bottom: 1rem;
463
+ }
464
+
465
+ .card {
466
+ width: 100%;
467
+ }
468
  }
469
  </style>
470
  </head>
471
  <body>
472
  <div class="container">
473
+ <div class="header">
474
+ <div class="user-controls">
475
+ <div>
476
+ <span>ํ—ˆ๊น…ํŽ˜์ด์Šค ๊ณ„์ •: </span>
477
+ <span id="currentUser">๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ</span>
478
+ </div>
479
+
480
+ <div id="loginSection" class="login-section">
481
+ <input type="password" id="tokenInput" placeholder="ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ ์ž…๋ ฅ" />
482
+ <button id="loginButton">์ธ์ฆํ•˜๊ธฐ</button>
483
+ <div class="token-help">
484
+ API ํ† ํฐ์€ <a href="https://huggingface.co/settings/tokens" target="_blank">ํ—ˆ๊น…ํŽ˜์ด์Šค ํ† ํฐ ํŽ˜์ด์ง€</a>์—์„œ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
485
+ </div>
486
+ </div>
487
+
488
+ <div id="loggedInSection" class="logged-in-section">
489
+ <button id="logoutButton" class="logout">๋กœ๊ทธ์•„์›ƒ</button>
490
  </div>
491
  </div>
492
  </div>
493
 
494
+ <div class="filter-controls">
495
+ <label>
496
+ <input type="checkbox" id="showOnlyLiked" />
497
+ ๋‚ด๊ฐ€ ์ข‹์•„์š”ํ•œ URL๋งŒ ๋ณด๊ธฐ
498
+ </label>
499
+ <label style="margin-left: 1rem;">
500
+ <input type="text" id="searchInput" placeholder="URL ๋˜๋Š” ์ œ๋ชฉ์œผ๋กœ ๊ฒ€์ƒ‰" style="width: 250px;" />
501
+ </label>
 
502
  </div>
503
+
504
+ <div id="statusMessage" class="status-message"></div>
505
+
506
+ <div id="loadingIndicator" class="loading">์ฒ˜๋ฆฌ ์ค‘...</div>
507
+
508
+ <div id="cardsContainer" class="cards-container"></div>
509
  </div>
510
 
511
  <script>
512
+ // DOM ์š”์†Œ ์ฐธ์กฐ
513
+ const elements = {
514
+ tokenInput: document.getElementById('tokenInput'),
515
+ loginButton: document.getElementById('loginButton'),
516
+ logoutButton: document.getElementById('logoutButton'),
517
+ currentUser: document.getElementById('currentUser'),
518
+ cardsContainer: document.getElementById('cardsContainer'),
519
+ loadingIndicator: document.getElementById('loadingIndicator'),
520
+ statusMessage: document.getElementById('statusMessage'),
521
+ showOnlyLiked: document.getElementById('showOnlyLiked'),
522
+ searchInput: document.getElementById('searchInput'),
523
+ loginSection: document.getElementById('loginSection'),
524
+ loggedInSection: document.getElementById('loggedInSection')
525
+ };
526
+
527
+ // ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ƒํƒœ
528
+ const state = {
529
+ username: null,
530
+ likedModels: {},
531
+ isLoading: false
532
+ };
533
+
534
+ // ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ ํ•จ์ˆ˜
535
+ function setLoading(isLoading) {
536
+ state.isLoading = isLoading;
537
+ elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
538
+ }
539
+
540
+ // ์ƒํƒœ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜
541
+ function showMessage(message, isError = false) {
542
+ elements.statusMessage.textContent = message;
543
+ elements.statusMessage.className = `status-message ${isError ? 'error' : 'success'}`;
544
+ elements.statusMessage.style.display = 'block';
545
 
546
+ // 3์ดˆ ํ›„ ๋ฉ”์‹œ์ง€ ์‚ฌ๋ผ์ง
547
+ setTimeout(() => {
548
+ elements.statusMessage.style.display = 'none';
549
+ }, 3000);
550
+ }
551
+
552
+ // API ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜
553
+ async function handleApiResponse(response) {
554
+ if (!response.ok) {
555
+ const errorText = await response.text();
556
+ throw new Error(`API ์˜ค๋ฅ˜ (${response.status}): ${errorText}`);
557
+ }
558
+ return response.json();
559
+ }
560
+
561
+ // ์„ธ์…˜ ์ƒํƒœ ํ™•์ธ
562
+ async function checkSessionStatus() {
563
+ try {
564
+ const response = await fetch('/api/session-status');
565
+ const data = await handleApiResponse(response);
566
+
567
+ if (data.logged_in) {
568
+ state.username = data.username;
569
+ elements.currentUser.textContent = data.username;
570
+ elements.loginSection.style.display = 'none';
571
+ elements.loggedInSection.style.display = 'block';
572
+
573
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
574
+ loadUrls();
575
+ }
576
+ } catch (error) {
577
+ console.error('์„ธ์…˜ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
578
+ }
579
+ }
580
+
581
+ // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
582
+ async function login(token) {
583
+ if (!token.trim()) {
584
+ showMessage('ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', true);
585
  return;
586
  }
587
 
588
+ setLoading(true);
 
 
589
 
590
  try {
 
591
  const formData = new FormData();
592
  formData.append('token', token);
593
 
594
+ const response = await fetch('/api/login', {
 
595
  method: 'POST',
596
  body: formData
597
  });
598
 
599
+ const data = await handleApiResponse(response);
600
+
601
+ if (data.success) {
602
+ state.username = data.username;
603
+ state.likedModels = data.liked_models || {};
604
+
605
+ elements.currentUser.textContent = state.username;
606
+ elements.loginSection.style.display = 'none';
607
+ elements.loggedInSection.style.display = 'block';
608
+
609
+ showMessage(`${state.username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`);
610
+
611
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
612
+ loadUrls();
613
+ } else {
614
+ showMessage(data.message || '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
615
+ }
616
+ } catch (error) {
617
+ console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error);
618
+ showMessage(`๋กœ๊ทธ์ธ ์˜ค๋ฅ˜: ${error.message}`, true);
619
+ } finally {
620
+ setLoading(false);
621
+ }
622
+ }
623
+
624
+ // ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
625
+ async function logout() {
626
+ setLoading(true);
627
+
628
+ try {
629
+ const response = await fetch('/api/logout', {
630
+ method: 'POST'
631
+ });
632
+
633
+ const data = await handleApiResponse(response);
634
+
635
+ if (data.success) {
636
+ state.username = null;
637
+ state.likedModels = {};
638
+
639
+ elements.currentUser.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
640
+ elements.tokenInput.value = '';
641
+ elements.loginSection.style.display = 'block';
642
+ elements.loggedInSection.style.display = 'none';
643
+
644
+ showMessage('๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
645
+
646
+ // ์นด๋“œ ์ดˆ๊ธฐํ™”
647
+ elements.cardsContainer.innerHTML = '';
648
+ }
649
+ } catch (error) {
650
+ console.error('๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜:', error);
651
+ showMessage(`๋กœ๊ทธ์•„์›ƒ ์˜ค๋ฅ˜: ${error.message}`, true);
652
+ } finally {
653
+ setLoading(false);
654
+ }
655
+ }
656
+
657
+ // URL ๋ชฉ๋ก ๋กœ๋“œ
658
+ async function loadUrls() {
659
+ setLoading(true);
660
+
661
+ try {
662
+ const showOnlyLiked = elements.showOnlyLiked.checked;
663
+ const searchText = elements.searchInput.value;
664
+
665
+ const response = await fetch(`/api/urls?liked=${showOnlyLiked}&search=${encodeURIComponent(searchText)}`);
666
+ const urls = await handleApiResponse(response);
667
+
668
+ renderCards(urls);
669
+ } catch (error) {
670
+ console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
671
+ showMessage(`URL ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`, true);
672
+ } finally {
673
+ setLoading(false);
674
+ }
675
+ }
676
+
677
+ // ์ข‹์•„์š” ํ† ๊ธ€
678
+ async function toggleLike(url, button, currentLiked) {
679
+ if (!state.username) {
680
+ showMessage('์ข‹์•„์š”๋ฅผ ํ•˜๋ ค๋ฉด ํ—ˆ๊น…ํŽ˜์ด์Šค API ํ† ํฐ์œผ๋กœ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', true);
681
+ return;
682
+ }
683
+
684
+ setLoading(true);
685
+
686
+ try {
687
+ const response = await fetch('/api/toggle-like', {
688
+ method: 'POST',
689
+ headers: {
690
+ 'Content-Type': 'application/json'
691
+ },
692
+ body: JSON.stringify({
693
+ url: url,
694
+ currentLiked: currentLiked
695
+ })
696
+ });
697
+
698
+ const data = await handleApiResponse(response);
699
 
700
+ if (data.success) {
701
+ if (currentLiked) {
702
+ button.classList.remove('liked');
703
+ button.classList.add('not-liked');
704
+ } else {
705
+ button.classList.add('liked');
706
+ button.classList.remove('not-liked');
707
+ }
708
+
709
+ showMessage(data.message);
710
+
711
+ // ํ•„ํ„ฐ๋ง๋œ ๊ฒฝ์šฐ ๋ชฉ๋ก ๋‹ค์‹œ ๋กœ๋“œ
712
+ if (elements.showOnlyLiked.checked) {
713
+ loadUrls();
714
+ }
715
+ } else {
716
+ showMessage(data.message, true);
717
+ }
718
  } catch (error) {
719
+ console.error('์ข‹์•„์š” ํ† ๊ธ€ ์˜ค๋ฅ˜:', error);
720
+ showMessage(`์ข‹์•„์š” ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: ${error.message}`, true);
 
 
721
  } finally {
722
+ setLoading(false);
723
  }
724
  }
725
 
726
+ // ์นด๋“œ ๋ Œ๋”๋ง
727
+ function renderCards(urls) {
728
+ elements.cardsContainer.innerHTML = '';
729
+
730
+ if (!urls || urls.length === 0) {
731
+ const noResultsMsg = document.createElement('p');
732
+ noResultsMsg.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
733
+ noResultsMsg.style.padding = '1rem';
734
+ noResultsMsg.style.fontStyle = 'italic';
735
+ elements.cardsContainer.appendChild(noResultsMsg);
736
+ return;
737
+ }
738
+
739
+ urls.forEach(item => {
740
+ const { url, title, is_liked } = item;
741
+
742
+ // ์นด๋“œ ์ƒ์„ฑ
743
+ const card = document.createElement('div');
744
+ card.className = 'card';
745
+
746
+ // ์ œ๋ชฉ
747
+ const titleEl = document.createElement('h3');
748
+ titleEl.textContent = title;
749
+ card.appendChild(titleEl);
750
+
751
+ // URL ๋งํฌ
752
+ const linkEl = document.createElement('a');
753
+ linkEl.href = url;
754
+ linkEl.textContent = url;
755
+ linkEl.target = '_blank';
756
+ card.appendChild(linkEl);
757
+
758
+ // ์ข‹์•„์š” ๋ฒ„ํŠผ (โ™ฅ ์•„์ด์ฝ˜)
759
+ const likeBtn = document.createElement('button');
760
+ likeBtn.className = `like-button ${is_liked ? 'liked' : 'not-liked'}`;
761
+ likeBtn.textContent = 'โ™ฅ';
762
+
763
+ likeBtn.addEventListener('click', function(e) {
764
+ e.preventDefault();
765
+ toggleLike(url, likeBtn, is_liked);
766
+ });
767
+ card.appendChild(likeBtn);
768
+
769
+ // ์นด๋“œ ์ถ”๊ฐ€
770
+ elements.cardsContainer.appendChild(card);
771
+ });
772
+ }
773
+
774
+ // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
775
+ elements.loginButton.addEventListener('click', () => {
776
+ login(elements.tokenInput.value);
777
+ });
778
+
779
+ elements.logoutButton.addEventListener('click', logout);
780
+
781
+ // ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ
782
+ elements.tokenInput.addEventListener('keypress', (event) => {
783
  if (event.key === 'Enter') {
784
+ login(elements.tokenInput.value);
785
  }
786
  });
787
+
788
+ // ํ•„ํ„ฐ๋ง ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
789
+ elements.showOnlyLiked.addEventListener('change', loadUrls);
790
+ elements.searchInput.addEventListener('input', () => {
791
+ // ์ž…๋ ฅ ์ง€์—ฐ ์ฒ˜๋ฆฌ (ํƒ€์ดํ•‘ํ•  ๋•Œ๋งˆ๋‹ค API ํ˜ธ์ถœ ๋ฐฉ์ง€)
792
+ clearTimeout(state.searchTimeout);
793
+ state.searchTimeout = setTimeout(loadUrls, 300);
794
+ });
795
+
796
+ // ์ดˆ๊ธฐํ™”
797
+ checkSessionStatus();
798
  </script>
799
  </body>
800
  </html>
801
  ''')
802
 
803
  # ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค์—์„œ๋Š” 7860 ํฌํŠธ ์‚ฌ์šฉ
804
+ app.run(host='0.0.0.0', port=7860)