acecalisto3 commited on
Commit
0ece1c3
·
verified ·
1 Parent(s): 5390e3d

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +120 -172
index.html CHANGED
@@ -35,7 +35,7 @@
35
  width: 100%;
36
  height: 600px;
37
  pointer-events: none;
38
- margin-top: 48px; /* Match header height */
39
  }
40
 
41
  .grid-container {
@@ -51,6 +51,7 @@
51
  border-radius: 10px;
52
  border: 1px solid rgb(55 65 81);
53
  }
 
54
  .grid-item:hover {
55
  filter: brightness(75%);
56
  }
@@ -92,7 +93,6 @@
92
  line-height: 1.2;
93
  }
94
 
95
- /* Modal styles */
96
  .modal-overlay {
97
  position: fixed;
98
  top: 0;
@@ -105,6 +105,7 @@
105
  justify-content: center;
106
  z-index: 1000;
107
  }
 
108
  .modal-content {
109
  background: white;
110
  padding: 2rem;
@@ -114,6 +115,7 @@
114
  max-height: 80vh;
115
  overflow-y: auto;
116
  }
 
117
  .space-item {
118
  display: flex;
119
  align-items: center;
@@ -121,6 +123,7 @@
121
  padding: 0.5rem;
122
  border-bottom: 1px solid #eee;
123
  }
 
124
  [x-cloak] { display: none !important; }
125
  </style>
126
 
@@ -135,6 +138,8 @@
135
  this.themes = data.themes;
136
  this.totalPages = data.totalPages;
137
  },
 
 
138
  themes: [],
139
  filter: "tool",
140
  sort: "likes",
@@ -151,140 +156,43 @@
151
  spaceTypes: {},
152
  authError: '',
153
 
 
154
  buttonClass(attr, filter) {
155
- if (this[attr] === filter) {
156
- return "text-orange-600 bg-gradient-to-br from-orange-300 to-orange-100 px-2 md:px-3 py-1 rounded-full";
157
- }
158
- return "text-gray-800 hover:to-orange-300/100 hover:text-orange-600 dark:hover:bg-white";
159
- },
160
-
161
- async switchData() {
162
- this.page = 1;
163
- this.useTestData = !this.useTestData;
164
- const data = await this.getThemes(this.page, this.sort, this.useTestData);
165
- this.themes = data.themes;
166
- this.totalPages = data.totalPages;
167
- },
168
-
169
- async sortThemes(sort) {
170
- this.sort = sort;
171
- this.page = 1;
172
- const data = await this.getThemes(this.page, this.sort, this.useTestData);
173
- this.themes = data.themes;
174
- this.totalPages = data.totalPages;
175
- },
176
-
177
- async filterType(filter) {
178
- this.filter = filter;
179
- this.page = 1;
180
- if (this.searchQuery) {
181
- await this.searchThemes();
182
- } else {
183
- const data = await this.getThemes(this.page, this.sort, this.useTestData);
184
- this.themes = data.themes;
185
- this.totalPages = data.totalPages;
186
- }
187
- },
188
-
189
- async searchThemes() {
190
- this.page = 1;
191
- const data = await this.getThemes(this.page, this.sort, this.useTestData);
192
- this.themes = data.themes;
193
- this.totalPages = data.totalPages;
194
- },
195
-
196
- async switchSearchType(type) {
197
- this.searchType = type;
198
- if (this.searchQuery) {
199
- await this.searchThemes();
200
- }
201
- },
202
-
203
- async getThemes(page, sort, useTestData) {
204
- let data;
205
- if (useTestData) {
206
- const res = await fetch(
207
- `https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/test_data.json`
208
- );
209
- data = await res.json();
210
- } else {
211
- const searchFilters = this.filter === 'tool' ? 'tool' : 'smolagents';
212
- let searchUrl;
213
- if (this.searchQuery) {
214
- if (this.searchType === 'semantic') {
215
- searchUrl = `https://huggingface.co/api/spaces/semantic-search?limit=100&filter=${searchFilters}&q=${encodeURIComponent(this.searchQuery)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
216
- } else {
217
- searchUrl = `https://huggingface.co/api/spaces?limit=100&filter=${searchFilters}&search=${encodeURIComponent(this.searchQuery)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
218
- }
219
- } else {
220
- searchUrl = `https://huggingface.co/api/spaces?limit=100&filter=${encodeURIComponent(searchFilters)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
221
- }
222
- const res = await fetch(searchUrl);
223
- data = await res.json();
224
- console.log(data);
225
- // Transform the API response to match the expected format
226
- data = data.filter(item => item.runtime?.stage === "RUNNING").map(item => ({
227
- id: item.id,
228
- subdomain: `https://${item.subdomain}.hf.space`,
229
- likes: item.likes,
230
- lastModified: item.lastModified,
231
- type: this.filter // Add type based on current filter
232
- }));
233
- }
234
-
235
- if (sort === 'likes') {
236
- data.sort((a, b) => (b.likes - a.likes));
237
- } else {
238
- data.sort((a, b) => (new Date(b.lastModified) - new Date(a.lastModified)));
239
- }
240
-
241
- const pageThemes = data.slice((page - 1) * 15, page * 15);
242
-
243
- console.log(pageThemes);
244
-
245
- return {
246
- themes: pageThemes,
247
- totalPages: Math.ceil(data.length / 15)
248
- };
249
- },
250
-
251
- async nextPage() {
252
- if (this.page < this.totalPages) {
253
- this.page += 1;
254
- const data = await this.getThemes(this.page, this.sort, this.useTestData);
255
- this.themes = this.themes.concat(data.themes);
256
- this.totalPages = data.totalPages;
257
- }
258
  },
