Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Job Status</title> | |
| <style> | |
| :root { | |
| color-scheme: light; | |
| --bg: #f4f0e8; | |
| --panel: rgba(255, 255, 255, 0.9); | |
| --panel-border: rgba(33, 37, 41, 0.12); | |
| --text: #1f2937; | |
| --muted: #5b6472; | |
| --accent: #2563a8; | |
| --shadow: 0 18px 50px rgba(32, 41, 48, 0.12); | |
| } | |
| body { | |
| margin: 0; | |
| font-family: "Segoe UI", Tahoma, sans-serif; | |
| color: var(--text); | |
| background: | |
| radial-gradient(circle at top left, rgba(37, 99, 168, 0.14), transparent 28%), | |
| linear-gradient(180deg, #faf7f1 0%, var(--bg) 100%); | |
| } | |
| .wrap { max-width: 1180px; margin: 0 auto; padding: 32px 20px 56px; } | |
| h2, h3 { margin-top: 0; } | |
| .topline { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; align-items: center; margin-bottom: 16px; } | |
| .pill { display: inline-flex; align-items: center; gap: 8px; border-radius: 999px; padding: 8px 12px; background: rgba(37, 99, 168, 0.1); color: var(--accent); font-weight: 700; } | |
| .card { border: 1px solid var(--panel-border); border-radius: 18px; padding: 18px; background: var(--panel); box-shadow: var(--shadow); backdrop-filter: blur(10px); } | |
| .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; } | |
| .stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; margin-bottom: 16px; } | |
| .stat { padding: 12px 14px; border-radius: 14px; background: rgba(37, 99, 168, 0.06); border: 1px solid rgba(37, 99, 168, 0.12); } | |
| .stat .k { font-size: 12px; color: var(--muted); margin-bottom: 6px; } | |
| .stat .v { font-size: 18px; font-weight: 700; } | |
| pre { white-space: pre-wrap; word-wrap: break-word; background: #f9fafb; padding: 14px; border-radius: 12px; overflow-x: auto; } | |
| a { color: var(--accent); text-decoration: none; } | |
| a:hover { text-decoration: underline; } | |
| .artifact { display: grid; gap: 10px; } | |
| .artifact img { width: 100%; height: auto; border-radius: 12px; border: 1px solid rgba(0,0,0,0.08); background: white; } | |
| .meta { color: var(--muted); font-size: 14px; line-height: 1.6; } | |
| .progress-shell { | |
| margin: 14px 0 10px; | |
| background: rgba(37, 99, 168, 0.10); | |
| border: 1px solid rgba(37, 99, 168, 0.14); | |
| border-radius: 999px; | |
| height: 16px; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| width: 0%; | |
| border-radius: inherit; | |
| background: linear-gradient(90deg, #2563a8, #3b82f6); | |
| transition: width 0.35s ease; | |
| } | |
| .progress-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 12px; | |
| margin-top: 8px; | |
| font-size: 13px; | |
| color: var(--muted); | |
| } | |
| .device-banner { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 10px; | |
| padding: 12px 14px; | |
| border-radius: 14px; | |
| background: rgba(37, 99, 168, 0.06); | |
| border: 1px solid rgba(37, 99, 168, 0.12); | |
| } | |
| .device-banner .device-value { | |
| font-weight: 800; | |
| color: var(--accent); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <div class="topline"> | |
| <div> | |
| <h2>Job Status: {{ job_id }}</h2> | |
| <div class="meta">Updated at {{ job.updated_at }}</div> | |
| </div> | |
| <div style="display:flex; align-items:center; gap:10px; flex-wrap:wrap; justify-content:flex-end;"> | |
| {% if can_stop %} | |
| <button id="stop-job-btn" type="button" style="padding:10px 14px; border:none; border-radius:999px; background:#b94b4b; color:white; font-weight:700; cursor:pointer; box-shadow: var(--shadow);">Stop Job</button> | |
| {% endif %} | |
| <div class="pill">{{ job.status|upper }}</div> | |
| </div> | |
| </div> | |
| <div class="card" style="margin-bottom: 16px;"> | |
| <div class="device-banner"> | |
| <div> | |
| <strong>Current Device</strong> | |
| <div class="meta">{{ job.device_name if job.device_name else 'Detecting device...' }}</div> | |
| </div> | |
| <div class="device-value">{{ job.device_label if job.device_label else 'Detecting...' }}{% if job.device_backend %} ({{ job.device_backend }}){% endif %}</div> | |
| </div> | |
| <div class="meta"><strong>Message:</strong> {{ job.message }}</div> | |
| <div class="progress-shell" aria-label="Training progress"> | |
| <div class="progress-bar" style="width: {{ job.progress if job.progress is not none else 0 }}%;"></div> | |
| </div> | |
| <div class="progress-row"> | |
| <span>{{ job.phase|replace('_', ' ')|title if job.phase else 'Queued' }}</span> | |
| <span>{{ job.progress if job.progress is not none else 0 }}%</span> | |
| </div> | |
| {% if summary.label_mapping_source %} | |
| <div class="meta" style="margin-top: 10px;"><strong>Label Mapping:</strong> {{ summary.label_mapping_source }}</div> | |
| {% endif %} | |
| {% if job.get('current_epoch') is not none and job.get('total_epochs') is not none and job.get('total_epochs') > 0 %} | |
| <div class="progress-row" style="margin-top: 4px;"> | |
| <span>Epoch {{ job.get('current_epoch', 0) }}/{{ job.get('total_epochs', 0) }}</span> | |
| <span>{{ job.status|upper }}</span> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <div class="grid"> | |
| <div class="card"> | |
| <h3>Training Visuals</h3> | |
| <div class="artifact"> | |
| {% for item in visual_artifacts %} | |
| <div> | |
| <div class="meta"><strong>{{ item.key|replace('_', ' ')|title }}</strong> - <a href="{{ item.url }}">download</a></div> | |
| <img src="{{ item.url }}" alt="{{ item.key }}"> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h3>Classification Report</h3> | |
| {% if report_text %} | |
| <pre>{{ report_text }}</pre> | |
| {% else %} | |
| <div class="meta">The report has not been generated yet, or training is still in progress.</div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| <div class="grid" style="margin-top: 16px;"> | |
| <div class="card"> | |
| <h3>Text and File Downloads</h3> | |
| <div class="meta"> | |
| {% for item in download_artifacts %} | |
| <div style="margin-bottom: 8px;"> | |
| <strong>{{ item.key|replace('_', ' ')|title }}</strong>: | |
| <a href="{{ item.url }}">{{ item.filename }}</a> | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| </div> | |
| {% if job.traceback %} | |
| <div class="card" style="margin-top: 16px;"> | |
| <h3>Traceback</h3> | |
| <pre>{{ job.traceback }}</pre> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <script> | |
| const stopButton = document.getElementById('stop-job-btn'); | |
| if (stopButton) { | |
| stopButton.addEventListener('click', async () => { | |
| const confirmed = window.confirm('Cancel the current training job? This will stop the process immediately.'); | |
| if (!confirmed) { | |
| return; | |
| } | |
| stopButton.disabled = true; | |
| stopButton.textContent = 'Stopping...'; | |
| try { | |
| const response = await fetch('/stop/{{ job_id }}', { | |
| method: 'POST', | |
| headers: { | |
| 'Accept': 'application/json' | |
| } | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}`); | |
| } | |
| window.location.reload(); | |
| } catch (error) { | |
| alert(`Failed to stop the job: ${error}`); | |
| stopButton.disabled = false; | |
| stopButton.textContent = 'Stop Job'; | |
| } | |
| }); | |
| } | |
| setTimeout(() => { window.location.reload(); }, 5000); | |
| </script> | |
| </body> | |
| </html> | |