testdeep123 commited on
Commit
5e61cf8
·
verified ·
1 Parent(s): 034c4aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -651
app.py CHANGED
@@ -10,657 +10,149 @@ HF_TOKEN = os.getenv("HF_TOKEN")
10
  app = Flask(__name__)
11
  api = HfApi()
12
 
 
13
  TEMPLATE = """
14
  <!DOCTYPE html>
15
- <html>
16
  <head>
17
  <meta charset="UTF-8">
18
- <title>HuggingFace Drive - {{ path or 'Root' }}</title>
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
 
20
  <style>
21
- * {
22
- margin: 0;
23
- padding: 0;
24
- box-sizing: border-box;
25
- }
26
-
27
- body {
28
- background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%);
29
- color: #e2e8f0;
30
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
31
- min-height: 100vh;
32
- overflow-x: hidden;
33
- }
34
-
35
- .container {
36
- max-width: 1200px;
37
- margin: 0 auto;
38
- padding: 20px;
39
- }
40
-
41
- .header {
42
- background: rgba(255, 255, 255, 0.05);
43
- backdrop-filter: blur(10px);
44
- border-radius: 16px;
45
- padding: 24px;
46
- margin-bottom: 24px;
47
- border: 1px solid rgba(255, 255, 255, 0.1);
48
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
49
- }
50
-
51
- .title {
52
- font-size: 2rem;
53
- font-weight: 700;
54
- margin-bottom: 16px;
55
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
56
- -webkit-background-clip: text;
57
- -webkit-text-fill-color: transparent;
58
- background-clip: text;
59
- }
60
-
61
- .breadcrumb {
62
- display: flex;
63
- align-items: center;
64
- gap: 8px;
65
- margin-bottom: 20px;
66
- flex-wrap: wrap;
67
- }
68
-
69
- .breadcrumb-item {
70
- background: rgba(255, 255, 255, 0.1);
71
- color: #94a3b8;
72
- padding: 6px 12px;
73
- border-radius: 20px;
74
- font-size: 0.875rem;
75
- cursor: pointer;
76
- transition: all 0.3s ease;
77
- border: 1px solid rgba(255, 255, 255, 0.1);
78
- }
79
-
80
- .breadcrumb-item:hover {
81
- background: rgba(102, 126, 234, 0.2);
82
- color: #e2e8f0;
83
- transform: translateY(-1px);
84
- }
85
-
86
- .breadcrumb-item.active {
87
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
88
- color: white;
89
- }
90
-
91
- .breadcrumb-separator {
92
- color: #64748b;
93
- font-size: 0.875rem;
94
- }
95
-
96
- .actions {
97
- display: flex;
98
- gap: 12px;
99
- margin-bottom: 24px;
100
- flex-wrap: wrap;
101
- }
102
-
103
- .upload-container {
104
- position: relative;
105
- overflow: hidden;
106
- display: inline-block;
107
- }
108
-
109
- .file-input {
110
- position: absolute;
111
- left: -9999px;
112
- opacity: 0;
113
- }
114
-
115
- .btn {
116
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
117
- color: white;
118
- border: none;
119
- padding: 12px 24px;
120
- border-radius: 12px;
121
- cursor: pointer;
122
- font-weight: 600;
123
- font-size: 0.875rem;
124
- transition: all 0.3s ease;
125
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
126
- border: 1px solid rgba(255, 255, 255, 0.1);
127
- }
128
-
129
- .btn:hover {
130
- transform: translateY(-2px);
131
- box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
132
- }
133
-
134
- .btn-secondary {
135
- background: rgba(255, 255, 255, 0.1);
136
- color: #e2e8f0;
137
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
138
- }
139
-
140
- .btn-secondary:hover {
141
- background: rgba(255, 255, 255, 0.15);
142
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
143
- }
144
-
145
- .file-grid {
146
- display: grid;
147
- gap: 16px;
148
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
149
- }
150
-
151
- .file-item {
152
- background: rgba(255, 255, 255, 0.05);
153
- backdrop-filter: blur(10px);
154
- border: 1px solid rgba(255, 255, 255, 0.1);
155
- border-radius: 16px;
156
- padding: 20px;
157
- transition: all 0.3s ease;
158
- cursor: pointer;
159
- position: relative;
160
- overflow: hidden;
161
- }
162
-
163
- .file-item::before {
164
- content: '';
165
- position: absolute;
166
- top: 0;
167
- left: -100%;
168
- width: 100%;
169
- height: 100%;
170
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
171
- transition: left 0.5s ease;
172
- }
173
-
174
- .file-item:hover::before {
175
- left: 100%;
176
- }
177
-
178
- .file-item:hover {
179
- transform: translateY(-4px);
180
- box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3);
181
- border-color: rgba(102, 126, 234, 0.3);
182
- }
183
-
184
- .file-content {
185
- display: flex;
186
- align-items: center;
187
- justify-content: space-between;
188
- position: relative;
189
- z-index: 2;
190
- }
191
-
192
- .file-info {
193
- display: flex;
194
- align-items: center;
195
- gap: 12px;
196
- flex: 1;
197
- }
198
-
199
- .file-icon {
200
- font-size: 1.5rem;
201
- width: 40px;
202
- height: 40px;
203
- display: flex;
204
- align-items: center;
205
- justify-content: center;
206
- border-radius: 10px;
207
- background: rgba(255, 255, 255, 0.1);
208
- }
209
-
210
- .folder-icon {
211
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
212
- }
213
-
214
- .file-name {
215
- font-weight: 500;
216
- font-size: 1rem;
217
- color: #f1f5f9;
218
- white-space: nowrap;
219
- overflow: hidden;
220
- text-overflow: ellipsis;
221
- }
222
-
223
- .dropdown {
224
- position: relative;
225
- }
226
-
227
- .dropdown-toggle {
228
- background: rgba(255, 255, 255, 0.1);
229
- border: none;
230
- color: #94a3b8;
231
- padding: 8px;
232
- border-radius: 8px;
233
- cursor: pointer;
234
- transition: all 0.3s ease;
235
- font-size: 1.25rem;
236
- width: 36px;
237
- height: 36px;
238
- display: flex;
239
- align-items: center;
240
- justify-content: center;
241
- }
242
-
243
- .dropdown-toggle:hover {
244
- background: rgba(255, 255, 255, 0.2);
245
- color: #e2e8f0;
246
- }
247
-
248
- .dropdown-menu {
249
- position: absolute;
250
- top: 100%;
251
- right: 0;
252
- background: rgba(15, 15, 35, 0.95);
253
- backdrop-filter: blur(20px);
254
- border: 1px solid rgba(255, 255, 255, 0.1);
255
- border-radius: 12px;
256
- padding: 8px;
257
- min-width: 150px;
258
- opacity: 0;
259
- visibility: hidden;
260
- transform: translateY(-10px);
261
- transition: all 0.3s ease;
262
- z-index: 1000;
263
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
264
- }
265
-
266
- .dropdown.active .dropdown-menu {
267
- opacity: 1;
268
- visibility: visible;
269
- transform: translateY(0);
270
- }
271
-
272
- .dropdown-item {
273
- display: flex;
274
- align-items: center;
275
- gap: 8px;
276
- padding: 10px 12px;
277
- border-radius: 8px;
278
- cursor: pointer;
279
- transition: all 0.2s ease;
280
- font-size: 0.875rem;
281
- color: #cbd5e1;
282
- }
283
-
284
- .dropdown-item:hover {
285
- background: rgba(255, 255, 255, 0.1);
286
- color: #f1f5f9;
287
- }
288
-
289
- .dropdown-item.danger:hover {
290
- background: rgba(239, 68, 68, 0.2);
291
- color: #fca5a5;
292
- }
293
-
294
- /* Modal Styles */
295
- .modal-overlay {
296
- position: fixed;
297
- top: 0;
298
- left: 0;
299
- width: 100%;
300
- height: 100%;
301
- background: rgba(0, 0, 0, 0.7);
302
- backdrop-filter: blur(5px);
303
- display: flex;
304
- align-items: center;
305
- justify-content: center;
306
- z-index: 2000;
307
- opacity: 0;
308
- visibility: hidden;
309
- transition: all 0.3s ease;
310
- }
311
-
312
- .modal-overlay.active {
313
- opacity: 1;
314
- visibility: visible;
315
- }
316
-
317
- .modal {
318
- background: rgba(15, 15, 35, 0.95);
319
- backdrop-filter: blur(20px);
320
- border: 1px solid rgba(255, 255, 255, 0.1);
321
- border-radius: 20px;
322
- padding: 32px;
323
- max-width: 400px;
324
- width: 90%;
325
- transform: scale(0.8);
326
- transition: all 0.3s ease;
327
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
328
- }
329
-
330
- .modal-overlay.active .modal {
331
- transform: scale(1);
332
- }
333
-
334
- .modal-title {
335
- font-size: 1.25rem;
336
- font-weight: 600;
337
- margin-bottom: 16px;
338
- color: #f1f5f9;
339
- }
340
-
341
- .modal-input {
342
- width: 100%;
343
- background: rgba(255, 255, 255, 0.1);
344
- border: 1px solid rgba(255, 255, 255, 0.2);
345
- border-radius: 12px;
346
- padding: 12px 16px;
347
- color: #f1f5f9;
348
- font-size: 1rem;
349
- margin-bottom: 20px;
350
- }
351
-
352
- .modal-input:focus {
353
- outline: none;
354
- border-color: #667eea;
355
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
356
- }
357
-
358
- .modal-actions {
359
- display: flex;
360
- gap: 12px;
361
- justify-content: flex-end;
362
- }
363
-
364
- .btn-danger {
365
- background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
366
- box-shadow: 0 4px 15px rgba(239, 68, 68, 0.3);
367
- }
368
-
369
- .btn-danger:hover {
370
- box-shadow: 0 8px 25px rgba(239, 68, 68, 0.4);
371
- }
372
-
373
- /* Responsive Design */
374
- @media (max-width: 768px) {
375
- .container {
376
- padding: 16px;
377
- }
378
-
379
- .file-grid {
380
- grid-template-columns: 1fr;
381
- }
382
-
383
- .actions {
384
- flex-direction: column;
385
- }
386
-
387
- .breadcrumb {
388
- flex-direction: column;
389
- align-items: flex-start;
390
- }
391
- }
392
-
393
- /* Custom Scrollbar */
394
- ::-webkit-scrollbar {
395
- width: 8px;
396
- }
397
-
398
- ::-webkit-scrollbar-track {
399
- background: rgba(255, 255, 255, 0.1);
400
- }
401
-
402
- ::-webkit-scrollbar-thumb {
403
- background: rgba(102, 126, 234, 0.5 design);
404
- border-radius: 4px;
405
- }
406
-
407
- ::-webkit-scrollbar-thumb:hover {
408
- background: rgba(102, 126, 234, 0.7);
409
- }
410
  </style>
411
  </head>
412
  <body>
413
  <div class="container">
414
  <div class="header">
415
- <h1 class="title">🚀 HuggingFace Drive</h1>
416
-
417
- <!-- Breadcrumb Navigation -->
418
  <div class="breadcrumb">
419
- <span class="breadcrumb-item {{ 'active' if not path else '' }}" onclick="nav('')">
420
- 🏠 Home
421
- </span>
422
  {% if path %}
423
- {% set parts = path.split('/') %}
424
  {% for i in range(parts|length) %}
425
- <span class="breadcrumb-separator">›</span>
426
- {% set current_path = parts[:i+1]|join('/') %}
427
- <span class="breadcrumb-item {{ 'active' if current_path == path else '' }}"
428
- onclick="nav('{{ current_path }}')">
429
- 📁 {{ parts[i] }}
430
- </span>
431
  {% endfor %}
432
  {% endif %}
433
  </div>
434
-
435
- <!-- Actions -->
436
  <div class="actions">
437
- <form action="/upload" method="post" enctype="multipart/form-data" class="upload-container">
438
- <input type="file" name="file" required class="file-input" id="fileInput">
439
  <input type="hidden" name="path" value="{{ path }}">
440
- <label for="fileInput" class="btn">📤 Upload File</label>
441
  </form>
442
- <button class="btn btn-secondary" onclick="showCreateFolderModal('{{ path }}')">
443
- 📁 New Folder
444
- </button>
445
  </div>
446
  </div>
447
-
448
- <!-- File Grid -->
449
  <div class="file-grid">
450
  {% for item in items %}
451
- <div class="file-item">
452
- <div class="file-content">
453
- <div class="file-info" onclick="{% if item.type=='dir' %}nav('{{ item.path }}'){% endif %}">
454
- <div class="file-icon {{ 'folder-icon' if item.type=='dir' else '' }}">
455
- {{ '📁' if item.type=='dir' else '📄' }}
456
- </div>
457
- <div class="file-name">{{ item.name }}</div>
458
- </div>
459
-
460
- <div class="dropdown">
461
- <button class="dropdown-toggle" onclick="toggleDropdown(this)">⋮</button>
462
- <div class="dropdown-menu">
463
- {% if item.type == 'file' %}
464
- <div class="dropdown-item" onclick="download('{{ item.path }}')">
465
- 📥 Download
466
- </div>
467
- {% endif %}
468
- <div class="dropdown-item" onclick="showRenameModal('{{ item.path }}', '{{ item.name }}')">
469
- ✏️ Rename
470
- </div>
471
- <div class="dropdown-item danger" onclick="showDeleteModal('{{ item.path }}')">
472
- 🗑️ Delete
473
- </div>
474
- </div>
475
- </div>
476
  </div>
477
  </div>
 
478
  {% endfor %}
479
  </div>
480
  </div>
481
 
482
- <!-- Create Folder Modal -->
483
- <div class="modal-overlay" id="createFolderModal">
484
  <div class="modal">
485
- <h3 class="modal-title">Create New Folder</h3>
486
- <input type="text" class="modal-input" id="folderNameInput" placeholder="Enter folder name">
487
  <div class="modal-actions">
488
- <button class="btn btn-secondary" onclick="closeModal('createFolderModal')">Cancel</button>
489
- <button class="btn" onclick="confirmCreateFolder()">Create</button>
490
  </div>
491
  </div>
492
  </div>
493
-
494
- <!-- Rename Modal -->
495
  <div class="modal-overlay" id="renameModal">
496
  <div class="modal">
497
- <h3 class="modal-title">Rename Item</h3>
498
- <input type="text" class="modal-input" id="renameInput" placeholder="Enter new name">
499
  <div class="modal-actions">
500
  <button class="btn btn-secondary" onclick="closeModal('renameModal')">Cancel</button>
501
- <button class="btn" onclick="confirmRename()">Rename</button>
502
  </div>
503
  </div>
504
  </div>
505
-
506
- <!-- Delete Confirmation Modal -->
507
  <div class="modal-overlay" id="deleteModal">
508
  <div class="modal">
509
- <h3 class="modal-title">Confirm Deletion</h3>
510
- <p style="margin-bottom: 20px; color: #cbd5e1;">Are you sure you want to delete this item? This action cannot be undone.</p>
511
  <div class="modal-actions">
512
  <button class="btn btn-secondary" onclick="closeModal('deleteModal')">Cancel</button>
513
- <button class="btn btn-danger" onclick="confirmDelete()">Delete</button>
514
  </div>
515
  </div>
516
  </div>
517
 
518
  <script>
519
- let currentRenamePath = '';
520
- let currentDeletePath = '';
521
- let currentFolderPath = '';
522
- let activeDropdown = null;
523
-
524
- function nav(p) {
525
- location.href = '/?path=' + encodeURIComponent(p);
526
- }
527
-
528
- function download(path) {
529
- window.open('/download?path=' + encodeURIComponent(path), '_blank');
530
- }
531
-
532
- function toggleDropdown(button) {
533
- const dropdown = button.closest('.dropdown');
534
- if (activeDropdown && activeDropdown !== dropdown) {
535
- activeDropdown.classList.remove('active');
536
- }
537
- dropdown.classList.toggle('active');
538
- activeDropdown = dropdown.classList.contains('active') ? dropdown : null;
539
- }
540
-
541
- function showCreateFolderModal(currentPath) {
542
- currentFolderPath = currentPath;
543
- document.getElementById('folderNameInput').value = '';
544
- showModal('createFolderModal');
545
- setTimeout(() => document.getElementById('folderNameInput').focus(), 100);
546
- }
547
-
548
- function showRenameModal(path, currentName) {
549
- currentRenamePath = path;
550
- document.getElementById('renameInput').value = currentName;
551
- showModal('renameModal');
552
- setTimeout(() => document.getElementById('renameInput').focus(), 100);
553
- closeAllDropdowns();
554
- }
555
-
556
- function showDeleteModal(path) {
557
- currentDeletePath = path;
558
- showModal('deleteModal');
559
- closeAllDropdowns();
560
- }
561
-
562
- function showModal(modalId) {
563
- document.getElementById(modalId).classList.add('active');
564
- }
565
-
566
- function closeModal(modalId) {
567
- document.getElementById(modalId).classList.remove('active');
568
- }
569
-
570
- function closeAllDropdowns() {
571
- document.querySelectorAll('.dropdown.active').forEach(dropdown => {
572
- dropdown.classList.remove('active');
573
- });
574
- activeDropdown = null;
575
- }
576
-
577
- async function confirmCreateFolder() {
578
- const name = document.getElementById('folderNameInput').value.trim();
579
- if (!name) return;
580
- const folderPath = currentFolderPath ? `${currentFolderPath}/${name}` : name;
581
- try {
582
- const response = await fetch('/create_folder', {
583
- method: 'POST',
584
- headers: { 'Content-Type': 'application/json' },
585
- body: JSON.stringify({ path: folderPath })
586
- });
587
- if (response.ok) {
588
- closeModal('createFolderModal');
589
- location.reload();
590
- }
591
- } catch (error) {
592
- console.error('Create folder failed:', error);
593
- }
594
- }
595
-
596
- async function confirmRename() {
597
- const newName = document.getElementById('renameInput').value.trim();
598
- if (!newName) return;
599
- const oldPathParts = currentRenamePath.split('/');
600
- oldPathParts[oldPathParts.length - 1] = newName;
601
- const newPath = oldPathParts.join('/');
602
- try {
603
- const response = await fetch('/rename', {
604
- method: 'POST',
605
- headers: { 'Content-Type': 'application/json' },
606
- body: JSON.stringify({ old_path: currentRenamePath, new_path: newPath })
607
- });
608
- if (response.ok) {
609
- closeModal('renameModal');
610
- location.reload();
611
- }
612
- } catch (error) {
613
- console.error('Rename failed:', error);
614
- }
615
- }
616
-
617
- async function confirmDelete() {
618
- try {
619
- const response = await fetch('/delete', {
620
- method: 'POST',
621
- headers: { 'Content-Type': 'application/json' },
622
- body: JSON.stringify({ path: currentDeletePath })
623
- });
624
- if (response.ok) {
625
- closeModal('deleteModal');
626
- location.reload();
627
- }
628
- } catch (error) {
629
- console.error('Delete failed:', error);
630
- }
631
- }
632
-
633
- document.addEventListener('click', function(e) {
634
- if (!e.target.closest('.dropdown')) {
635
- closeAllDropdowns();
636
- }
637
- });
638
-
639
- document.getElementById('fileInput').addEventListener('change', function(e) {
640
- if (e.target.files.length > 0) {
641
- e.target.closest('form').submit();
642
- }
643
- });
644
-
645
- document.querySelectorAll('.modal-overlay').forEach(overlay => {
646
- overlay.addEventListener('click', function(e) {
647
- if (e.target === overlay) {
648
- overlay.classList.remove('active');
649
- }
650
- });
651
- });
652
-
653
- document.getElementById('folderNameInput').addEventListener('keypress', function(e) {
654
- if (e.key === 'Enter') {
655
- confirmCreateFolder();
656
- }
657
- });
658
-
659
- document.getElementById('renameInput').addEventListener('keypress', function(e) {
660
- if (e.key === 'Enter') {
661
- confirmRename();
662
- }
663
- });
664
  </script>
665
  </body>
666
  </html>
@@ -669,76 +161,65 @@ TEMPLATE = """
669
  def list_folder(path=""):