259
 
 
260
  async authenticate() {
261
- this.authError = ''; // Reset error message
262
  try {
263
  // Validate token format
264
  if (!this.hfToken || this.hfToken.length < 10) {
265
- throw new Error('Invalid token format');
266
  }
267
 
268
- // Fetch user info to validate token
269
- const response = await fetch('https://huggingface.co/api/whoami', {
270
  headers: { Authorization: `Bearer ${this.hfToken}` }
271
  });
272
 
273
- if (!response.ok) {
274
- throw new Error('Authentication failed. Please check your token.');
 
275
  }
276
 
277
- const userData = await response.json();
278
  console.log('Authenticated as:', userData.name);
279
 
280
- // Fetch user spaces
281
  const spacesRes = await fetch(
282
- `https://huggingface.co/api/spaces?user=${userData.name}`,
283
  { headers: { Authorization: `Bearer ${this.hfToken}` } }
284
  );
285
 
286
  if (!spacesRes.ok) {
287
- throw new Error('Failed to fetch spaces. Please try again.');
288
  }
289
 
290
  const spacesData = await spacesRes.json();
@@ -297,16 +205,20 @@
297
  lastModified: space.lastModified
298
  }));
299
 
 
300
  this.spaceTypes = Object.fromEntries(
301
  this.userSpaces.map(space => [space.id, 'tool'])
302
  );
303
  this.authenticated = true;
 
304
  } catch (error) {
305
  console.error('Authentication error:', error);
306
  this.authError = error.message;
 
307
  }
308
  },
309
 
 
310
  importSpaces() {
311
  const newThemes = this.selectedSpaces.map(id => {
312
  const space = this.userSpaces.find(s => s.id === id);
@@ -318,24 +230,80 @@
318
 
319
  this.themes = [...this.themes, ...newThemes];
320
  this.showImport = false;
 
 
 
 
321
  this.hfToken = '';
322
  this.authenticated = false;
323
  this.userSpaces = [];
324
  this.selectedSpaces = [];
325
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
326
  }));
 
327
  Alpine.start();
328
  </script>
329
  </head>
330
 
331
  <body class="pb-10 pt-5 bg-white relative">
