testdeep123 commited on
Commit
a6ae3e4
·
verified ·
1 Parent(s): dbc771b

Update app.py

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