670
  prefix = path.strip("/") + ("/" if path else "")
671
  all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
672
- seen = set()
673
- items = []
674
  for f in all_files:
675
  if not f.startswith(prefix): continue
676
  rest = f[len(prefix):]
677
  if "/" in rest:
678
  dir_name = rest.split("/")[0]
679
- dir_path = (prefix + dir_name).strip("/")
680
  if dir_path not in seen:
681
- seen.add(dir_path)
682
- items.append({"type":"dir","name":dir_name,"path":dir_path})
683
  else:
684
- items.append({"type":"file","name":rest,"path":(prefix + rest).strip("/")})
685
- items.sort(key=lambda x: (x["type"]!="dir", x["name"].lower()))
686
  return items
687
 
688
- @app.route("/", methods=["GET"])
689
  def index():
690
- path = request.args.get("path","").strip("/")
691
  return render_template_string(TEMPLATE, items=list_folder(path), path=path)
692
 
693
- @app.route("/download", methods=["GET"])
694
  def download():
695
- p = request.args.get("path","")
696
- local = hf_hub_download(repo_id=REPO_ID, filename=p, repo_type="dataset", token=HF_TOKEN, cache_dir=tempfile.gettempdir())
697
- return send_file(local, as_attachment=True, download_name=os.path.basename(p))
698
 