332
  <section x-data="themesData">
333
- <!-- Add Import Button -->
334
  <div class="container px-6 mx-auto mb-4">
335
- <button
336
- @click="showImport = true"
337
- class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
338
- >
339
  Import Your Spaces
340
  </button>
341
  </div>
@@ -353,16 +321,10 @@
353
  class="w-full p-2 border rounded"
354
  />
355
  <div class="flex gap-2">
356
- <button
357
- @click="authenticate()"
358
- class="px-4 py-2 bg-green-600 text-white rounded"
359
- >
360
  Authenticate
361
  </button>
362
- <button
363
- @click="showImport = false"
364
- class="px-4 py-2 bg-gray-600 text-white rounded"
365
- >
366
  Cancel
367
  </button>
368
  </div>
@@ -376,16 +338,9 @@
376
  <div class="space-y-2">
377
  <template x-for="space in userSpaces" :key="space.id">
378
  <div class="space-item">
379
- <input
380
- type="checkbox"
381
- x-model="selectedSpaces"
382
- :value="space.id"
383
- >
384
  <span x-text="space.id"></span>
385
- <select
386
- x-model="spaceTypes[space.id]"
387
- class="ml-auto px-2 py-1 border rounded"
388
- >
389
  <option value="tool">Tool</option>
390
  <option value="agent">Agent</option>
391
  </select>
@@ -393,16 +348,10 @@
393
  </template>
394
  </div>
395
  <div class="flex gap-2">
396
- <button
397
- @click="importSpaces()"
398
- class="px-4 py-2 bg-blue-600 text-white rounded"
399
- >
400
  Import Selected
401
  </button>
402
- <button
403
- @click="showImport = false"
404
- class="px-4 py-2 bg-gray-600 text-white rounded"
405
- >
406
  Cancel
407
  </button>
408
  </div>
@@ -411,14 +360,17 @@
411
  </div>
412
  </div>
413
 
414
- <!-- Existing header and grid remain unchanged -->
415
  <section class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-14 mx-auto relative">
416
  <div class="col-span-2 lg:col-span-1 flex flex-col gap-14 row-start">
417
  <div class="flex items-center gap-2">
418
- <img src="https://camo.githubusercontent.com/a8c1f1d12aa3114010c6e74b29d47fee91d8da10a915f065c38e6d0ea7f16568/68747470733a2f2f68756767696e67666163652e636f2f64617461736574732f68756767696e67666163652f646f63756d656e746174696f6e2d696d616765732f7265736f6c76652f6d61696e2f736d6f6c6167656e74732f6d6173636f742e706e67" alt="Smolagents mascot" class="w-14 h-14 flex-shrink-0">
 
 
419
  <h1 class="text-xl font-semibold text-gray-800 break-words">smolagents and tools gallery</h1>
420
  </div>
421
  </div>
 
422
  <div class="col-span-2 md:col-span-3 flex items-center gap-14 flex flex-wrap lg-auto lg:ml-auto text-sm">
423
  <div class="flex flex-col gap-2">
424
  <div class="flex items-center">
@@ -433,47 +385,43 @@
433
  </div>
434
  <div class="flex gap-2">
435
  <span class="md:px-3 py-1 text-gray-800">type</span>
436
- <button
437
- :class="buttonClass('filter', 'tool')"
438
- @click="filterType('tool')"
439
- >
440
  Tools
441
  </button>
442
- <button
443
- :class="buttonClass('filter', 'agent')"
444
- @click="filterType('agent')"
445
- >
446
  Agents
447
  </button>
448
  </div>
449
  <div class="flex gap-2">
450
  <span class="md:px-3 py-1 text-gray-800">sort by</span>
451
- <button
452
- :class="buttonClass('sort', 'likes')"
453
- @click="sortThemes('likes')"
454
- >
455
  Most Likes
456
  </button>
457
- <button
458
- :class="buttonClass('sort', 'recent')"
459
- @click="sortThemes('recent')"
460
- >
461
  Recent
