vectorplasticity commited on
Commit
e1e24cf
ยท
verified ยท
1 Parent(s): d46c341

Upload templates/index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. templates/index.html +340 -196
templates/index.html CHANGED
@@ -3,329 +3,473 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>OpenRouter Models</title>
7
  <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  body {
10
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
- background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
 
12
  min-height: 100vh;
13
- color: #e0e0e0;
14
  }
 
15
  .container {
16
  max-width: 1400px;
17
  margin: 0 auto;
18
- padding: 2rem;
19
  }
 
20
  header {
21
  text-align: center;
22
- margin-bottom: 3rem;
23
- padding: 2rem;
24
- background: rgba(255,255,255,0.05);
25
- border-radius: 20px;
26
- backdrop-filter: blur(10px);
27
  border: 1px solid rgba(255,255,255,0.1);
28
  }
29
- h1 {
 
30
  font-size: 2.5rem;
31
- margin-bottom: 0.5rem;
32
- background: linear-gradient(135deg, #00d4ff, #7b2cbf);
33
  -webkit-background-clip: text;
34
  -webkit-text-fill-color: transparent;
 
35
  }
36
- .subtitle {
37
- color: #888;
 
38
  font-size: 1.1rem;
39
  }
 
40
  .controls {
41
  display: flex;
42
- gap: 1rem;
43
- margin-bottom: 2rem;
44
  flex-wrap: wrap;
 
 
 
 
 
45
  align-items: center;
46
  }
 
47
  .search-box {
48
  flex: 1;
49
- min-width: 300px;
50
- padding: 1rem 1.5rem;
51
- border: none;
52
- border-radius: 12px;
53
- background: rgba(255,255,255,0.1);
54
- color: white;
 
 
 
 
 
55
  font-size: 1rem;
56
- outline: none;
57
- transition: all 0.3s;
58
  }
59
- .search-box:focus {
60
- background: rgba(255,255,255,0.15);
61
- box-shadow: 0 0 20px rgba(0,212,255,0.3);
 
62
  }
63
- .search-box::placeholder { color: #888; }
64
  .btn {
65
- padding: 1rem 1.5rem;
66
  border: none;
67
- border-radius: 12px;
68
  cursor: pointer;
69
- font-size: 0.9rem;
70
  font-weight: 600;
71
- transition: all 0.3s;
72
  text-decoration: none;
73
  display: inline-flex;
74
  align-items: center;
75
- gap: 0.5rem;
 
 
76
  }
 
77
  .btn-primary {
78
- background: linear-gradient(135deg, #00d4ff, #7b2cbf);
79
  color: white;
80
  }
 
 
 
 
 
 
81
  .btn-secondary {
82
- background: rgba(255,255,255,0.1);
83
- color: white;
84
- border: 1px solid rgba(255,255,255,0.2);
85
  }
86
- .btn:hover {
87
- transform: translateY(-2px);
88
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);
89
  }
 
90
  .stats-bar {
91
- display: flex;
92
- gap: 2rem;
93
- margin-bottom: 2rem;
94
- padding: 1rem 2rem;
95
- background: rgba(255,255,255,0.05);
96
  border-radius: 12px;
 
 
 
 
 
97
  }
 
98
  .stat {
99
- display: flex;
100
- flex-direction: column;
101
  }
 
102
  .stat-value {
103
- font-size: 1.5rem;
104
- font-weight: bold;
105
- color: #00d4ff;
106
  }
 
107
  .stat-label {
108
- font-size: 0.8rem;
109
- color: #888;
110
- text-transform: uppercase;
111
  }
 
 
 
 
 
 
 
 
112
  table {
113
  width: 100%;
114
  border-collapse: collapse;
115
- background: rgba(255,255,255,0.05);
116
- border-radius: 20px;
117
- overflow: hidden;
118
- backdrop-filter: blur(10px);
119
- }
120
- thead {
121
- background: linear-gradient(135deg, rgba(0,212,255,0.2), rgba(123,44,191,0.2));
122
  }
 
123
  th {
124
- padding: 1.2rem 1rem;
 
125
  text-align: left;
126
  font-weight: 600;
127
- text-transform: uppercase;
128
- font-size: 0.75rem;
129
- letter-spacing: 0.5px;
130
  cursor: pointer;
131
  user-select: none;
132
- transition: all 0.3s;
133
  position: relative;
 
134
  }
 
135
  th:hover {
136
- background: rgba(255,255,255,0.1);
137
  }
138
- th.sortable::after {
139
- content: 'โ†•๏ธ';
140
- margin-left: 0.5rem;
141
- font-size: 0.7rem;
142
  opacity: 0.5;
143
  }
144
- th.sorted-asc::after {
 
145
  content: 'โ–ฒ';
146
  opacity: 1;
147
  }
148
- th.sorted-desc::after {
 
149
  content: 'โ–ผ';
150
  opacity: 1;
151
  }
152
- tbody tr {
 
 
153
  border-bottom: 1px solid rgba(255,255,255,0.05);
154
- transition: all 0.3s;
155
- }
156
- tbody tr:hover {
157
- background: rgba(255,255,255,0.08);
158
  }
159
- td {
160
- padding: 1rem;
161
- font-size: 0.9rem;
162
  }
 
163
  .model-name {
 
164
  font-weight: 600;
165
- color: #fff;
166
- }
167
- .provider {
168
- color: #888;
169
- font-size: 0.8rem;
170
  }
171
- .context {
172
- background: rgba(0,212,255,0.2);
173
- padding: 0.3rem 0.8rem;
174
- border-radius: 20px;
175
- font-size: 0.8rem;
176
- color: #00d4ff;
177
- }
178
- .price {
179
- font-family: monospace;
180
  font-size: 0.85rem;
181
- color: #a0e0a0;
 
182
  }
 
183
  .architecture {
184
  display: flex;
185
- gap: 0.3rem;
186
  flex-wrap: wrap;
187
  }
 
188
  .badge {
189
- padding: 0.2rem 0.6rem;
190
- border-radius: 12px;
191
- font-size: 0.7rem;
192
- text-transform: uppercase;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  }
194
- .badge-modality {
195
- background: rgba(123,44,191,0.3);
196
- color: #e0a0ff;
197
  }
198
- .badge-tool {
199
- background: rgba(0,212,255,0.3);
200
- color: #a0e0ff;
 
 
 
 
 
 
 
 
 
 
201
  }
202
- .hidden { display: none; }
203
  footer {
204
- margin-top: 3rem;
205
  text-align: center;
206
- color: #666;
207
- padding: 2rem;
 
 
 
 
 
 
 
208
  }
 
209
  @media (max-width: 768px) {
210
- .container { padding: 1rem; }
211
- table { font-size: 0.8rem; }
212
- td { padding: 0.5rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
  </style>
215
  </head>
216
  <body>
217
  <div class="container">
218
  <header>
219
- <h1>๐Ÿค– OpenRouter Models</h1>
220
- <p class="subtitle">Explore {{ count }} available LLMs from {{ providers }} providers</p>
221
  </header>
222
 
223
  <div class="stats-bar">
224
  <div class="stat">
225
- <span class="stat-value">{{ count }}</span>
226
- <span class="stat-label">Total Models</span>
227
  </div>
228
  <div class="stat">
229
- <span class="stat-value">{{ providers }}</span>
230
- <span class="stat-label">Providers</span>
231
  </div>
232
  <div class="stat">
233
- <span class="stat-value">{{ updated }}</span>
234
- <span class="stat-label">Last Updated</span>
235
  </div>
236
  </div>
237
 
238
  <div class="controls">
239
- <input type="text" class="search-box" id="searchBox" placeholder="๐Ÿ” Search models, providers, or architecture...">
240
- <a href="/api/raw" class="btn btn-secondary" target="_blank">Raw JSON</a>
241
- <a href="/api/download" class="btn btn-primary" download>๐Ÿ“ฅ Download JSON</a>
 
 
 
242
  </div>
243
 
244
- <table id="modelsTable">
245
- <thead>
246
- <tr>
247
- <th class="sortable" data-col="name">Model Name</th>
248
- <th class="sortable" data-col="provider">Provider</th>
249
- <th class="sortable" data-col="context">Context</th>
250
- <th class="sortable" data-col="prompt">Prompt $/M</th>
251
- <th class="sortable" data-col="completion">Completion $/M</th>
252
- <th>Architecture</th>
253
- </tr>
254
- </thead>
255
- <tbody>
256
- {% for model in models %}
257
- <tr data-name="{{ model.id }}" data-provider="{{ model.id.split('/')[0] }}" data-context="{{ model.context_length | default(0) }}" data-prompt="{{ model.prompt_cost }}" data-completion="{{ model.completion_cost }}">
258
- <td>
259
- <div class="model-name">{{ model.id.split('/')[-1] | truncate(30) }}</div>
260
- <div class="provider">{{ model.id }}</div>
261
- </td>
262
- <td class="provider">{{ model.id.split('/')[0] }}</td>
263
- <td><span class="context">{{ '{:,}'.format(model.context_length | int) if model.context_length else 'Unknown' }}</span></td>
264
- <td class="price">${{ '%.6f' % model.prompt_cost }}</td>
265
- <td class="price">${{ '%.6f' % model.completion_cost }}</td>
266
- <td>
267
- <div class="architecture">
268
- {% for mod in model.architecture.get('modality', '').split('->') %}
269
- {% if mod %}
270
- <span class="badge badge-modality">{{ mod }}</span>
271
- {% endif %}
272
- {% endfor %}
273
- {% if model.architecture.get('instruct_type') %}
274
- <span class="badge badge-tool">{{ model.architecture.instruct_type }}</span>
275
- {% endif %}
276
- </div>
277
- </td>
278
- </tr>
279
- {% endfor %}
280
- </tbody>
281
- </table>
 
 
 
 
 
282
 
283
  <footer>
284
- <p>Data auto-refreshes every 3 hours โ€ข Powered by <a href="https://openrouter.ai" target="_blank" style="color:#00d4ff;">OpenRouter</a></p>
 
285
  </footer>
286
  </div>
287
 
288
  <script>
 
 
289
  // Search functionality
290
- document.getElementById('searchBox').addEventListener('input', function(e) {
291
- const term = e.target.value.toLowerCase();
292
- document.querySelectorAll('#modelsTable tbody tr').forEach(row => {
 
 
 
293
  const text = row.textContent.toLowerCase();
294
- row.classList.toggle('hidden', !text.includes(term));
 
 
 
 
 
295
  });
 
 
 
296
  });
297
 
298
- // Sorting functionality
299
- let sortDir = {};
300
- document.querySelectorAll('th.sortable').forEach(th => {
301
- th.addEventListener('click', function() {
302
- const col = this.dataset.col;
303
- const table = document.getElementById('modelsTable');
304
- const tbody = table.querySelector('tbody');
305
- const rows = Array.from(tbody.querySelectorAll('tr'));
306
-
307
- document.querySelectorAll('th').forEach(h => h.classList.remove('sorted-asc', 'sorted-desc'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- sortDir[col] = !sortDir[col];
310
- this.classList.add(sortDir[col] ? 'sorted-asc' : 'sorted-desc');
 
311
 
312
- rows.sort((a, b) => {
313
- let aVal = a.dataset[col];
314
- let bVal = b.dataset[col];
315
-
316
- if (!isNaN(aVal) && !isNaN(bVal)) {
317
- aVal = parseFloat(aVal) || 0;
318
- bVal = parseFloat(bVal) || 0;
319
- }
320
-
321
- if (aVal < bVal) return sortDir[col] ? -1 : 1;
322
- if (aVal > bVal) return sortDir[col] ? 1 : -1;
323
- return 0;
324
- });
325
 
326
- rows.forEach(row => tbody.appendChild(row));
327
  });
328
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  </script>
330
  </body>
331
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>OpenRouter Models Explorer</title>
7
  <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --bg-primary: #0f172a;
16
+ --bg-secondary: #1e293b;
17
+ --bg-tertiary: #334155;
18
+ --accent-primary: #3b82f6;
19
+ --accent-secondary: #8b5cf6;
20
+ --text-primary: #f8fafc;
21
+ --text-secondary: #94a3b8;
22
+ --success: #22c55e;
23
+ --warning: #f59e0b;
24
+ --error: #ef4444;
25
+ }
26
+
27
  body {
28
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
29
+ background: var(--bg-primary);
30
+ color: var(--text-primary);
31
  min-height: 100vh;
32
+ padding: 20px;
33
  }
34
+
35
  .container {
36
  max-width: 1400px;
37
  margin: 0 auto;
 
38
  }
39
+
40
  header {
41
  text-align: center;
42
+ padding: 40px 20px;
43
+ background: linear-gradient(135deg, var(--bg-secondary) 0%, var(--bg-tertiary) 100%);
44
+ border-radius: 16px;
45
+ margin-bottom: 30px;
 
46
  border: 1px solid rgba(255,255,255,0.1);
47
  }
48
+
49
+ header h1 {
50
  font-size: 2.5rem;
51
+ margin-bottom: 10px;
52
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
53
  -webkit-background-clip: text;
54
  -webkit-text-fill-color: transparent;
55
+ background-clip: text;
56
  }
57
+
58
+ header p {
59
+ color: var(--text-secondary);
60
  font-size: 1.1rem;
61
  }
62
+
63
  .controls {
64
  display: flex;
 
 
65
  flex-wrap: wrap;
66
+ gap: 15px;
67
+ margin-bottom: 20px;
68
+ padding: 20px;
69
+ background: var(--bg-secondary);
70
+ border-radius: 12px;
71
  align-items: center;
72
  }
73
+
74
  .search-box {
75
  flex: 1;
76
+ min-width: 280px;
77
+ position: relative;
78
+ }
79
+
80
+ .search-box input {
81
+ width: 100%;
82
+ padding: 12px 20px;
83
+ border: 2px solid var(--bg-tertiary);
84
+ border-radius: 8px;
85
+ background: var(--bg-primary);
86
+ color: var(--text-primary);
87
  font-size: 1rem;
88
+ transition: all 0.3s ease;
 
89
  }
90
+
91
+ .search-box input:focus {
92
+ outline: none;
93
+ border-color: var(--accent-primary);
94
  }
95
+
96
  .btn {
97
+ padding: 12px 24px;
98
  border: none;
99
+ border-radius: 8px;
100
  cursor: pointer;
 
101
  font-weight: 600;
 
102
  text-decoration: none;
103
  display: inline-flex;
104
  align-items: center;
105
+ gap: 8px;
106
+ transition: all 0.3s ease;
107
+ font-size: 0.95rem;
108
  }
109
+
110
  .btn-primary {
111
+ background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
112
  color: white;
113
  }
114
+
115
+ .btn-primary:hover {
116
+ transform: translateY(-2px);
117
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
118
+ }
119
+
120
  .btn-secondary {
121
+ background: var(--bg-tertiary);
122
+ color: var(--text-primary);
 
123
  }
124
+
125
+ .btn-secondary:hover {
126
+ background: var(--accent-primary);
127
  }
128
+
129
  .stats-bar {
130
+ background: var(--bg-secondary);
131
+ padding: 15px 20px;
 
 
 
132
  border-radius: 12px;
133
+ margin-bottom: 20px;
134
+ display: flex;
135
+ gap: 30px;
136
+ flex-wrap: wrap;
137
+ justify-content: center;
138
  }
139
+
140
  .stat {
141
+ text-align: center;
 
142
  }
143
+
144
  .stat-value {
145
+ font-size: 2rem;
146
+ font-weight: 700;
147
+ color: var(--accent-primary);
148
  }
149
+
150
  .stat-label {
151
+ color: var(--text-secondary);
152
+ font-size: 0.9rem;
 
153
  }
154
+
155
+ .table-container {
156
+ overflow-x: auto;
157
+ background: var(--bg-secondary);
158
+ border-radius: 12px;
159
+ border: 1px solid rgba(255,255,255,0.1);
160
+ }
161
+
162
  table {
163
  width: 100%;
164
  border-collapse: collapse;
165
+ min-width: 900px;
 
 
 
 
 
 
166
  }
167
+
168
  th {
169
+ background: var(--bg-tertiary);
170
+ padding: 16px;
171
  text-align: left;
172
  font-weight: 600;
 
 
 
173
  cursor: pointer;
174
  user-select: none;
 
175
  position: relative;
176
+ transition: background 0.2s;
177
  }
178
+
179
  th:hover {
180
+ background: var(--accent-primary);
181
  }
182
+
183
+ th .sort-indicator {
184
+ margin-left: 8px;
 
185
  opacity: 0.5;
186
  }
187
+
188
+ th.sort-asc .sort-indicator::after {
189
  content: 'โ–ฒ';
190
  opacity: 1;
191
  }
192
+
193
+ th.sort-desc .sort-indicator::after {
194
  content: 'โ–ผ';
195
  opacity: 1;
196
  }
197
+
198
+ td {
199
+ padding: 14px 16px;
200
  border-bottom: 1px solid rgba(255,255,255,0.05);
201
+ color: var(--text-secondary);
 
 
 
202
  }
203
+
204
+ tr:hover {
205
+ background: rgba(59, 130, 246, 0.05);
206
  }
207
+
208
  .model-name {
209
+ color: var(--text-primary);
210
  font-weight: 600;
 
 
 
 
 
211
  }
212
+
213
+ .model-id {
 
 
 
 
 
 
 
214
  font-size: 0.85rem;
215
+ color: var(--text-secondary);
216
+ font-family: monospace;
217
  }
218
+
219
  .architecture {
220
  display: flex;
221
+ gap: 6px;
222
  flex-wrap: wrap;
223
  }
224
+
225
  .badge {
226
+ padding: 4px 10px;
227
+ border-radius: 20px;
228
+ font-size: 0.75rem;
229
+ font-weight: 500;
230
+ }
231
+
232
+ .badge-primary {
233
+ background: rgba(59, 130, 246, 0.2);
234
+ color: var(--accent-primary);
235
+ }
236
+
237
+ .badge-success {
238
+ background: rgba(34, 197, 94, 0.2);
239
+ color: var(--success);
240
+ }
241
+
242
+ .badge-warning {
243
+ background: rgba(245, 158, 11, 0.2);
244
+ color: var(--warning);
245
+ }
246
+
247
+ .context {
248
+ font-family: monospace;
249
+ color: var(--accent-secondary);
250
+ }
251
+
252
+ .price {
253
+ font-family: monospace;
254
+ font-weight: 600;
255
+ }
256
+
257
+ .price input {
258
+ color: var(--success);
259
  }
260
+
261
+ .price output {
262
+ color: var(--success);
263
  }
264
+
265
+ .status {
266
+ display: inline-flex;
267
+ align-items: center;
268
+ gap: 6px;
269
+ }
270
+
271
+ .status::before {
272
+ content: '';
273
+ width: 8px;
274
+ height: 8px;
275
+ border-radius: 50%;
276
+ background: var(--success);
277
  }
278
+
279
  footer {
 
280
  text-align: center;
281
+ padding: 40px;
282
+ color: var(--text-secondary);
283
+ margin-top: 20px;
284
+ }
285
+
286
+ .no-results {
287
+ text-align: center;
288
+ padding: 60px;
289
+ color: var(--text-secondary);
290
  }
291
+
292
  @media (max-width: 768px) {
293
+ header h1 {
294
+ font-size: 1.8rem;
295
+ }
296
+
297
+ .controls {
298
+ flex-direction: column;
299
+ width: 100%;
300
+ }
301
+
302
+ .search-box {
303
+ min-width: 100%;
304
+ }
305
+
306
+ .btn {
307
+ width: 100%;
308
+ justify-content: center;
309
+ }
310
  }
311
  </style>
312
  </head>
313
  <body>
314
  <div class="container">
315
  <header>
316
+ <h1>๐Ÿ”ฎ OpenRouter Models Explorer</h1>
317
+ <p>Explore {{ models|length }} AI models with real-time sorting and search</p>
318
  </header>
319
 
320
  <div class="stats-bar">
321
  <div class="stat">
322
+ <div class="stat-value">{{ models|length }}</div>
323
+ <div class="stat-label">Total Models</div>
324
  </div>
325
  <div class="stat">
326
+ <div class="stat-value">{{ unique_providers }}</div>
327
+ <div class="stat-label">Providers</div>
328
  </div>
329
  <div class="stat">
330
+ <div class="stat-value" id="visible-count">{{ models|length }}</div>
331
+ <div class="stat-label">Visible</div>
332
  </div>
333
  </div>
334
 
335
  <div class="controls">
336
+ <div class="search-box">
337
+ <input type="text" id="searchInput" placeholder="๐Ÿ” Search models, providers, or architecture...">
338
+ </div>
339
+ <a href="/api/raw" target="_blank" class="btn btn-secondary">๐Ÿ“„ View JSON</a>
340
+ <a href="/api/download" class="btn btn-primary">โฌ‡๏ธ Download</a>
341
+ <a href="https://openrouter.ai" target="_blank" class="btn btn-secondary">OpenRouter โ†’</a>
342
  </div>
343
 
344
+ <div class="table-container">
345
+ <table id="modelsTable">
346
+ <thead>
347
+ <tr>
348
+ <th onclick="sortTable(0)">Model <span class="sort-indicator">โ‡…</span></th>
349
+ <th onclick="sortTable(1)">Provider <span class="sort-indicator">โ‡…</span></th>
350
+ <th onclick="sortTable(2)">Architecture <span class="sort-indicator">โ‡…</span></th>
351
+ <th onclick="sortTable(3)" style="text-align: right;">Context <span class="sort-indicator">โ‡…</span></th>
352
+ <th onclick="sortTable(4)" style="text-align: right;">Prompt $/1M <span class="sort-indicator">โ‡…</span></th>
353
+ <th onclick="sortTable(5)" style="text-align: right;">Completion $/1M <span class="sort-indicator">โ‡…</span></th>
354
+ <th>Status</th>
355
+ </tr>
356
+ </thead>
357
+ <tbody>
358
+ {% for model in models %}
359
+ <tr>
360
+ <td>
361
+ <div class="model-name">{{ model.name.split('/', 1)[-1] if '/' in model.name else model.name }}</div>
362
+ <div class="model-id">{{ model.id }}</div>
363
+ </td>
364
+ <td>
365
+ <span class="badge badge-primary">{{ model.provider }}</span>
366
+ </td>
367
+ <td>
368
+ <div class="architecture">
369
+ {% for arch in model.architecture %}
370
+ <span class="badge badge-success">{{ arch }}</span>
371
+ {% endfor %}
372
+ </div>
373
+ </td>
374
+ <td style="text-align: right;" class="context">{{ "{:,}".format(model.context) }}</td>
375
+ <td style="text-align: right;" class="price">${{ "{:.4f}".format(model.prompt_price) }}</td>
376
+ <td style="text-align: right;" class="price">${{ "{:.4f}".format(model.completion_price) }}</td>
377
+ <td><span class="status">Available</span></td>
378
+ </tr>
379
+ {% endfor %}
380
+ </tbody>
381
+ </table>
382
+ <div id="noResults" class="no-results" style="display: none;">
383
+ <h3>No models found</h3>
384
+ <p>Try adjusting your search criteria</p>
385
+ </div>
386
+ </div>
387
 
388
  <footer>
389
+ <p>Data updates every 3 hours โ€ข Powered by OpenRouter API</p>
390
+ <p style="margin-top: 10px; font-size: 0.85rem;">Built with Flask on HuggingFace Spaces</p>
391
  </footer>
392
  </div>
393
 
394
  <script>
395
+ let currentSort = { column: -1, direction: 'asc' };
396
+
397
  // Search functionality
398
+ document.getElementById('searchInput').addEventListener('input', function(e) {
399
+ const searchTerm = e.target.value.toLowerCase();
400
+ const rows = document.querySelectorAll('#modelsTable tbody tr');
401
+ let visibleCount = 0;
402
+
403
+ rows.forEach(row => {
404
  const text = row.textContent.toLowerCase();
405
+ if (text.includes(searchTerm)) {
406
+ row.style.display = '';
407
+ visibleCount++;
408
+ } else {
409
+ row.style.display = 'none';
410
+ }
411
  });
412
+
413
+ document.getElementById('visible-count').textContent = visibleCount;
414
+ document.getElementById('noResults').style.display = visibleCount === 0 ? 'block' : 'none';
415
  });
416
 
417
+ // Sort functionality
418
+ function sortTable(columnIndex) {
419
+ const table = document.getElementById('modelsTable');
420
+ const tbody = table.querySelector('tbody');
421
+ const rows = Array.from(tbody.querySelectorAll('tr'));
422
+ const headers = table.querySelectorAll('th');
423
+
424
+ // Update sort direction
425
+ if (currentSort.column === columnIndex) {
426
+ currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
427
+ } else {
428
+ currentSort.direction = 'asc';
429
+ currentSort.column = columnIndex;
430
+ }
431
+
432
+ // Update header classes
433
+ headers.forEach((header, index) => {
434
+ header.classList.remove('sort-asc', 'sort-desc');
435
+ if (index === columnIndex) {
436
+ header.classList.add(currentSort.direction === 'asc' ? 'sort-asc' : 'sort-desc');
437
+ }
438
+ });
439
+
440
+ // Sort rows
441
+ rows.sort((a, b) => {
442
+ const aText = a.cells[columnIndex].textContent.trim();
443
+ const bText = b.cells[columnIndex].textContent.trim();
444
 
445
+ // Check if numeric
446
+ const aNum = parseFloat(aText.replace(/[$,]/g, ''));
447
+ const bNum = parseFloat(bText.replace(/[$,]/g, ''));
448
 
449
+ let comparison;
450
+ if (!isNaN(aNum) && !isNaN(bNum)) {
451
+ comparison = aNum - bNum;
452
+ } else {
453
+ comparison = aText.localeCompare(bText);
454
+ }
 
 
 
 
 
 
 
455
 
456
+ return currentSort.direction === 'asc' ? comparison : -comparison;
457
  });
458
+
459
+ // Reorder rows
460
+ rows.forEach(row => tbody.appendChild(row));
461
+ }
462
+
463
+ // Auto-refresh indicator
464
+ let lastUpdate = new Date();
465
+ setInterval(() => {
466
+ const now = new Date();
467
+ const elapsed = Math.floor((now - lastUpdate) / 1000);
468
+ const hours = Math.floor(elapsed / 3600);
469
+ const minutes = Math.floor((elapsed % 3600) / 60);
470
+
471
+ // Could update a "last updated" timestamp here
472
+ }, 60000); // Every minute
473
  </script>
474
  </body>
475
  </html>