4cko
/usr/local/rvm/gems/ruby-3.4.7/bin:/usr/local/rvm/gems/ruby-3.4.7@global/bin:/usr/local/rvm/rubies/ruby-3.4.7/bin:/home/codespace/.vscode-remote/data/User/globalStorage/github.copilot-chat/debugCommand:/home/codespace/.vscode-remote/data/User/globalStorage/github.copilot-chat/copilotCli:/vscode/bin/linux-x64/7e7950df89d055b5a378379db9ee14290772148a/bin/remote-cli:/home/codespace/.local/bin:/home/codespace/.dotnet:/home/codespace/nvm/current/bin:/home/codespace/.php/current/bin:/home/codespace/.python/current/bin:/home/codespace/java/current/bin:/home/codespace/.ruby/current/bin:/home/codespace/.local/bin:/usr/local/python/current/bin:/usr/local/py-utils/bin:/usr/local/jupyter:/usr/local/oryx:/usr/local/go/bin:/go/bin:/usr/local/sdkman/bin:/usr/local/sdkman/candidates/java/current/bin:/usr/local/sdkman/candidates/gradle/current/bin:/usr/local/sdkman/candidates/maven/current/bin:/usr/local/sdkman/candidates/ant/current/bin:/usr/local/rvm/gems/default/bin:/usr/local/rvm/gems/default@global/bin:/usr/local/rvm/rubies/default/bin:/usr/local/share/rbenv/bin:/usr/local/php/current/bin:/opt/conda/bin:/usr/local/nvs:/usr/local/share/nvm/versions/node/v24.14.0/bin:/usr/local/hugo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/share/dotnet:/home/codespace/.dotnet/tools:/usr/local/rvm/bin
a2c64e7 | <html lang="id"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Vico - Hugging Face Upload</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| } | |
| .container { | |
| background: white; | |
| border-radius: 10px; | |
| box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); | |
| max-width: 600px; | |
| width: 100%; | |
| padding: 40px; | |
| } | |
| h1 { | |
| color: #333; | |
| margin-bottom: 10px; | |
| font-size: 28px; | |
| } | |
| .subtitle { | |
| color: #666; | |
| margin-bottom: 30px; | |
| font-size: 14px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| color: #333; | |
| font-weight: 600; | |
| font-size: 14px; | |
| } | |
| input[type="text"], | |
| input[type="password"], | |
| textarea { | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid #e0e0e0; | |
| border-radius: 5px; | |
| font-size: 14px; | |
| transition: border-color 0.3s; | |
| font-family: 'Courier New', monospace; | |
| } | |
| input[type="text"]:focus, | |
| input[type="password"]:focus, | |
| textarea:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| input[type="file"] { | |
| padding: 10px; | |
| border: 2px dashed #667eea; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| } | |
| .file-input-wrapper { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| button { | |
| width: 100%; | |
| padding: 12px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 5px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); | |
| } | |
| button:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| margin: 20px 0; | |
| } | |
| .spinner { | |
| border: 3px solid #f3f3f3; | |
| border-top: 3px solid #667eea; | |
| border-radius: 50%; | |
| width: 30px; | |
| height: 30px; | |
| animation: spin 1s linear infinite; | |
| margin: 0 auto 10px; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .result { | |
| display: none; | |
| margin-top: 20px; | |
| padding: 15px; | |
| border-radius: 5px; | |
| font-size: 14px; | |
| } | |
| .result.success { | |
| background: #d4edda; | |
| color: #155724; | |
| border: 1px solid #c3e6cb; | |
| } | |
| .result.error { | |
| background: #f8d7da; | |
| color: #721c24; | |
| border: 1px solid #f5c6cb; | |
| } | |
| .result-content { | |
| margin-top: 10px; | |
| background: rgba(255, 255, 255, 0.5); | |
| padding: 10px; | |
| border-radius: 3px; | |
| overflow-x: auto; | |
| font-family: 'Courier New', monospace; | |
| font-size: 12px; | |
| } | |
| .file-info { | |
| display: none; | |
| background: #f5f5f5; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin-bottom: 10px; | |
| font-size: 13px; | |
| color: #666; | |
| } | |
| .file-info.show { | |
| display: block; | |
| } | |
| .helper-text { | |
| font-size: 12px; | |
| color: #999; | |
| margin-top: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🚀 Vico</h1> | |
| <p class="subtitle">Upload file ke Hugging Face Dataset</p> | |
| <form id="uploadForm"> | |
| <!-- Hugging Face Token --> | |
| <div class="form-group"> | |
| <label for="token">Hugging Face Token *</label> | |
| <input | |
| type="password" | |
| id="token" | |
| placeholder="hf_xxxxxxxxxxxxxxxxxxxxxxx" | |
| required | |
| > | |
| <p class="helper-text"> | |
| Dapatkan di: <a href="https://huggingface.co/settings/tokens" target="_blank"> | |
| huggingface.co/settings/tokens</a> | |
| </p> | |
| </div> | |
| <!-- Repository ID --> | |
| <div class="form-group"> | |
| <label for="repoId">Repository ID *</label> | |
| <input | |
| type="text" | |
| id="repoId" | |
| placeholder="username/dataset-name" | |
| required | |
| > | |
| <p class="helper-text">Format: username/dataset-name (lowercase)</p> | |
| </div> | |
| <!-- Subfolder --> | |
| <div class="form-group"> | |
| <label for="subfolder">Subfolder (Opsional)</label> | |
| <input | |
| type="text" | |
| id="subfolder" | |
| placeholder="data, datasets, dll" | |
| > | |
| <p class="helper-text">Biarkan kosong untuk upload ke root</p> | |
| </div> | |
| <!-- File Upload --> | |
| <div class="form-group"> | |
| <label for="file">Pilih File *</label> | |
| <div class="file-input-wrapper"> | |
| <input | |
| type="file" | |
| id="file" | |
| required | |
| > | |
| </div> | |
| <div id="fileInfo" class="file-info"> | |
| <span id="fileName"></span> | |
| (<span id="fileSize"></span>) | |
| </div> | |
| </div> | |
| <!-- Submit Button --> | |
| <button type="submit" id="submitBtn">Upload File</button> | |
| <!-- Loading State --> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <p>Mengupload...</p> | |
| </div> | |
| <!-- Result Message --> | |
| <div class="result" id="result"> | |
| <div id="resultContent"></div> | |
| </div> | |
| </form> | |
| </div> | |
| <script> | |
| const API_URL = 'http://localhost:3000/api/upload'; | |
| // File input handler | |
| document.getElementById('file').addEventListener('change', function(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const fileInfo = document.getElementById('fileInfo'); | |
| document.getElementById('fileName').textContent = file.name; | |
| document.getElementById('fileSize').textContent = formatFileSize(file.size); | |
| fileInfo.classList.add('show'); | |
| } | |
| }); | |
| // Format file size | |
| 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 Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; | |
| } | |
| // Form submit handler | |
| document.getElementById('uploadForm').addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const file = document.getElementById('file').files[0]; | |
| const token = document.getElementById('token').value; | |
| const repoId = document.getElementById('repoId').value; | |
| const subfolder = document.getElementById('subfolder').value; | |
| if (!file || !token || !repoId) { | |
| alert('Harap isi semua field yang diperlukan'); | |
| return; | |
| } | |
| // Show loading state | |
| const submitBtn = document.getElementById('submitBtn'); | |
| const loading = document.getElementById('loading'); | |
| const result = document.getElementById('result'); | |
| submitBtn.disabled = true; | |
| loading.style.display = 'block'; | |
| result.style.display = 'none'; | |
| try { | |
| // Create FormData | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| formData.append('repo_id', repoId); | |
| formData.append('token', token); | |
| if (subfolder) { | |
| formData.append('subfolder', subfolder); | |
| } | |
| // Upload | |
| const response = await fetch(API_URL, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| // Show result | |
| if (data.success) { | |
| result.classList.remove('error'); | |
| result.classList.add('success'); | |
| result.innerHTML = ` | |
| <strong>✅ ${data.message}</strong> | |
| <div class="result-content"> | |
| <strong>File:</strong> ${data.data.file_name}<br> | |
| <strong>Size:</strong> ${formatFileSize(data.data.file_size)}<br> | |
| <strong>Repo:</strong> ${data.data.repo_id}<br> | |
| <strong>URL:</strong> <a href="${data.data.url}" target="_blank">${data.data.url}</a><br> | |
| <strong>Time:</strong> ${new Date(data.data.upload_timestamp).toLocaleString('id-ID')} | |
| </div> | |
| `; | |
| } else { | |
| result.classList.remove('success'); | |
| result.classList.add('error'); | |
| result.innerHTML = ` | |
| <strong>❌ ${data.error}</strong> | |
| <div class="result-content">${JSON.stringify(data, null, 2)}</div> | |
| `; | |
| } | |
| result.style.display = 'block'; | |
| } catch (error) { | |
| result.classList.remove('success'); | |
| result.classList.add('error'); | |
| result.innerHTML = ` | |
| <strong>❌ Error: ${error.message}</strong> | |
| <div class="result-content"> | |
| Pastikan API server berjalan di ${API_URL} | |
| </div> | |
| `; | |
| result.style.display = 'block'; | |
| } finally { | |
| submitBtn.disabled = false; | |
| loading.style.display = 'none'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |