cutechicken commited on
Commit
91332db
ยท
verified ยท
1 Parent(s): 3c3a731

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +209 -72
index.html CHANGED
@@ -47,8 +47,8 @@
47
  color: white;
48
  -webkit-text-stroke: 1px #333;
49
  }
50
- /* ๋กœ๊ทธ์ธ ์ƒํƒœ ํ‘œ์‹œ ๋ฐ ์ปจํŠธ๋กค */
51
- .user-controls {
52
  padding: 1rem;
53
  margin-bottom: 1rem;
54
  background-color: #f0f0f0;
@@ -57,6 +57,24 @@
57
  justify-content: space-between;
58
  align-items: center;
59
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  /* ๋กœ๋”ฉ ํ‘œ์‹œ */
61
  .loading {
62
  position: fixed;
@@ -70,26 +88,42 @@
70
  align-items: center;
71
  z-index: 1000;
72
  font-size: 1.5rem;
73
- display: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
  </style>
76
  </head>
77
  <body>
78
- <!-- ๋กœ๊ทธ์ธ ์ƒํƒœ ๋ฐ ์‚ฌ์šฉ์ž ์ปจํŠธ๋กค -->
79
- <div class="user-controls" id="userControls">
80
  <div>
81
- <span>๋กœ๊ทธ์ธ: </span>
82
- <span id="loginStatus">๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ</span>
83
  </div>
84
  <div>
85
- <input type="text" id="usernameInput" placeholder="์‚ฌ์šฉ์ž ์ด๋ฆ„" />
86
- <button id="loginButton">๋กœ๊ทธ์ธ</button>
87
- <button id="logoutButton" style="display: none;">๋กœ๊ทธ์•„์›ƒ</button>
88
  </div>
89
  </div>
90
 
 
 
 
91
  <!-- ๋กœ๋”ฉ ํ‘œ์‹œ -->
92
- <div class="loading" id="loadingIndicator">
93
  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
94
  </div>
95
 
@@ -97,21 +131,26 @@
97
  <div class="container" id="cardsContainer"></div>
98
 
99
  <script>
 
 
 
100
  // ์ƒํƒœ ๊ด€๋ฆฌ
101
  const state = {
102
- currentUser: null,
103
- likedUrls: {},
 
104
  isLoading: false
105
  };
106
 
107
  // DOM ์š”์†Œ ์ฐธ์กฐ
108
  const elements = {
109
- loginStatus: document.getElementById('loginStatus'),
110
- usernameInput: document.getElementById('usernameInput'),
111
- loginButton: document.getElementById('loginButton'),
112
  logoutButton: document.getElementById('logoutButton'),
 
113
  cardsContainer: document.getElementById('cardsContainer'),
114
- loadingIndicator: document.getElementById('loadingIndicator')
 
115
  };
116
 
117
  // Hugging Face์˜ spaces/models URL ๋ชฉ๋ก
@@ -125,10 +164,18 @@
125
  "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
126
  "https://huggingface.co/spaces/ginigen/Ovis2-8B",
127
  "https://huggingface.co/spaces/ginigen/Graph-Mind",
128
- // ์ „์ฒด URL ๋ชฉ๋ก ์ค‘ ์ผ๋ถ€๋งŒ ํ‘œ์‹œ (์‹ค์ œ๋กœ๋Š” ๋ชจ๋“  URL์„ ํฌํ•จํ•ด์•ผ ํ•จ)
129
- // ... ๋‚˜๋จธ์ง€ URL
130
  ];
131
 
 
 
 
 
 
 
 
 
 
132
  // URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ (์–ธ๋”๋ฐ”, ํ•˜์ดํ”ˆ์„ ๊ณต๋ฐฑ์œผ๋กœ ๋ณ€ํ™˜)
133
  function extractTitle(url) {
134
  const parts = url.split("/");
@@ -142,54 +189,124 @@
142
  elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
143
  }
144
 
145
- // ์‚ฌ์šฉ์ž ์ข‹์•„์š” ๋ฐ์ดํ„ฐ ์ €์žฅ (์„œ๋ฒ„ API ํ˜ธ์ถœ๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ)
146
- function saveLikes(username, likes) {
147
- return new Promise((resolve) => {
148
- // ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ์„œ๋ฒ„ API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ DB์— ์ €์žฅ
149
- // ์˜ˆ์‹œ๋กœ localStorage๋ฅผ ์‚ฌ์šฉ (์‚ฌ์šฉ์ž๋ณ„ ์ข‹์•„์š” ๋ฐ์ดํ„ฐ ์ €์žฅ)
150
- localStorage.setItem(`likes_${username}`, JSON.stringify(likes));
151
- setTimeout(resolve, 300); // ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
152
- });
 
 
153
  }
154
 
155
- // ์‚ฌ์šฉ์ž ์ข‹์•„์š” ๋ฐ์ดํ„ฐ ๋กœ๋“œ (์„œ๋ฒ„ API ํ˜ธ์ถœ๋กœ ๋Œ€์ฒด ๊ฐ€๋Šฅ)
156
- function loadLikes(username) {
157
- return new Promise((resolve) => {
158
- // ์‹ค์ œ ๊ตฌํ˜„์—์„œ๋Š” ์„œ๋ฒ„ API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ DB์—์„œ ๋ถˆ๋Ÿฌ์˜ด
159
- // ์˜ˆ์‹œ๋กœ localStorage๋ฅผ ์‚ฌ์šฉ
160
- const likes = JSON.parse(localStorage.getItem(`likes_${username}`)) || {};
161
- setTimeout(() => resolve(likes), 300); // ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
 
 
 
 
 
 
 
 
 
162
  });
163
  }
164
 
165
- // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ
166
- async function login(username) {
167
- if (!username.trim()) {
168
- alert('์‚ฌ์šฉ์ž ์ด๋ฆ„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  return;
170
  }
171
 
172
  setLoading(true);
173
 
174
  try {
175
- // ์‚ฌ์šฉ์ž ์ข‹์•„์š” ๋ฐ์ดํ„ฐ ๋กœ๋“œ
176
- const likes = await loadLikes(username);
177
 
178
- // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
179
- state.currentUser = username;
180
- state.likedUrls = likes;
 
 
 
181
 
182
  // UI ์—…๋ฐ์ดํŠธ
183
- elements.loginStatus.textContent = `${username}๋‹˜์œผ๋กœ ๋กœ๊ทธ์ธ๋จ`;
184
- elements.usernameInput.style.display = 'none';
185
- elements.loginButton.style.display = 'none';
186
  elements.logoutButton.style.display = 'inline-block';
187
 
 
 
188
  // ์นด๋“œ ๋‹ค์‹œ ๋ Œ๋”๋ง
189
  renderCards();
190
  } catch (error) {
191
- alert('๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
192
- console.error('๋กœ๊ทธ์ธ ์˜ค๋ฅ˜:', error);
 
193
  } finally {
194
  setLoading(false);
195
  }
@@ -198,45 +315,54 @@
198
  // ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
199
  function logout() {
200
  // ์ƒํƒœ ์ดˆ๊ธฐํ™”
201
- state.currentUser = null;
202
- state.likedUrls = {};
 
203
 
204
  // UI ์—…๋ฐ์ดํŠธ
205
- elements.loginStatus.textContent = '๋กœ๊ทธ์ธ๋˜์ง€ ์•Š์Œ';
206
- elements.usernameInput.style.display = 'inline-block';
207
- elements.usernameInput.value = '';
208
- elements.loginButton.style.display = 'inline-block';
209
  elements.logoutButton.style.display = 'none';
210
 
 
 
211
  // ์นด๋“œ ๋‹ค์‹œ ๋ Œ๋”๋ง
212
  renderCards();
213
  }
214
 
215
  // ์ข‹์•„์š” ํ† ๊ธ€ ์ฒ˜๋ฆฌ
216
  async function toggleLike(url, button) {
217
- if (!state.currentUser) {
218
- alert('์ข‹์•„์š”๋ฅผ ํ•˜๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.');
219
  return;
220
  }
221
 
 
 
 
 
222
  setLoading(true);
223
 
224
  try {
225
- // ์ข‹์•„์š” ์ƒํƒœ ํ† ๊ธ€
226
- if (state.likedUrls[url]) {
227
- delete state.likedUrls[url];
 
 
 
228
  button.classList.remove("liked");
229
  button.classList.add("not-liked");
 
230
  } else {
231
- state.likedUrls[url] = true;
232
  button.classList.add("liked");
233
  button.classList.remove("not-liked");
 
234
  }
235
-
236
- // ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ €์žฅ
237
- await saveLikes(state.currentUser, state.likedUrls);
238
  } catch (error) {
239
- alert('์ข‹์•„์š” ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
240
  console.error('์ข‹์•„์š” ์˜ค๋ฅ˜:', error);
241
  } finally {
242
  setLoading(false);
@@ -269,11 +395,22 @@
269
  const likeBtn = document.createElement("button");
270
  likeBtn.className = "like-button";
271
  likeBtn.textContent = "โ™ฅ";
272
- if (state.likedUrls[url]) {
273
- likeBtn.classList.add("liked");
274
- } else {
 
 
 
 
 
 
 
 
 
 
275
  likeBtn.classList.add("not-liked");
276
  }
 
277
  likeBtn.addEventListener("click", function(e) {
278
  e.preventDefault(); // ๋งํฌ ํด๋ฆญ ๋ฐฉ์ง€
279
  toggleLike(url, likeBtn);
@@ -285,16 +422,16 @@
285
  }
286
 
287
  // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
288
- elements.loginButton.addEventListener('click', () => {
289
- login(elements.usernameInput.value);
290
  });
291
 
292
  elements.logoutButton.addEventListener('click', logout);
293
 
294
- // ์—”ํ„ฐ ํ‚ค๋กœ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ
295
- elements.usernameInput.addEventListener('keypress', (event) => {
296
  if (event.key === 'Enter') {
297
- login(elements.usernameInput.value);
298
  }
299
  });
300
 
 
47
  color: white;
48
  -webkit-text-stroke: 1px #333;
49
  }
50
+ /* ํ† ํฐ ์ž…๋ ฅ ๋ฐ ์ƒํƒœ ํ‘œ์‹œ ์˜์—ญ */
51
+ .auth-controls {
52
  padding: 1rem;
53
  margin-bottom: 1rem;
54
  background-color: #f0f0f0;
 
57
  justify-content: space-between;
58
  align-items: center;
59
  }
60
+ .auth-controls input {
61
+ padding: 0.5rem;
62
+ width: 300px;
63
+ border: 1px solid #ccc;
64
+ border-radius: 4px;
65
+ }
66
+ .auth-controls button {
67
+ padding: 0.5rem 1rem;
68
+ margin-left: 0.5rem;
69
+ background-color: #4CAF50;
70
+ color: white;
71
+ border: none;
72
+ border-radius: 4px;
73
+ cursor: pointer;
74
+ }
75
+ .auth-controls button:hover {
76
+ background-color: #45a049;
77
+ }
78
  /* ๋กœ๋”ฉ ํ‘œ์‹œ */
79
  .loading {
80
  position: fixed;
 
88
  align-items: center;
89
  z-index: 1000;
90
  font-size: 1.5rem;
91
+ }
92
+ /* ์ƒํƒœ ๋ฉ”์‹œ์ง€ */
93
+ .status-message {
94
+ margin: 1rem;
95
+ padding: 1rem;
96
+ border-radius: 4px;
97
+ }
98
+ .success {
99
+ background-color: #dff0d8;
100
+ color: #3c763d;
101
+ }
102
+ .error {
103
+ background-color: #f2dede;
104
+ color: #a94442;
105
  }
106
  </style>
107
  </head>
108
  <body>
109
+ <!-- ํ† ํฐ ์ž…๋ ฅ ๋ฐ ์ธ์ฆ ์ปจํŠธ๋กค -->
110
+ <div class="auth-controls" id="authControls">
111
  <div>
112
+ <span>Hugging Face ์ธ์ฆ: </span>
113
+ <span id="authStatus">์ธ์ฆ๋˜์ง€ ์•Š์Œ</span>
114
  </div>
115
  <div>
116
+ <input type="password" id="tokenInput" placeholder="Hugging Face API ํ† ํฐ ์ž…๋ ฅ" />
117
+ <button id="authButton">์ธ์ฆํ•˜๊ธฐ</button>
118
+ <button id="logoutButton" style="display: none; background-color: #f44336;">๋กœ๊ทธ์•„์›ƒ</button>
119
  </div>
120
  </div>
121
 
122
+ <!-- ์ƒํƒœ ๋ฉ”์‹œ์ง€ -->
123
+ <div id="statusMessage" class="status-message" style="display: none;"></div>
124
+
125
  <!-- ๋กœ๋”ฉ ํ‘œ์‹œ -->
126
+ <div class="loading" id="loadingIndicator" style="display: none;">
127
  ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘...
128
  </div>
129
 
 
131
  <div class="container" id="cardsContainer"></div>
132
 
133
  <script>
134
+ // API ์—”๋“œํฌ์ธํŠธ
135
+ const HF_API_BASE = 'https://huggingface.co/api';
136
+
137
  // ์ƒํƒœ ๊ด€๋ฆฌ
138
  const state = {
139
+ token: null,
140
+ username: null,
141
+ likedModels: {},
142
  isLoading: false
143
  };
144
 
145
  // DOM ์š”์†Œ ์ฐธ์กฐ
146
  const elements = {
147
+ tokenInput: document.getElementById('tokenInput'),
148
+ authButton: document.getElementById('authButton'),
 
149
  logoutButton: document.getElementById('logoutButton'),
150
+ authStatus: document.getElementById('authStatus'),
151
  cardsContainer: document.getElementById('cardsContainer'),
152
+ loadingIndicator: document.getElementById('loadingIndicator'),
153
+ statusMessage: document.getElementById('statusMessage')
154
  };
155
 
156
  // Hugging Face์˜ spaces/models URL ๋ชฉ๋ก
 
164
  "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
165
  "https://huggingface.co/spaces/ginigen/Ovis2-8B",
166
  "https://huggingface.co/spaces/ginigen/Graph-Mind",
167
+ // ๋‚˜๋จธ์ง€ URL ๋ชฉ๋ก... ์‹ค์ œ๋กœ๋Š” ๋ชจ๋“  URL์„ ํฌํ•จํ•ด์•ผ ํ•จ
 
168
  ];
169
 
170
+ // URL์—์„œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ID ์ถ”์ถœ
171
+ function extractModelInfo(url) {
172
+ const parts = url.split('/');
173
+ const type = parts[3]; // spaces ๋˜๋Š” models
174
+ const owner = parts[4];
175
+ const repo = parts[5];
176
+ return { type, owner, repo, fullId: `${owner}/${repo}` };
177
+ }
178
+
179
  // URL์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์„ ์ œ๋ชฉ์œผ๋กœ ์ถ”์ถœ (์–ธ๋”๋ฐ”, ํ•˜์ดํ”ˆ์„ ๊ณต๋ฐฑ์œผ๋กœ ๋ณ€ํ™˜)
180
  function extractTitle(url) {
181
  const parts = url.split("/");
 
189
  elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
190
  }
191
 
192
+ // ์ƒํƒœ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ํ•จ์ˆ˜
193
+ function showMessage(message, isError = false) {
194
+ elements.statusMessage.textContent = message;
195
+ elements.statusMessage.className = `status-message ${isError ? 'error' : 'success'}`;
196
+ elements.statusMessage.style.display = 'block';
197
+
198
+ // 3์ดˆ ํ›„ ๋ฉ”์‹œ์ง€ ์‚ฌ๋ผ์ง
199
+ setTimeout(() => {
200
+ elements.statusMessage.style.display = 'none';
201
+ }, 3000);
202
  }
203
 
204
+ // Hugging Face API ํ˜ธ์ถœ ํ•จ์ˆ˜
205
+ async function fetchWithToken(endpoint, options = {}) {
206
+ if (!state.token) {
207
+ throw new Error('์ธ์ฆ ํ† ํฐ์ด ์—†์Šต๋‹ˆ๋‹ค.');
208
+ }
209
+
210
+ const url = `${HF_API_BASE}${endpoint}`;
211
+ const headers = {
212
+ 'Authorization': `Bearer ${state.token}`,
213
+ 'Content-Type': 'application/json',
214
+ ...options.headers
215
+ };
216
+
217
+ return fetch(url, {
218
+ ...options,
219
+ headers
220
  });
221
  }
222
 
223
+ // ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
224
+ async function fetchUserInfo() {
225
+ const response = await fetchWithToken('/whoami-v2');
226
+ if (!response.ok) {
227
+ throw new Error('์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
228
+ }
229
+ return response.json();
230
+ }
231
+
232
+ // ์‚ฌ์šฉ์ž๊ฐ€ ์ข‹์•„์š”ํ•œ ๋ชจ๋ธ/์ŠคํŽ˜์ด์Šค ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
233
+ async function fetchLikedRepos() {
234
+ try {
235
+ // ์ข‹์•„์š”ํ•œ ๋ชจ๋ธ ๊ฐ€์ ธ์˜ค๊ธฐ
236
+ const modelsResponse = await fetchWithToken('/me/likes');
237
+
238
+ if (!modelsResponse.ok) {
239
+ throw new Error('์ข‹์•„์š” ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š”๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
240
+ }
241
+
242
+ const likedModels = await modelsResponse.json();
243
+
244
+ // ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ (๋น ๋ฅธ ๊ฒ€์ƒ‰์„ ์œ„ํ•ด)
245
+ const likedMap = {};
246
+ likedModels.forEach(model => {
247
+ likedMap[`${model.owner}/${model.name}`] = true;
248
+ });
249
+
250
+ return likedMap;
251
+ } catch (error) {
252
+ console.error('์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ ์˜ค๋ฅ˜:', error);
253
+ throw error;
254
+ }
255
+ }
256
+
257
+ // ์ข‹์•„์š” ํ† ๊ธ€ API ํ˜ธ์ถœ
258
+ async function toggleLikeAPI(type, owner, repo, isLiked) {
259
+ try {
260
+ const method = isLiked ? 'DELETE' : 'POST';
261
+ const response = await fetchWithToken(`/${type}/${owner}/${repo}/like`, {
262
+ method
263
+ });
264
+
265
+ if (!response.ok) {
266
+ throw new Error(`์ข‹์•„์š” ${isLiked ? '์ทจ์†Œ' : '์ถ”๊ฐ€'} ์‹คํŒจ`);
267
+ }
268
+
269
+ return response.ok;
270
+ } catch (error) {
271
+ console.error('์ข‹์•„์š” ํ† ๊ธ€ API ์˜ค๋ฅ˜:', error);
272
+ throw error;
273
+ }
274
+ }
275
+
276
+ // ์ธ์ฆ ์ฒ˜๋ฆฌ
277
+ async function authenticate(token) {
278
+ if (!token.trim()) {
279
+ showMessage('ํ† ํฐ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', true);
280
  return;
281
  }
282
 
283
  setLoading(true);
284
 
285
  try {
286
+ // ํ† ํฐ ์ €์žฅ
287
+ state.token = token;
288
 
289
+ // ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ
290
+ const userInfo = await fetchUserInfo();
291
+ state.username = userInfo.name || userInfo.user.username;
292
+
293
+ // ์ข‹์•„์š” ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
294
+ state.likedModels = await fetchLikedRepos();
295
 
296
  // UI ์—…๋ฐ์ดํŠธ
297
+ elements.authStatus.textContent = `${state.username}๋‹˜์œผ๋กœ ์ธ์ฆ๋จ`;
298
+ elements.tokenInput.style.display = 'none';
299
+ elements.authButton.style.display = 'none';
300
  elements.logoutButton.style.display = 'inline-block';
301
 
302
+ showMessage('์ธ์ฆ ์„ฑ๊ณต! ์ข‹์•„์š” ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์™”์Šต๋‹ˆ๋‹ค.');
303
+
304
  // ์นด๋“œ ๋‹ค์‹œ ๋ Œ๋”๋ง
305
  renderCards();
306
  } catch (error) {
307
+ console.error('์ธ์ฆ ์˜ค๋ฅ˜:', error);
308
+ showMessage('์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ํ† ํฐ์„ ํ™•์ธํ•ด์ฃผ์„ธ์š”.', true);
309
+ state.token = null;
310
  } finally {
311
  setLoading(false);
312
  }
 
315
  // ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ
316
  function logout() {
317
  // ์ƒํƒœ ์ดˆ๊ธฐํ™”
318
+ state.token = null;
319
+ state.username = null;
320
+ state.likedModels = {};
321
 
322
  // UI ์—…๋ฐ์ดํŠธ
323
+ elements.authStatus.textContent = '์ธ์ฆ๋˜์ง€ ์•Š์Œ';
324
+ elements.tokenInput.style.display = 'inline-block';
325
+ elements.tokenInput.value = '';
326
+ elements.authButton.style.display = 'inline-block';
327
  elements.logoutButton.style.display = 'none';
328
 
329
+ showMessage('๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
330
+
331
  // ์นด๋“œ ๋‹ค์‹œ ๋ Œ๋”๋ง
332
  renderCards();
333
  }
334
 
335
  // ์ข‹์•„์š” ํ† ๊ธ€ ์ฒ˜๋ฆฌ
336
  async function toggleLike(url, button) {
337
+ if (!state.token) {
338
+ showMessage('์ข‹์•„์š”๋ฅผ ํ•˜๋ ค๋ฉด HF ํ† ํฐ์œผ๋กœ ์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', true);
339
  return;
340
  }
341
 
342
+ const modelInfo = extractModelInfo(url);
343
+ const modelId = modelInfo.fullId;
344
+ const isCurrentlyLiked = state.likedModels[modelId] || false;
345
+
346
  setLoading(true);
347
 
348
  try {
349
+ // API ํ˜ธ์ถœ
350
+ await toggleLikeAPI(modelInfo.type, modelInfo.owner, modelInfo.repo, isCurrentlyLiked);
351
+
352
+ // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
353
+ if (isCurrentlyLiked) {
354
+ delete state.likedModels[modelId];
355
  button.classList.remove("liked");
356
  button.classList.add("not-liked");
357
+ showMessage(`${modelInfo.repo}์— ๋Œ€ํ•œ ์ข‹์•„์š”๋ฅผ ์ทจ์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.`);
358
  } else {
359
+ state.likedModels[modelId] = true;
360
  button.classList.add("liked");
361
  button.classList.remove("not-liked");
362
+ showMessage(`${modelInfo.repo}๋ฅผ ์ข‹์•„์š” ํ–ˆ์Šต๋‹ˆ๋‹ค.`);
363
  }
 
 
 
364
  } catch (error) {
365
+ showMessage('์ข‹์•„์š” ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.', true);
366
  console.error('์ข‹์•„์š” ์˜ค๋ฅ˜:', error);
367
  } finally {
368
  setLoading(false);
 
395
  const likeBtn = document.createElement("button");
396
  likeBtn.className = "like-button";
397
  likeBtn.textContent = "โ™ฅ";
398
+
399
+ // ์ข‹์•„์š” ์ƒํƒœ ์„ค์ •
400
+ try {
401
+ const modelInfo = extractModelInfo(url);
402
+ const isLiked = state.token && state.likedModels[modelInfo.fullId];
403
+
404
+ if (isLiked) {
405
+ likeBtn.classList.add("liked");
406
+ } else {
407
+ likeBtn.classList.add("not-liked");
408
+ }
409
+ } catch (e) {
410
+ // URL ํŒŒ์‹ฑ ์˜ค๋ฅ˜ ๋“ฑ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
411
  likeBtn.classList.add("not-liked");
412
  }
413
+
414
  likeBtn.addEventListener("click", function(e) {
415
  e.preventDefault(); // ๋งํฌ ํด๋ฆญ ๋ฐฉ์ง€
416
  toggleLike(url, likeBtn);
 
422
  }
423
 
424
  // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์„ค์ •
425
+ elements.authButton.addEventListener('click', () => {
426
+ authenticate(elements.tokenInput.value);
427
  });
428
 
429
  elements.logoutButton.addEventListener('click', logout);
430
 
431
+ // ์—”ํ„ฐ ํ‚ค๋กœ ์ธ์ฆ ๊ฐ€๋Šฅํ•˜๊ฒŒ
432
+ elements.tokenInput.addEventListener('keypress', (event) => {
433
  if (event.key === 'Enter') {
434
+ authenticate(elements.tokenInput.value);
435
  }
436
  });
437