462
  </button>
463
  </div>
464
  </div>
465
  </section>
466
- <div class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mx-auto my-8 relative">
467
- <template x-for="theme in themes" :key="theme.id">
468
- <div class="grid-item">
469
- <div class="grid-item-header">
470
- <h2 class="text-sm font-medium text-white" x-text="theme.id"></h2>
 
 
 
 
 
 
 
 
 
471
  </div>
472
- <iframe :src="`${theme.subdomain}?_=${new Date().getTime()}`" :alt="theme.id" scrolling="no" frameborder="0" loading="lazy"></iframe>
473
- <a :href="`https://huggingface.co/spaces/${theme.id}`" target="_blank"></a>
474
- </div>
475
- </template>
476
  </div>
 
 
477
  <div class="h-12 relative" x-intersect="nextPage" data-iframe-height></div>
478
  </section>
479
  </body>
 
35
  width: 100%;
36
  height: 600px;
37
  pointer-events: none;
38
+ margin-top: 48px;
39
  }
40
 
41
  .grid-container {
 
51
  border-radius: 10px;
52
  border: 1px solid rgb(55 65 81);
53
  }
54
+
55
  .grid-item:hover {
56
  filter: brightness(75%);
57
  }
 
93
  line-height: 1.2;
94
  }
95
 
 
96
  .modal-overlay {
97
  position: fixed;
98
  top: 0;
 
105
  justify-content: center;
106
  z-index: 1000;
107
  }
108
+
109
  .modal-content {
110
  background: white;
111
  padding: 2rem;
 
115
  max-height: 80vh;
116
  overflow-y: auto;
117
  }
118
+
119
  .space-item {
120
  display: flex;
121
  align-items: center;
 
123
  padding: 0.5rem;
124
  border-bottom: 1px solid #eee;
125
  }
126
+
127
  [x-cloak] { display: none !important; }
128
  </style>
129
 
 
138
  this.themes = data.themes;
139
  this.totalPages = data.totalPages;
140
  },
141
+
142
+ // State management
143
  themes: [],
144
  filter: "tool",
145
  sort: "likes",
 
156
  spaceTypes: {},
157
  authError: '',
158
 
159
+ // UI Helpers
160
  buttonClass(attr, filter) {
161
+ return this[attr] === filter
162
+ ? "text-orange-600 bg-gradient-to-br from-orange-300 to-orange-100 px-2 md:px-3 py-1 rounded-full"
163
+ : "text-gray-800 hover:to-orange-300/100 hover:text-orange-600 dark:hover:bg-white";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  },
165
 
166
+ // Authentication Logic
167
  async authenticate() {
168
+ this.authError = '';
169
  try {
170
  // Validate token format
171
  if (!this.hfToken || this.hfToken.length < 10) {
172
+ throw new Error('Invalid token format - must be at least 10 characters');
173
  }
174
 
175
+ // Verify token with HF API
176
+ const userRes = await fetch('https://huggingface.co/api/whoami', {
177
  headers: { Authorization: `Bearer ${this.hfToken}` }
178
  });
179
 
180
+ if (!userRes.ok) {
181
+ const errorData = await userRes.json();
182
+ throw new Error(errorData.error || 'Invalid credentials');
183
  }
184
 
185
+ const userData = await userRes.json();
186
  console.log('Authenticated as:', userData.name);
187
 
188
+ // Fetch user's spaces
189
  const spacesRes = await fetch(
190
+ `https://huggingface.co/api/spaces?user=${encodeURIComponent(userData.name)}`,
191
  { headers: { Authorization: `Bearer ${this.hfToken}` } }
192
  );
193
 
194
  if (!spacesRes.ok) {
195
+ throw new Error('Failed to fetch spaces - check token permissions');
196
  }
197
 
198
  const spacesData = await spacesRes.json();
 
205
  lastModified: space.lastModified
206
  }));
207
 
