testdeep123 commited on
Commit
ae9572b
·
verified ·
1 Parent(s): 0780858

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1194 -0
app.py ADDED
@@ -0,0 +1,1194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import requests
4
+ from urllib.parse import urlparse, unquote
5
+ from flask import Flask, render_template_string, request, redirect, send_file, jsonify
6
+
7
+ from huggingface_hub import HfApi, hf_hub_download, upload_file, delete_file
8
+ import time
9
+
10
+ app = Flask(__name__)
11
+ app.secret_key = os.urandom(24)
12
+
13
+ REPO_ID = None
14
+ HF_TOKEN = None
15
+
16
+ api = HfApi()
17
+
18
+ TEMPLATE = """
19
+ <!DOCTYPE html>
20
+ <html lang="en">
21
+ <head>
22
+ <meta charset="UTF-8">
23
+ <title>SUS Drive - {{ path or 'Root' }}</title>
24
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
+ <link rel="preconnect" href="https://fonts.googleapis.com">
26
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
27
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
28
+ <style>
29
+ :root {
30
+ --bg-color: #0d1117;
31
+ --glass-bg: rgba(22, 27, 34, 0.6);
32
+ --border-color: rgba(255, 255, 255, 0.1);
33
+ --border-color-hover: rgba(56, 189, 248, 0.4);
34
+ --text-primary: #e6edf3;
35
+ --text-secondary: #7d8590;
36
+ --accent-primary: #38bdf8;
37
+ --accent-primary-hover: #7dd3fc;
38
+ --accent-success: #2dd4bf;
39
+ --accent-danger: #f87171;
40
+ --shadow-color: rgba(0, 0, 0, 0.2);
41
+ --glow-color: rgba(56, 189, 248, 0.2);
42
+ --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
43
+ --radius-md: 10px;
44
+ --radius-sm: 6px;
45
+ --transition-fast: all 0.2s cubic-bezier(0.42, 0.13, 0.76, 0.28);
46
+ --transition-slow: all 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
47
+ }
48
+ * {
49
+ margin: 0;
50
+ padding: 0;
51
+ box-sizing: border-box;
52
+ }
53
+ @keyframes aurora {
54
+ 0% { background-position: 0% 50%; }
55
+ 50% { background-position: 100% 50%; }
56
+ 100% { background-position: 0% 50%; }
57
+ }
58
+ body {
59
+ background-color: var(--bg-color);
60
+ background-image: radial-gradient(at 20% 10%, hsla(212,80%,30%,0.3) 0px, transparent 50%),
61
+ radial-gradient(at 80% 20%, hsla(280,70%,40%,0.2) 0px, transparent 50%),
62
+ radial-gradient(at 50% 80%, hsla(180,80%,35%,0.3) 0px, transparent 50%);
63
+ background-attachment: fixed;
64
+ background-size: 200% 200%;
65
+ animation: aurora 15s ease infinite;
66
+ color: var(--text-primary);
67
+ font-family: var(--font-family);
68
+ line-height: 1.6;
69
+ min-height: 100vh;
70
+ overflow-x: hidden;
71
+ }
72
+ .container {
73
+ max-width: 1600px;
74
+ margin: 0 auto;
75
+ padding: 2rem;
76
+ }
77
+ .header {
78
+ background: var(--glass-bg);
79
+ backdrop-filter: blur(20px);
80
+ -webkit-backdrop-filter: blur(20px);
81
+ border: 1px solid var(--border-color);
82
+ border-radius: var(--radius-md);
83
+ padding: 1.5rem;
84
+ margin-bottom: 2.5rem;
85
+ display: flex;
86
+ flex-wrap: wrap;
87
+ align-items: center;
88
+ justify-content: space-between;
89
+ gap: 1.5rem;
90
+ box-shadow: 0 8px 32px 0 var(--shadow-color);
91
+ }
92
+ .title {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 0.75rem;
96
+ font-size: 1.5rem;
97
+ font-weight: 700;
98
+ color: var(--text-primary);
99
+ }
100
+ .title-icon {
101
+ width: 28px;
102
+ height: 28px;
103
+ color: var(--accent-primary);
104
+ transition: var(--transition-fast);
105
+ }
106
+ .title:hover .title-icon {
107
+ transform: rotate(-10deg) scale(1.05);
108
+ }
109
+ .breadcrumb {
110
+ display: flex;
111
+ align-items: center;
112
+ flex-wrap: wrap;
113
+ gap: 0.25rem;
114
+ background: rgba(0,0,0,0.2);
115
+ padding: 0.5rem;
116
+ border-radius: var(--radius-sm);
117
+ }
118
+ .breadcrumb-item {
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 0.5rem;
122
+ padding: 0.4rem 0.8rem;
123
+ border-radius: 4px;
124
+ cursor: pointer;
125
+ color: var(--text-secondary);
126
+ transition: var(--transition-fast);
127
+ font-size: 0.9rem;
128
+ font-weight: 500;
129
+ }
130
+ .breadcrumb-item:hover {
131
+ background: rgba(255, 255, 255, 0.1);
132
+ color: var(--text-primary);
133
+ }
134
+ .breadcrumb-item.active {
135
+ color: var(--accent-primary);
136
+ background: rgba(56, 189, 248, 0.1);
137
+ }
138
+ .breadcrumb-separator {
139
+ color: var(--text-secondary);
140
+ user-select: none;
141
+ font-size: 1rem;
142
+ opacity: 0.5;
143
+ }
144
+ .actions {
145
+ display: flex;
146
+ flex-wrap: wrap;
147
+ gap: 0.75rem;
148
+ }
149
+ .btn {
150
+ display: inline-flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ gap: 0.5rem;
154
+ padding: 0.7rem 1.25rem;
155
+ border: 1px solid transparent;
156
+ border-radius: var(--radius-sm);
157
+ font-size: 0.9rem;
158
+ font-weight: 600;
159
+ cursor: pointer;
160
+ transition: var(--transition-fast);
161
+ text-decoration: none;
162
+ white-space: nowrap;
163
+ }
164
+ .btn .icon {
165
+ width: 16px;
166
+ height: 16px;
167
+ }
168
+ .btn:hover {
169
+ transform: translateY(-2px);
170
+ }
171
+ .btn-primary {
172
+ background: var(--accent-primary);
173
+ color: var(--bg-color);
174
+ border-color: var(--accent-primary);
175
+ }
176
+ .btn-primary:hover {
177
+ background: var(--accent-primary-hover);
178
+ border-color: var(--accent-primary-hover);
179
+ box-shadow: 0 4px 15px rgba(56, 189, 248, 0.2);
180
+ }
181
+ .btn-secondary {
182
+ background: var(--glass-bg);
183
+ color: var(--text-primary);
184
+ border: 1px solid var(--border-color);
185
+ }
186
+ .btn-secondary:hover {
187
+ background: rgba(255, 255, 255, 0.1);
188
+ border-color: var(--border-color-hover);
189
+ }
190
+ .btn-success {
191
+ background: var(--accent-success);
192
+ color: var(--bg-color);
193
+ border-color: var(--accent-success);
194
+ }
195
+ .btn-success:hover {
196
+ filter: brightness(1.1);
197
+ box-shadow: 0 4px 15px rgba(45, 212, 191, 0.2);
198
+ }
199
+ .btn-danger {
200
+ background: var(--accent-danger);
201
+ color: var(--bg-color);
202
+ border-color: var(--accent-danger);
203
+ }
204
+ .btn-danger:hover {
205
+ filter: brightness(1.1);
206
+ box-shadow: 0 4px 15px rgba(248, 113, 113, 0.2);
207
+ }
208
+ .file-input-wrapper {
209
+ position: relative;
210
+ display: inline-block;
211
+ }
212
+ .file-input {
213
+ position: absolute;
214
+ left: -9999px;
215
+ opacity: 0;
216
+ }
217
+ @keyframes fadeInGrid {
218
+ from { opacity: 0; transform: translateY(20px); }
219
+ to { opacity: 1; transform: translateY(0); }
220
+ }
221
+ .file-grid {
222
+ display: grid;
223
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
224
+ gap: 1.5rem;
225
+ animation: fadeInGrid 0.5s ease-out forwards;
226
+ }
227
+ .file-item {
228
+ background: var(--glassлад: var(--glass-bg);
229
+ border: 1px solid var(--border-color);
230
+ border-radius: var(--radius-md);
231
+ transition: var(--transition-fast);
232
+ position: relative;
233
+ }
234
+ .file-item:hover {
235
+ transform: translateY(-5px);
236
+ border-color: var(--border-color-hover);
237
+ box-shadow: 0 0 20px 0 var(--glow-color);
238
+ z-index: 10;
239
+ }
240
+ .file-item-content {
241
+ padding: 1rem;
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 1rem;
245
+ cursor: pointer;
246
+ height: 100%;
247
+ text-decoration: none;
248
+ color: inherit;
249
+ }
250
+ .file-icon-wrapper {
251
+ flex-shrink: 0;
252
+ width: 48px;
253
+ height: 48px;
254
+ border-radius: var(--radius-sm);
255
+ display: grid;
256
+ place-items: center;
257
+ transition: var(--transition-fast);
258
+ }
259
+ .file-icon-wrapper .icon {
260
+ width: 24px;
261
+ height: 24px;
262
+ }
263
+ .file-icon-wrapper.dir {
264
+ background: rgba(56, 189, 248, 0.1);
265
+ color: var(--accent-primary);
266
+ }
267
+ .file-icon-wrapper.file {
268
+ background: rgba(45, 212, 191, 0.1);
269
+ color: var(--accent-success);
270
+ }
271
+ .file-item:hover .file-icon-wrapper {
272
+ transform: scale(1.05);
273
+ }
274
+ .file-details {
275
+ overflow: hidden;
276
+ }
277
+ .file-name {
278
+ font-weight: 500;
279
+ white-space: nowrap;
280
+ overflow: hidden;
281
+ text-overflow: ellipsis;
282
+ color: var(--text-primary);
283
+ }
284
+ .file-meta {
285
+ font-size: 0.875rem;
286
+ color: var(--text-secondary);
287
+ }
288
+ .storage-usage {
289
+ margin-top: 1rem;
290
+ font-size: 0.9rem;
291
+ color: var(--text-secondary);
292
+ }
293
+ .progress-bar {
294
+ background: var(--border-color);
295
+ border-radius: var(--radius-sm);
296
+ height: 8px;
297
+ overflow: hidden;
298
+ }
299
+ .progress {
300
+ background: var(--accent-primary);
301
+ height: 100%;
302
+ transition: width 0.3s ease;
303
+ }
304
+ .dropdown {
305
+ position: absolute;
306
+ top: 0.5rem;
307
+ right: 0.5rem;
308
+ }
309
+ .dropdown-toggle {
310
+ background: none;
311
+ border: none;
312
+ color: var(--text-secondary);
313
+ cursor: pointer;
314
+ width: 32px;
315
+ height: 32px;
316
+ border-radius: 50%;
317
+ display: grid;
318
+ place-items: center;
319
+ transition: var(--transition-fast);
320
+ }
321
+ .dropdown-toggle .icon { width: 18px; height: 18px; }
322
+ .dropdown-toggle:hover {
323
+ background: rgba(255, 255, 255, 0.1);
324
+ color: var(--text-primary);
325
+ }
326
+ .dropdown-menu {
327
+ position: absolute;
328
+ top: calc(100% + 5px);
329
+ right: 0;
330
+ background: var(--glass-bg);
331
+ backdrop-filter: blur(10px);
332
+ border: 1px solid var(--border-color);
333
+ border-radius: var(--radius-sm);
334
+ box-shadow: 0 4px 12px var(--shadow-color);
335
+ min-width: 180px;
336
+ z-index: 1000;
337
+ opacity: 0;
338
+ visibility: hidden;
339
+ transform: translateY(-10px) scale(0.95);
340
+ transform-origin: top right;
341
+ transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
342
+ padding: 0.5rem;
343
+ }
344
+ .dropdown.active .dropdown-menu {
345
+ opacity: 1;
346
+ visibility: visible;
347
+ transform: translateY(0) scale(1);
348
+ }
349
+ .dropdown-item {
350
+ display: flex;
351
+ align-items: center;
352
+ gap: 0.75rem;
353
+ width: 100%;
354
+ padding: 0.6rem 0.8rem;
355
+ border: none;
356
+ background: none;
357
+ color: var(--text-primary);
358
+ font-size: 0.9rem;
359
+ font-weight: 500;
360
+ cursor: pointer;
361
+ transition: var(--transition-fast);
362
+ text-align: left;
363
+ border-radius: 4px;
364
+ }
365
+ .dropdown-item .icon { width: 16px; height: 16px; color: var(--text-secondary); }
366
+ .dropdown-item:hover {
367
+ background: rgba(255, 255, 255, 0.1);
368
+ color: var(--text-primary);
369
+ }
370
+ .dropdown-item:hover .icon {
371
+ color: var(--text-primary);
372
+ }
373
+ .dropdown-item.danger { color: var(--accent-danger); }
374
+ .dropdown-item.danger:hover { background: rgba(248, 113, 113, 0.1); }
375
+ .dropdown-item.danger .icon { color: var(--accent-danger); }
376
+ .modal-overlay {
377
+ position: fixed;
378
+ inset: 0;
379
+ background: rgba(13, 17, 23, 0.7);
380
+ display: grid;
381
+ place-items: center;
382
+ z-index: 2000;
383
+ opacity: 0;
384
+ visibility: hidden;
385
+ transition: var(--transition-fast);
386
+ backdrop-filter: blur(8px);
387
+ }
388
+ .modal-overlay.active {
389
+ opacity: 1;
390
+ visibility: visible;
391
+ }
392
+ .modal {
393
+ background: var(--glass-bg);
394
+ border: 1px solid var(--border-color);
395
+ border-radius: var(--radius-md);
396
+ box-shadow: 0 16px 60px 0 var(--shadow-color);
397
+ width: 90%;
398
+ max-width: 450px;
399
+ max-height: 90vh;
400
+ overflow-y: auto;
401
+ transform: scale(0.95);
402
+ transition: var(--transition-slow);
403
+ }
404
+ .modal-overlay.active .modal {
405
+ transform: scale(1);
406
+ }
407
+ .modal-title {
408
+ padding: 1.5rem 1.5rem 0.5rem;
409
+ font-size: 1.25rem;
410
+ font-weight: 600;
411
+ border-bottom: 1px solid var(--border-color);
412
+ }
413
+ .modal-body {
414
+ padding: 1.5rem;
415
+ }
416
+ .modal-input {
417
+ width: 100%;
418
+ padding: 0.75rem 1rem;
419
+ border: 1px solid var(--border-color);
420
+ border-radius: var(--radius-sm);
421
+ background: rgba(0,0,0,0.2);
422
+ color: var(--text-primary);
423
+ font-size: 1rem;
424
+ transition: var(--transition-fast);
425
+ font-family: var(--font-family);
426
+ }
427
+ .modal-input:focus {
428
+ outline: none;
429
+ border-color: var(--accent-primary);
430
+ box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.2);
431
+ }
432
+ .modal-actions {
433
+ padding: 0 1.5rem 1.5rem;
434
+ display: flex;
435
+ gap: 0.75rem;
436
+ justify-content: flex-end;
437
+ }
438
+ .upload-overlay {
439
+ position: fixed;
440
+ inset: 0;
441
+ background: rgba(13, 17, 23, 0.85);
442
+ display: flex;
443
+ flex-direction: column;
444
+ align-items: center;
445
+ justify-content: center;
446
+ z-index: 3000;
447
+ opacity: 0;
448
+ visibility: hidden;
449
+ transition: var(--transition-fast);
450
+ backdrop-filter: blur(10px);
451
+ }
452
+ .upload-overlay.active {
453
+ opacity: 1;
454
+ visibility: visible;
455
+ }
456
+ @keyframes spin {
457
+ to { transform: rotate(360deg); }
458
+ }
459
+ .loading-spinner {
460
+ width: 40px;
461
+ height: 40px;
462
+ border: 4px solid var(--border-color);
463
+ border-top-color: var(--accent-primary);
464
+ border-radius: 50%;
465
+ animation: spin 0.8s linear infinite;
466
+ }
467
+ .upload-text {
468
+ margin-top: 1.5rem;
469
+ font-size: 1.1rem;
470
+ font-weight: 500;
471
+ color: var(--text-secondary);
472
+ }
473
+ .btn .loading-spinner {
474
+ width: 18px;
475
+ height: 18px;
476
+ border-width: 3px;
477
+ margin: 0;
478
+ }
479
+ .empty-state {
480
+ text-align: center;
481
+ padding: 4rem 1rem;
482
+ color: var(--text-secondary);
483
+ border: 2px dashed var(--border-color);
484
+ border-radius: var(--radius-md);
485
+ margin-top: 2rem;
486
+ }
487
+ .empty-state .icon {
488
+ width: 64px;
489
+ height: 64px;
490
+ margin: 0 auto 1.5rem;
491
+ opacity: 0.4;
492
+ }
493
+ .empty-state h3 {
494
+ color: var(--text-primary);
495
+ font-size: 1.5rem;
496
+ margin-bottom: 0.5rem;
497
+ }
498
+ ::-webkit-scrollbar { width: 12px; }
499
+ ::-webkit-scrollbar-track { background: var(--bg-color); }
500
+ ::-webkit-scrollbar-thumb {
501
+ background-color: var(--border-color);
502
+ border-radius: 10px;
503
+ border: 3px solid var(--bg-color);
504
+ }
505
+ ::-webkit-scrollbar-thumb:hover { background-color: var(--text-secondary); }
506
+ @media (max-width: 1024px) {
507
+ .container { padding: 1.5rem; }
508
+ .header { flex-direction: column; align-items: stretch; }
509
+ .file-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
510
+ }
511
+ @media (max-width: 768px) {
512
+ body { animation: none; }
513
+ .container { padding: 1rem; }
514
+ .header { padding: 1rem; }
515
+ .breadcrumb { justify-content: flex-start; }
516
+ .actions { flex-direction: column; }
517
+ .btn { width: 100%; }
518
+ .file-grid { grid-template-columns: 1fr; gap: 1rem; }
519
+ .modal { width: 95%; }
520
+ .modal-actions { flex-direction: column-reverse; }
521
+ }
522
+ @media (prefers-reduced-motion: reduce) {
523
+ * {
524
+ animation-duration: 0.01ms !important;
525
+ animation-iteration-count: 1 !important;
526
+ transition-duration: 0.01ms !important;
527
+ scroll-behavior: auto !important;
528
+ }
529
+ }
530
+ </style>
531
+ </head>
532
+ <body>
533
+ <div class="upload-overlay" id="uploadOverlay">
534
+ <div class="loading-spinner"></div>
535
+ <p class="upload-text">Processing...</p>
536
+ </div>
537
+ <div class="container">
538
+ <header class="header">
539
+ <h1 class="title">
540
+ <svg class="title-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
541
+ <path d="M20 18H4V9.252a1 1 0 01.382-.78l1.698-1.357A2 2 0 017.152 7H9V5a2 2 0 012-2h2a2 2 0 012 2v2h1.848a2 2 0 011.072.315l1.698 1.357A1 1 0 0120 9.252V18zM18 9.873l-1.41-1.127a1 1 0 00-.536-.158H7.946a1 1 0 00-.536.158L6 9.873V16h12V9.873zM13 5h-2V4a1 1 0 011-1h0a1 1 0 011 1v1z"/>
542
+ </svg>
543
+ SUS Drive
544
+ </h1>
545
+ <div class="storage-usage">
546
+ <p>Storage Used: {{ usage_percentage|round(2) }}% of 100GB</p>
547
+ <div class="progress-bar">
548
+ <div class="progress" style="width: {{ usage_percentage }}%;"></div>
549
+ </div>
550
+ </div>
551
+ <nav class="breadcrumb">
552
+ <span class="breadcrumb-item {{ 'active' if not path else '' }}" onclick="nav('')">
553
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
554
+ <path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
555
+ </svg>
556
+ Home
557
+ </span>
558
+ {% if path %}
559
+ {% set parts = path.split('/') %}
560
+ {% for i in range(parts|length) %}
561
+ <span class="breadcrumb-separator">/</span>
562
+ {% set current_path = parts[:i+1]|join('/') %}
563
+ <span class="breadcrumb-item {{ 'active' if current_path == path else '' }}"
564
+ onclick="nav('{{ current_path }}')">
565
+ {{ parts[i] }}
566
+ </span>
567
+ {% endfor %}
568
+ {% endif %}
569
+ </nav>
570
+ <div class="actions">
571
+ <form action="/upload" method="post" enctype="multipart/form-data" id="uploadForm">
572
+ <div class="file-input-wrapper">
573
+ <label for="fileInput" class="btn btn-primary">
574
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
575
+ <path d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"/>
576
+ </svg>
577
+ Upload File
578
+ </label>
579
+ <input type="file" name="file" required class="file-input" id="fileInput">
580
+ <input type="hidden" name="path" value="{{ path }}">
581
+ </div>
582
+ </form>
583
+ <button class="btn btn-secondary" onclick="showFolderModal('{{ path }}')">
584
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
585
+ <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
586
+ </svg>
587
+ New Folder
588
+ </button>
589
+ <button class="btn btn-secondary" onclick="showUrlDownloadModal('{{ path }}')">
590
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
591
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L6.293 6.707z" clip-rule="evenodd" />
592
+ </svg>
593
+ Download from URL
594
+ </button>
595
+ </div>
596
+ </header>
597
+ <main>
598
+ {% if items %}
599
+ <div class="file-grid">
600
+ {% for item in items %}
601
+ <div class="file-item">
602
+ {% if item.type == 'dir' %}
603
+ <a href="/?path={{ item.path }}" class="file-item-content">
604
+ {% else %}
605
+ <a href="/download?path={{ item.path }}" class="file-item-content" download="{{ item.name }}">
606
+ {% endif %}
607
+ <div class="file-icon-wrapper {{ item.type }}">
608
+ {% if item.type == 'dir' %}
609
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
610
+ <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
611
+ </svg>
612
+ {% else %}
613
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
614
+ <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
615
+ </svg>
616
+ {% endif %}
617
+ </div>
618
+ <div class="file-details">
619
+ <div class="file-name" title="{{ item.name }}">{{ item.name }}</div>
620
+ <div class="file-meta">
621
+ {% if item.type == 'dir' %}
622
+ Directory
623
+ {% else %}
624
+ File • {{ item.size_str }}
625
+ {% endif %}
626
+ </div>
627
+ </div>
628
+ </a>
629
+ <div class="dropdown">
630
+ <button class="dropdown-toggle" onclick="toggleDropdown(event, this)">
631
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
632
+ <path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/>
633
+ </svg>
634
+ </button>
635
+ <div class="dropdown-menu">
636
+ {% if item.type == 'file' %}
637
+ <button class="dropdown-item" onclick="window.location.href='/download?path={{ item.path }}'">
638
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" /></svg>
639
+ Download
640
+ </button>
641
+ {% endif %}
642
+ <button class="dropdown-item" onclick="showRenameModal('{{ item.path }}', '{{ item.name }}')">
643
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z" /><path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd" /></svg>
644
+ Rename
645
+ </button>
646
+ <button class="dropdown-item danger" onclick="showDeleteModal('{{ item.path }}', '{{ item.name }}')">
647
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm4 0a1 1 0 012 0v6a1 1 0 11-2 0V8z" clip-rule="evenodd" /></svg>
648
+ Delete
649
+ </button>
650
+ </div>
651
+ </div>
652
+ </div>
653
+ {% endfor %}
654
+ </div>
655
+ {% else %}
656
+ <div class="empty-state">
657
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
658
+ <path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
659
+ </svg>
660
+ <h3>This folder is empty</h3>
661
+ <p>Upload a file or create a new folder to get started.</p>
662
+ </div>
663
+ {% endif %}
664
+ </main>
665
+ </div>
666
+ <div class="modal-overlay" id="renameModal">
667
+ <div class="modal">
668
+ <h3 class="modal-title">Rename Item</h3>
669
+ <div class="modal-body">
670
+ <input type="text" class="modal-input" id="renameInput" placeholder="Enter new name">
671
+ </div>
672
+ <div class="modal-actions">
673
+ <button class="btn btn-secondary" onclick="closeModal('renameModal')">Cancel</button>
674
+ <button class="btn btn-primary" onclick="confirmRename()" id="renameConfirmBtn">
675
+ <span id="renameBtnText">Rename</span>
676
+ <span id="renameBtnSpinner" class="loading-spinner" style="display: none;"></span>
677
+ </button>
678
+ </div>
679
+ </div>
680
+ </div>
681
+ <div class="modal-overlay" id="deleteModal">
682
+ <div class="modal">
683
+ <h3 class="modal-title">Confirm Deletion</h3>
684
+ <div class="modal-body">
685
+ <p>Are you sure you want to delete <strong id="deleteItemName" style="color: var(--accent-danger);"></strong>? This action cannot be undone.</p>
686
+ </div>
687
+ <div class="modal-actions">
688
+ <button class="btn btn-secondary" onclick="closeModal('deleteModal')">Cancel</button>
689
+ <button class="btn btn-danger" onclick="confirmDelete()" id="deleteConfirmBtn">
690
+ <span id="deleteBtnText">Delete</span>
691
+ <span id="deleteBtnSpinner" class="loading-spinner" style="display: none;"></span>
692
+ </button>
693
+ </div>
694
+ </div>
695
+ </div>
696
+ <div class="modal-overlay" id="folderModal">
697
+ <div class="modal">
698
+ <h3 class="modal-title">Create New Folder</h3>
699
+ <div class="modal-body">
700
+ <input type="text" class="modal-input" id="folderInput" placeholder="Enter folder name">
701
+ </div>
702
+ <div class="modal-actions">
703
+ <button class="btn btn-secondary" onclick="closeModal('folderModal')">Cancel</button>
704
+ <button class="btn btn-primary" onclick="confirmCreateFolder()" id="folderConfirmBtn">
705
+ <span id="folderBtnText">Create</span>
706
+ <span id="folderBtnSpinner" class="loading-spinner" style="display: none;"></span>
707
+ </button>
708
+ </div>
709
+ </div>
710
+ </div>
711
+ <div class="modal-overlay" id="urlDownloadModal">
712
+ <div class="modal">
713
+ <h3 class="modal-title">Download from URL</h3>
714
+ <div class="modal-body">
715
+ <input type="text" class="modal-input" id="urlInput" placeholder="https://example.com/file.pdf" style="margin-bottom: 1rem;">
716
+ <input type="text" class="modal-input" id="filenameInput" placeholder="Custom filename (optional)">
717
+ <p style="font-size: 0.875rem; color: var(--text-secondary); margin-top: 0.75rem;">
718
+ The file will be downloaded directly to the current directory in your repository.
719
+ </p>
720
+ </div>
721
+ <div class="modal-actions">
722
+ <button class="btn btn-secondary" onclick="closeModal('urlDownloadModal')">Cancel</button>
723
+ <button class="btn btn-success" onclick="confirmUrlDownload()" id="urlDownloadConfirmBtn">
724
+ <span id="urlDownloadBtnText">Download</span>
725
+ <span id="urlDownloadBtnSpinner" class="loading-spinner" style="display: none;"></span>
726
+ </button>
727
+ </div>
728
+ </div>
729
+ </div>
730
+ <script>
731
+ let currentRenamePath = '';
732
+ let currentDeletePath = '';
733
+ let currentFolderPath = '';
734
+ let currentUrlDownloadPath = '';
735
+ let activeDropdown = null;
736
+ function nav(path) {
737
+ window.location.href = '/?path=' + encodeURIComponent(path);
738
+ }
739
+ function toggleDropdown(event, button) {
740
+ event.stopPropagation();
741
+ const dropdown = button.closest('.dropdown');
742
+ if (activeDropdown && activeDropdown !== dropdown) {
743
+ activeDropdown.classList.remove('active');
744
+ }
745
+ dropdown.classList.toggle('active');
746
+ activeDropdown = dropdown.classList.contains('active') ? dropdown : null;
747
+ }
748
+ function closeAllDropdowns() {
749
+ if (activeDropdown) {
750
+ activeDropdown.classList.remove('active');
751
+ activeDropdown = null;
752
+ }
753
+ }
754
+ function showSpinner(btnId, textId, spinnerId) {
755
+ const btn = document.getElementById(btnId);
756
+ const text = document.getElementById(textId);
757
+ const spinner = document.getElementById(spinnerId);
758
+ btn.disabled = true;
759
+ text.style.display = 'none';
760
+ spinner.style.display = 'inline-block';
761
+ }
762
+ function hideSpinner(btnId, textId, spinnerId) {
763
+ const btn = document.getElementById(btnId);
764
+ const text = document.getElementById(textId);
765
+ const spinner = document.getElementById(spinnerId);
766
+ btn.disabled = false;
767
+ text.style.display = 'inline-block';
768
+ spinner.style.display = 'none';
769
+ }
770
+ function showModal(modalId) {
771
+ const modal = document.getElementById(modalId);
772
+ modal.classList.add('active');
773
+ document.body.style.overflow = 'hidden';
774
+ const firstInput = modal.querySelector('.modal-input');
775
+ if (firstInput) {
776
+ setTimeout(() => {
777
+ firstInput.focus();
778
+ if (firstInput.type === 'text') {
779
+ firstInput.select();
780
+ }
781
+ }, 100);
782
+ }
783
+ }
784
+ function closeModal(modalId) {
785
+ const modal = document.getElementById(modalId);
786
+ modal.classList.remove('active');
787
+ document.body.style.overflow = '';
788
+ }
789
+ function showRenameModal(path, currentName) {
790
+ closeAllDropdowns();
791
+ currentRenamePath = path;
792
+ document.getElementById('renameInput').value = currentName;
793
+ showModal('renameModal');
794
+ }
795
+ function showDeleteModal(path, name) {
796
+ closeAllDropdowns();
797
+ currentDeletePath = path;
798
+ document.getElementById('deleteItemName').textContent = name;
799
+ showModal('deleteModal');
800
+ }
801
+ function showFolderModal(path) {
802
+ currentFolderPath = path;
803
+ document.getElementById('folderInput').value = '';
804
+ showModal('folderModal');
805
+ }
806
+ function showUrlDownloadModal(path) {
807
+ currentUrlDownloadPath = path;
808
+ document.getElementById('urlInput').value = '';
809
+ document.getElementById('filenameInput').value = '';
810
+ showModal('urlDownloadModal');
811
+ }
812
+ async function performAction(url, body, btnId, textId, spinnerId, modalToClose) {
813
+ showSpinner(btnId, textId, spinnerId);
814
+ try {
815
+ const response = await fetch(url, {
816
+ method: 'POST',
817
+ headers: { 'Content-Type': 'application/json' },
818
+ body: JSON.stringify(body)
819
+ });
820
+ const result = await response.json();
821
+ if (response.ok) {
822
+ if (modalToClose) closeModal(modalToClose);
823
+ showUploadOverlay(result.message || 'Operation successful!');
824
+ setTimeout(() => {
825
+ window.location.reload();
826
+ }, 1200);
827
+ } else {
828
+ throw new Error(result.error || 'Operation failed');
829
+ }
830
+ } catch (error) {
831
+ console.error('Action failed:', error);
832
+ alert(`Operation failed: ${error.message}`);
833
+ hideSpinner(btnId, textId, spinnerId);
834
+ }
835
+ }
836
+ function confirmRename() {
837
+ const newName = document.getElementById('renameInput').value.trim();
838
+ if (!newName) {
839
+ alert('Please enter a valid name');
840
+ return;
841
+ }
842
+ performAction('/rename', { old_path: currentRenamePath, new_path: newName }, 'renameConfirmBtn', 'renameBtnText', 'renameBtnSpinner', 'renameModal');
843
+ }
844
+ function confirmDelete() {
845
+ performAction('/delete', { path: currentDeletePath }, 'deleteConfirmBtn', 'deleteBtnText', 'deleteBtnSpinner', 'deleteModal');
846
+ }
847
+ function confirmCreateFolder() {
848
+ const folderName = document.getElementById('folderInput').value.trim();
849
+ if (!folderName || folderName.includes('/')) {
850
+ alert('Please enter a valid folder name without slashes.');
851
+ return;
852
+ }
853
+ const folderPath = currentFolderPath ? `${currentFolderPath}/${folderName}` : folderName;
854
+ performAction('/create_folder', { path: folderPath }, 'folderConfirmBtn', 'folderBtnText', 'folderBtnSpinner', 'folderModal');
855
+ }
856
+ function confirmUrlDownload() {
857
+ const url = document.getElementById('urlInput').value.trim();
858
+ const customFilename = document.getElementById('filenameInput').value.trim();
859
+ if (!url) {
860
+ alert('Please enter a valid URL');
861
+ return;
862
+ }
863
+ try { new URL(url); }
864
+ catch(e) {
865
+ alert('Please enter a valid URL format.');
866
+ return;
867
+ }
868
+ performAction('/download_url', { url: url, path: currentUrlDownloadPath, filename: customFilename || null }, 'urlDownloadConfirmBtn', 'urlDownloadBtnText', 'urlDownloadBtnSpinner', 'urlDownloadModal');
869
+ }
870
+ function showUploadOverlay(message = 'Processing...') {
871
+ const overlay = document.getElementById('uploadOverlay');
872
+ overlay.querySelector('.upload-text').textContent = message;
873
+ overlay.classList.add('active');
874
+ }
875
+ function hideUploadOverlay() {
876
+ document.getElementById('uploadOverlay').classList.remove('active');
877
+ }
878
+ document.addEventListener('DOMContentLoaded', function() {
879
+ document.getElementById('fileInput').addEventListener('change', function(e) {
880
+ if (this.files.length > 0) {
881
+ showUploadOverlay(`Uploading ${this.files[0].name}...`);
882
+ document.getElementById('uploadForm').submit();
883
+ }
884
+ });
885
+ const setupModalKeyListener = (modalId, confirmFunction) => {
886
+ document.getElementById(modalId).addEventListener('keypress', (e) => {
887
+ if (e.key === 'Enter' && e.target.tagName === 'INPUT') {
888
+ e.preventDefault();
889
+ confirmFunction();
890
+ }
891
+ });
892
+ };
893
+ setupModalKeyListener('renameModal', confirmRename);
894
+ setupModalKeyListener('folderModal', confirmCreateFolder);
895
+ setupModalKeyListener('urlDownloadModal', confirmUrlDownload);
896
+ document.addEventListener('click', (e) => {
897
+ if (!e.target.closest('.dropdown')) {
898
+ closeAllDropdowns();
899
+ }
900
+ if (e.target.classList.contains('modal-overlay')) {
901
+ closeModal(e.target.id);
902
+ }
903
+ });
904
+ document.addEventListener('keydown', (e) => {
905
+ if (e.key === 'Escape') {
906
+ const openModal = document.querySelector('.modal-overlay.active');
907
+ if (openModal) {
908
+ closeModal(openModal.id);
909
+ } else {
910
+ closeAllDropdowns();
911
+ }
912
+ }
913
+ });
914
+ });
915
+ </script>
916
+ </body>
917
+ </html>
918
+ """
919
+ def format_size(size):
920
+ if size is None:
921
+ return "Directory"
922
+ for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
923
+ if size < 1024:
924
+ return f"{size:.2f} {unit}"
925
+ size /= 1024
926
+ return f"{size:.2f} PB"
927
+ def list_folder(path=""):
928
+ if not REPO_ID or not HF_TOKEN:
929
+ return []
930
+ prefix = path.strip("/") + ("/" if path else "")
931
+ try:
932
+ files_info = api.get_paths_info(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN, path=prefix, recursive=False)
933
+ except Exception as e:
934
+ print(f"Error listing files: {e}")
935
+ return []
936
+ items = []
937
+ seen_dirs = set()
938
+ for file_info in files_info:
939
+ rel_path = file_info.path[len(prefix):] if prefix else file_info.path
940
+ if not rel_path:
941
+ continue
942
+ if "/" in rel_path:
943
+ dir_name = rel_path.split("/")[0]
944
+ dir_path = (prefix + dir_name).strip("/")
945
+ if dir_path not in seen_dirs:
946
+ seen_dirs.add(dir_path)
947
+ items.append({
948
+ "type": "dir",
949
+ "name": dir_name,
950
+ "path": dir_path,
951
+ "size": None,
952
+ "size_str": "Directory"
953
+ })
954
+ else:
955
+ items.append({
956
+ "type": "file",
957
+ "name": rel_path,
958
+ "path": file_info.path,
959
+ "size": file_info.size,
960
+ "size_str": format_size(file_info.size)
961
+ })
962
+ items.sort(key=lambda x: (x["type"] != "dir", x["name"].lower()))
963
+ return items
964
+ def get_total_size():
965
+ if not REPO_ID or not HF_TOKEN:
966
+ return 0
967
+ try:
968
+ all_files_info = api.get_paths_info(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN, path="/", recursive=True)
969
+ total_size = sum(file_info.size for file_info in all_files_info if file_info.type == "file")
970
+ return total_size
971
+ except Exception as e:
972
+ print(f"Error getting total size: {e}")
973
+ return 0
974
+ @app.route("/set_credentials", methods=["GET", "POST"])
975
+ def set_credentials():
976
+ global REPO_ID, HF_TOKEN
977
+ if request.method == "POST":
978
+ REPO_ID = request.form.get("repo_id")
979
+ HF_TOKEN = request.form.get("hf_token")
980
+ if not REPO_ID or not HF_TOKEN:
981
+ return "Missing repo_id or hf_token", 400
982
+ return redirect("/")
983
+ return """
984
+ <form method="post">
985
+ <label for="repo_id">Dataset ID (e.g., username/datasetname):</label>
986
+ <input type="text" id="repo_id" name="repo_id" required><br>
987
+ <label for="hf_token">Hugging Face Token:</label>
988
+ <input type="password" id="hf_token" name="hf_token" required><br>
989
+ <input type="submit" value="Set Credentials">
990
+ </form>
991
+ """
992
+ @app.route("/", methods=["GET"])
993
+ def index():
994
+ if not REPO_ID or not HF_TOKEN:
995
+ return redirect("/set_credentials")
996
+ path = request.args.get("path", "").strip("/")
997
+ items = list_folder(path)
998
+ total_used = get_total_size()
999
+ total_available = 100 * 1024 * 1024 * 1024
1000
+ usage_percentage = (total_used / total_available) * 100 if total_available > 0 else 0
1001
+ return render_template_string(TEMPLATE, items=items, path=path, usage_percentage=usage_percentage)
1002
+ @app.route("/download", methods=["GET"])
1003
+ def download():
1004
+ if not REPO_ID or not HF_TOKEN:
1005
+ return "Credentials not set", 403
1006
+ file_path = request.args.get("path", "")
1007
+ if not file_path:
1008
+ return "No file path provided", 400
1009
+ try:
1010
+ local_path = hf_hub_download(
1011
+ repo_id=REPO_ID,
1012
+ filename=file_path,
1013
+ repo_type="dataset",
1014
+ token=HF_TOKEN,
1015
+ cache_dir=tempfile.gettempdir()
1016
+ )
1017
+ return send_file(
1018
+ local_path,
1019
+ as_attachment=True,
1020
+ download_name=os.path.basename(file_path)
1021
+ )
1022
+ except Exception as e:
1023
+ return f"Error downloading file: {str(e)}", 500
1024
+ @app.route("/upload", methods=["POST"])
1025
+ def upload():
1026
+ if not REPO_ID or not HF_TOKEN:
1027
+ return "Credentials not set", 403
1028
+ if 'file' not in request.files:
1029
+ return "No file provided", 400
1030
+ file = request.files["file"]
1031
+ if file.filename == '':
1032
+ return "No file selected", 400
1033
+ path = request.form.get("path", "").strip("/")
1034
+ dest_path = f"{path}/{file.filename}".strip("/")
1035
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
1036
+ file.save(tmp_file.name)
1037
+ try:
1038
+ upload_file(
1039
+ path_or_fileobj=tmp_file.name,
1040
+ path_in_repo=dest_path,
1041
+ repo_id=REPO_ID,
1042
+ repo_type="dataset",
1043
+ token=HF_TOKEN
1044
+ )
1045
+ except Exception as e:
1046
+ os.unlink(tmp_file.name)
1047
+ return f"Error uploading file: {str(e)}", 500
1048
+ finally:
1049
+ try:
1050
+ os.unlink(tmp_file.name)
1051
+ except:
1052
+ pass
1053
+ return redirect(f"/?path={path}")
1054
+ @app.route("/download_url", methods=["POST"])
1055
+ def download_url():
1056
+ if not REPO_ID or not HF_TOKEN:
1057
+ return jsonify({"error": "Credentials not set"}), 403
1058
+ data = request.get_json()
1059
+ url = data.get("url", "").strip()
1060
+ path = data.get("path", "").strip("/")
1061
+ custom_filename = data.get("filename", "").strip()
1062
+ if not url:
1063
+ return jsonify({"error": "No URL provided"}), 400
1064
+ if not url.startswith(('http://', 'https://')):
1065
+ return jsonify({"error": "Invalid URL. Must start with http:// or https://"}), 400
1066
+ try:
1067
+ filename = get_filename_from_url(url, custom_filename)
1068
+ dest_path = f"{path}/{filename}".strip("/")
1069
+ response = requests.get(url, stream=True, timeout=30)
1070
+ response.raise_for_status()
1071
+ content_length = response.headers.get('content-length')
1072
+ if content_length and int(content_length) > 5 * 1024 * 1024 * 1024:
1073
+ return jsonify({"error": "File too large. Maximum size is 5GB"}), 400
1074
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
1075
+ for chunk in response.iter_content(chunk_size=8192):
1076
+ tmp_file.write(chunk)
1077
+ tmp_file.flush()
1078
+ upload_file(
1079
+ path_or_fileobj=tmp_file.name,
1080
+ path_in_repo=dest_path,
1081
+ repo_id=REPO_ID,
1082
+ repo_type="dataset",
1083
+ token=HF_TOKEN
1084
+ )
1085
+ os.unlink(tmp_file.name)
1086
+ return jsonify({"status": "success", "message": "File downloaded and uploaded successfully"})
1087
+ except requests.exceptions.RequestException as e:
1088
+ return jsonify({"error": f"Failed to download from URL: {str(e)}"}), 400
1089
+ except Exception as e:
1090
+ return jsonify({"error": f"Upload failed: {str(e)}"}), 500
1091
+ @app.route("/delete", methods=["POST"])
1092
+ def delete():
1093
+ if not REPO_ID or not HF_TOKEN:
1094
+ return jsonify({"error": "Credentials not set"}), 403
1095
+ data = request.get_json()
1096
+ delete_path = data.get("path", "").strip("/")
1097
+ if not delete_path:
1098
+ return jsonify({"error": "No path provided"}), 400
1099
+ try:
1100
+ all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
1101
+ deleted_count = 0
1102
+ for file_path in all_files:
1103
+ if file_path == delete_path or file_path.startswith(delete_path.rstrip("/") + "/"):
1104
+ delete_file(
1105
+ repo_id=REPO_ID,
1106
+ path_in_repo=file_path,
1107
+ repo_type="dataset",
1108
+ token=HF_TOKEN
1109
+ )
1110
+ deleted_count += 1
1111
+ if deleted_count == 0:
1112
+ return jsonify({"error": "No files found to delete"}), 404
1113
+ return jsonify({"status": "success", "message": f"Deleted {deleted_count} file(s)"})
1114
+ except Exception as e:
1115
+ return jsonify({"error": f"Delete failed: {str(e)}"}), 500
1116
+ @app.route("/create_folder", methods=["POST"])
1117
+ def create_folder():
1118
+ if not REPO_ID or not HF_TOKEN:
1119
+ return jsonify({"error": "Credentials not set"}), 403
1120
+ data = request.get_json()
1121
+ folder_path = data.get("path", "").strip("/")
1122
+ if not folder_path:
1123
+ return jsonify({"error": "No folder path provided"}), 400
1124
+ keep_file_path = f"{folder_path}/.keep"
1125
+ try:
1126
+ with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
1127
+ tmp_file.write(b"# This file keeps the folder in git\n")
1128
+ tmp_file.flush()
1129
+ upload_file(
1130
+ path_or_fileobj=tmp_file.name,
1131
+ path_in_repo=keep_file_path,
1132
+ repo_id=REPO_ID,
1133
+ repo_type="dataset",
1134
+ token=HF_TOKEN
1135
+ )
1136
+ os.unlink(tmp_file.name)
1137
+ return jsonify({"status": "success", "message": "Folder created successfully"})
1138
+ except Exception as e:
1139
+ return jsonify({"error": f"Failed to create folder: {str(e)}"}), 500
1140
+ @app.route("/rename", methods=["POST"])
1141
+ def rename():
1142
+ if not REPO_ID or not HF_TOKEN:
1143
+ return jsonify({"error": "Credentials not set"}), 403
1144
+ data = request.get_json()
1145
+ old_path = data.get("old_path", "").strip("/")
1146
+ new_name = data.get("new_path", "").strip()
1147
+ if not old_path or not new_name:
1148
+ return jsonify({"error": "Missing old path or new name"}), 400
1149
+ try:
1150
+ parent_dir = "/".join(old_path.split("/")[:-1]) if "/" in old_path else ""
1151
+ new_path = f"{parent_dir}/{new_name}".strip("/")
1152
+ all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
1153
+ renamed_count = 0
1154
+ for file_path in all_files:
1155
+ if file_path == old_path or file_path.startswith(old_path + "/"):
1156
+ relative_path = file_path[len(old_path):].lstrip("/")
1157
+ new_file_path = (new_path + "/" + relative_path).strip("/")
1158
+ local_path = hf_hub_download(
1159
+ repo_id=REPO_ID,
1160
+ filename=file_path,
1161
+ repo_type="dataset",
1162
+ token=HF_TOKEN,
1163
+ cache_dir=tempfile.gettempdir()
1164
+ )
1165
+ upload_file(
1166
+ path_or_fileobj=local_path,
1167
+ path_in_repo=new_file_path,
1168
+ repo_id=REPO_ID,
1169
+ repo_type="dataset",
1170
+ token=HF_TOKEN
1171
+ )
1172
+ delete_file(
1173
+ repo_id=REPO_ID,
1174
+ path_in_repo=file_path,
1175
+ repo_type="dataset",
1176
+ token=HF_TOKEN
1177
+ )
1178
+ renamed_count += 1
1179
+ if renamed_count == 0:
1180
+ return jsonify({"error": "No files found to rename"}), 404
1181
+ return jsonify({"status": "success", "message": f"Renamed {renamed_count} file(s)"})
1182
+ except Exception as e:
1183
+ return jsonify({"error": f"Rename failed: {str(e)}"}), 500
1184
+ def get_filename_from_url(url, custom_filename=None):
1185
+ if custom_filename:
1186
+ return custom_filename
1187
+ parsed_url = urlparse(url)
1188
+ path = unquote(parsed_url.path)
1189
+ filename = os.path.basename(path)
1190
+ if not filename or '.' not in filename:
1191
+ filename = f"downloaded_file_{int(time.time())}"
1192
+ return filename
1193
+ if __name__ == "__main__":
1194
+ app.run(debug=True, host="0.0.0.0")