Alamgirapi commited on
Commit
f90e0d6
Β·
verified Β·
1 Parent(s): a7248ed

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +748 -480
templates/index.html CHANGED
@@ -1,481 +1,749 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>HuggingFace Uploader</title>
7
- <style>
8
- body {
9
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
- max-width: 800px;
11
- margin: 0 auto;
12
- padding: 20px;
13
- background-color: #f8f9fa;
14
- }
15
- .container {
16
- background: white;
17
- padding: 30px;
18
- border-radius: 10px;
19
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
20
- }
21
- h1 {
22
- color: #333;
23
- text-align: center;
24
- margin-bottom: 30px;
25
- }
26
- .form-group {
27
- margin-bottom: 20px;
28
- }
29
- label {
30
- display: block;
31
- margin-bottom: 5px;
32
- font-weight: 600;
33
- color: #555;
34
- }
35
- input, select, textarea {
36
- width: 100%;
37
- padding: 12px;
38
- border: 2px solid #ddd;
39
- border-radius: 5px;
40
- font-size: 14px;
41
- transition: border-color 0.3s;
42
- }
43
- input:focus, select:focus, textarea:focus {
44
- outline: none;
45
- border-color: #007bff;
46
- }
47
- .file-input-container {
48
- border: 2px dashed #ddd;
49
- border-radius: 5px;
50
- padding: 20px;
51
- text-align: center;
52
- background-color: #f9f9f9;
53
- transition: all 0.3s;
54
- }
55
- .file-input-container:hover {
56
- border-color: #007bff;
57
- background-color: #f0f8ff;
58
- }
59
- .file-input-container.drag-over {
60
- border-color: #007bff;
61
- background-color: #e6f3ff;
62
- }
63
- #files {
64
- display: none;
65
- }
66
- .file-input-label {
67
- display: inline-block;
68
- background-color: #007bff;
69
- color: white;
70
- padding: 10px 20px;
71
- border-radius: 5px;
72
- cursor: pointer;
73
- margin-bottom: 10px;
74
- transition: background-color 0.3s;
75
- }
76
- .file-input-label:hover {
77
- background-color: #0056b3;
78
- }
79
- .upload-modes {
80
- display: flex;
81
- gap: 10px;
82
- margin-bottom: 15px;
83
- }
84
- .upload-mode {
85
- flex: 1;
86
- padding: 10px;
87
- border: 2px solid #ddd;
88
- border-radius: 5px;
89
- text-align: center;
90
- cursor: pointer;
91
- transition: all 0.3s;
92
- }
93
- .upload-mode.active {
94
- border-color: #007bff;
95
- background-color: #e6f3ff;
96
- }
97
- .upload-mode:hover {
98
- border-color: #007bff;
99
- }
100
- .file-list {
101
- margin-top: 15px;
102
- padding: 10px;
103
- background-color: #f8f9fa;
104
- border-radius: 5px;
105
- max-height: 200px;
106
- overflow-y: auto;
107
- }
108
- .file-item {
109
- display: flex;
110
- justify-content: space-between;
111
- align-items: center;
112
- padding: 5px 0;
113
- border-bottom: 1px solid #eee;
114
- }
115
- .file-item:last-child {
116
- border-bottom: none;
117
- }
118
- .remove-file {
119
- color: #dc3545;
120
- cursor: pointer;
121
- font-weight: bold;
122
- }
123
- .remove-file:hover {
124
- text-decoration: underline;
125
- }
126
- .btn {
127
- background-color: #28a745;
128
- color: white;
129
- padding: 12px 30px;
130
- border: none;
131
- border-radius: 5px;
132
- font-size: 16px;
133
- cursor: pointer;
134
- transition: background-color 0.3s;
135
- width: 100%;
136
- }
137
- .btn:hover {
138
- background-color: #218838;
139
- }
140
- .btn:disabled {
141
- background-color: #6c757d;
142
- cursor: not-allowed;
143
- }
144
- .progress-container {
145
- margin-top: 20px;
146
- display: none;
147
- }
148
- .progress-bar {
149
- width: 100%;
150
- height: 20px;
151
- background-color: #e9ecef;
152
- border-radius: 10px;
153
- overflow: hidden;
154
- }
155
- .progress-fill {
156
- height: 100%;
157
- background-color: #28a745;
158
- transition: width 0.3s ease;
159
- width: 0%;
160
- }
161
- .progress-text {
162
- text-align: center;
163
- margin-top: 10px;
164
- font-weight: 600;
165
- }
166
- .error {
167
- color: #dc3545;
168
- background-color: #f8d7da;
169
- border: 1px solid #f5c6cb;
170
- padding: 10px;
171
- border-radius: 5px;
172
- margin-top: 10px;
173
- }
174
- .success {
175
- color: #155724;
176
- background-color: #d4edda;
177
- border: 1px solid #c3e6cb;
178
- padding: 10px;
179
- border-radius: 5px;
180
- margin-top: 10px;
181
- }
182
- .help-text {
183
- font-size: 12px;
184
- color: #6c757d;
185
- margin-top: 5px;
186
- }
187
- .folder-instructions {
188
- background-color: #fff3cd;
189
- border: 1px solid #ffeaa7;
190
- padding: 10px;
191
- border-radius: 5px;
192
- margin-top: 10px;
193
- font-size: 14px;
194
- }
195
- </style>
196
- </head>
197
- <body>
198
- <div class="container">
199
- <h1>πŸ€— HuggingFace Uploader</h1>
200
-
201
- <form id="uploadForm" enctype="multipart/form-data">
202
- <div class="form-group">
203
- <label for="hf_token">HuggingFace Token:</label>
204
- <input type="password" id="hf_token" name="hf_token" required
205
- placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
206
- <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div>
207
- </div>
208
-
209
- <div class="form-group">
210
- <label for="repo_id">Repository ID:</label>
211
- <input type="text" id="repo_id" name="repo_id" required
212
- placeholder="your-username/repository-name">
213
- <div class="help-text">Format: username/repository-name</div>
214
- </div>
215
-
216
- <div class="form-group">
217
- <label for="repo_type">Repository Type:</label>
218
- <select id="repo_type" name="repo_type">
219
- <option value="space">Space</option>
220
- <option value="model">Model</option>
221
- <option value="dataset">Dataset</option>
222
- </select>
223
- </div>
224
-
225
- <div class="form-group">
226
- <label for="commit_message">Commit Message:</label>
227
- <textarea id="commit_message" name="commit_message" rows="2"
228
- placeholder="Upload via Flask App">Upload via Flask App</textarea>
229
- </div>
230
-
231
- <div class="form-group">
232
- <label>Upload Type:</label>
233
- <div class="upload-modes">
234
- <div class="upload-mode active" data-mode="folder">
235
- πŸ“ Folder
236
- </div>
237
- <div class="upload-mode" data-mode="files">
238
- πŸ“„ Files
239
- </div>
240
- <div class="upload-mode" data-mode="zip">
241
- πŸ“¦ ZIP Archive
242
- </div>
243
- </div>
244
- </div>
245
-
246
- <div class="form-group">
247
- <label>Select Files:</label>
248
- <div class="file-input-container" id="fileInputContainer">
249
- <label for="files" class="file-input-label">
250
- πŸ“Ž Choose Files or Drag & Drop
251
- </label>
252
- <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;">
253
- <div id="uploadInstructions">
254
- <p>πŸ“ <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
255
- <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
256
- </div>
257
- </div>
258
- <div class="folder-instructions">
259
- <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder.
260
- All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure.
261
- </div>
262
- <div id="fileList" class="file-list" style="display: none;"></div>
263
- </div>
264
-
265
- <button type="submit" class="btn" id="uploadBtn">πŸš€ Upload to HuggingFace</button>
266
- </form>
267
-
268
- <div class="progress-container" id="progressContainer">
269
- <div class="progress-bar">
270
- <div class="progress-fill" id="progressFill"></div>
271
- </div>
272
- <div class="progress-text" id="progressText">Starting upload...</div>
273
- </div>
274
-
275
- <div id="message"></div>
276
- </div>
277
-
278
- <script>
279
- let selectedFiles = [];
280
- let currentMode = 'folder';
281
-
282
- // Upload mode switching
283
- document.querySelectorAll('.upload-mode').forEach(mode => {
284
- mode.addEventListener('click', function() {
285
- document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active'));
286
- this.classList.add('active');
287
- currentMode = this.dataset.mode;
288
-
289
- const fileInput = document.getElementById('files');
290
- const instructions = document.getElementById('uploadInstructions');
291
-
292
- if (currentMode === 'zip') {
293
- fileInput.removeAttribute('webkitdirectory');
294
- fileInput.removeAttribute('multiple');
295
- fileInput.setAttribute('accept', '.zip');
296
- instructions.innerHTML = '<p>πŸ“¦ Select a ZIP file to upload</p>';
297
- } else if (currentMode === 'folder') {
298
- fileInput.setAttribute('webkitdirectory', '');
299
- fileInput.setAttribute('multiple', '');
300
- fileInput.removeAttribute('accept');
301
- instructions.innerHTML = `
302
- <p>πŸ“ <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
303
- <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
304
- `;
305
- } else {
306
- fileInput.removeAttribute('webkitdirectory');
307
- fileInput.setAttribute('multiple', '');
308
- fileInput.removeAttribute('accept');
309
- instructions.innerHTML = `
310
- <p>πŸ“„ <strong>Files Mode:</strong> Select one or more individual files</p>
311
- <p>Files will be uploaded to the root of your repository</p>
312
- `;
313
- }
314
-
315
- // Clear previous selections
316
- selectedFiles = [];
317
- updateFileList();
318
- });
319
- });
320
-
321
- // File input handling
322
- const fileInput = document.getElementById('files');
323
- const fileInputContainer = document.getElementById('fileInputContainer');
324
- const fileList = document.getElementById('fileList');
325
-
326
- fileInput.addEventListener('change', function(e) {
327
- selectedFiles = Array.from(e.target.files);
328
- updateFileList();
329
- });
330
-
331
- // Drag and drop handling
332
- fileInputContainer.addEventListener('dragover', function(e) {
333
- e.preventDefault();
334
- fileInputContainer.classList.add('drag-over');
335
- });
336
-
337
- fileInputContainer.addEventListener('dragleave', function(e) {
338
- e.preventDefault();
339
- fileInputContainer.classList.remove('drag-over');
340
- });
341
-
342
- fileInputContainer.addEventListener('drop', function(e) {
343
- e.preventDefault();
344
- fileInputContainer.classList.remove('drag-over');
345
-
346
- const items = Array.from(e.dataTransfer.items);
347
- selectedFiles = [];
348
-
349
- items.forEach(item => {
350
- if (item.kind === 'file') {
351
- selectedFiles.push(item.getAsFile());
352
- }
353
- });
354
-
355
- updateFileList();
356
- });
357
-
358
- function updateFileList() {
359
- if (selectedFiles.length === 0) {
360
- fileList.style.display = 'none';
361
- return;
362
- }
363
-
364
- fileList.style.display = 'block';
365
- fileList.innerHTML = '';
366
-
367
- selectedFiles.forEach((file, index) => {
368
- const fileItem = document.createElement('div');
369
- fileItem.className = 'file-item';
370
- const displayName = file.webkitRelativePath || file.name;
371
- fileItem.innerHTML = `
372
- <span>πŸ“„ ${displayName} (${formatFileSize(file.size)})</span>
373
- <span class="remove-file" onclick="removeFile(${index})">βœ•</span>
374
- `;
375
- fileList.appendChild(fileItem);
376
- });
377
- }
378
-
379
- function removeFile(index) {
380
- selectedFiles.splice(index, 1);
381
- updateFileList();
382
- }
383
-
384
- function formatFileSize(bytes) {
385
- if (bytes === 0) return '0 Bytes';
386
- const k = 1024;
387
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
388
- const i = Math.floor(Math.log(bytes) / Math.log(k));
389
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
390
- }
391
-
392
- // Form submission
393
- document.getElementById('uploadForm').addEventListener('submit', function(e) {
394
- e.preventDefault();
395
-
396
- if (selectedFiles.length === 0) {
397
- showMessage('Please select files to upload', 'error');
398
- return;
399
- }
400
-
401
- const formData = new FormData();
402
- formData.append('hf_token', document.getElementById('hf_token').value);
403
- formData.append('repo_id', document.getElementById('repo_id').value);
404
- formData.append('repo_type', document.getElementById('repo_type').value);
405
- formData.append('commit_message', document.getElementById('commit_message').value);
406
-
407
- // Add files to form data
408
- selectedFiles.forEach(file => {
409
- formData.append('files', file);
410
- });
411
-
412
- // Show progress
413
- document.getElementById('progressContainer').style.display = 'block';
414
- document.getElementById('uploadBtn').disabled = true;
415
- document.getElementById('uploadBtn').textContent = 'Uploading...';
416
-
417
- fetch('/upload', {
418
- method: 'POST',
419
- body: formData
420
- })
421
- .then(response => response.json())
422
- .then(data => {
423
- if (data.success) {
424
- showMessage(data.message, 'success');
425
- checkProgress(data.upload_id);
426
- } else {
427
- showMessage(data.error || 'Upload failed', 'error');
428
- resetForm();
429
- }
430
- })
431
- .catch(error => {
432
- showMessage('Error: ' + error.message, 'error');
433
- resetForm();
434
- });
435
- });
436
-
437
- function checkProgress(uploadId) {
438
- const progressFill = document.getElementById('progressFill');
439
- const progressText = document.getElementById('progressText');
440
-
441
- const interval = setInterval(() => {
442
- fetch(`/progress/${uploadId}`)
443
- .then(response => response.json())
444
- .then(data => {
445
- progressFill.style.width = data.progress + '%';
446
- progressText.textContent = data.status;
447
-
448
- if (data.completed || data.progress === 100) {
449
- clearInterval(interval);
450
- if (data.status.includes('Error')) {
451
- showMessage(data.status, 'error');
452
- } else {
453
- showMessage('Upload completed successfully! πŸŽ‰', 'success');
454
- }
455
- resetForm();
456
- }
457
- })
458
- .catch(error => {
459
- clearInterval(interval);
460
- showMessage('Error checking progress: ' + error.message, 'error');
461
- resetForm();
462
- });
463
- }, 2000);
464
- }
465
-
466
- function showMessage(message, type) {
467
- const messageDiv = document.getElementById('message');
468
- messageDiv.className = type;
469
- messageDiv.textContent = message;
470
- messageDiv.style.display = 'block';
471
- }
472
-
473
- function resetForm() {
474
- document.getElementById('uploadBtn').disabled = false;
475
- document.getElementById('uploadBtn').textContent = 'πŸš€ Upload to HuggingFace';
476
- document.getElementById('progressContainer').style.display = 'none';
477
- document.getElementById('progressFill').style.width = '0%';
478
- }
479
- </script>
480
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>HuggingFace Uploader</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
16
+ max-width: 900px;
17
+ margin: 0 auto;
18
+ padding: 20px;
19
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
20
+ min-height: 100vh;
21
+ position: relative;
22
+ }
23
+
24
+ body::before {
25
+ content: '';
26
+ position: fixed;
27
+ top: 0;
28
+ left: 0;
29
+ right: 0;
30
+ bottom: 0;
31
+ background:
32
+ radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
33
+ radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%);
34
+ pointer-events: none;
35
+ z-index: -1;
36
+ }
37
+
38
+ .container {
39
+ background: rgba(255, 255, 255, 0.95);
40
+ backdrop-filter: blur(10px);
41
+ padding: 40px;
42
+ border-radius: 20px;
43
+ box-shadow:
44
+ 0 20px 40px rgba(0, 0, 0, 0.1),
45
+ 0 0 0 1px rgba(255, 255, 255, 0.2);
46
+ border: 1px solid rgba(255, 255, 255, 0.3);
47
+ position: relative;
48
+ overflow: hidden;
49
+ }
50
+
51
+ .container::before {
52
+ content: '';
53
+ position: absolute;
54
+ top: 0;
55
+ left: 0;
56
+ right: 0;
57
+ height: 4px;
58
+ background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcf7f, #4ecdc4, #45b7d1);
59
+ border-radius: 20px 20px 0 0;
60
+ }
61
+
62
+ h1 {
63
+ color: #2c3e50;
64
+ text-align: center;
65
+ margin-bottom: 40px;
66
+ font-size: 2.5em;
67
+ font-weight: 800;
68
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
69
+ -webkit-background-clip: text;
70
+ -webkit-text-fill-color: transparent;
71
+ background-clip: text;
72
+ text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
73
+ }
74
+
75
+ .form-group {
76
+ margin-bottom: 25px;
77
+ position: relative;
78
+ }
79
+
80
+ label {
81
+ display: block;
82
+ margin-bottom: 8px;
83
+ font-weight: 600;
84
+ color: #34495e;
85
+ font-size: 14px;
86
+ text-transform: uppercase;
87
+ letter-spacing: 0.5px;
88
+ }
89
+
90
+ input, select, textarea {
91
+ width: 100%;
92
+ padding: 16px 20px;
93
+ border: 2px solid #e0e6ed;
94
+ border-radius: 12px;
95
+ font-size: 16px;
96
+ transition: all 0.3s ease;
97
+ background: rgba(255, 255, 255, 0.9);
98
+ backdrop-filter: blur(5px);
99
+ }
100
+
101
+ input:focus, select:focus, textarea:focus {
102
+ outline: none;
103
+ border-color: #667eea;
104
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
105
+ transform: translateY(-2px);
106
+ }
107
+
108
+ .file-input-container {
109
+ border: 3px dashed #e0e6ed;
110
+ border-radius: 16px;
111
+ padding: 40px 20px;
112
+ text-align: center;
113
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
114
+ transition: all 0.3s ease;
115
+ position: relative;
116
+ overflow: hidden;
117
+ }
118
+
119
+ .file-input-container::before {
120
+ content: '';
121
+ position: absolute;
122
+ top: 0;
123
+ left: -100%;
124
+ width: 100%;
125
+ height: 100%;
126
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
127
+ transition: left 0.6s ease;
128
+ }
129
+
130
+ .file-input-container:hover::before {
131
+ left: 100%;
132
+ }
133
+
134
+ .file-input-container:hover {
135
+ border-color: #667eea;
136
+ background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%);
137
+ transform: translateY(-2px);
138
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
139
+ }
140
+
141
+ .file-input-container.drag-over {
142
+ border-color: #667eea;
143
+ background: linear-gradient(135deg, #e6f3ff 0%, #d1ecf1 100%);
144
+ transform: scale(1.02);
145
+ box-shadow: 0 15px 40px rgba(102, 126, 234, 0.3);
146
+ }
147
+
148
+ #files {
149
+ display: none;
150
+ }
151
+
152
+ .file-input-label {
153
+ display: inline-block;
154
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
155
+ color: white;
156
+ padding: 16px 32px;
157
+ border-radius: 12px;
158
+ cursor: pointer;
159
+ margin-bottom: 15px;
160
+ transition: all 0.3s ease;
161
+ font-weight: 600;
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.5px;
164
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
165
+ position: relative;
166
+ overflow: hidden;
167
+ }
168
+
169
+ .file-input-label::before {
170
+ content: '';
171
+ position: absolute;
172
+ top: 0;
173
+ left: -100%;
174
+ width: 100%;
175
+ height: 100%;
176
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
177
+ transition: left 0.6s ease;
178
+ }
179
+
180
+ .file-input-label:hover::before {
181
+ left: 100%;
182
+ }
183
+
184
+ .file-input-label:hover {
185
+ transform: translateY(-2px);
186
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
187
+ }
188
+
189
+ .upload-modes {
190
+ display: grid;
191
+ grid-template-columns: repeat(3, 1fr);
192
+ gap: 15px;
193
+ margin-bottom: 25px;
194
+ }
195
+
196
+ .upload-mode {
197
+ padding: 20px 15px;
198
+ border: 2px solid #e0e6ed;
199
+ border-radius: 12px;
200
+ text-align: center;
201
+ cursor: pointer;
202
+ transition: all 0.3s ease;
203
+ font-weight: 600;
204
+ background: rgba(255, 255, 255, 0.9);
205
+ backdrop-filter: blur(5px);
206
+ position: relative;
207
+ overflow: hidden;
208
+ }
209
+
210
+ .upload-mode::before {
211
+ content: '';
212
+ position: absolute;
213
+ top: 0;
214
+ left: 0;
215
+ right: 0;
216
+ bottom: 0;
217
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
218
+ opacity: 0;
219
+ transition: opacity 0.3s ease;
220
+ }
221
+
222
+ .upload-mode span {
223
+ position: relative;
224
+ z-index: 1;
225
+ }
226
+
227
+ .upload-mode.active {
228
+ border-color: #667eea;
229
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
230
+ color: white;
231
+ transform: translateY(-2px);
232
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
233
+ }
234
+
235
+ .upload-mode:hover {
236
+ border-color: #667eea;
237
+ transform: translateY(-2px);
238
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2);
239
+ }
240
+
241
+ .file-list {
242
+ margin-top: 20px;
243
+ padding: 20px;
244
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
245
+ border-radius: 12px;
246
+ max-height: 250px;
247
+ overflow-y: auto;
248
+ border: 1px solid rgba(0, 0, 0, 0.05);
249
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
250
+ }
251
+
252
+ .file-item {
253
+ display: flex;
254
+ justify-content: space-between;
255
+ align-items: center;
256
+ padding: 12px 0;
257
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
258
+ transition: all 0.3s ease;
259
+ }
260
+
261
+ .file-item:last-child {
262
+ border-bottom: none;
263
+ }
264
+
265
+ .file-item:hover {
266
+ background: rgba(102, 126, 234, 0.05);
267
+ border-radius: 8px;
268
+ padding: 12px 10px;
269
+ }
270
+
271
+ .remove-file {
272
+ color: #e74c3c;
273
+ cursor: pointer;
274
+ font-weight: bold;
275
+ padding: 6px 10px;
276
+ border-radius: 6px;
277
+ transition: all 0.3s ease;
278
+ background: rgba(231, 76, 60, 0.1);
279
+ }
280
+
281
+ .remove-file:hover {
282
+ background: #e74c3c;
283
+ color: white;
284
+ transform: scale(1.1);
285
+ }
286
+
287
+ .btn {
288
+ background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
289
+ color: white;
290
+ padding: 18px 40px;
291
+ border: none;
292
+ border-radius: 12px;
293
+ font-size: 18px;
294
+ font-weight: 600;
295
+ cursor: pointer;
296
+ transition: all 0.3s ease;
297
+ width: 100%;
298
+ text-transform: uppercase;
299
+ letter-spacing: 0.5px;
300
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3);
301
+ position: relative;
302
+ overflow: hidden;
303
+ }
304
+
305
+ .btn::before {
306
+ content: '';
307
+ position: absolute;
308
+ top: 0;
309
+ left: -100%;
310
+ width: 100%;
311
+ height: 100%;
312
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
313
+ transition: left 0.6s ease;
314
+ }
315
+
316
+ .btn:hover::before {
317
+ left: 100%;
318
+ }
319
+
320
+ .btn:hover {
321
+ transform: translateY(-2px);
322
+ box-shadow: 0 8px 25px rgba(0, 184, 148, 0.4);
323
+ }
324
+
325
+ .btn:disabled {
326
+ background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%);
327
+ cursor: not-allowed;
328
+ transform: none;
329
+ box-shadow: none;
330
+ }
331
+
332
+ .progress-container {
333
+ margin-top: 30px;
334
+ display: none;
335
+ }
336
+
337
+ .progress-bar {
338
+ width: 100%;
339
+ height: 12px;
340
+ background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%);
341
+ border-radius: 6px;
342
+ overflow: hidden;
343
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
344
+ position: relative;
345
+ }
346
+
347
+ .progress-fill {
348
+ height: 100%;
349
+ background: linear-gradient(90deg, #00b894, #00a085, #fdcb6e, #e17055);
350
+ background-size: 200% 100%;
351
+ animation: progressShine 2s linear infinite;
352
+ transition: width 0.3s ease;
353
+ width: 0%;
354
+ border-radius: 6px;
355
+ box-shadow: 0 0 10px rgba(0, 184, 148, 0.5);
356
+ }
357
+
358
+ @keyframes progressShine {
359
+ 0% { background-position: 200% 0; }
360
+ 100% { background-position: -200% 0; }
361
+ }
362
+
363
+ .progress-text {
364
+ text-align: center;
365
+ margin-top: 15px;
366
+ font-weight: 600;
367
+ color: #2c3e50;
368
+ font-size: 16px;
369
+ }
370
+
371
+ .error {
372
+ color: #e74c3c;
373
+ background: linear-gradient(135deg, #fdcbcb 0%, #fecaca 100%);
374
+ border: 1px solid #f5c6cb;
375
+ padding: 16px 20px;
376
+ border-radius: 12px;
377
+ margin-top: 20px;
378
+ box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1);
379
+ border-left: 4px solid #e74c3c;
380
+ }
381
+
382
+ .success {
383
+ color: #00b894;
384
+ background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
385
+ border: 1px solid #c3e6cb;
386
+ padding: 16px 20px;
387
+ border-radius: 12px;
388
+ margin-top: 20px;
389
+ box-shadow: 0 4px 15px rgba(0, 184, 148, 0.1);
390
+ border-left: 4px solid #00b894;
391
+ }
392
+
393
+ .help-text {
394
+ font-size: 13px;
395
+ color: #7f8c8d;
396
+ margin-top: 8px;
397
+ font-style: italic;
398
+ }
399
+
400
+ .folder-instructions {
401
+ background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%);
402
+ border: 1px solid #ffeaa7;
403
+ padding: 16px 20px;
404
+ border-radius: 12px;
405
+ margin-top: 20px;
406
+ font-size: 14px;
407
+ box-shadow: 0 4px 15px rgba(255, 234, 167, 0.2);
408
+ border-left: 4px solid #fdcb6e;
409
+ }
410
+
411
+ #uploadInstructions {
412
+ font-size: 16px;
413
+ color: #2c3e50;
414
+ line-height: 1.6;
415
+ }
416
+
417
+ #uploadInstructions p {
418
+ margin-bottom: 10px;
419
+ }
420
+
421
+ #uploadInstructions strong {
422
+ color: #667eea;
423
+ }
424
+
425
+ /* Responsive design */
426
+ @media (max-width: 768px) {
427
+ .container {
428
+ padding: 20px;
429
+ margin: 10px;
430
+ }
431
+
432
+ .upload-modes {
433
+ grid-template-columns: 1fr;
434
+ }
435
+
436
+ h1 {
437
+ font-size: 2em;
438
+ }
439
+
440
+ .file-input-container {
441
+ padding: 30px 15px;
442
+ }
443
+ }
444
+
445
+ /* Smooth scrollbar for file list */
446
+ .file-list::-webkit-scrollbar {
447
+ width: 6px;
448
+ }
449
+
450
+ .file-list::-webkit-scrollbar-track {
451
+ background: rgba(0, 0, 0, 0.1);
452
+ border-radius: 3px;
453
+ }
454
+
455
+ .file-list::-webkit-scrollbar-thumb {
456
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
457
+ border-radius: 3px;
458
+ }
459
+
460
+ .file-list::-webkit-scrollbar-thumb:hover {
461
+ background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%);
462
+ }
463
+ </style>
464
+ </head>
465
+ <body>
466
+ <div class="container">
467
+ <h1>πŸ€— HuggingFace Uploader</h1>
468
+
469
+ <form id="uploadForm" enctype="multipart/form-data">
470
+ <div class="form-group">
471
+ <label for="hf_token">HuggingFace Token:</label>
472
+ <input type="password" id="hf_token" name="hf_token" required
473
+ placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
474
+ <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div>
475
+ </div>
476
+
477
+ <div class="form-group">
478
+ <label for="repo_id">Repository ID:</label>
479
+ <input type="text" id="repo_id" name="repo_id" required
480
+ placeholder="your-username/repository-name">
481
+ <div class="help-text">Format: username/repository-name</div>
482
+ </div>
483
+
484
+ <div class="form-group">
485
+ <label for="repo_type">Repository Type:</label>
486
+ <select id="repo_type" name="repo_type">
487
+ <option value="space">Space</option>
488
+ <option value="model">Model</option>
489
+ <option value="dataset">Dataset</option>
490
+ </select>
491
+ </div>
492
+
493
+ <div class="form-group">
494
+ <label for="commit_message">Commit Message:</label>
495
+ <textarea id="commit_message" name="commit_message" rows="2"
496
+ placeholder="Upload via Flask App">Upload via Flask App</textarea>
497
+ </div>
498
+
499
+ <div class="form-group">
500
+ <label>Upload Type:</label>
501
+ <div class="upload-modes">
502
+ <div class="upload-mode active" data-mode="folder">
503
+ <span>πŸ“ Folder</span>
504
+ </div>
505
+ <div class="upload-mode" data-mode="files">
506
+ <span>πŸ“„ Files</span>
507
+ </div>
508
+ <div class="upload-mode" data-mode="zip">
509
+ <span>πŸ“¦ ZIP Archive</span>
510
+ </div>
511
+ </div>
512
+ </div>
513
+
514
+ <div class="form-group">
515
+ <label>Select Files:</label>
516
+ <div class="file-input-container" id="fileInputContainer">
517
+ <label for="files" class="file-input-label">
518
+ πŸ“Ž Choose Files or Drag & Drop
519
+ </label>
520
+ <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;">
521
+ <div id="uploadInstructions">
522
+ <p>πŸ“ <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
523
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
524
+ </div>
525
+ </div>
526
+ <div class="folder-instructions">
527
+ <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder.
528
+ All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure.
529
+ </div>
530
+ <div id="fileList" class="file-list" style="display: none;"></div>
531
+ </div>
532
+
533
+ <button type="submit" class="btn" id="uploadBtn">πŸš€ Upload to HuggingFace</button>
534
+ </form>
535
+
536
+ <div class="progress-container" id="progressContainer">
537
+ <div class="progress-bar">
538
+ <div class="progress-fill" id="progressFill"></div>
539
+ </div>
540
+ <div class="progress-text" id="progressText">Starting upload...</div>
541
+ </div>
542
+
543
+ <div id="message"></div>
544
+ </div>
545
+
546
+ <script>
547
+ let selectedFiles = [];
548
+ let currentMode = 'folder';
549
+
550
+ // Upload mode switching
551
+ document.querySelectorAll('.upload-mode').forEach(mode => {
552
+ mode.addEventListener('click', function() {
553
+ document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active'));
554
+ this.classList.add('active');
555
+ currentMode = this.dataset.mode;
556
+
557
+ const fileInput = document.getElementById('files');
558
+ const instructions = document.getElementById('uploadInstructions');
559
+
560
+ if (currentMode === 'zip') {
561
+ fileInput.removeAttribute('webkitdirectory');
562
+ fileInput.removeAttribute('multiple');
563
+ fileInput.setAttribute('accept', '.zip');
564
+ instructions.innerHTML = '<p>πŸ“¦ Select a ZIP file to upload</p>';
565
+ } else if (currentMode === 'folder') {
566
+ fileInput.setAttribute('webkitdirectory', '');
567
+ fileInput.setAttribute('multiple', '');
568
+ fileInput.removeAttribute('accept');
569
+ instructions.innerHTML = `
570
+ <p>πŸ“ <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p>
571
+ <p>All files and subdirectories will be uploaded maintaining the folder structure</p>
572
+ `;
573
+ } else {
574
+ fileInput.removeAttribute('webkitdirectory');
575
+ fileInput.setAttribute('multiple', '');
576
+ fileInput.removeAttribute('accept');
577
+ instructions.innerHTML = `
578
+ <p>πŸ“„ <strong>Files Mode:</strong> Select one or more individual files</p>
579
+ <p>Files will be uploaded to the root of your repository</p>
580
+ `;
581
+ }
582
+
583
+ // Clear previous selections
584
+ selectedFiles = [];
585
+ updateFileList();
586
+ });
587
+ });
588
+
589
+ // File input handling
590
+ const fileInput = document.getElementById('files');
591
+ const fileInputContainer = document.getElementById('fileInputContainer');
592
+ const fileList = document.getElementById('fileList');
593
+
594
+ fileInput.addEventListener('change', function(e) {
595
+ selectedFiles = Array.from(e.target.files);
596
+ updateFileList();
597
+ });
598
+
599
+ // Drag and drop handling
600
+ fileInputContainer.addEventListener('dragover', function(e) {
601
+ e.preventDefault();
602
+ fileInputContainer.classList.add('drag-over');
603
+ });
604
+
605
+ fileInputContainer.addEventListener('dragleave', function(e) {
606
+ e.preventDefault();
607
+ fileInputContainer.classList.remove('drag-over');
608
+ });
609
+
610
+ fileInputContainer.addEventListener('drop', function(e) {
611
+ e.preventDefault();
612
+ fileInputContainer.classList.remove('drag-over');
613
+
614
+ const items = Array.from(e.dataTransfer.items);
615
+ selectedFiles = [];
616
+
617
+ items.forEach(item => {
618
+ if (item.kind === 'file') {
619
+ selectedFiles.push(item.getAsFile());
620
+ }
621
+ });
622
+
623
+ updateFileList();
624
+ });
625
+
626
+ function updateFileList() {
627
+ if (selectedFiles.length === 0) {
628
+ fileList.style.display = 'none';
629
+ return;
630
+ }
631
+
632
+ fileList.style.display = 'block';
633
+ fileList.innerHTML = '';
634
+
635
+ selectedFiles.forEach((file, index) => {
636
+ const fileItem = document.createElement('div');
637
+ fileItem.className = 'file-item';
638
+ const displayName = file.webkitRelativePath || file.name;
639
+ fileItem.innerHTML = `
640
+ <span>πŸ“„ ${displayName} (${formatFileSize(file.size)})</span>
641
+ <span class="remove-file" onclick="removeFile(${index})">βœ•</span>
642
+ `;
643
+ fileList.appendChild(fileItem);
644
+ });
645
+ }
646
+
647
+ function removeFile(index) {
648
+ selectedFiles.splice(index, 1);
649
+ updateFileList();
650
+ }
651
+
652
+ function formatFileSize(bytes) {
653
+ if (bytes === 0) return '0 Bytes';
654
+ const k = 1024;
655
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
656
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
657
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
658
+ }
659
+
660
+ // Form submission
661
+ document.getElementById('uploadForm').addEventListener('submit', function(e) {
662
+ e.preventDefault();
663
+
664
+ if (selectedFiles.length === 0) {
665
+ showMessage('Please select files to upload', 'error');
666
+ return;
667
+ }
668
+
669
+ const formData = new FormData();
670
+ formData.append('hf_token', document.getElementById('hf_token').value);
671
+ formData.append('repo_id', document.getElementById('repo_id').value);
672
+ formData.append('repo_type', document.getElementById('repo_type').value);
673
+ formData.append('commit_message', document.getElementById('commit_message').value);
674
+
675
+ // Add files to form data
676
+ selectedFiles.forEach(file => {
677
+ formData.append('files', file);
678
+ });
679
+
680
+ // Show progress
681
+ document.getElementById('progressContainer').style.display = 'block';
682
+ document.getElementById('uploadBtn').disabled = true;
683
+ document.getElementById('uploadBtn').textContent = 'Uploading...';
684
+
685
+ fetch('/upload', {
686
+ method: 'POST',
687
+ body: formData
688
+ })
689
+ .then(response => response.json())
690
+ .then(data => {
691
+ if (data.success) {
692
+ showMessage(data.message, 'success');
693
+ checkProgress(data.upload_id);
694
+ } else {
695
+ showMessage(data.error || 'Upload failed', 'error');
696
+ resetForm();
697
+ }
698
+ })
699
+ .catch(error => {
700
+ showMessage('Error: ' + error.message, 'error');
701
+ resetForm();
702
+ });
703
+ });
704
+
705
+ function checkProgress(uploadId) {
706
+ const progressFill = document.getElementById('progressFill');
707
+ const progressText = document.getElementById('progressText');
708
+
709
+ const interval = setInterval(() => {
710
+ fetch(`/progress/${uploadId}`)
711
+ .then(response => response.json())
712
+ .then(data => {
713
+ progressFill.style.width = data.progress + '%';
714
+ progressText.textContent = data.status;
715
+
716
+ if (data.completed || data.progress === 100) {
717
+ clearInterval(interval);
718
+ if (data.status.includes('Error')) {
719
+ showMessage(data.status, 'error');
720
+ } else {
721
+ showMessage('Upload completed successfully! πŸŽ‰', 'success');
722
+ }
723
+ resetForm();
724
+ }
725
+ })
726
+ .catch(error => {
727
+ clearInterval(interval);
728
+ showMessage('Error checking progress: ' + error.message, 'error');
729
+ resetForm();
730
+ });
731
+ }, 2000);
732
+ }
733
+
734
+ function showMessage(message, type) {
735
+ const messageDiv = document.getElementById('message');
736
+ messageDiv.className = type;
737
+ messageDiv.textContent = message;
738
+ messageDiv.style.display = 'block';
739
+ }
740
+
741
+ function resetForm() {
742
+ document.getElementById('uploadBtn').disabled = false;
743
+ document.getElementById('uploadBtn').textContent = 'πŸš€ Upload to HuggingFace';
744
+ document.getElementById('progressContainer').style.display = 'none';
745
+ document.getElementById('progressFill').style.width = '0%';
746
+ }
747
+ </script>
748
+ </body>
749
  </html>