208
+ // Initialize default types
209
  this.spaceTypes = Object.fromEntries(
210
  this.userSpaces.map(space => [space.id, 'tool'])
211
  );
212
  this.authenticated = true;
213
+
214
  } catch (error) {
215
  console.error('Authentication error:', error);
216
  this.authError = error.message;
217
+ this.authenticated = false;
218
  }
219
  },
220
 
221
+ // Space Import Logic
222
  importSpaces() {
223
  const newThemes = this.selectedSpaces.map(id => {
224
  const space = this.userSpaces.find(s => s.id === id);
 
230
 
231
  this.themes = [...this.themes, ...newThemes];
232
  this.showImport = false;
233
+ this.resetAuthState();
234
+ },
235
+
236
+ resetAuthState() {
237
  this.hfToken = '';
238
  this.authenticated = false;
239
  this.userSpaces = [];
240
  this.selectedSpaces = [];
241
  },
242
+
243
+ // Data Fetching
244
+ async getThemes(page, sort, useTestData) {
245
+ let data;
246
+ if (useTestData) {
247
+ const res = await fetch(
248
+ 'https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/test_data.json'
249
+ );
250
+ data = await res.json();
251
+ } else {
252
+ const searchFilters = this.filter === 'tool' ? 'tool' : 'smolagents';
253
+ let searchUrl;
254
+
255
+ if (this.searchQuery) {
256
+ searchUrl = this.searchType === 'semantic'
257
+ ? `https://huggingface.co/api/spaces/semantic-search?limit=100&filter=${searchFilters}&q=${encodeURIComponent(this.searchQuery)}`
258
+ : `https://huggingface.co/api/spaces?limit=100&filter=${searchFilters}&search=${encodeURIComponent(this.searchQuery)}`;
259
+ } else {
260
+ searchUrl = `https://huggingface.co/api/spaces?limit=100&filter=${encodeURIComponent(searchFilters)}`;
261
+ }
262
+
263
+ const res = await fetch(searchUrl);
264
+ data = await res.json();
265
+ data = data
266
+ .filter(item => item.runtime?.stage === "RUNNING")
267
+ .map(item => ({
268
+ id: item.id,
269
+ subdomain: `https://${item.subdomain}.hf.space`,
270
+ likes: item.likes,
271
+ lastModified: item.lastModified,
272
+ type: this.filter
273
+ }));
274
+ }
275
+
276
+ data.sort((a, b) => sort === 'likes'
277
+ ? b.likes - a.likes
278
+ : new Date(b.lastModified) - new Date(a.lastModified)
279
+ );
280
+
281
+ return {
282
+ themes: data.slice((page - 1) * 15, page * 15),
283
+ totalPages: Math.ceil(data.length / 15)
284
+ };
285
+ },
286
+
287
+ // Pagination
288
+ async nextPage() {
289
+ if (this.page < this.totalPages) {
290
+ this.page += 1;
291
+ const data = await this.getThemes(this.page, this.sort, this.useTestData);
292
+ this.themes = this.themes.concat(data.themes);
293
+ this.totalPages = data.totalPages;
294
+ }
295
+ }
296
  }));
297
+
298
  Alpine.start();
299
  </script>
300
  </head>
301
 
302
  <body class="pb-10 pt-5 bg-white relative">
303
  <section x-data="themesData">
304
+ <!-- Import Button -->
305
  <div class="container px-6 mx-auto mb-4">
306
+ <button @click="showImport = true" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
 
 
 
307
  Import Your Spaces
308
  </button>
309
  </div>
 
321
  class="w-full p-2 border rounded"
322
  />
323
  <div class="flex gap-2">
324
+ <button @click="authenticate()" class="px-4 py-2 bg-green-600 text-white rounded">
 
 
 
325
  Authenticate
326
  </button>
327
+ <button @click="showImport = false" class="px-4 py-2 bg-gray-600 text-white rounded">
 
 
 
328
  Cancel
329
  </button>
330
  </div>
 
