Alamgirapi commited on
Commit
08dc85a
Β·
verified Β·
1 Parent(s): 9f9ffcd

Upload via Flask App

Browse files
Files changed (3) hide show
  1. templates/index.html +481 -0
  2. templates/index1.html +481 -0
  3. templates/index2.html +962 -0
templates/index.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
templates/index1.html ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>
templates/index2.html ADDED
@@ -0,0 +1,962 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 Pro</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, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 900px;
23
+ margin: 0 auto;
24
+ background: white;
25
+ border-radius: 20px;
26
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
27
+ overflow: hidden;
28
+ }
29
+
30
+ .header {
31
+ background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%);
32
+ color: white;
33
+ padding: 30px;
34
+ text-align: center;
35
+ }
36
+
37
+ .header h1 {
38
+ font-size: 2.5em;
39
+ margin-bottom: 10px;
40
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
41
+ }
42
+
43
+ .header p {
44
+ font-size: 1.1em;
45
+ opacity: 0.9;
46
+ }
47
+
48
+ .content {
49
+ padding: 40px;
50
+ }
51
+
52
+ .tabs {
53
+ display: flex;
54
+ margin-bottom: 30px;
55
+ border-bottom: 2px solid #f0f0f0;
56
+ }
57
+
58
+ .tab {
59
+ padding: 15px 25px;
60
+ cursor: pointer;
61
+ border: none;
62
+ background: none;
63
+ font-size: 16px;
64
+ font-weight: 600;
65
+ color: #666;
66
+ border-bottom: 3px solid transparent;
67
+ transition: all 0.3s ease;
68
+ }
69
+
70
+ .tab.active {
71
+ color: #007bff;
72
+ border-bottom-color: #007bff;
73
+ }
74
+
75
+ .tab:hover {
76
+ background-color: #f8f9fa;
77
+ }
78
+
79
+ .tab-content {
80
+ display: none;
81
+ }
82
+
83
+ .tab-content.active {
84
+ display: block;
85
+ }
86
+
87
+ .form-group {
88
+ margin-bottom: 25px;
89
+ }
90
+
91
+ .form-row {
92
+ display: flex;
93
+ gap: 20px;
94
+ }
95
+
96
+ .form-row .form-group {
97
+ flex: 1;
98
+ }
99
+
100
+ label {
101
+ display: block;
102
+ margin-bottom: 8px;
103
+ font-weight: 600;
104
+ color: #333;
105
+ font-size: 14px;
106
+ }
107
+
108
+ input, select, textarea {
109
+ width: 100%;
110
+ padding: 12px 16px;
111
+ border: 2px solid #e1e5e9;
112
+ border-radius: 8px;
113
+ font-size: 14px;
114
+ transition: all 0.3s ease;
115
+ background-color: #fff;
116
+ }
117
+
118
+ input:focus, select:focus, textarea:focus {
119
+ outline: none;
120
+ border-color: #007bff;
121
+ box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
122
+ }
123
+
124
+ .checkbox-group {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: 10px;
128
+ }
129
+
130
+ .checkbox-group input[type="checkbox"] {
131
+ width: auto;
132
+ margin: 0;
133
+ }
134
+
135
+ .file-upload-section {
136
+ background: #f8f9fa;
137
+ border-radius: 12px;
138
+ padding: 30px;
139
+ margin: 25px 0;
140
+ border: 2px dashed #dee2e6;
141
+ transition: all 0.3s ease;
142
+ }
143
+
144
+ .file-upload-section.drag-over {
145
+ border-color: #007bff;
146
+ background-color: #e6f3ff;
147
+ transform: scale(1.02);
148
+ }
149
+
150
+ .upload-modes {
151
+ display: flex;
152
+ gap: 15px;
153
+ margin-bottom: 25px;
154
+ }
155
+
156
+ .upload-mode {
157
+ flex: 1;
158
+ padding: 20px;
159
+ border: 2px solid #e1e5e9;
160
+ border-radius: 12px;
161
+ text-align: center;
162
+ cursor: pointer;
163
+ transition: all 0.3s ease;
164
+ background: white;
165
+ }
166
+
167
+ .upload-mode.active {
168
+ border-color: #007bff;
169
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
170
+ color: white;
171
+ transform: translateY(-2px);
172
+ box-shadow: 0 4px 12px rgba(0,123,255,0.3);
173
+ }
174
+
175
+ .upload-mode:hover:not(.active) {
176
+ border-color: #007bff;
177
+ transform: translateY(-1px);
178
+ }
179
+
180
+ .upload-mode .icon {
181
+ font-size: 2em;
182
+ margin-bottom: 10px;
183
+ }
184
+
185
+ .upload-mode .title {
186
+ font-weight: 600;
187
+ margin-bottom: 5px;
188
+ }
189
+
190
+ .upload-mode .description {
191
+ font-size: 12px;
192
+ opacity: 0.8;
193
+ }
194
+
195
+ .file-input-container {
196
+ text-align: center;
197
+ padding: 40px;
198
+ background: white;
199
+ border-radius: 12px;
200
+ border: 2px dashed #dee2e6;
201
+ transition: all 0.3s ease;
202
+ }
203
+
204
+ .file-input-container:hover {
205
+ border-color: #007bff;
206
+ background-color: #f8f9ff;
207
+ }
208
+
209
+ .file-input-label {
210
+ display: inline-block;
211
+ background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);
212
+ color: white;
213
+ padding: 15px 30px;
214
+ border-radius: 50px;
215
+ cursor: pointer;
216
+ font-weight: 600;
217
+ transition: all 0.3s ease;
218
+ margin-bottom: 15px;
219
+ }
220
+
221
+ .file-input-label:hover {
222
+ transform: translateY(-2px);
223
+ box-shadow: 0 4px 12px rgba(0,123,255,0.3);
224
+ }
225
+
226
+ .file-list {
227
+ margin-top: 20px;
228
+ max-height: 300px;
229
+ overflow-y: auto;
230
+ background: white;
231
+ border-radius: 8px;
232
+ border: 1px solid #e1e5e9;
233
+ }
234
+
235
+ .file-item {
236
+ display: flex;
237
+ justify-content: space-between;
238
+ align-items: center;
239
+ padding: 12px 16px;
240
+ border-bottom: 1px solid #f0f0f0;
241
+ transition: background-color 0.2s ease;
242
+ }
243
+
244
+ .file-item:last-child {
245
+ border-bottom: none;
246
+ }
247
+
248
+ .file-item:hover {
249
+ background-color: #f8f9fa;
250
+ }
251
+
252
+ .file-info {
253
+ display: flex;
254
+ align-items: center;
255
+ gap: 10px;
256
+ }
257
+
258
+ .file-icon {
259
+ font-size: 1.2em;
260
+ }
261
+
262
+ .file-name {
263
+ font-weight: 500;
264
+ color: #333;
265
+ }
266
+
267
+ .file-size {
268
+ color: #666;
269
+ font-size: 0.9em;
270
+ }
271
+
272
+ .remove-file {
273
+ background: #ff4757;
274
+ color: white;
275
+ border: none;
276
+ border-radius: 50%;
277
+ width: 24px;
278
+ height: 24px;
279
+ cursor: pointer;
280
+ font-size: 12px;
281
+ transition: all 0.2s ease;
282
+ }
283
+
284
+ .remove-file:hover {
285
+ background: #ff3742;
286
+ transform: scale(1.1);
287
+ }
288
+
289
+ .upload-btn {
290
+ background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
291
+ color: white;
292
+ border: none;
293
+ padding: 15px 40px;
294
+ border-radius: 50px;
295
+ font-size: 16px;
296
+ font-weight: 600;
297
+ cursor: pointer;
298
+ transition: all 0.3s ease;
299
+ width: 100%;
300
+ margin-top: 20px;
301
+ }
302
+
303
+ .upload-btn:hover:not(:disabled) {
304
+ transform: translateY(-2px);
305
+ box-shadow: 0 4px 12px rgba(40,167,69,0.3);
306
+ }
307
+
308
+ .upload-btn:disabled {
309
+ opacity: 0.6;
310
+ cursor: not-allowed;
311
+ }
312
+
313
+ .progress-container {
314
+ margin-top: 20px;
315
+ display: none;
316
+ }
317
+
318
+ .progress-bar {
319
+ width: 100%;
320
+ height: 20px;
321
+ background: #e1e5e9;
322
+ border-radius: 10px;
323
+ overflow: hidden;
324
+ position: relative;
325
+ }
326
+
327
+ .progress-fill {
328
+ height: 100%;
329
+ background: linear-gradient(90deg, #007bff 0%, #0056b3 100%);
330
+ width: 0%;
331
+ transition: width 0.3s ease;
332
+ position: relative;
333
+ }
334
+
335
+ .progress-text {
336
+ position: absolute;
337
+ top: 50%;
338
+ left: 50%;
339
+ transform: translate(-50%, -50%);
340
+ color: white;
341
+ font-weight: 600;
342
+ font-size: 12px;
343
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
344
+ }
345
+
346
+ .status-text {
347
+ margin-top: 10px;
348
+ text-align: center;
349
+ color: #666;
350
+ font-size: 14px;
351
+ }
352
+
353
+ .alert {
354
+ padding: 15px 20px;
355
+ border-radius: 8px;
356
+ margin-bottom: 20px;
357
+ font-weight: 500;
358
+ }
359
+
360
+ .alert-success {
361
+ background: #d4edda;
362
+ color: #155724;
363
+ border: 1px solid #c3e6cb;
364
+ }
365
+
366
+ .alert-error {
367
+ background: #f8d7da;
368
+ color: #721c24;
369
+ border: 1px solid #f5c6cb;
370
+ }
371
+
372
+ .alert-info {
373
+ background: #d1ecf1;
374
+ color: #0c5460;
375
+ border: 1px solid #bee5eb;
376
+ }
377
+
378
+ .history-item {
379
+ background: #f8f9fa;
380
+ border-radius: 8px;
381
+ padding: 15px;
382
+ margin-bottom: 15px;
383
+ border-left: 4px solid #007bff;
384
+ }
385
+
386
+ .history-item.success {
387
+ border-left-color: #28a745;
388
+ }
389
+
390
+ .history-item.error {
391
+ border-left-color: #dc3545;
392
+ }
393
+
394
+ .history-header {
395
+ display: flex;
396
+ justify-content: space-between;
397
+ align-items: center;
398
+ margin-bottom: 8px;
399
+ }
400
+
401
+ .history-repo {
402
+ font-weight: 600;
403
+ color: #333;
404
+ }
405
+
406
+ .history-time {
407
+ color: #666;
408
+ font-size: 0.9em;
409
+ }
410
+
411
+ .history-status {
412
+ display: flex;
413
+ align-items: center;
414
+ gap: 5px;
415
+ }
416
+
417
+ .status-badge {
418
+ padding: 4px 8px;
419
+ border-radius: 12px;
420
+ font-size: 0.8em;
421
+ font-weight: 600;
422
+ }
423
+
424
+ .status-badge.success {
425
+ background: #d4edda;
426
+ color: #155724;
427
+ }
428
+
429
+ .status-badge.error {
430
+ background: #f8d7da;
431
+ color: #721c24;
432
+ }
433
+
434
+ .help-text {
435
+ font-size: 0.9em;
436
+ color: #666;
437
+ margin-top: 5px;
438
+ }
439
+
440
+ .help-section {
441
+ margin-bottom: 30px;
442
+ }
443
+
444
+ .help-section h4 {
445
+ color: #333;
446
+ margin-bottom: 15px;
447
+ font-size: 1.2em;
448
+ }
449
+
450
+ .help-section ul, .help-section ol {
451
+ margin-left: 20px;
452
+ margin-bottom: 15px;
453
+ }
454
+
455
+ .help-section li {
456
+ margin-bottom: 8px;
457
+ line-height: 1.5;
458
+ }
459
+
460
+ .help-section code {
461
+ background: #f8f9fa;
462
+ padding: 2px 6px;
463
+ border-radius: 4px;
464
+ font-family: 'Courier New', monospace;
465
+ font-size: 0.9em;
466
+ }
467
+
468
+ .help-section a {
469
+ color: #007bff;
470
+ text-decoration: none;
471
+ }
472
+
473
+ .help-section a:hover {
474
+ text-decoration: underline;
475
+ }
476
+
477
+ .hidden {
478
+ display: none;
479
+ }
480
+
481
+ @media (max-width: 768px) {
482
+ .form-row {
483
+ flex-direction: column;
484
+ }
485
+
486
+ .upload-modes {
487
+ flex-direction: column;
488
+ }
489
+
490
+ .tabs {
491
+ flex-wrap: wrap;
492
+ }
493
+
494
+ .tab {
495
+ flex: 1;
496
+ min-width: 120px;
497
+ }
498
+
499
+ .container {
500
+ margin: 10px;
501
+ }
502
+
503
+ .content {
504
+ padding: 20px;
505
+ }
506
+ }
507
+ </style>
508
+ </head>
509
+ <body>
510
+ <div class="container">
511
+ <div class="header">
512
+ <h1>πŸ€— HuggingFace Uploader Pro</h1>
513
+ <p>Upload your files and folders to HuggingFace Hub with ease</p>
514
+ </div>
515
+
516
+ <div class="content">
517
+ <div class="tabs">
518
+ <button class="tab active" onclick="showTab('upload')">πŸ“€ Upload</button>
519
+ <button class="tab" onclick="showTab('history')">πŸ“Š History</button>
520
+ <button class="tab" onclick="showTab('help')">❓ Help</button>
521
+ </div>
522
+
523
+ <div id="upload" class="tab-content active">
524
+ <form id="uploadForm" enctype="multipart/form-data">
525
+ <div class="form-row">
526
+ <div class="form-group">
527
+ <label for="hf_token">HuggingFace Token *</label>
528
+ <input type="password" id="hf_token" name="hf_token" required
529
+ placeholder="hf_xxxxxxxxxxxx">
530
+ <div class="help-text">Get your token from <a href="https://huggingface.co/settings/tokens" target="_blank">HuggingFace Settings</a></div>
531
+ </div>
532
+ <div class="form-group">
533
+ <label for="repo_id">Repository ID *</label>
534
+ <input type="text" id="repo_id" name="repo_id" required
535
+ placeholder="username/repository-name">
536
+ <div class="help-text">Format: username/repository-name</div>
537
+ </div>
538
+ </div>
539
+
540
+ <div class="form-row">
541
+ <div class="form-group">
542
+ <label for="repo_type">Repository Type</label>
543
+ <select id="repo_type" name="repo_type">
544
+ <option value="space">Space</option>
545
+ <option value="model">Model</option>
546
+ <option value="dataset">Dataset</option>
547
+ </select>
548
+ </div>
549
+ <div class="form-group">
550
+ <label for="commit_message">Commit Message</label>
551
+ <input type="text" id="commit_message" name="commit_message"
552
+ placeholder="Upload via HuggingFace Uploader Pro">
553
+ </div>
554
+ </div>
555
+
556
+ <div class="form-group">
557
+ <div class="checkbox-group">
558
+ <input type="checkbox" id="create_repo" name="create_repo">
559
+ <label for="create_repo">Create repository if it doesn't exist</label>
560
+ </div>
561
+ </div>
562
+
563
+ <div class="file-upload-section" id="dropZone">
564
+ <div class="upload-modes">
565
+ <div class="upload-mode active" id="fileMode" onclick="setUploadMode('file')">
566
+ <div class="icon">πŸ“„</div>
567
+ <div class="title">Files</div>
568
+ <div class="description">Upload individual files or archives</div>
569
+ </div>
570
+ <div class="upload-mode" id="folderMode" onclick="setUploadMode('folder')">
571
+ <div class="icon">πŸ“</div>
572
+ <div class="title">Folder</div>
573
+ <div class="description">Upload entire folder structure</div>
574
+ </div>
575
+ </div>
576
+
577
+ <div class="file-input-container">
578
+ <label for="fileInput" class="file-input-label">
579
+ <span id="uploadModeText">πŸ“€ Choose Files</span>
580
+ </label>
581
+ <input type="file" id="fileInput" name="files" multiple style="display: none;">
582
+ <div id="dragText">or drag and drop files here</div>
583
+ </div>
584
+
585
+ <div id="fileList" class="file-list hidden"></div>
586
+ </div>
587
+
588
+ <button type="submit" class="upload-btn" id="uploadBtn">
589
+ πŸš€ Upload to HuggingFace
590
+ </button>
591
+
592
+ <div class="progress-container" id="progressContainer">
593
+ <div class="progress-bar">
594
+ <div class="progress-fill" id="progressFill">
595
+ <div class="progress-text" id="progressText">0%</div>
596
+ </div>
597
+ </div>
598
+ <div class="status-text" id="statusText">Preparing upload...</div>
599
+ </div>
600
+ </form>
601
+
602
+ <div id="alertContainer"></div>
603
+ </div>
604
+
605
+ <div id="history" class="tab-content">
606
+ <h3>Upload History</h3>
607
+ <div id="historyList">
608
+ <div class="alert-info">No upload history available.</div>
609
+ </div>
610
+ </div>
611
+
612
+ <div id="help" class="tab-content">
613
+ <div class="help-section">
614
+ <h4>πŸš€ Getting Started</h4>
615
+ <ol>
616
+ <li>Get your HuggingFace token from <a href="https://huggingface.co/settings/tokens" target="_blank">Settings</a></li>
617
+ <li>Enter your repository ID in the format: <code>username/repository-name</code></li>
618
+ <li>Select the repository type (Space, Model, or Dataset)</li>
619
+ <li>Choose your files or folders to upload</li>
620
+ <li>Click "Upload to HuggingFace" and wait for completion</li>
621
+ </ol>
622
+ </div>
623
+
624
+ <div class="help-section">
625
+ <h4>πŸ“ Supported File Types</h4>
626
+ <ul>
627
+ <li><strong>Code:</strong> .py, .js, .html, .css, .json, .md, .yaml, .yml, .toml, .ini, .cfg</li>
628
+ <li><strong>Documents:</strong> .txt, .pdf, .docx, .csv, .xml</li>
629
+ <li><strong>Images:</strong> .png, .jpg, .jpeg, .gif, .svg, .ico, .webp</li>
630
+ <li><strong>Media:</strong> .mp4, .webm, .ogg, .mp3, .wav, .flac, .aac</li>
631
+ <li><strong>Archives:</strong> .zip, .tar, .gz, .tgz (automatically extracted)</li>
632
+ <li><strong>ML Models:</strong> .pkl, .h5, .hdf5, .pt, .pth, .onnx, .pb, .tflite, .safetensors</li>
633
+ <li><strong>Notebooks:</strong> .ipynb</li>
634
+ <li><strong>Fonts:</strong> .woff, .woff2, .ttf, .otf, .eot</li>
635
+ </ul>
636
+ </div>
637
+
638
+ <div class="help-section">
639
+ <h4>πŸ”§ Features</h4>
640
+ <ul>
641
+ <li><strong>Drag & Drop:</strong> Simply drag files into the upload area</li>
642
+ <li><strong>Folder Upload:</strong> Maintain directory structure when uploading folders</li>
643
+ <li><strong>Archive Extraction:</strong> ZIP and TAR files are automatically extracted</li>
644
+ <li><strong>Progress Tracking:</strong> Real-time upload progress and status</li>
645
+ <li><strong>Auto Repository Creation:</strong> Create new repositories on the fly</li>
646
+ <li><strong>Upload History:</strong> Track all your uploads</li>
647
+ </ul>
648
+ </div>
649
+
650
+ <div class="help-section">
651
+ <h4>πŸ› οΈ Tips</h4>
652
+ <ul>
653
+ <li>Use descriptive commit messages for better version control</li>
654
+ <li>Large files may take longer to upload - be patient!</li>
655
+ <li>Check the file type restrictions before uploading</li>
656
+ <li>Repository names should be lowercase with hyphens</li>
657
+ <li>Make sure your HuggingFace token has write permissions</li>
658
+ </ul>
659
+ </div>
660
+ </div>
661
+ </div>
662
+ </div>
663
+
664
+ <script>
665
+ let selectedFiles = [];
666
+ let uploadMode = 'file';
667
+
668
+ // Tab switching
669
+ function showTab(tabName) {
670
+ // Hide all tabs
671
+ document.querySelectorAll('.tab-content').forEach(tab => {
672
+ tab.classList.remove('active');
673
+ });
674
+ document.querySelectorAll('.tab').forEach(tab => {
675
+ tab.classList.remove('active');
676
+ });
677
+
678
+ // Show selected tab
679
+ document.getElementById(tabName).classList.add('active');
680
+ event.target.classList.add('active');
681
+
682
+ // Load history if history tab is selected
683
+ if (tabName === 'history') {
684
+ loadHistory();
685
+ }
686
+ }
687
+
688
+ // Upload mode switching
689
+ function setUploadMode(mode) {
690
+ uploadMode = mode;
691
+ document.querySelectorAll('.upload-mode').forEach(el => {
692
+ el.classList.remove('active');
693
+ });
694
+ document.getElementById(mode + 'Mode').classList.add('active');
695
+
696
+ const fileInput = document.getElementById('fileInput');
697
+ const modeText = document.getElementById('uploadModeText');
698
+
699
+ if (mode === 'folder') {
700
+ fileInput.setAttribute('webkitdirectory', '');
701
+ fileInput.setAttribute('directory', '');
702
+ modeText.textContent = 'πŸ“ Choose Folder';
703
+ } else {
704
+ fileInput.removeAttribute('webkitdirectory');
705
+ fileInput.removeAttribute('directory');
706
+ modeText.textContent = 'πŸ“€ Choose Files';
707
+ }
708
+
709
+ // Clear selected files when switching modes
710
+ selectedFiles = [];
711
+ updateFileList();
712
+ }
713
+
714
+ // Drag and drop functionality
715
+ const dropZone = document.getElementById('dropZone');
716
+ const fileInput = document.getElementById('fileInput');
717
+
718
+ dropZone.addEventListener('dragover', (e) => {
719
+ e.preventDefault();
720
+ dropZone.classList.add('drag-over');
721
+ });
722
+
723
+ dropZone.addEventListener('dragleave', (e) => {
724
+ e.preventDefault();
725
+ dropZone.classList.remove('drag-over');
726
+ });
727
+
728
+ dropZone.addEventListener('drop', (e) => {
729
+ e.preventDefault();
730
+ dropZone.classList.remove('drag-over');
731
+
732
+ const files = Array.from(e.dataTransfer.files);
733
+ handleFiles(files);
734
+ });
735
+
736
+ fileInput.addEventListener('change', (e) => {
737
+ const files = Array.from(e.target.files);
738
+ handleFiles(files);
739
+ });
740
+
741
+ function handleFiles(files) {
742
+ selectedFiles = files;
743
+ updateFileList();
744
+ }
745
+
746
+ function updateFileList() {
747
+ const fileList = document.getElementById('fileList');
748
+
749
+ if (selectedFiles.length === 0) {
750
+ fileList.classList.add('hidden');
751
+ return;
752
+ }
753
+
754
+ fileList.classList.remove('hidden');
755
+ fileList.innerHTML = '';
756
+
757
+ selectedFiles.forEach((file, index) => {
758
+ const fileItem = document.createElement('div');
759
+ fileItem.className = 'file-item';
760
+
761
+ const fileIcon = getFileIcon(file.name);
762
+ const fileSize = formatFileSize(file.size);
763
+
764
+ fileItem.innerHTML = `
765
+ <div class="file-info">
766
+ <span class="file-icon">${fileIcon}</span>
767
+ <span class="file-name">${file.name}</span>
768
+ <span class="file-size">(${fileSize})</span>
769
+ </div>
770
+ <button type="button" class="remove-file" onclick="removeFile(${index})">Γ—</button>
771
+ `;
772
+
773
+ fileList.appendChild(fileItem);
774
+ });
775
+ }
776
+
777
+ function removeFile(index) {
778
+ selectedFiles.splice(index, 1);
779
+ updateFileList();
780
+ }
781
+
782
+ function getFileIcon(filename) {
783
+ const ext = filename.split('.').pop().toLowerCase();
784
+ const iconMap = {
785
+ 'py': '🐍', 'js': 'πŸ’›', 'html': '🌐', 'css': '🎨',
786
+ 'json': 'πŸ“‹', 'md': 'πŸ“', 'txt': 'πŸ“„', 'pdf': 'πŸ“•',
787
+ 'png': 'πŸ–ΌοΈ', 'jpg': 'πŸ–ΌοΈ', 'jpeg': 'πŸ–ΌοΈ', 'gif': 'πŸ–ΌοΈ',
788
+ 'zip': 'πŸ“¦', 'tar': 'πŸ“¦', 'gz': 'πŸ“¦', 'tgz': 'πŸ“¦',
789
+ 'ipynb': 'πŸ““', 'xlsx': 'πŸ“Š', 'csv': 'πŸ“ˆ',
790
+ 'mp4': '🎬', 'mp3': '🎡', 'wav': '🎡'
791
+ };
792
+ return iconMap[ext] || 'πŸ“„';
793
+ }
794
+
795
+ function formatFileSize(bytes) {
796
+ if (bytes === 0) return '0 Bytes';
797
+ const k = 1024;
798
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
799
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
800
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
801
+ }
802
+
803
+ // Form submission
804
+ document.getElementById('uploadForm').addEventListener('submit', async (e) => {
805
+ e.preventDefault();
806
+
807
+ if (selectedFiles.length === 0) {
808
+ showAlert('Please select files to upload', 'error');
809
+ return;
810
+ }
811
+
812
+ const formData = new FormData();
813
+ formData.append('hf_token', document.getElementById('hf_token').value);
814
+ formData.append('repo_id', document.getElementById('repo_id').value);
815
+ formData.append('repo_type', document.getElementById('repo_type').value);
816
+ formData.append('commit_message', document.getElementById('commit_message').value);
817
+ formData.append('create_repo', document.getElementById('create_repo').checked);
818
+
819
+ selectedFiles.forEach(file => {
820
+ formData.append('files', file);
821
+ });
822
+
823
+ const uploadBtn = document.getElementById('uploadBtn');
824
+ const progressContainer = document.getElementById('progressContainer');
825
+
826
+ uploadBtn.disabled = true;
827
+ uploadBtn.textContent = '⏳ Uploading...';
828
+ progressContainer.style.display = 'block';
829
+
830
+ try {
831
+ const response = await fetch('/upload', {
832
+ method: 'POST',
833
+ body: formData
834
+ });
835
+
836
+ const result = await response.json();
837
+
838
+ if (response.ok) {
839
+ showAlert('Upload started successfully!', 'success');
840
+ pollProgress(result.upload_id);
841
+ } else {
842
+ throw new Error(result.error || 'Upload failed');
843
+ }
844
+ } catch (error) {
845
+ showAlert(error.message, 'error');
846
+ resetUploadForm();
847
+ }
848
+ });
849
+
850
+ function pollProgress(uploadId) {
851
+ const interval = setInterval(async () => {
852
+ try {
853
+ const response = await fetch(`/progress/${uploadId}`);
854
+ const data = await response.json();
855
+
856
+ updateProgress(data.progress, data.status);
857
+
858
+ if (data.completed || data.status.toLowerCase().includes('error')) {
859
+ clearInterval(interval);
860
+ resetUploadForm();
861
+
862
+ if (data.completed) {
863
+ showAlert('Upload completed successfully!', 'success');
864
+ } else {
865
+ showAlert(data.status, 'error');
866
+ }
867
+ }
868
+ } catch (error) {
869
+ clearInterval(interval);
870
+ resetUploadForm();
871
+ showAlert('Failed to get upload progress', 'error');
872
+ }
873
+ }, 2000);
874
+ }
875
+
876
+ function updateProgress(progress, status) {
877
+ const progressFill = document.getElementById('progressFill');
878
+ const progressText = document.getElementById('progressText');
879
+ const statusText = document.getElementById('statusText');
880
+
881
+ progressFill.style.width = `${progress}%`;
882
+ progressText.textContent = `${progress}%`;
883
+ statusText.textContent = status;
884
+ }
885
+
886
+ function resetUploadForm() {
887
+ const uploadBtn = document.getElementById('uploadBtn');
888
+ const progressContainer = document.getElementById('progressContainer');
889
+
890
+ uploadBtn.disabled = false;
891
+ uploadBtn.textContent = 'πŸš€ Upload to HuggingFace';
892
+ progressContainer.style.display = 'none';
893
+
894
+ // Reset progress
895
+ updateProgress(0, 'Ready to upload');
896
+ }
897
+
898
+ function showAlert(message, type) {
899
+ const alertContainer = document.getElementById('alertContainer');
900
+ const alertDiv = document.createElement('div');
901
+ alertDiv.className = `alert alert-${type}`;
902
+ alertDiv.textContent = message;
903
+
904
+ alertContainer.innerHTML = '';
905
+ alertContainer.appendChild(alertDiv);
906
+
907
+ // Auto-hide after 5 seconds
908
+ setTimeout(() => {
909
+ alertDiv.remove();
910
+ }, 5000);
911
+ }
912
+
913
+ // Load upload history
914
+ async function loadHistory() {
915
+ try {
916
+ const response = await fetch('/history');
917
+ const data = await response.json();
918
+ const historyList = document.getElementById('historyList');
919
+
920
+ if (data.history.length === 0) {
921
+ historyList.innerHTML = '<div class="alert-info">No upload history available.</div>';
922
+ return;
923
+ }
924
+
925
+ historyList.innerHTML = '';
926
+ data.history.forEach(item => {
927
+ const historyItem = document.createElement('div');
928
+ historyItem.className = `history-item ${item.status}`;
929
+
930
+ const timestamp = new Date(item.timestamp).toLocaleString();
931
+
932
+ historyItem.innerHTML = `
933
+ <div class="history-header">
934
+ <span class="history-repo">${item.repo_id}</span>
935
+ <span class="history-time">${timestamp}</span>
936
+ </div>
937
+ <div class="history-status">
938
+ <span class="status-badge ${item.status}">${item.status}</span>
939
+ <span>${item.repo_type}</span>
940
+ </div>
941
+ <div class="history-message">${item.message}</div>
942
+ `;
943
+
944
+ historyList.appendChild(historyItem);
945
+ });
946
+ } catch (error) {
947
+ document.getElementById('historyList').innerHTML =
948
+ '<div class="alert-error">Failed to load history</div>';
949
+ }
950
+ }
951
+
952
+ // Initialize
953
+ document.addEventListener('DOMContentLoaded', () => {
954
+ // Set default upload mode
955
+ setUploadMode('file');
956
+
957
+ // Load history on page load
958
+ loadHistory();
959
+ });
960
+ </script>
961
+ </body>
962
+ </html>