prithivMLmods commited on
Commit
218eba0
·
verified ·
1 Parent(s): 6f8930e

update [node.js (vite) ] ✅

Browse files
Files changed (7) hide show
  1. index.css +881 -0
  2. index.html +177 -0
  3. index.tsx +511 -0
  4. metadata.json +5 -0
  5. package.json +23 -0
  6. tsconfig.json +29 -0
  7. vite.config.ts +23 -0
index.css ADDED
@@ -0,0 +1,881 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700&display=swap');
2
+
3
+ :root {
4
+ --bg: #ffffff;
5
+ --text: #1a1a2e;
6
+ --text-secondary: #64748b;
7
+ --header-bg: #f8fafc;
8
+ --border: #e2e8f0;
9
+ --accent: #ff9d00;
10
+ --accent-hover: #e68a00;
11
+ --accent-light: rgba(255, 157, 0, 0.08);
12
+ --accent-border: rgba(255, 157, 0, 0.25);
13
+ --panel-bg: #ffffff;
14
+ --folder-color: #ff9d00;
15
+ --file-color: #94a3b8;
16
+ --line-num: #cbd5e1;
17
+ --tree-line: #94a3b8;
18
+ --btn-bg: #f1f5f9;
19
+ --btn-border: #e2e8f0;
20
+ --btn-hover: #e2e8f0;
21
+ --shadow: rgba(0, 0, 0, 0.06);
22
+ --shadow-lg: rgba(0, 0, 0, 0.1);
23
+ --model-color: #f59e0b;
24
+ --dataset-color: #10b981;
25
+ --space-color: #6366f1;
26
+ --tag-model: rgba(245, 158, 11, 0.1);
27
+ --tag-dataset: rgba(16, 185, 129, 0.1);
28
+ --tag-space: rgba(99, 102, 241, 0.1);
29
+ --mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
30
+ --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
31
+ --radius: 10px;
32
+ --radius-sm: 6px;
33
+ }
34
+
35
+ [data-theme="dark"] {
36
+ --bg: #0f172a;
37
+ --text: #e2e8f0;
38
+ --text-secondary: #94a3b8;
39
+ --header-bg: #1e293b;
40
+ --border: #334155;
41
+ --accent: #fbbf24;
42
+ --accent-hover: #f59e0b;
43
+ --accent-light: rgba(251, 191, 36, 0.08);
44
+ --accent-border: rgba(251, 191, 36, 0.2);
45
+ --panel-bg: #1e293b;
46
+ --folder-color: #fbbf24;
47
+ --file-color: #64748b;
48
+ --line-num: #475569;
49
+ --tree-line: #475569;
50
+ --btn-bg: #1e293b;
51
+ --btn-border: #334155;
52
+ --btn-hover: #334155;
53
+ --shadow: rgba(0, 0, 0, 0.3);
54
+ --shadow-lg: rgba(0, 0, 0, 0.5);
55
+ --tag-model: rgba(251, 191, 36, 0.1);
56
+ --tag-dataset: rgba(16, 185, 129, 0.1);
57
+ --tag-space: rgba(99, 102, 241, 0.12);
58
+ }
59
+
60
+ * { box-sizing: border-box; margin: 0; padding: 0; }
61
+
62
+ body {
63
+ font-family: var(--sans);
64
+ background: var(--bg);
65
+ color: var(--text);
66
+ transition: background 0.3s ease, color 0.3s ease;
67
+ -webkit-font-smoothing: antialiased;
68
+ -moz-osx-font-smoothing: grayscale;
69
+ text-rendering: optimizeLegibility;
70
+ }
71
+
72
+ button, input, select { font-family: inherit; }
73
+ a { text-decoration: none; color: inherit; }
74
+
75
+ .app-container {
76
+ max-width: 1000px;
77
+ margin: 0 auto;
78
+ padding: 24px 20px 40px;
79
+ min-height: 100vh;
80
+ display: flex;
81
+ flex-direction: column;
82
+ }
83
+
84
+ /* Header */
85
+ header {
86
+ display: flex;
87
+ justify-content: space-between;
88
+ align-items: center;
89
+ margin-bottom: 32px;
90
+ padding-top: 8px;
91
+ }
92
+
93
+ .brand a {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 12px;
97
+ font-size: 1.4rem;
98
+ font-weight: 700;
99
+ letter-spacing: -0.02em;
100
+ }
101
+
102
+ .brand-icon {
103
+ width: 38px;
104
+ height: 38px;
105
+ background: linear-gradient(135deg, #ff9d00 0%, #ffb340 100%);
106
+ border-radius: var(--radius);
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ color: white;
111
+ font-size: 1.1rem;
112
+ box-shadow: 0 2px 8px rgba(255, 157, 0, 0.3);
113
+ }
114
+
115
+ .header-actions {
116
+ display: flex;
117
+ gap: 12px;
118
+ align-items: center;
119
+ }
120
+
121
+ .icon-btn {
122
+ background: var(--btn-bg);
123
+ border: 1px solid var(--btn-border);
124
+ font-size: 1.1rem;
125
+ color: var(--text);
126
+ width: 38px;
127
+ height: 38px;
128
+ cursor: pointer;
129
+ border-radius: var(--radius);
130
+ transition: all 0.2s;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ }
135
+
136
+ .icon-btn:hover { background: var(--btn-hover); border-color: var(--accent); }
137
+
138
+ .text-btn {
139
+ background: var(--btn-bg);
140
+ border: 1px solid var(--btn-border);
141
+ font-size: 0.82rem;
142
+ color: var(--text);
143
+ cursor: pointer;
144
+ font-weight: 600;
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 6px;
148
+ padding: 8px 14px;
149
+ border-radius: var(--radius);
150
+ transition: all 0.2s;
151
+ letter-spacing: -0.01em;
152
+ }
153
+
154
+ .text-btn:hover { border-color: var(--accent); color: var(--accent); }
155
+
156
+ .sun-icon { display: none !important; }
157
+ [data-theme="dark"] .sun-icon { display: inline-block !important; color: #fbbf24; }
158
+ [data-theme="dark"] .moon-icon { display: none !important; }
159
+ [data-theme="light"] .moon-icon { display: inline-block !important; color: #64748b; }
160
+
161
+ /* Token Panel */
162
+ .token-panel {
163
+ background: var(--header-bg);
164
+ border: 1px solid var(--border);
165
+ padding: 16px;
166
+ border-radius: var(--radius);
167
+ margin-bottom: 20px;
168
+ animation: slideDown 0.25s ease-out;
169
+ }
170
+
171
+ @keyframes slideDown {
172
+ from { opacity: 0; transform: translateY(-8px); }
173
+ to { opacity: 1; transform: translateY(0); }
174
+ }
175
+
176
+ .token-inner {
177
+ display: flex;
178
+ gap: 10px;
179
+ align-items: center;
180
+ }
181
+
182
+ .token-inner i { color: var(--accent); font-size: 0.9rem; }
183
+
184
+ .token-inner input {
185
+ background: var(--bg);
186
+ border: 1px solid var(--border);
187
+ padding: 9px 14px;
188
+ border-radius: var(--radius-sm);
189
+ flex: 1;
190
+ color: var(--text);
191
+ font-family: var(--mono);
192
+ font-size: 0.82rem;
193
+ }
194
+
195
+ .token-inner input:focus {
196
+ outline: none;
197
+ border-color: var(--accent);
198
+ box-shadow: 0 0 0 3px var(--accent-border);
199
+ }
200
+
201
+ .token-inner button {
202
+ background: var(--accent);
203
+ color: white;
204
+ border: none;
205
+ padding: 9px 18px;
206
+ border-radius: var(--radius-sm);
207
+ cursor: pointer;
208
+ font-weight: 600;
209
+ font-size: 0.82rem;
210
+ transition: background 0.2s;
211
+ }
212
+
213
+ .token-inner button:hover { background: var(--accent-hover); }
214
+
215
+ .token-warning {
216
+ font-size: 0.75rem;
217
+ color: var(--text-secondary);
218
+ margin: 10px 0 0 0;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 6px;
222
+ }
223
+
224
+ .token-warning i { color: #f59e0b; font-size: 0.75rem; }
225
+
226
+ /* Search */
227
+ .search-section {
228
+ display: flex;
229
+ gap: 10px;
230
+ flex-wrap: wrap;
231
+ margin-bottom: 20px;
232
+ }
233
+
234
+ .type-selector {
235
+ display: flex;
236
+ border: 1px solid var(--border);
237
+ border-radius: var(--radius);
238
+ overflow: hidden;
239
+ height: 44px;
240
+ }
241
+
242
+ .type-btn {
243
+ background: var(--btn-bg);
244
+ border: none;
245
+ border-right: 1px solid var(--border);
246
+ padding: 0 16px;
247
+ cursor: pointer;
248
+ font-size: 0.82rem;
249
+ font-weight: 600;
250
+ color: var(--text-secondary);
251
+ display: flex;
252
+ align-items: center;
253
+ gap: 7px;
254
+ transition: all 0.2s;
255
+ white-space: nowrap;
256
+ letter-spacing: -0.01em;
257
+ }
258
+
259
+ .type-btn:last-child { border-right: none; }
260
+ .type-btn:hover { background: var(--btn-hover); color: var(--text); }
261
+
262
+ .type-btn.active-model {
263
+ background: var(--tag-model);
264
+ color: var(--model-color);
265
+ }
266
+
267
+ .type-btn.active-dataset {
268
+ background: var(--tag-dataset);
269
+ color: var(--dataset-color);
270
+ }
271
+
272
+ .type-btn.active-space {
273
+ background: var(--tag-space);
274
+ color: var(--space-color);
275
+ }
276
+
277
+ .input-group {
278
+ background: var(--header-bg);
279
+ border: 1px solid var(--border);
280
+ border-radius: var(--radius);
281
+ display: flex;
282
+ align-items: center;
283
+ padding: 0 14px;
284
+ height: 44px;
285
+ flex: 1;
286
+ transition: border-color 0.2s, box-shadow 0.2s;
287
+ }
288
+
289
+ .input-group:focus-within {
290
+ border-color: var(--accent);
291
+ box-shadow: 0 0 0 3px var(--accent-border);
292
+ }
293
+
294
+ .prefix {
295
+ color: var(--text-secondary);
296
+ font-size: 0.8rem;
297
+ margin-right: 4px;
298
+ white-space: nowrap;
299
+ font-family: var(--mono);
300
+ font-weight: 500;
301
+ }
302
+
303
+ input {
304
+ background: transparent;
305
+ border: none;
306
+ color: var(--text);
307
+ width: 100%;
308
+ outline: none;
309
+ font-size: 0.88rem;
310
+ font-family: var(--mono);
311
+ font-weight: 500;
312
+ }
313
+
314
+ .branch-input { max-width: 150px; }
315
+
316
+ .primary-btn {
317
+ background: var(--accent);
318
+ color: white;
319
+ border: none;
320
+ padding: 0 28px;
321
+ border-radius: var(--radius);
322
+ font-weight: 700;
323
+ cursor: pointer;
324
+ height: 44px;
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 10px;
328
+ font-size: 0.85rem;
329
+ transition: all 0.2s;
330
+ letter-spacing: -0.01em;
331
+ box-shadow: 0 2px 6px rgba(255, 157, 0, 0.2);
332
+ }
333
+
334
+ .primary-btn:hover {
335
+ background: var(--accent-hover);
336
+ box-shadow: 0 4px 12px rgba(255, 157, 0, 0.3);
337
+ transform: translateY(-1px);
338
+ }
339
+
340
+ .primary-btn:disabled { opacity: 0.6; cursor: wait; transform: none; }
341
+
342
+ /* Status */
343
+ #statusMsg {
344
+ padding: 12px 16px;
345
+ margin-bottom: 16px;
346
+ font-size: 0.85rem;
347
+ text-align: center;
348
+ font-weight: 500;
349
+ border-radius: var(--radius);
350
+ letter-spacing: -0.01em;
351
+ }
352
+
353
+ .error {
354
+ color: #ef4444;
355
+ background: rgba(239, 68, 68, 0.08);
356
+ border: 1px solid rgba(239, 68, 68, 0.15);
357
+ }
358
+
359
+ .loading {
360
+ color: var(--accent);
361
+ background: var(--accent-light);
362
+ border: 1px solid var(--accent-border);
363
+ }
364
+
365
+ /* Empty State */
366
+ .empty-state { text-align: center; }
367
+
368
+ .empty-state h3 {
369
+ font-weight: 600;
370
+ margin-top: 16px;
371
+ letter-spacing: -0.02em;
372
+ }
373
+
374
+ .empty-state p {
375
+ color: var(--text-secondary);
376
+ margin-bottom: 24px;
377
+ font-size: 0.92rem;
378
+ }
379
+
380
+ .tag-cloud {
381
+ display: flex;
382
+ justify-content: center;
383
+ gap: 10px;
384
+ flex-wrap: wrap;
385
+ max-width: 850px;
386
+ margin: 0 auto;
387
+ }
388
+
389
+ .repo-tag {
390
+ background: var(--btn-bg);
391
+ border: 1px solid var(--border);
392
+ color: var(--text);
393
+ padding: 8px 16px;
394
+ border-radius: 24px;
395
+ cursor: pointer;
396
+ font-size: 0.8rem;
397
+ font-family: var(--mono);
398
+ font-weight: 500;
399
+ transition: all 0.2s;
400
+ display: flex;
401
+ align-items: center;
402
+ gap: 8px;
403
+ }
404
+
405
+ .repo-tag:hover {
406
+ border-color: var(--accent);
407
+ transform: translateY(-2px);
408
+ box-shadow: 0 4px 12px var(--shadow);
409
+ }
410
+
411
+ .tag-type {
412
+ font-size: 0.65rem;
413
+ padding: 2px 7px;
414
+ border-radius: 8px;
415
+ font-weight: 700;
416
+ text-transform: uppercase;
417
+ letter-spacing: 0.5px;
418
+ }
419
+
420
+ .tag-type.model { background: var(--tag-model); color: var(--model-color); }
421
+ .tag-type.dataset { background: var(--tag-dataset); color: var(--dataset-color); }
422
+ .tag-type.space { background: var(--tag-space); color: var(--space-color); }
423
+
424
+ .homepage-section { margin-bottom: 36px; }
425
+
426
+ .homepage-section h3 {
427
+ font-size: 0.85rem;
428
+ color: var(--text-secondary);
429
+ text-transform: uppercase;
430
+ letter-spacing: 1.5px;
431
+ font-weight: 600;
432
+ margin-bottom: 16px;
433
+ }
434
+
435
+ /* Repo Info */
436
+ .repo-info-card {
437
+ background: var(--header-bg);
438
+ border: 1px solid var(--border);
439
+ border-radius: var(--radius);
440
+ padding: 14px 20px;
441
+ margin-bottom: 12px;
442
+ display: none;
443
+ animation: slideDown 0.25s ease-out;
444
+ }
445
+
446
+ .repo-info-card.visible { display: block; }
447
+
448
+ .repo-info-header {
449
+ display: flex;
450
+ justify-content: space-between;
451
+ align-items: center;
452
+ flex-wrap: wrap;
453
+ gap: 10px;
454
+ }
455
+
456
+ .repo-info-left {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 12px;
460
+ }
461
+
462
+ .repo-type-badge {
463
+ font-size: 0.68rem;
464
+ padding: 4px 10px;
465
+ border-radius: 8px;
466
+ font-weight: 700;
467
+ text-transform: uppercase;
468
+ letter-spacing: 0.5px;
469
+ }
470
+
471
+ .repo-type-badge.model { background: var(--tag-model); color: var(--model-color); }
472
+ .repo-type-badge.dataset { background: var(--tag-dataset); color: var(--dataset-color); }
473
+ .repo-type-badge.space { background: var(--tag-space); color: var(--space-color); }
474
+
475
+ .repo-info-name {
476
+ font-size: 1rem;
477
+ font-weight: 600;
478
+ font-family: var(--mono);
479
+ letter-spacing: -0.02em;
480
+ }
481
+
482
+ .repo-info-name a { color: var(--accent); }
483
+ .repo-info-name a:hover { text-decoration: underline; }
484
+
485
+ .repo-info-stats {
486
+ display: flex;
487
+ gap: 18px;
488
+ font-size: 0.8rem;
489
+ color: var(--text-secondary);
490
+ font-weight: 500;
491
+ }
492
+
493
+ .repo-info-stats span {
494
+ display: flex;
495
+ align-items: center;
496
+ gap: 5px;
497
+ }
498
+
499
+ /* Tree Wrapper */
500
+ .tree-wrapper {
501
+ border: 1px solid var(--border);
502
+ border-radius: var(--radius);
503
+ display: flex;
504
+ flex-direction: column;
505
+ background: var(--panel-bg);
506
+ box-shadow: 0 4px 16px var(--shadow);
507
+ overflow: visible;
508
+ position: relative;
509
+ z-index: 10;
510
+ }
511
+
512
+ .toolbar {
513
+ background: var(--header-bg);
514
+ padding: 10px 16px;
515
+ border-bottom: 1px solid var(--border);
516
+ display: flex;
517
+ justify-content: space-between;
518
+ align-items: center;
519
+ border-radius: var(--radius) var(--radius) 0 0;
520
+ }
521
+
522
+ .tools-group {
523
+ display: flex;
524
+ gap: 8px;
525
+ }
526
+
527
+ .tool-btn {
528
+ background: var(--btn-bg);
529
+ border: 1px solid var(--btn-border);
530
+ color: var(--text);
531
+ padding: 7px 14px;
532
+ border-radius: var(--radius-sm);
533
+ font-size: 0.8rem;
534
+ font-weight: 600;
535
+ cursor: pointer;
536
+ display: flex;
537
+ align-items: center;
538
+ gap: 7px;
539
+ transition: all 0.2s;
540
+ letter-spacing: -0.01em;
541
+ }
542
+
543
+ .tool-btn:hover { background: var(--btn-hover); border-color: var(--tree-line); }
544
+
545
+ .action-btn {
546
+ color: #fff;
547
+ background: var(--accent);
548
+ border-color: var(--accent);
549
+ }
550
+
551
+ .action-btn:hover {
552
+ background: var(--accent-hover);
553
+ border-color: var(--accent-hover);
554
+ }
555
+
556
+ /* Dropdowns */
557
+ .dropdown { position: relative; }
558
+
559
+ .dropdown-content {
560
+ display: none;
561
+ position: absolute;
562
+ top: 100%;
563
+ left: 0;
564
+ margin-top: 6px;
565
+ background: var(--panel-bg);
566
+ border: 1px solid var(--border);
567
+ border-radius: var(--radius);
568
+ box-shadow: 0 12px 32px var(--shadow-lg);
569
+ z-index: 100;
570
+ min-width: 200px;
571
+ padding: 6px;
572
+ backdrop-filter: blur(10px);
573
+ }
574
+
575
+ .dropdown.active .dropdown-content { display: block; }
576
+
577
+ .dropdown-content a {
578
+ display: block;
579
+ padding: 10px 14px;
580
+ color: var(--text);
581
+ font-size: 0.85rem;
582
+ border-radius: var(--radius-sm);
583
+ font-weight: 500;
584
+ transition: all 0.15s;
585
+ }
586
+
587
+ .dropdown-content a:hover {
588
+ background: var(--accent);
589
+ color: white;
590
+ }
591
+
592
+ /* Terminal */
593
+ .terminal-window {
594
+ background: var(--panel-bg);
595
+ display: flex;
596
+ overflow: auto;
597
+ font-family: var(--mono);
598
+ font-size: 13px;
599
+ line-height: 1.7;
600
+ border-radius: 0 0 var(--radius) var(--radius);
601
+ }
602
+
603
+ .line-col {
604
+ padding: 16px 12px;
605
+ text-align: right;
606
+ color: var(--line-num);
607
+ border-right: 1px solid var(--border);
608
+ min-width: 48px;
609
+ user-select: none;
610
+ font-size: 0.75rem;
611
+ font-weight: 500;
612
+ }
613
+
614
+ .code-col {
615
+ padding: 16px;
616
+ flex: 1;
617
+ white-space: pre;
618
+ }
619
+
620
+ .tree-line {
621
+ display: flex;
622
+ align-items: center;
623
+ height: 26px;
624
+ border-radius: 4px;
625
+ padding: 0 4px;
626
+ }
627
+
628
+ .tree-line:hover { background: var(--accent-light); }
629
+
630
+ .t-prefix {
631
+ color: var(--tree-line);
632
+ white-space: pre;
633
+ margin-right: 4px;
634
+ }
635
+
636
+ .t-icon {
637
+ width: 22px;
638
+ text-align: center;
639
+ display: inline-block;
640
+ margin-right: 7px;
641
+ font-size: 0.85rem;
642
+ }
643
+
644
+ .fa-folder { color: var(--folder-color); }
645
+ .fa-file { color: var(--file-color); }
646
+
647
+ .t-name {
648
+ color: var(--text);
649
+ font-weight: 500;
650
+ }
651
+
652
+ .t-name.folder { font-weight: 700; }
653
+
654
+ .t-size {
655
+ color: var(--text-secondary);
656
+ font-size: 0.72rem;
657
+ margin-left: 12px;
658
+ font-weight: 500;
659
+ opacity: 0.8;
660
+ }
661
+
662
+ .sub-copy {
663
+ opacity: 0;
664
+ margin-left: 8px;
665
+ background: none;
666
+ border: none;
667
+ color: var(--text-secondary);
668
+ cursor: pointer;
669
+ padding: 3px;
670
+ font-size: 0.78rem;
671
+ transition: all 0.2s;
672
+ display: flex;
673
+ align-items: center;
674
+ }
675
+
676
+ .tree-line:hover .sub-copy { opacity: 1; }
677
+ .sub-copy:hover { color: var(--accent); transform: scale(1.15); }
678
+
679
+ .fa-check { color: #22c55e; }
680
+
681
+ #lineNumbers div { height: 26px; display: flex; align-items: center; justify-content: flex-end; }
682
+
683
+ .lfs-badge {
684
+ font-size: 0.6rem;
685
+ padding: 1px 6px;
686
+ border-radius: 4px;
687
+ background: var(--tag-dataset);
688
+ color: var(--dataset-color);
689
+ margin-left: 8px;
690
+ font-weight: 700;
691
+ letter-spacing: 0.5px;
692
+ }
693
+
694
+ /* Tree Search */
695
+ .tree-search {
696
+ padding: 10px 16px;
697
+ border-bottom: 1px solid var(--border);
698
+ background: var(--header-bg);
699
+ }
700
+
701
+ .tree-search input {
702
+ width: 100%;
703
+ padding: 9px 14px;
704
+ background: var(--bg);
705
+ border: 1px solid var(--border);
706
+ border-radius: var(--radius-sm);
707
+ color: var(--text);
708
+ font-family: var(--mono);
709
+ font-size: 0.82rem;
710
+ font-weight: 500;
711
+ }
712
+
713
+ .tree-search input:focus {
714
+ border-color: var(--accent);
715
+ outline: none;
716
+ box-shadow: 0 0 0 3px var(--accent-border);
717
+ }
718
+
719
+ /* Overlay */
720
+ .overlay {
721
+ position: fixed;
722
+ top: 0; left: 0;
723
+ width: 100%; height: 100%;
724
+ background: rgba(0, 0, 0, 0.4);
725
+ backdrop-filter: blur(6px);
726
+ display: none;
727
+ justify-content: center;
728
+ align-items: center;
729
+ z-index: 1000;
730
+ }
731
+
732
+ .overlay.visible { display: flex; }
733
+
734
+ .overlay-content {
735
+ background: var(--panel-bg);
736
+ padding: 32px;
737
+ border-radius: 16px;
738
+ width: 90%;
739
+ max-width: 480px;
740
+ border: 1px solid var(--border);
741
+ box-shadow: 0 24px 48px var(--shadow-lg);
742
+ position: relative;
743
+ animation: slideUp 0.25s ease-out;
744
+ }
745
+
746
+ @keyframes slideUp {
747
+ from { transform: translateY(12px); opacity: 0; }
748
+ to { transform: translateY(0); opacity: 1; }
749
+ }
750
+
751
+ .overlay-content h3 {
752
+ margin: 0 0 20px 0;
753
+ font-size: 1.15rem;
754
+ font-weight: 700;
755
+ letter-spacing: -0.02em;
756
+ }
757
+
758
+ .share-box {
759
+ display: flex;
760
+ gap: 10px;
761
+ margin-top: 12px;
762
+ }
763
+
764
+ .share-box input {
765
+ background: var(--header-bg);
766
+ border: 1px solid var(--border);
767
+ padding: 12px 14px;
768
+ border-radius: var(--radius-sm);
769
+ color: var(--text);
770
+ font-family: var(--mono);
771
+ font-size: 0.85rem;
772
+ flex: 1;
773
+ font-weight: 500;
774
+ }
775
+
776
+ .share-box button {
777
+ background: var(--accent);
778
+ color: white;
779
+ border: none;
780
+ border-radius: var(--radius-sm);
781
+ padding: 0 20px;
782
+ cursor: pointer;
783
+ font-size: 1rem;
784
+ transition: background 0.2s;
785
+ }
786
+
787
+ .share-box button:hover { background: var(--accent-hover); }
788
+
789
+ .close-btn {
790
+ position: absolute;
791
+ top: 16px; right: 16px;
792
+ background: var(--btn-bg);
793
+ border: 1px solid var(--border);
794
+ width: 32px; height: 32px;
795
+ border-radius: 50%;
796
+ font-size: 1.1rem;
797
+ color: var(--text-secondary);
798
+ cursor: pointer;
799
+ display: flex;
800
+ align-items: center;
801
+ justify-content: center;
802
+ transition: all 0.2s;
803
+ }
804
+
805
+ .close-btn:hover { background: var(--btn-hover); color: var(--text); }
806
+
807
+ /* Footer */
808
+ .footer {
809
+ padding: 40px 20px;
810
+ text-align: center;
811
+ font-family: var(--mono);
812
+ font-weight: 500;
813
+ font-size: 0.75rem;
814
+ color: var(--text-secondary);
815
+ margin-top: auto;
816
+ }
817
+
818
+ .footer a {
819
+ color: var(--accent);
820
+ font-weight: 600;
821
+ text-decoration: none;
822
+ }
823
+
824
+ .footer a:hover { text-decoration: underline; }
825
+
826
+ /* Spinner */
827
+ .spinner {
828
+ display: inline-block;
829
+ width: 14px; height: 14px;
830
+ border: 2px solid var(--accent-border);
831
+ border-top-color: var(--accent);
832
+ border-radius: 50%;
833
+ animation: spin 0.6s linear infinite;
834
+ margin-right: 8px;
835
+ vertical-align: middle;
836
+ }
837
+
838
+ @keyframes spin { to { transform: rotate(360deg); } }
839
+ @keyframes fadeIn { to { opacity: 1; } }
840
+
841
+ /* Responsive */
842
+ @media (max-width: 640px) {
843
+ .search-section { flex-direction: column; }
844
+ .primary-btn { width: 100%; justify-content: center; }
845
+ .branch-input { max-width: 100%; }
846
+
847
+ .toolbar {
848
+ flex-direction: column;
849
+ align-items: stretch;
850
+ gap: 10px;
851
+ padding: 12px;
852
+ }
853
+
854
+ .tools-group {
855
+ width: 100%;
856
+ justify-content: space-between;
857
+ flex-wrap: wrap;
858
+ }
859
+
860
+ .tools-group.right { justify-content: flex-end; }
861
+ .repo-info-header { flex-direction: column; align-items: flex-start; }
862
+
863
+ .type-selector { width: 100%; }
864
+ .type-btn { flex: 1; justify-content: center; padding: 0 10px; }
865
+ .repo-info-stats { flex-wrap: wrap; gap: 10px; }
866
+ }
867
+
868
+ /* Scrollbar */
869
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
870
+ ::-webkit-scrollbar-thumb {
871
+ background: var(--border);
872
+ border-radius: 10px;
873
+ }
874
+ ::-webkit-scrollbar-thumb:hover { background: var(--tree-line); }
875
+ ::-webkit-scrollbar-track { background: transparent; }
876
+
877
+ /* Selection */
878
+ ::selection {
879
+ background: var(--accent);
880
+ color: white;
881
+ }
index.html ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HuggingFace Repo Structure Visualizer : HFTree</title>
7
+ <meta name="description" content="Effortlessly explore and visualize the file structure of any HuggingFace model, dataset, or space online without cloning.">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <link rel="stylesheet" href="index.css">
10
+ <script type="importmap">
11
+ {
12
+ "imports": {
13
+ "react": "https://esm.sh/react@19.0.0",
14
+ "react-dom": "https://esm.sh/react-dom@19.0.0",
15
+ "lucide-react": "https://esm.sh/lucide-react@0.474.0",
16
+ "@google/genai": "https://esm.sh/@google/genai@0.2.1"
17
+ }
18
+ }
19
+ </script>
20
+ <link rel="stylesheet" href="/index.css">
21
+ </head>
22
+ <body>
23
+ <div class="app-container">
24
+ <header>
25
+ <div class="brand">
26
+ <a href="#">
27
+ <div class="brand-icon"><i class="fas fa-tree"></i></div>
28
+ <span>hf-tree🤗</span>
29
+ </a>
30
+ </div>
31
+ <div class="header-actions">
32
+ <button id="privateRepoBtn" class="text-btn" title="Access Private Repos">
33
+ <i class="fas fa-lock"></i> Token
34
+ </button>
35
+ <button id="themeToggle" class="icon-btn" aria-label="Toggle Theme">
36
+ <i class="fas fa-sun sun-icon"></i>
37
+ <i class="fas fa-moon moon-icon"></i>
38
+ </button>
39
+ </div>
40
+ </header>
41
+
42
+ <div id="tokenSection" class="token-panel" style="display: none;">
43
+ <div class="token-inner">
44
+ <i class="fas fa-key"></i>
45
+ <input type="password" id="hfToken" placeholder="Paste HuggingFace Access Token (hf_...)">
46
+ <button id="saveTokenBtn">Save</button>
47
+ <button id="clearTokenBtn" style="display:none;">Clear</button>
48
+ </div>
49
+ <p class="token-warning">
50
+ <i class="fas fa-exclamation-triangle"></i> Token is saved to your browser's <b>LocalStorage</b>. Do not use on public computers.
51
+ </p>
52
+ </div>
53
+
54
+ <div class="search-section">
55
+ <div class="type-selector">
56
+ <button class="type-btn active-model" data-type="models" id="typeModel">
57
+ <i class="fas fa-cube"></i> Model
58
+ </button>
59
+ <button class="type-btn" data-type="datasets" id="typeDataset">
60
+ <i class="fas fa-database"></i> Dataset
61
+ </button>
62
+ <button class="type-btn" data-type="spaces" id="typeSpace">
63
+ <i class="fas fa-rocket"></i> Space
64
+ </button>
65
+ </div>
66
+ <div class="input-group main-input">
67
+ <span class="prefix" id="urlPrefix">hf.co/</span>
68
+ <input type="text" id="repoInput" placeholder="username/repo-name" autocomplete="off">
69
+ </div>
70
+ <div class="input-group branch-input">
71
+ <span class="prefix">rev:</span>
72
+ <input type="text" id="branchInput" placeholder="main" autocomplete="off">
73
+ </div>
74
+ <button id="fetchBtn" class="primary-btn">
75
+ <i class="fas fa-search"></i> Fetch
76
+ </button>
77
+ </div>
78
+
79
+ <div id="statusMsg" style="display: none;"></div>
80
+
81
+ <div id="repoInfoCard" class="repo-info-card">
82
+ <div class="repo-info-header">
83
+ <div class="repo-info-left">
84
+ <span class="repo-type-badge" id="repoTypeBadge">MODEL</span>
85
+ <span class="repo-info-name" id="repoInfoName"></span>
86
+ </div>
87
+ <div class="repo-info-stats" id="repoInfoStats"></div>
88
+ </div>
89
+ </div>
90
+
91
+ <div id="emptyState" class="empty-state">
92
+ <div class="homepage-section">
93
+ <h3 style="font-size: 1.3rem; text-transform: none; letter-spacing: -0.02em; color: var(--text);">
94
+ 🤗 Explore HuggingFace Repositories
95
+ </h3>
96
+ <p>Visualize the file tree of any Model, Dataset, or Space</p>
97
+ </div>
98
+ <div class="homepage-section">
99
+ <h3>Featured</h3>
100
+ <div id="featuredCloud" class="tag-cloud"></div>
101
+ </div>
102
+ <div class="homepage-section">
103
+ <h3>Popular Models</h3>
104
+ <div id="modelCloud" class="tag-cloud"></div>
105
+ </div>
106
+ <div class="homepage-section">
107
+ <h3>Popular Datasets</h3>
108
+ <div id="datasetCloud" class="tag-cloud"></div>
109
+ </div>
110
+ </div>
111
+
112
+ <div id="treeWrapper" class="tree-wrapper" style="display: none;">
113
+ <div class="toolbar">
114
+ <div class="tools-group">
115
+ <div class="dropdown">
116
+ <button class="tool-btn" id="sortBtnLabel">
117
+ <i class="fas fa-sort-amount-down"></i> Sort
118
+ </button>
119
+ <div class="dropdown-content">
120
+ <a href="#" data-sort="folder-az">Folders First (A-Z)</a>
121
+ <a href="#" data-sort="folder-za">Folders First (Z-A)</a>
122
+ <a href="#" data-sort="alpha-az">Name (A-Z)</a>
123
+ <a href="#" data-sort="alpha-za">Name (Z-A)</a>
124
+ <a href="#" data-sort="size-desc">Size (Largest)</a>
125
+ <a href="#" data-sort="size-asc">Size (Smallest)</a>
126
+ </div>
127
+ </div>
128
+ <div class="dropdown">
129
+ <button class="tool-btn" id="styleBtnLabel">
130
+ <i class="fas fa-code-branch"></i> Style
131
+ </button>
132
+ <div class="dropdown-content">
133
+ <a href="#" data-style="classic">Classic (└──)</a>
134
+ <a href="#" data-style="slashed">Slashed (/src)</a>
135
+ <a href="#" data-style="plus">ASCII (+--)</a>
136
+ <a href="#" data-style="minimal">Minimal (Indent)</a>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ <div class="tools-group right">
141
+ <button id="shareBtn" class="tool-btn">
142
+ <i class="fas fa-share-alt"></i> Share
143
+ </button>
144
+ <button id="copyAllBtn" class="tool-btn action-btn">
145
+ <i class="far fa-copy"></i> Copy Tree
146
+ </button>
147
+ </div>
148
+ </div>
149
+ <div class="tree-search">
150
+ <input type="text" id="treeFilter" placeholder="Filter files… (e.g. .safetensors, config, README)">
151
+ </div>
152
+ <div class="terminal-window">
153
+ <div id="lineNumbers" class="line-col"></div>
154
+ <div id="treeContent" class="code-col"></div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="footer">
159
+ Built by <a href="https://hf.co/prithivMLmods" target="_blank" rel="noopener">prithivMLmods</a>
160
+ </div>
161
+ </div>
162
+
163
+ <div id="shareOverlay" class="overlay">
164
+ <div class="overlay-content">
165
+ <h3>Share View</h3>
166
+ <div class="share-box">
167
+ <input type="text" id="shareInput" readonly>
168
+ <button id="copyShareBtn"><i class="far fa-copy"></i></button>
169
+ </div>
170
+ <button id="closeOverlay" class="close-btn">&times;</button>
171
+ </div>
172
+ </div>
173
+
174
+ <script type="module" src="index.tsx"></script>
175
+ <script type="module" src="/index.tsx"></script>
176
+ </body>
177
+ </html>
index.tsx ADDED
@@ -0,0 +1,511 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * HFTree — HuggingFace Repo Tree Visualizer
3
+ * Core logic implementation as a TypeScript module.
4
+ */
5
+
6
+ const HF_API = 'https://huggingface.co/api';
7
+
8
+ // Configuration
9
+ const FEATURED = [
10
+ { repo: 'prithivMLmods/Qwen3-VL-4B-Instruct-Unredacted-MAX', type: 'models', label: 'Qwen3-VL-4B-Instruct-Unredacted-MAX' },
11
+ { repo: 'prithivMLmods/Qwen3-VL-abliterated-MAX-Fast', type: 'spaces', label: 'Qwen3-VL Space' },
12
+ { repo: 'prithivMLmods/LAP2-K-Think-v1.b', type: 'datasets', label: 'LAP2-K-Think' },
13
+ ];
14
+
15
+ const POPULAR_MODELS = [
16
+ { repo: 'meta-llama/Llama-3.1-8B-Instruct', type: 'models' },
17
+ { repo: 'mistralai/Mistral-7B-Instruct-v0.3', type: 'models' },
18
+ { repo: 'google/gemma-2-9b-it', type: 'models' },
19
+ { repo: 'Qwen/Qwen2.5-7B-Instruct', type: 'models' },
20
+ { repo: 'stabilityai/stable-diffusion-xl-base-1.0', type: 'models' },
21
+ { repo: 'openai/whisper-large-v3', type: 'models' },
22
+ { repo: 'microsoft/Phi-3-mini-4k-instruct', type: 'models' },
23
+ { repo: 'black-forest-labs/FLUX.1-dev', type: 'models' },
24
+ ];
25
+
26
+ const POPULAR_DATASETS = [
27
+ { repo: 'tatsu-lab/alpaca', type: 'datasets' },
28
+ { repo: 'databricks/databricks-dolly-15k', type: 'datasets' },
29
+ { repo: 'Open-Orca/OpenOrca', type: 'datasets' },
30
+ { repo: 'HuggingFaceFW/fineweb', type: 'datasets' },
31
+ { repo: 'allenai/c4', type: 'datasets' },
32
+ ];
33
+
34
+ let currentData: any[] = [];
35
+ let currentSort = 'folder-az';
36
+ let currentStyle = 'classic';
37
+ let currentType = 'models';
38
+ let currentRepo = '';
39
+ let currentBranch = 'main';
40
+ let filterText = '';
41
+
42
+ // DOM Cache
43
+ const els: any = {
44
+ repo: document.getElementById('repoInput'),
45
+ branch: document.getElementById('branchInput'),
46
+ fetchBtn: document.getElementById('fetchBtn'),
47
+ wrapper: document.getElementById('treeWrapper'),
48
+ empty: document.getElementById('emptyState'),
49
+ status: document.getElementById('statusMsg'),
50
+ lineNums: document.getElementById('lineNumbers'),
51
+ treeContent: document.getElementById('treeContent'),
52
+ privateBtn: document.getElementById('privateRepoBtn'),
53
+ tokenPanel: document.getElementById('tokenSection'),
54
+ hfToken: document.getElementById('hfToken'),
55
+ saveToken: document.getElementById('saveTokenBtn'),
56
+ clearToken: document.getElementById('clearTokenBtn'),
57
+ copyAll: document.getElementById('copyAllBtn'),
58
+ shareBtn: document.getElementById('shareBtn'),
59
+ overlay: document.getElementById('shareOverlay'),
60
+ shareInput: document.getElementById('shareInput'),
61
+ copyShareBtn: document.getElementById('copyShareBtn'),
62
+ closeOverlay: document.getElementById('closeOverlay'),
63
+ sortBtnLabel: document.getElementById('sortBtnLabel'),
64
+ styleBtnLabel: document.getElementById('styleBtnLabel'),
65
+ dropdowns: document.querySelectorAll('.dropdown'),
66
+ urlPrefix: document.getElementById('urlPrefix'),
67
+ typeModel: document.getElementById('typeModel'),
68
+ typeDataset: document.getElementById('typeDataset'),
69
+ typeSpace: document.getElementById('typeSpace'),
70
+ repoInfoCard: document.getElementById('repoInfoCard'),
71
+ repoTypeBadge: document.getElementById('repoTypeBadge'),
72
+ repoInfoName: document.getElementById('repoInfoName'),
73
+ repoInfoStats: document.getElementById('repoInfoStats'),
74
+ treeFilter: document.getElementById('treeFilter'),
75
+ };
76
+
77
+ // --- Helpers ---
78
+
79
+ function getHeaders() {
80
+ const t = localStorage.getItem('hft_token');
81
+ const h: Record<string, string> = { 'Accept': 'application/json' };
82
+ if (t && t.trim()) h['Authorization'] = `Bearer ${t.trim()}`;
83
+ return h;
84
+ }
85
+
86
+ function getRepoPageUrl(type: string, repo: string) {
87
+ if (type === 'datasets') return `https://huggingface.co/datasets/${repo}`;
88
+ if (type === 'spaces') return `https://huggingface.co/spaces/${repo}`;
89
+ return `https://huggingface.co/${repo}`;
90
+ }
91
+
92
+ function formatSize(bytes: number) {
93
+ if (!bytes) return '0 B';
94
+ const u = ['B', 'KB', 'MB', 'GB', 'TB'];
95
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
96
+ return `${(bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1)} ${u[i]}`;
97
+ }
98
+
99
+ function escapeRegex(s: string) {
100
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
101
+ }
102
+
103
+ function getFileIcon(name: string) {
104
+ const ext = name.split('.').pop()?.toLowerCase() || '';
105
+ const nl = name.toLowerCase();
106
+ if (nl === 'dockerfile') return 'fa-brands fa-docker';
107
+ if (nl === 'license' || nl === 'licence') return 'fa-solid fa-scale-balanced';
108
+ if (nl === 'readme.md' || nl === 'readme') return 'fa-solid fa-book';
109
+ if (nl === 'requirements.txt') return 'fa-solid fa-list-check';
110
+ if (nl === '.gitignore' || nl === '.gitattributes') return 'fa-brands fa-git-alt';
111
+ const map: Record<string, string> = {
112
+ py: 'fa-brands fa-python', js: 'fa-brands fa-js', ts: 'fa-brands fa-js',
113
+ jsx: 'fa-brands fa-react', tsx: 'fa-brands fa-react',
114
+ html: 'fa-brands fa-html5', css: 'fa-brands fa-css3-alt',
115
+ md: 'fa-solid fa-file-lines', json: 'fa-solid fa-file-code',
116
+ yaml: 'fa-solid fa-file-code', yml: 'fa-solid fa-file-code', toml: 'fa-solid fa-file-code',
117
+ txt: 'fa-solid fa-file-lines', csv: 'fa-solid fa-file-csv',
118
+ png: 'fa-solid fa-file-image', jpg: 'fa-solid fa-file-image', jpeg: 'fa-solid fa-file-image',
119
+ gif: 'fa-solid fa-file-image', svg: 'fa-solid fa-file-image', webp: 'fa-solid fa-file-image',
120
+ mp4: 'fa-solid fa-file-video', mp3: 'fa-solid fa-file-audio', wav: 'fa-solid fa-file-audio',
121
+ pdf: 'fa-solid fa-file-pdf', zip: 'fa-solid fa-file-zipper', tar: 'fa-solid fa-file-zipper', gz: 'fa-solid fa-file-zipper',
122
+ safetensors: 'fa-solid fa-cube', bin: 'fa-solid fa-cube', gguf: 'fa-solid fa-cube',
123
+ pt: 'fa-solid fa-cube', pth: 'fa-solid fa-cube', onnx: 'fa-solid fa-cube', h5: 'fa-solid fa-cube',
124
+ parquet: 'fa-solid fa-database', arrow: 'fa-solid fa-database',
125
+ rs: 'fa-brands fa-rust', go: 'fa-brands fa-golang', java: 'fa-brands fa-java',
126
+ sh: 'fa-solid fa-terminal', bash: 'fa-solid fa-terminal',
127
+ };
128
+ return map[ext] || 'fa-regular fa-file';
129
+ }
130
+
131
+ // --- API Methods ---
132
+
133
+ async function fetchTree(type: string, repo: string, branch: string, path: string = '') {
134
+ const url = path
135
+ ? `${HF_API}/${type}/${repo}/tree/${branch}/${path}`
136
+ : `${HF_API}/${type}/${repo}/tree/${branch}`;
137
+ const resp = await fetch(url, { headers: getHeaders() });
138
+ if (!resp.ok) {
139
+ if (resp.status === 401) throw new Error('401: Unauthorized. Check your token.');
140
+ if (resp.status === 403) throw new Error('403: Forbidden / Rate Limited.');
141
+ if (resp.status === 404) {
142
+ if (branch === 'main' && !path) {
143
+ const r2 = await fetch(`${HF_API}/${type}/${repo}/tree/master`, { headers: getHeaders() });
144
+ if (r2.ok) return { items: await r2.json(), switchedBranch: 'master' };
145
+ }
146
+ throw new Error('404: Repository not found (or is private).');
147
+ }
148
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
149
+ }
150
+ return { items: await resp.json(), switchedBranch: null };
151
+ }
152
+
153
+ async function fetchFullTree(type: string, repo: string, branch: string) {
154
+ let all: any[] = [], switched: string | null = null;
155
+ async function recurse(path: string) {
156
+ const r = await fetchTree(type, repo, switched || branch, path);
157
+ if (r.switchedBranch && !switched) switched = r.switchedBranch;
158
+ for (const item of r.items) {
159
+ all.push({
160
+ path: item.path,
161
+ type: item.type === 'directory' ? 'tree' : 'blob',
162
+ size: item.size || 0,
163
+ lfs: item.lfs || null,
164
+ });
165
+ if (item.type === 'directory') {
166
+ try { await recurse(item.path); }
167
+ catch (e: any) { console.warn(`Skip ${item.path}: ${e.message}`); }
168
+ }
169
+ }
170
+ }
171
+ await recurse('');
172
+ return { tree: all, branch: switched || branch };
173
+ }
174
+
175
+ // --- UI Methods ---
176
+
177
+ function buildVisualHierarchy(sortedList: any[]) {
178
+ const root: any = { children: [] }, map: any = { '': root };
179
+ sortedList.forEach(item => { map[item.path] = { ...item, name: item.path.split('/').pop(), children: [] }; });
180
+ sortedList.forEach(item => {
181
+ const pts = item.path.split('/'); pts.pop();
182
+ (map[pts.join('/')] || root).children.push(map[item.path]);
183
+ });
184
+ const result: any[] = [];
185
+ function traverse(node: any, prefix: string, isLast: boolean) {
186
+ const conn = isLast ? "└── " : "├── ";
187
+ result.push({
188
+ name: node.name, path: node.path, type: node.type,
189
+ size: node.size, lfs: node.lfs,
190
+ prefix: prefix + conn,
191
+ indent: prefix.replace(/│ /g, ' ').replace(/├── /g, ' ').replace(/└── /g, ' ') + ' '
192
+ });
193
+ if (node.children && node.children.length) {
194
+ const cp = prefix + (isLast ? " " : "│ ");
195
+ node.children.forEach((c: any, i: number) => traverse(c, cp, i === node.children.length - 1));
196
+ }
197
+ }
198
+ root.children.forEach((c: any, i: number) => traverse(c, "", i === root.children.length - 1));
199
+ return result;
200
+ }
201
+
202
+ function sortTree(data: any[], mode: string) {
203
+ const s = [...data];
204
+ s.sort((a, b) => {
205
+ const ad = a.type === 'tree' ? 1 : 0, bd = b.type === 'tree' ? 1 : 0;
206
+ const an = a.path.toLowerCase(), bn = b.path.toLowerCase();
207
+ switch (mode) {
208
+ case 'folder-az': if (ad !== bd) return bd - ad; return an.localeCompare(bn);
209
+ case 'folder-za': if (ad !== bd) return bd - ad; return bn.localeCompare(an);
210
+ case 'alpha-az': return an.localeCompare(bn);
211
+ case 'alpha-za': return bn.localeCompare(an);
212
+ case 'size-desc': return (b.size || 0) - (a.size || 0);
213
+ case 'size-asc': return (a.size || 0) - (b.size || 0);
214
+ default: return 0;
215
+ }
216
+ });
217
+ return s;
218
+ }
219
+
220
+ function render() {
221
+ els.lineNums.innerHTML = '';
222
+ els.treeContent.innerHTML = '';
223
+ let data = currentData;
224
+ if (filterText) {
225
+ const mp = new Set();
226
+ currentData.forEach(item => {
227
+ if (item.path.toLowerCase().includes(filterText)) {
228
+ mp.add(item.path);
229
+ const pts = item.path.split('/');
230
+ for (let i = 1; i < pts.length; i++) mp.add(pts.slice(0, i).join('/'));
231
+ }
232
+ });
233
+ data = currentData.filter(i => mp.has(i.path));
234
+ }
235
+ const sorted = sortTree(data, currentSort);
236
+ const hier = buildVisualHierarchy(sorted);
237
+ const fn = document.createDocumentFragment(), fl = document.createDocumentFragment();
238
+ hier.forEach((item, idx) => {
239
+ const n = document.createElement('div');
240
+ n.textContent = (idx + 1).toString();
241
+ fn.appendChild(n);
242
+ const row = document.createElement('div');
243
+ row.className = 'tree-line';
244
+ row.style.animation = `fadeIn 0.1s forwards ${Math.min(idx * 2, 500)}ms`;
245
+ row.style.opacity = '0';
246
+ const ic = item.type === 'tree' ? 'fa-solid fa-folder' : getFileIcon(item.name);
247
+ const tc = item.type === 'tree' ? 'folder' : 'file';
248
+ let pre = item.prefix, nm = item.name;
249
+ if (currentStyle === 'minimal') pre = item.indent;
250
+ if (currentStyle === 'plus') pre = pre.replace(/└──/g, '+--').replace(/├──/g, '+--').replace(/│/g, '|');
251
+ if (currentStyle === 'slashed') {
252
+ if (item.type === 'tree') nm = `/${item.name}`;
253
+ pre = item.prefix.replace(/│/g, ' ').replace(/├──/g, ' ').replace(/└──/g, ' ');
254
+ }
255
+ const sz = item.type === 'blob' && item.size ? `<span class="t-size">${formatSize(item.size)}</span>` : '';
256
+ const lf = item.lfs ? `<span class="lfs-badge">LFS</span>` : '';
257
+ let nh = nm;
258
+ if (filterText && nm.toLowerCase().includes(filterText)) {
259
+ const rx = new RegExp(`(${escapeRegex(filterText)})`, 'gi');
260
+ nh = nm.replace(rx, '<mark style="background:var(--accent-light);color:var(--accent);padding:0 2px;border-radius:2px;">$1</mark>');
261
+ }
262
+ row.innerHTML = `<span class="t-prefix">${pre}</span><span class="t-icon"><i class="${ic}"></i></span><span class="t-name ${tc}">${nh}</span>${sz}${lf}<button class="sub-copy" data-path="${item.path}" title="Copy Path"><i class="far fa-copy"></i></button>`;
263
+ fl.appendChild(row);
264
+ });
265
+ els.lineNums.appendChild(fn);
266
+ els.treeContent.appendChild(fl);
267
+ }
268
+
269
+ function showMsg(text: string, type: string) {
270
+ els.status.style.display = text ? 'block' : 'none';
271
+ els.status.innerHTML = text;
272
+ els.status.className = type;
273
+ }
274
+
275
+ function showRepoInfo(repo: string, branch: string) {
276
+ const tc = currentType === 'models' ? 'model' : currentType === 'datasets' ? 'dataset' : 'space';
277
+ const tl = currentType === 'models' ? 'MODEL' : currentType === 'datasets' ? 'DATASET' : 'SPACE';
278
+ els.repoTypeBadge.className = `repo-type-badge ${tc}`;
279
+ els.repoTypeBadge.textContent = tl;
280
+ els.repoInfoName.innerHTML = `<a href="${getRepoPageUrl(currentType, repo)}" target="_blank" rel="noopener">${repo}</a>`;
281
+ const fc = currentData.filter(i => i.type === 'blob').length;
282
+ const dc = currentData.filter(i => i.type === 'tree').length;
283
+ const ts = currentData.reduce((a, i) => a + (i.size || 0), 0);
284
+ els.repoInfoStats.innerHTML = `
285
+ <span><i class="far fa-file"></i> ${fc} files</span>
286
+ <span><i class="far fa-folder"></i> ${dc} folders</span>
287
+ <span><i class="fas fa-weight-hanging"></i> ${formatSize(ts)}</span>
288
+ <span><i class="fas fa-code-branch"></i> ${branch}</span>`;
289
+ els.repoInfoCard.classList.add('visible');
290
+ }
291
+
292
+ async function loadTree() {
293
+ const repo = els.repo.value.trim();
294
+ const branch = els.branch.value.trim() || 'main';
295
+ if (!repo.includes('/')) return showMsg("Invalid format. Use 'username/repo-name'", "error");
296
+ currentRepo = repo; currentBranch = branch;
297
+ filterText = ''; els.treeFilter.value = '';
298
+ if (els.empty) els.empty.style.display = 'none';
299
+ els.wrapper.style.display = 'none';
300
+ els.repoInfoCard.classList.remove('visible');
301
+ showMsg(`<span class="spinner"></span> Fetching ${currentType}/${repo} (${branch})…`, "loading");
302
+ els.fetchBtn.disabled = true;
303
+ try {
304
+ const ck = `hft_${currentType}_${repo}_${branch}`;
305
+ const ta = !!localStorage.getItem('hft_token');
306
+ const cached = !ta ? sessionStorage.getItem(ck) : null;
307
+ if (cached) {
308
+ const p = JSON.parse(cached);
309
+ currentData = p.tree; currentBranch = p.branch;
310
+ showMsg("", "");
311
+ } else {
312
+ const r = await fetchFullTree(currentType, repo, branch);
313
+ currentData = r.tree; currentBranch = r.branch;
314
+ if (r.branch !== branch) {
315
+ showMsg(`Branch '${branch}' not found → '${r.branch}'.`, "loading");
316
+ els.branch.value = r.branch;
317
+ } else showMsg("", "");
318
+ if (!ta) try { sessionStorage.setItem(ck, JSON.stringify({ tree: currentData, branch: currentBranch })); } catch (e) { }
319
+ }
320
+ window.location.hash = `${currentType}/${repo}/${currentBranch}`;
321
+ document.title = `${repo} — HFTree`;
322
+ showRepoInfo(repo, currentBranch);
323
+ render();
324
+ els.wrapper.style.display = 'flex';
325
+ } catch (err: any) {
326
+ console.error(err);
327
+ const m = err.message;
328
+ if (m.includes('401')) {
329
+ showMsg(`
330
+ <i class="fas fa-lock"></i> <b>Unauthorized (401)</b><br>
331
+ Check your HuggingFace token or Provide one.
332
+ `, "error");
333
+ } else if (m.includes('403')) {
334
+ showMsg("Rate limited or forbidden. Try adding a token.", "error");
335
+ } else if (m.includes('404')) {
336
+ showMsg("Repository not found. Check name and type.", "error");
337
+ } else {
338
+ showMsg(m, "error");
339
+ }
340
+ els.empty.style.display = 'block';
341
+ } finally { els.fetchBtn.disabled = false; }
342
+ }
343
+
344
+ function handleCopy(btn: any, text: string, isSub: boolean) {
345
+ navigator.clipboard.writeText(text).then(() => {
346
+ if (!isSub) {
347
+ const oh = btn.innerHTML;
348
+ btn.innerHTML = `<i class="fas fa-check"></i> Copied`;
349
+ setTimeout(() => { btn.innerHTML = oh; }, 1500);
350
+ } else {
351
+ const ic = btn.querySelector('i'), oc = ic.className;
352
+ ic.className = "fas fa-check";
353
+ setTimeout(() => { ic.className = oc; }, 1500);
354
+ }
355
+ }).catch(e => console.error("Copy failed", e));
356
+ }
357
+
358
+ function generateAsciiTree(data: any[]) {
359
+ const sorted = sortTree(data, currentSort);
360
+ const hier = buildVisualHierarchy(sorted);
361
+ const lines = [`${currentRepo} (${currentBranch})`];
362
+ hier.forEach(item => {
363
+ let pre = item.prefix, nm = item.name;
364
+ if (currentStyle === 'minimal') pre = item.indent;
365
+ if (currentStyle === 'plus') pre = pre.replace(/└──/g, '+--').replace(/├──/g, '+--').replace(/│/g, '|');
366
+ if (currentStyle === 'slashed') {
367
+ if (item.type === 'tree') nm = `/${nm}`;
368
+ pre = item.prefix.replace(/│/g, ' ').replace(/├──/g, ' ').replace(/└──/g, ' ');
369
+ }
370
+ const sz = item.type === 'blob' && item.size ? ` (${formatSize(item.size)})` : '';
371
+ lines.push(`${pre}${nm}${sz}`);
372
+ });
373
+ return lines.join('\n');
374
+ }
375
+
376
+ function setType(type: string) {
377
+ currentType = type;
378
+ [els.typeModel, els.typeDataset, els.typeSpace].forEach(b => b.classList.remove('active-model', 'active-dataset', 'active-space'));
379
+ if (type === 'models') { els.typeModel.classList.add('active-model'); els.urlPrefix.textContent = 'hf.co/'; }
380
+ if (type === 'datasets') { els.typeDataset.classList.add('active-dataset'); els.urlPrefix.textContent = 'hf.co/datasets/'; }
381
+ if (type === 'spaces') { els.typeSpace.classList.add('active-space'); els.urlPrefix.textContent = 'hf.co/spaces/'; }
382
+ }
383
+
384
+ function checkSavedToken() {
385
+ const t = localStorage.getItem('hft_token');
386
+ if (t) {
387
+ els.hfToken.value = t;
388
+ els.privateBtn.innerHTML = `<i class="fas fa-lock-open"></i> Active`;
389
+ els.privateBtn.style.color = 'var(--accent)';
390
+ els.saveToken.style.display = 'none';
391
+ els.clearToken.style.display = 'inline-block';
392
+ } else {
393
+ els.privateBtn.innerHTML = `<i class="fas fa-lock"></i> Token`;
394
+ els.privateBtn.style.color = '';
395
+ els.saveToken.style.display = 'inline-block';
396
+ els.clearToken.style.display = 'none';
397
+ }
398
+ }
399
+
400
+ // --- Init ---
401
+
402
+ function init() {
403
+ // Theme
404
+ const savedTheme = localStorage.getItem('hft_theme') || 'light';
405
+ document.documentElement.setAttribute('data-theme', savedTheme);
406
+ document.getElementById('themeToggle')?.addEventListener('click', () => {
407
+ const n = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
408
+ document.documentElement.setAttribute('data-theme', n);
409
+ localStorage.setItem('hft_theme', n);
410
+ });
411
+
412
+ checkSavedToken();
413
+
414
+ // Dropdowns
415
+ [els.sortBtnLabel, els.styleBtnLabel].forEach(btn => {
416
+ btn.addEventListener('click', (e: any) => {
417
+ e.stopPropagation();
418
+ const p = btn.parentElement;
419
+ els.dropdowns.forEach((d: any) => { if (d !== p) d.classList.remove('active'); });
420
+ p.classList.toggle('active');
421
+ });
422
+ });
423
+ document.querySelectorAll('[data-sort]').forEach(el => {
424
+ el.addEventListener('click', (e: any) => {
425
+ e.preventDefault();
426
+ currentSort = e.target.dataset.sort;
427
+ els.sortBtnLabel.innerHTML = `<i class="fas fa-sort-amount-down"></i> ${e.target.innerText}`;
428
+ els.dropdowns.forEach((d: any) => d.classList.remove('active'));
429
+ render();
430
+ });
431
+ });
432
+ document.querySelectorAll('[data-style]').forEach(el => {
433
+ el.addEventListener('click', (e: any) => {
434
+ e.preventDefault();
435
+ currentStyle = e.target.dataset.style;
436
+ els.styleBtnLabel.innerHTML = `<i class="fas fa-code-branch"></i> ${e.target.innerText}`;
437
+ els.dropdowns.forEach((d: any) => d.classList.remove('active'));
438
+ render();
439
+ });
440
+ });
441
+ document.addEventListener('click', () => els.dropdowns.forEach((d: any) => d.classList.remove('active')));
442
+
443
+ // Event Listeners
444
+ els.fetchBtn.addEventListener('click', loadTree);
445
+ els.copyAll.addEventListener('click', () => handleCopy(els.copyAll, generateAsciiTree(currentData), false));
446
+ els.privateBtn.addEventListener('click', () => {
447
+ els.tokenPanel.style.display = els.tokenPanel.style.display === 'none' ? 'block' : 'none';
448
+ });
449
+ els.saveToken.addEventListener('click', () => {
450
+ const t = els.hfToken.value.trim();
451
+ if (t) localStorage.setItem('hft_token', t);
452
+ checkSavedToken();
453
+ showMsg("Token saved.", "loading");
454
+ setTimeout(() => showMsg("", ""), 1500);
455
+ });
456
+ els.clearToken.addEventListener('click', () => {
457
+ localStorage.removeItem('hft_token');
458
+ els.hfToken.value = '';
459
+ checkSavedToken();
460
+ showMsg("Token cleared.", "loading");
461
+ setTimeout(() => showMsg("", ""), 1500);
462
+ });
463
+ [els.typeModel, els.typeDataset, els.typeSpace].forEach(b =>
464
+ b.addEventListener('click', () => setType(b.dataset.type))
465
+ );
466
+ els.treeFilter.addEventListener('input', (e: any) => { filterText = e.target.value.trim().toLowerCase(); render(); });
467
+ els.treeContent.onclick = (e: any) => {
468
+ const b = e.target.closest('.sub-copy');
469
+ if (b) handleCopy(b, b.dataset.path, true);
470
+ };
471
+
472
+ // Share Overlay
473
+ els.shareBtn.addEventListener('click', (e: any) => {
474
+ e.stopPropagation();
475
+ els.shareInput.value = window.location.href;
476
+ els.overlay.classList.add('visible');
477
+ });
478
+ els.closeOverlay.addEventListener('click', () => els.overlay.classList.remove('visible'));
479
+ els.overlay.addEventListener('click', (e: any) => { if (e.target === els.overlay) els.overlay.classList.remove('visible'); });
480
+ els.shareInput.addEventListener('click', () => els.shareInput.select());
481
+ els.copyShareBtn.addEventListener('click', () => handleCopy(els.copyShareBtn, els.shareInput.value, true));
482
+
483
+ // Featured Tags
484
+ const createRepoTag = (label: string, type: string, repo: string) => {
485
+ const btn = document.createElement('button');
486
+ btn.className = 'repo-tag';
487
+ const tc = type === 'models' ? 'model' : type === 'datasets' ? 'dataset' : 'space';
488
+ const tl = type === 'models' ? 'MODEL' : type === 'datasets' ? 'DATA' : 'SPACE';
489
+ btn.innerHTML = `<span class="tag-type ${tc}">${tl}</span> ${label}`;
490
+ btn.addEventListener('click', () => {
491
+ setType(type); els.repo.value = repo; els.branch.value = 'main'; loadTree();
492
+ });
493
+ return btn;
494
+ };
495
+ FEATURED.forEach(i => document.getElementById('featuredCloud')?.appendChild(createRepoTag(i.label || i.repo, i.type, i.repo)));
496
+ POPULAR_MODELS.forEach(i => document.getElementById('modelCloud')?.appendChild(createRepoTag(i.repo, i.type, i.repo)));
497
+ POPULAR_DATASETS.forEach(i => document.getElementById('datasetCloud')?.appendChild(createRepoTag(i.repo, i.type, i.repo)));
498
+
499
+ // Hash check
500
+ if (window.location.hash && window.location.hash.startsWith('#')) {
501
+ const p = window.location.hash.substring(1).split('/');
502
+ if (p.length >= 3 && ['models', 'datasets', 'spaces'].includes(p[0])) {
503
+ setType(p[0]);
504
+ els.repo.value = p[1] + '/' + p[2];
505
+ els.branch.value = p[3] || 'main';
506
+ loadTree();
507
+ }
508
+ }
509
+ }
510
+
511
+ init();
metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "name": "HFTree",
3
+ "description": "Visualize HuggingFace repository structures without cloning.",
4
+ "requestFramePermissions": []
5
+ }
package.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "hftree",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "19.0.0",
13
+ "react-dom": "19.0.0",
14
+ "lucide-react": "0.474.0",
15
+ "@google/genai": "0.2.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.14.0",
19
+ "@vitejs/plugin-react": "^5.0.0",
20
+ "typescript": "~5.8.2",
21
+ "vite": "^6.2.0"
22
+ }
23
+ }
tsconfig.json ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "types": [
14
+ "node"
15
+ ],
16
+ "moduleResolution": "bundler",
17
+ "isolatedModules": true,
18
+ "moduleDetection": "force",
19
+ "allowJs": true,
20
+ "jsx": "react-jsx",
21
+ "paths": {
22
+ "@/*": [
23
+ "./*"
24
+ ]
25
+ },
26
+ "allowImportingTsExtensions": true,
27
+ "noEmit": true
28
+ }
29
+ }
vite.config.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import { defineConfig, loadEnv } from 'vite';
3
+ import react from '@vitejs/plugin-react';
4
+
5
+ export default defineConfig(({ mode }) => {
6
+ const env = loadEnv(mode, '.', '');
7
+ return {
8
+ server: {
9
+ port: 3000,
10
+ host: '0.0.0.0',
11
+ },
12
+ plugins: [react()],
13
+ define: {
14
+ 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
15
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
16
+ },
17
+ resolve: {
18
+ alias: {
19
+ '@': path.resolve(__dirname, '.'),
20
+ }
21
+ }
22
+ };
23
+ });