338
  <div class="space-y-2">
339
  <template x-for="space in userSpaces" :key="space.id">
340
  <div class="space-item">
341
+ <input type="checkbox" x-model="selectedSpaces" :value="space.id">
 
 
 
 
342
  <span x-text="space.id"></span>
343
+ <select x-model="spaceTypes[space.id]" class="ml-auto px-2 py-1 border rounded">
 
 
 
344
  <option value="tool">Tool</option>
345
  <option value="agent">Agent</option>
346
  </select>
 
348
  </template>
349
  </div>
350
  <div class="flex gap-2">
351
+ <button @click="importSpaces()" class="px-4 py-2 bg-blue-600 text-white rounded">
 
 
 
352
  Import Selected
353
  </button>
354
+ <button @click="showImport = false" class="px-4 py-2 bg-gray-600 text-white rounded">
 
 
 
355
  Cancel
356
  </button>
357
  </div>
 
360
  </div>
361
  </div>
362
 
363
+ <!-- Main Content -->
364
  <section class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-14 mx-auto relative">
365
  <div class="col-span-2 lg:col-span-1 flex flex-col gap-14 row-start">
366
  <div class="flex items-center gap-2">
367
+ <img src="https://camo.githubusercontent.com/a8c1f1d12aa3114010c6e74b29d47fee91d8da10a915f065c38e6d0ea7f16568/68747470733a2f2f68756767696e67666163652e636f2f64617461736574732f68756767696e67666163652f646f63756d656e746174696f6e2d696d616765732f7265736f6c76652f6d61696e2f736d6f6c6167656e74732f6d6173636f742e706e67"
368
+ alt="Smolagents mascot"
369
+ class="w-14 h-14 flex-shrink-0">
370
  <h1 class="text-xl font-semibold text-gray-800 break-words">smolagents and tools gallery</h1>
371
  </div>
372
  </div>
373
+
374
  <div class="col-span-2 md:col-span-3 flex items-center gap-14 flex flex-wrap lg-auto lg:ml-auto text-sm">
375
  <div class="flex flex-col gap-2">
376
  <div class="flex items-center">
 
385
  </div>
386
  <div class="flex gap-2">
387
  <span class="md:px-3 py-1 text-gray-800">type</span>
388
+ <button :class="buttonClass('filter', 'tool')" @click="filterType('tool')">
 
 
 
389
  Tools
390
  </button>
391
+ <button :class="buttonClass('filter', 'agent')" @click="filterType('agent')">
 
 
 
392
  Agents
393
  </button>
394
  </div>
395
  <div class="flex gap-2">
396
  <span class="md:px-3 py-1 text-gray-800">sort by</span>
397
+ <button :class="buttonClass('sort', 'likes')" @click="sortThemes('likes')">
 
 
 
398
  Most Likes
399
  </button>
400
+ <button :class="buttonClass('sort', 'recent')" @click="sortThemes('recent')">
 
 
 
401
  Recent
402
  </button>
403
  </div>
404
  </div>
405
  </section>
406
+
407
+ <!-- Grid Display -->
408
+ <div class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mx-auto my-8 relative">
409
+ <template x-for="theme in themes" :key="theme.id">
410
+ <div class="grid-item">
411
+ <div class="grid-item-header">
412
+ <h2 class="text-sm font-medium text-white" x-text="theme.id"></h2>
413
+ </div>
414
+ <iframe :src="`${theme.subdomain}?_=${new Date().getTime()}`"
415
+ :alt="theme.id"
416
+ scrolling="no"
417
+ frameborder="0"
418
+ loading="lazy"></iframe>
419
+ <a :href="`https://huggingface.co/spaces/${theme.id}`" target="_blank"></a>
420
  </div>
421
+ </template>
 
 
 
422
  </div>
423
+
424
+ <!-- Infinite Scroll Trigger -->
425
  <div class="h-12 relative" x-intersect="nextPage" data-iframe-height></div>
426
  </section>
427
  </body>