Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>HuggingFace Uploader</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| position: relative; | |
| } | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: | |
| radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), | |
| radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%); | |
| pointer-events: none; | |
| z-index: -1; | |
| } | |
| .container { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| padding: 40px; | |
| border-radius: 20px; | |
| box-shadow: | |
| 0 20px 40px rgba(0, 0, 0, 0.1), | |
| 0 0 0 1px rgba(255, 255, 255, 0.2); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .container::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 4px; | |
| background: linear-gradient(90deg, #ff6b6b, #ffd93d, #6bcf7f, #4ecdc4, #45b7d1); | |
| border-radius: 20px 20px 0 0; | |
| } | |
| h1 { | |
| color: #2c3e50; | |
| text-align: center; | |
| margin-bottom: 40px; | |
| font-size: 2.5em; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
| } | |
| .form-group { | |
| margin-bottom: 25px; | |
| position: relative; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: #34495e; | |
| font-size: 14px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| input, select, textarea { | |
| width: 100%; | |
| padding: 16px 20px; | |
| border: 2px solid #e0e6ed; | |
| border-radius: 12px; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(5px); | |
| } | |
| input:focus, select:focus, textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
| transform: translateY(-2px); | |
| } | |
| .file-input-container { | |
| border: 3px dashed #e0e6ed; | |
| border-radius: 16px; | |
| padding: 40px 20px; | |
| text-align: center; | |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .file-input-container::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent); | |
| transition: left 0.6s ease; | |
| } | |
| .file-input-container:hover::before { | |
| left: 100%; | |
| } | |
| .file-input-container:hover { | |
| border-color: #667eea; | |
| background: linear-gradient(135deg, #f0f8ff 0%, #e6f3ff 100%); | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2); | |
| } | |
| .file-input-container.drag-over { | |
| border-color: #667eea; | |
| background: linear-gradient(135deg, #e6f3ff 0%, #d1ecf1 100%); | |
| transform: scale(1.02); | |
| box-shadow: 0 15px 40px rgba(102, 126, 234, 0.3); | |
| } | |
| #files { | |
| display: none; | |
| } | |
| .file-input-label { | |
| display: inline-block; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 16px 32px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| margin-bottom: 15px; | |
| transition: all 0.3s ease; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .file-input-label::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); | |
| transition: left 0.6s ease; | |
| } | |
| .file-input-label:hover::before { | |
| left: 100%; | |
| } | |
| .file-input-label:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); | |
| } | |
| .upload-modes { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 15px; | |
| margin-bottom: 25px; | |
| } | |
| .upload-mode { | |
| padding: 20px 15px; | |
| border: 2px solid #e0e6ed; | |
| border-radius: 12px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| font-weight: 600; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(5px); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .upload-mode::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .upload-mode span { | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .upload-mode.active { | |
| border-color: #667eea; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); | |
| } | |
| .upload-mode:hover { | |
| border-color: #667eea; | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(102, 126, 234, 0.2); | |
| } | |
| .file-list { | |
| margin-top: 20px; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | |
| border-radius: 12px; | |
| max-height: 250px; | |
| overflow-y: auto; | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05); | |
| } | |
| .file-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 12px 0; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s ease; | |
| } | |
| .file-item:last-child { | |
| border-bottom: none; | |
| } | |
| .file-item:hover { | |
| background: rgba(102, 126, 234, 0.05); | |
| border-radius: 8px; | |
| padding: 12px 10px; | |
| } | |
| .remove-file { | |
| color: #e74c3c; | |
| cursor: pointer; | |
| font-weight: bold; | |
| padding: 6px 10px; | |
| border-radius: 6px; | |
| transition: all 0.3s ease; | |
| background: rgba(231, 76, 60, 0.1); | |
| } | |
| .remove-file:hover { | |
| background: #e74c3c; | |
| color: white; | |
| transform: scale(1.1); | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #00b894 0%, #00a085 100%); | |
| color: white; | |
| padding: 18px 40px; | |
| border: none; | |
| border-radius: 12px; | |
| font-size: 18px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| width: 100%; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| box-shadow: 0 4px 15px rgba(0, 184, 148, 0.3); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); | |
| transition: left 0.6s ease; | |
| } | |
| .btn:hover::before { | |
| left: 100%; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(0, 184, 148, 0.4); | |
| } | |
| .btn:disabled { | |
| background: linear-gradient(135deg, #95a5a6 0%, #7f8c8d 100%); | |
| cursor: not-allowed; | |
| transform: none; | |
| box-shadow: none; | |
| } | |
| .progress-container { | |
| margin-top: 30px; | |
| display: none; | |
| } | |
| .progress-bar { | |
| width: 100%; | |
| height: 12px; | |
| background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 100%); | |
| border-radius: 6px; | |
| overflow: hidden; | |
| box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1); | |
| position: relative; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #00b894, #00a085, #fdcb6e, #e17055); | |
| background-size: 200% 100%; | |
| animation: progressShine 2s linear infinite; | |
| transition: width 0.3s ease; | |
| width: 0%; | |
| border-radius: 6px; | |
| box-shadow: 0 0 10px rgba(0, 184, 148, 0.5); | |
| } | |
| @keyframes progressShine { | |
| 0% { background-position: 200% 0; } | |
| 100% { background-position: -200% 0; } | |
| } | |
| .progress-text { | |
| text-align: center; | |
| margin-top: 15px; | |
| font-weight: 600; | |
| color: #2c3e50; | |
| font-size: 16px; | |
| } | |
| .error { | |
| color: #e74c3c; | |
| background: linear-gradient(135deg, #fdcbcb 0%, #fecaca 100%); | |
| border: 1px solid #f5c6cb; | |
| padding: 16px 20px; | |
| border-radius: 12px; | |
| margin-top: 20px; | |
| box-shadow: 0 4px 15px rgba(231, 76, 60, 0.1); | |
| border-left: 4px solid #e74c3c; | |
| } | |
| .success { | |
| color: #00b894; | |
| background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%); | |
| border: 1px solid #c3e6cb; | |
| padding: 16px 20px; | |
| border-radius: 12px; | |
| margin-top: 20px; | |
| box-shadow: 0 4px 15px rgba(0, 184, 148, 0.1); | |
| border-left: 4px solid #00b894; | |
| } | |
| .help-text { | |
| font-size: 13px; | |
| color: #7f8c8d; | |
| margin-top: 8px; | |
| font-style: italic; | |
| } | |
| .folder-instructions { | |
| background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); | |
| border: 1px solid #ffeaa7; | |
| padding: 16px 20px; | |
| border-radius: 12px; | |
| margin-top: 20px; | |
| font-size: 14px; | |
| box-shadow: 0 4px 15px rgba(255, 234, 167, 0.2); | |
| border-left: 4px solid #fdcb6e; | |
| } | |
| #uploadInstructions { | |
| font-size: 16px; | |
| color: #2c3e50; | |
| line-height: 1.6; | |
| } | |
| #uploadInstructions p { | |
| margin-bottom: 10px; | |
| } | |
| #uploadInstructions strong { | |
| color: #667eea; | |
| } | |
| /* Responsive design */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 20px; | |
| margin: 10px; | |
| } | |
| .upload-modes { | |
| grid-template-columns: 1fr; | |
| } | |
| h1 { | |
| font-size: 2em; | |
| } | |
| .file-input-container { | |
| padding: 30px 15px; | |
| } | |
| } | |
| /* Smooth scrollbar for file list */ | |
| .file-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .file-list::-webkit-scrollbar-track { | |
| background: rgba(0, 0, 0, 0.1); | |
| border-radius: 3px; | |
| } | |
| .file-list::-webkit-scrollbar-thumb { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| border-radius: 3px; | |
| } | |
| .file-list::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🤗 HuggingFace Uploader</h1> | |
| <form id="uploadForm" enctype="multipart/form-data"> | |
| <div class="form-group"> | |
| <label for="hf_token">HuggingFace Token:</label> | |
| <input type="password" id="hf_token" name="hf_token" required | |
| placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> | |
| <div class="help-text">Get your token from: https://huggingface.co/settings/tokens</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="repo_id">Repository ID:</label> | |
| <input type="text" id="repo_id" name="repo_id" required | |
| placeholder="your-username/repository-name"> | |
| <div class="help-text">Format: username/repository-name</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="repo_type">Repository Type:</label> | |
| <select id="repo_type" name="repo_type"> | |
| <option value="space">Space</option> | |
| <option value="model">Model</option> | |
| <option value="dataset">Dataset</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="commit_message">Commit Message:</label> | |
| <textarea id="commit_message" name="commit_message" rows="2" | |
| placeholder="Upload via Flask App">Upload via Flask App</textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label>Upload Type:</label> | |
| <div class="upload-modes"> | |
| <div class="upload-mode active" data-mode="folder"> | |
| <span>📁 Folder</span> | |
| </div> | |
| <div class="upload-mode" data-mode="files"> | |
| <span>📄 Files</span> | |
| </div> | |
| <div class="upload-mode" data-mode="zip"> | |
| <span>📦 ZIP Archive</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label>Select Files:</label> | |
| <div class="file-input-container" id="fileInputContainer"> | |
| <label for="files" class="file-input-label"> | |
| 📎 Choose Files or Drag & Drop | |
| </label> | |
| <input type="file" id="files" name="files" multiple webkitdirectory style="display: none;"> | |
| <div id="uploadInstructions"> | |
| <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p> | |
| <p>All files and subdirectories will be uploaded maintaining the folder structure</p> | |
| </div> | |
| </div> | |
| <div class="folder-instructions"> | |
| <strong>Note:</strong> When uploading folders, the browser will ask you to select a folder. | |
| All files in the selected folder (including subfolders) will be uploaded to your HuggingFace repository with the same directory structure. | |
| </div> | |
| <div id="fileList" class="file-list" style="display: none;"></div> | |
| </div> | |
| <button type="submit" class="btn" id="uploadBtn">🚀 Upload to HuggingFace</button> | |
| </form> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill"></div> | |
| </div> | |
| <div class="progress-text" id="progressText">Starting upload...</div> | |
| </div> | |
| <div id="message"></div> | |
| </div> | |
| <script> | |
| let selectedFiles = []; | |
| let currentMode = 'folder'; | |
| // Upload mode switching | |
| document.querySelectorAll('.upload-mode').forEach(mode => { | |
| mode.addEventListener('click', function() { | |
| document.querySelectorAll('.upload-mode').forEach(m => m.classList.remove('active')); | |
| this.classList.add('active'); | |
| currentMode = this.dataset.mode; | |
| const fileInput = document.getElementById('files'); | |
| const instructions = document.getElementById('uploadInstructions'); | |
| if (currentMode === 'zip') { | |
| fileInput.removeAttribute('webkitdirectory'); | |
| fileInput.removeAttribute('multiple'); | |
| fileInput.setAttribute('accept', '.zip'); | |
| instructions.innerHTML = '<p>📦 Select a ZIP file to upload</p>'; | |
| } else if (currentMode === 'folder') { | |
| fileInput.setAttribute('webkitdirectory', ''); | |
| fileInput.setAttribute('multiple', ''); | |
| fileInput.removeAttribute('accept'); | |
| instructions.innerHTML = ` | |
| <p>📁 <strong>Folder Mode:</strong> Click "Choose Files" and select a folder to upload all its contents</p> | |
| <p>All files and subdirectories will be uploaded maintaining the folder structure</p> | |
| `; | |
| } else { | |
| fileInput.removeAttribute('webkitdirectory'); | |
| fileInput.setAttribute('multiple', ''); | |
| fileInput.removeAttribute('accept'); | |
| instructions.innerHTML = ` | |
| <p>📄 <strong>Files Mode:</strong> Select one or more individual files</p> | |
| <p>Files will be uploaded to the root of your repository</p> | |
| `; | |
| } | |
| // Clear previous selections | |
| selectedFiles = []; | |
| updateFileList(); | |
| }); | |
| }); | |
| // File input handling | |
| const fileInput = document.getElementById('files'); | |
| const fileInputContainer = document.getElementById('fileInputContainer'); | |
| const fileList = document.getElementById('fileList'); | |
| fileInput.addEventListener('change', function(e) { | |
| selectedFiles = Array.from(e.target.files); | |
| updateFileList(); | |
| }); | |
| // Drag and drop handling | |
| fileInputContainer.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| fileInputContainer.classList.add('drag-over'); | |
| }); | |
| fileInputContainer.addEventListener('dragleave', function(e) { | |
| e.preventDefault(); | |
| fileInputContainer.classList.remove('drag-over'); | |
| }); | |
| fileInputContainer.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| fileInputContainer.classList.remove('drag-over'); | |
| const items = Array.from(e.dataTransfer.items); | |
| selectedFiles = []; | |
| items.forEach(item => { | |
| if (item.kind === 'file') { | |
| selectedFiles.push(item.getAsFile()); | |
| } | |
| }); | |
| updateFileList(); | |
| }); | |
| function updateFileList() { | |
| if (selectedFiles.length === 0) { | |
| fileList.style.display = 'none'; | |
| return; | |
| } | |
| fileList.style.display = 'block'; | |
| fileList.innerHTML = ''; | |
| selectedFiles.forEach((file, index) => { | |
| const fileItem = document.createElement('div'); | |
| fileItem.className = 'file-item'; | |
| const displayName = file.webkitRelativePath || file.name; | |
| fileItem.innerHTML = ` | |
| <span>📄 ${displayName} (${formatFileSize(file.size)})</span> | |
| <span class="remove-file" onclick="removeFile(${index})">✕</span> | |
| `; | |
| fileList.appendChild(fileItem); | |
| }); | |
| } | |
| function removeFile(index) { | |
| selectedFiles.splice(index, 1); | |
| updateFileList(); | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB', 'GB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; | |
| } | |
| // Form submission | |
| document.getElementById('uploadForm').addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| if (selectedFiles.length === 0) { | |
| showMessage('Please select files to upload', 'error'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('hf_token', document.getElementById('hf_token').value); | |
| formData.append('repo_id', document.getElementById('repo_id').value); | |
| formData.append('repo_type', document.getElementById('repo_type').value); | |
| formData.append('commit_message', document.getElementById('commit_message').value); | |
| // Add files to form data | |
| selectedFiles.forEach(file => { | |
| formData.append('files', file); | |
| }); | |
| // Show progress | |
| document.getElementById('progressContainer').style.display = 'block'; | |
| document.getElementById('uploadBtn').disabled = true; | |
| document.getElementById('uploadBtn').textContent = 'Uploading...'; | |
| fetch('/upload', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.success) { | |
| showMessage(data.message, 'success'); | |
| checkProgress(data.upload_id); | |
| } else { | |
| showMessage(data.error || 'Upload failed', 'error'); | |
| resetForm(); | |
| } | |
| }) | |
| .catch(error => { | |
| showMessage('Error: ' + error.message, 'error'); | |
| resetForm(); | |
| }); | |
| }); | |
| function checkProgress(uploadId) { | |
| const progressFill = document.getElementById('progressFill'); | |
| const progressText = document.getElementById('progressText'); | |
| const interval = setInterval(() => { | |
| fetch(`/progress/${uploadId}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| progressFill.style.width = data.progress + '%'; | |
| progressText.textContent = data.status; | |
| if (data.completed || data.progress === 100) { | |
| clearInterval(interval); | |
| if (data.status.includes('Error')) { | |
| showMessage(data.status, 'error'); | |
| } else { | |
| showMessage('Upload completed successfully! 🎉', 'success'); | |
| } | |
| resetForm(); | |
| } | |
| }) | |
| .catch(error => { | |
| clearInterval(interval); | |
| showMessage('Error checking progress: ' + error.message, 'error'); | |
| resetForm(); | |
| }); | |
| }, 2000); | |
| } | |
| function showMessage(message, type) { | |
| const messageDiv = document.getElementById('message'); | |
| messageDiv.className = type; | |
| messageDiv.textContent = message; | |
| messageDiv.style.display = 'block'; | |
| } | |
| function resetForm() { | |
| document.getElementById('uploadBtn').disabled = false; | |
| document.getElementById('uploadBtn').textContent = '🚀 Upload to HuggingFace'; | |
| document.getElementById('progressContainer').style.display = 'none'; | |
| document.getElementById('progressFill').style.width = '0%'; | |
| } | |
| </script> | |
| </body> | |
| </html> |