JunhanCai's picture
Initial commit with GEMS model and Dockerfile
6918c6b
Raw
History Blame Contribute Delete
7.78 kB
<!doctype html>
<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>