699
- @app.route("/upload", methods=["POST"])
700
  def upload():
701
- file = request.files["file"]
702
- path = request.form.get("path","").strip("/")
703
- dest = f"{path}/{file.filename}".strip("/")
704
- tmp = tempfile.NamedTemporaryFile(delete=False)
705
- file.save(tmp.name)
706
- upload_file(path_or_fileobj=tmp.name, path_in_repo=dest, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
707
  return redirect(f"/?path={path}")
708
 
709
- @app.route("/delete", methods=["POST"])
710
  def delete():
711
- d = request.get_json()["path"]
712
- all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
713
- for f in all_files:
714
- if f == d or f.startswith(d.rstrip("/")+"/"):
715
- delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
716
- return jsonify(status="ok")
717
 
718
- @app.route("/create_folder", methods=["POST"])
719
  def create_folder():
720
- folder = request.get_json()["path"].strip("/")
721
- keep = f"{folder}/.keep"
722
- tmp = tempfile.NamedTemporaryFile(delete=False)
723
- tmp.write(b"")
724
- tmp.flush()
725
- upload_file(path_or_fileobj=tmp.name, path_in_repo=keep, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
726
- return jsonify(status="ok")
727
 
728
- @app.route("/rename", methods=["POST"])
729
  def rename():
730
- data = request.get_json()
731
- old, new = data["old_path"].strip("/"), data["new_path"].strip("/")
732
- all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
733
- for f in all_files:
734
- if f == old or f.startswith(old+"/"):
735
- rel = f[len(old):].lstrip("/")
736
- newp = (new + "/" + rel).strip("/")
737
- local = hf_hub_download(repo_id=REPO_ID, filename=f, repo_type="dataset",
738
- token=HF_TOKEN, cache_dir=tempfile.gettempdir())
739
- upload_file(path_or_fileobj=local, path_in_repo=newp, repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
740
- delete_file(repo_id=REPO_ID, path_in_repo=f, repo_type="dataset", token=HF_TOKEN)
741
- return jsonify(status="ok")
742
-
743
- if __name__ == "__main__":
744
- app.run(debug=True, host="0.0.0.0", port=7860)
 
10
  app = Flask(__name__)
11
  api = HfApi()
12
 
13
+ # HTML template with responsive layout and custom modals
14
  TEMPLATE = """
15
  <!DOCTYPE html>
16
+ <html lang="en">
17
  <head>
18
  <meta charset="UTF-8">
 
19
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>HuggingFace Drive - {{ path or 'Root' }}</title>
21
  <style>
22
+ /* Global Reset */
23
+ * { margin: 0; padding: 0; box-sizing: border-box; }
24
+ body { background: #1a1a2e; color: #e2e8f0; font-family: sans-serif; overflow-x: hidden; }
25
+ .container { max-width: 1200px; margin: auto; padding: 20px; }
26
+ .header { background: rgba(255,255,255,0.05); padding: 20px; border-radius: 12px; margin-bottom: 20px; }
27
+ .title { font-size: 2rem; background: linear-gradient(135deg,#667eea,#764ba2); -webkit-background-clip: text; color: transparent; }
28
+ .breadcrumb { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
29
+ .breadcrumb-item { background: rgba(255,255,255,0.1); padding: 6px 12px; border-radius: 16px; font-size: 0.9rem; cursor: pointer; }
30
+ .breadcrumb-item.active { background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
31
+ .actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 16px; }
32
+ .btn { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 0.9rem; }
33
+ .btn-primary { background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
34
+ .btn-secondary { background: rgba(255,255,255,0.1); color: #e2e8f0; }
35
+ .file-grid { display: grid; grid-template-columns: repeat(auto-fill,minmax(200px,1fr)); gap: 16px; }
36
+ .file-item { background: rgba(255,255,255,0.05); padding: 16px; border-radius: 12px; position: relative; overflow: hidden; }
37
+ .file-info { display: flex; align-items: center; gap: 8px; cursor: pointer; }
38
+ .file-icon { width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 6px; background: rgba(255,255,255,0.1); }
39
+ .dropdown { position: absolute; top: 10px; right: 10px; }
40
+ .dropdown-menu { display: none; position: absolute; top: 100%; right: 0; background: rgba(15,15,35,0.95); border-radius: 8px; padding: 8px; margin-top: 4px; }
41
+ .dropdown.active .dropdown-menu { display: block; }
42
+ .dropdown-item { padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 0.9rem; }
43
+ .dropdown-item:hover { background: rgba(255,255,255,0.1); }
44
+ .dropdown-item.danger { color: #f87171; }
45
+ /* Modals */
46
+ .modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.7); display: none; align-items: center; justify-content: center; }
47
+ .modal-overlay.active { display: flex; }
48
+ .modal { background: rgba(15,15,35,0.95); padding: 24px; border-radius: 12px; width: 90%; max-width: 400px; }
49
+ .modal h3 { margin-bottom: 12px; font-size: 1.2rem; }
50
+ .modal input { width: 100%; padding: 10px; border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; margin-bottom: 16px; background: rgba(255,255,255,0.1); color: #e2e8f0; }
51
+ .modal-actions { display: flex; justify-content: flex-end; gap: 10px; }
52
+ @media(max-width:768px) { .file-grid { grid-template-columns: 1fr; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  </style>
54
  </head>
55
  <body>
56
  <div class="container">
57
  <div class="header">
58
+ <div class="title">HuggingFace Drive</div>
 
 
59
  <div class="breadcrumb">
60
+ <div class="breadcrumb-item" onclick="nav('')">Home</div>
 
 
61
  {% if path %}
62
+ {% set parts=path.split('/') %}
63
  {% for i in range(parts|length) %}
64
+ <div class="breadcrumb-item{% if parts[:i+1]|join('/')==path %} active{% endif %}" onclick="nav('{{ parts[:i+1]|join('/') }}')">{{ parts[i] }}</div>
 
 
 
 
 
65
  {% endfor %}
66
  {% endif %}
67
  </div>
 
 
68
  <div class="actions">
69
+ <form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data">
70
+ <input type="file" name="file" id="fileInput" style="display:none;" onchange="document.getElementById('uploadForm').submit()">
71
  <input type="hidden" name="path" value="{{ path }}">
72
+ <button type="button" class="btn btn-primary" onclick="document.getElementById('fileInput').click()">Upload File</button>
73
  </form>
74
+ <button class="btn btn-secondary" onclick="openCreateModal()">New Folder</button>
 
 
75
  </div>
76
  </div>
 
 
77
  <div class="file-grid">
78
  {% for item in items %}
79
+ <div class="file-item">
80
+ <div class="file-info" onclick="{% if item.type=='dir' %}nav('{{ item.path }}'){% endif %}">
81
+ <div class="file-icon">{{ '📁' if item.type=='dir' else '📄' }}</div>
82
+ <div>{{ item.name }}</div>
83
+ </div>
84
+ <div class="dropdown">
85
+ <button onclick="toggleDropdown(this)">⋮</button>
86
+ <div class="dropdown-menu">
87
+ {% if item.type=='file' %}<div class="dropdown-item" onclick="download('{{ item.path }}')">Download</div>{% endif %}
88
+ <div class="dropdown-item" onclick="openRenameModal('{{ item.path }}','{{ item.name }}')">Rename</div>
89
+ <div class="dropdown-item danger" onclick="openDeleteModal('{{ item.path }}')">Delete</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  </div>
91
  </div>
92
+ </div>
93
  {% endfor %}
94
  </div>
95
  </div>
96
 
97
+ <!-- Modals -->
98
+ <div class="modal-overlay" id="createModal">
99
  <div class="modal">
100
+ <h3>Create New Folder</h3>
101
+ <input type="text" id="createName" placeholder="Folder name">
102
  <div class="modal-actions">
103
+ <button class="btn btn-secondary" onclick="closeModal('createModal')">Cancel</button>
104
+ <button class="btn btn-primary" onclick="confirmCreate()">Create</button>
105
  </div>
106
  </div>
107
  </div>
 
 
108
  <div class="modal-overlay" id="renameModal">
109
  <div class="modal">
110
+ <h3>Rename Item</h3>
111
+ <input type="text" id="renameInput">
112
  <div class="modal-actions">
113
  <button class="btn btn-secondary" onclick="closeModal('renameModal')">Cancel</button>
114
+ <button class="btn btn-primary" onclick="confirmRename()">Rename</button>
115
  </div>
116
  </div>
117
  </div>
 
 
118
  <div class="modal-overlay" id="deleteModal">
119
  <div class="modal">
120
+ <h3>Confirm Deletion</h3>
121
+ <p>Are you sure you want to delete this item?</p>
122
  <div class="modal-actions">
123
  <button class="btn btn-secondary" onclick="closeModal('deleteModal')">Cancel</button>
124
+ <button class="btn btn-primary danger" onclick="confirmDelete()">Delete</button>
125
  </div>
126
  </div>
127
  </div>
128
 
129
  <script>
130
+ let currentPath = '';
131
+ function nav(p) { window.location = '/?path='+encodeURIComponent(p); }
132
+ function download(p) { window.open('/download?path='+encodeURIComponent(p)); }
133
+ function toggleDropdown(btn) {
134
+ const d = btn.parentElement; d.classList.toggle('active');
135
+ }
136
+ function openCreateModal() { document.getElementById('createModal').classList.add('active'); }
137
+ function openRenameModal(path,name) { currentPath=path; document.getElementById('renameInput').value=name; document.getElementById('renameModal').classList.add('active'); }
138
+ function openDeleteModal(path) { currentPath=path; document.getElementById('deleteModal').classList.add('active'); }
139
+ function closeModal(id) { document.getElementById(id).classList.remove('active'); }
140
+ function confirmCreate() {
141
+ const name = document.getElementById('createName').value.trim(); if(!name) return;
142
+ fetch('/create_folder',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path: name})})
143
+ .then(r=>{closeModal('createModal');window.location.reload();});
144
+ }
145
+ function confirmRename() {
146
+ const newName = document.getElementById('renameInput').value.trim(); if(!newName) return;
147
+ fetch('/rename',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({old_path:currentPath,new_path:newName})})
148
+ .then(r=>{closeModal('renameModal');window.location.reload();});
149
+ }
150
+ function confirmDelete() {
151
+ fetch('/delete',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({path:currentPath})})
152
+ .then(r=>{closeModal('deleteModal');window.location.reload();});
153
+ }
154
+ // Close modals on overlay click
155
+ document.querySelectorAll('.modal-overlay').forEach(o=>o.addEventListener('click',e=>{if(e.target===o)o.classList.remove('active');}));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  </script>
157
  </body>
158
  </html>
 
161
  def list_folder(path=""):
162
  prefix = path.strip("/") + ("/" if path else "")
163
  all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
164
+ seen = set(); items = []
 
165
  for f in all_files:
166
  if not f.startswith(prefix): continue
167
  rest = f[len(prefix):]
168
  if "/" in rest:
169
  dir_name = rest.split("/")[0]
170
+ dir_path = (prefix+dir_name).strip("/")
171
  if dir_path not in seen:
172
+ seen.add(dir_path); items.append({"type":"dir","name":dir_name,"path":dir_path})
 
173
  else:
174
+ items.append({"type":"file","name":rest,"path":(prefix+rest).strip("/")})
175
+ items.sort(key=lambda x:(x['type']!='dir',x['name'].lower()))
176
  return items
177
 
178
+ @app.route('/',methods=['GET'])
179
  def index():
180
+ path = request.args.get('path','').strip('/')
181
  return render_template_string(TEMPLATE, items=list_folder(path), path=path)
182
 
183
+ @app.route('/download',methods=['GET'])
184
  def download():
185
+ p=request.args.get('path','')
186
+ local=hf_hub_download(repo_id=REPO_ID,filename=p,repo_type='dataset',token=HF_TOKEN,cache_dir=tempfile.gettempdir())
187
+ return send_file(local,as_attachment=True,download_name=os.path.basename(p))
188
 
189
+ @app.route('/upload',methods=['POST'])
190
  def upload():
191
+ file=request.files['file']; path=request.form.get('path','').strip('/')
192
+ dest=f"{path}/{file.filename}".strip('/')
193
+ tmp=tempfile.NamedTemporaryFile(delete=False); file.save(tmp.name)
194
+ upload_file(path_or_fileobj=tmp.name,path_in_repo=dest,repo_id=REPO_ID,repo_type='dataset',token=HF_TOKEN)
 
 
195
  return redirect(f"/?path={path}")
196
 
197
+ @app.route('/delete',methods=['POST'])
198
  def delete():
199
+ d=request.get_json()['path']
200
+ for f in api.list_repo_files(repo_id=REPO_ID,repo_type='dataset',token=HF_TOKEN):
201
+ if f==d or f.startswith(d.rstrip('/')+'/'):
202
+ delete_file(repo_id=REPO_ID,path_in_repo=f,repo_type='dataset',token=HF_TOKEN)
203
+ return jsonify(status='ok')
 
204
 
205
+ @app.route('/create_folder',methods=['POST'])
206
  def create_folder():
207
+ folder=request.get_json()['path'].strip('/')
208
+ keep=f"{folder}/.keep"
209
+ tmp=tempfile.NamedTemporaryFile(delete=False); tmp.write(b""); tmp.flush()
210
+ upload_file(path_or_fileobj=tmp.name,path_in_repo=keep,repo_id=REPO_ID,repo_type='dataset',token=HF_TOKEN)
211
+ return jsonify(status='ok')
 
 
212
 
213
+ @app.route('/rename',methods=['POST'])
214
  def rename():
215
+ data=request.get_json(); old,new=data['old_path'].strip('/'),data['new_path'].strip('/')
216
+ for f in api.list_repo_files(repo_id=REPO_ID,repo_type='dataset',token=HF_TOKEN):
217
+ if f==old or f.startswith(old+'/'):
218
+ rel=f[len(old):].lstrip('/'); newp=(new+'/'+rel).strip('/')
219
+ local=hf_hub_download(repo_id=REPO_ID,filename=f,repo_type='dataset',token=HF_TOKEN,cache_dir=tempfile.gettempdir())
220
+ upload_file(path_or_fileobj=local,path_in_repo=newp,repo_id=REPO_ID,repo_type='dataset',token=HF_TOKEN)
221
+ delete_file(repo_id=REPO_ID,path_in_repo=f,repo_type='dataset',token=HF_TOKEN)
222
+ return jsonify(status='ok')
223
+
224
+ if __name__=='__main__':
225
+ app.run(host='0.0.0.0',port=7860,debug=True)