2025-10-17 12:41:34 +00:00
|
|
|
|
<!doctype html>
|
|
|
|
|
|
<html lang="zh-CN" data-bs-theme="auto">
|
|
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
|
|
<title>训练结果预览</title>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Bootstrap & Icons(与前页一致) -->
|
|
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
|
|
|
|
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
|
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
/* 背景与玻璃卡片 —— 与你其它页保持一致 */
|
|
|
|
|
|
body {
|
|
|
|
|
|
min-height: 100vh;
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(1200px 600px at 20% 10%, rgba(13, 110, 253, .15), transparent 60%),
|
|
|
|
|
|
radial-gradient(800px 400px at 80% 90%, rgba(32, 201, 151, .18), transparent 60%),
|
|
|
|
|
|
linear-gradient(180deg, #f8f9fa, #eef2f7);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
|
|
|
|
body {
|
|
|
|
|
|
background:
|
|
|
|
|
|
radial-gradient(1200px 600px at 20% 10%, rgba(13, 110, 253, .25), transparent 60%),
|
|
|
|
|
|
radial-gradient(800px 400px at 80% 90%, rgba(32, 201, 151, .25), transparent 60%),
|
|
|
|
|
|
linear-gradient(180deg, #0b1020, #0e1326);
|
2025-09-24 19:09:35 +00:00
|
|
|
|
}
|
2025-10-17 12:41:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.glass-card {
|
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
|
-webkit-backdrop-filter: blur(10px);
|
|
|
|
|
|
background: rgba(255, 255, 255, .65);
|
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, .5);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[data-bs-theme="dark"] .glass-card {
|
|
|
|
|
|
background: rgba(17, 20, 34, .6);
|
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, .08);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 标题行 */
|
|
|
|
|
|
.section-title {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: .6rem;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hint {
|
|
|
|
|
|
font-size: .9rem;
|
|
|
|
|
|
opacity: .75
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 画布容器:响应式 16:9,可根据需要改比例 */
|
|
|
|
|
|
.chart-wrap {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
aspect-ratio: 16 / 9;
|
|
|
|
|
|
/* 最小高度,防止数据少时过扁 */
|
|
|
|
|
|
min-height: 280px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 让 canvas 完整填充容器(不改 id,仅加样式) */
|
|
|
|
|
|
#lossChart {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
inset: 0;
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* “无结果”提示:沿用 id,不改逻辑,仅美化 */
|
|
|
|
|
|
#none-exist {
|
|
|
|
|
|
margin: 0 0 1rem 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="container py-4">
|
|
|
|
|
|
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
|
|
|
|
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
|
|
|
|
|
|
<h1 class="section-title h4">
|
|
|
|
|
|
<i class="bi bi-graph-up-arrow"></i> 训练结果预览
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<div class="hint">Loss / Metric 曲线将根据你的训练日志自动绘制</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- “无结果”占位:保持 id,不改 hidden 的使用方式 -->
|
|
|
|
|
|
<p id="none-exist" class="alert alert-warning d-flex align-items-center" role="status" hidden>
|
|
|
|
|
|
<i class="bi bi-exclamation-triangle me-2"></i>
|
|
|
|
|
|
暂无可预览的数据,请先开始训练或检查日志输出。
|
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 图表区域 -->
|
|
|
|
|
|
<div class="chart-wrap rounded-3 border">
|
|
|
|
|
|
<canvas id="lossChart"></canvas>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 可选:底部小提示(不影响现有 JS) -->
|
|
|
|
|
|
<div class="mt-3 text-secondary small">
|
|
|
|
|
|
提示:如果你使用 Chart.js,建议设置 <code>responsive: true</code> 与 <code>maintainAspectRatio: false</code>,
|
|
|
|
|
|
本页样式已自动保证画布自适应容器尺寸。
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Bootstrap Bundle(含 Popper) -->
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
|
|
|
|
|
|
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
|
|
|
|
|
|
crossorigin="anonymous"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
|
|
|
|
<script src="api.js"></script>
|
|
|
|
|
|
<script type="module">
|
|
|
|
|
|
|
|
|
|
|
|
const API_RESULT = API_BASE + "status";
|
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
|
|
|
|
|
const ID = params.get("id");
|
|
|
|
|
|
|
|
|
|
|
|
const msg = document.getElementById("none-exist");
|
|
|
|
|
|
const cava = document.getElementById("lossChart")
|
|
|
|
|
|
|
|
|
|
|
|
if (!ID) {
|
|
|
|
|
|
msg.textContent = "missing id in URL";
|
|
|
|
|
|
msg.hidden = false;
|
|
|
|
|
|
cava.hidden = true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await axios.get(API_RESULT + "?id=" + encodeURIComponent(ID));
|
|
|
|
|
|
console.log(res);
|
|
|
|
|
|
const { exists, status } = res.data; // exists: boolean, status: "running" | "success" | "failed"...
|
|
|
|
|
|
const data = res.data;
|
|
|
|
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
|
|
msg.textContent = `id "${ID}" 不存在`;
|
|
|
|
|
|
msg.hidden = false;
|
|
|
|
|
|
cava.hidden = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (status == "running") {
|
|
|
|
|
|
msg.textContent = `id "${ID}" 仍在运行中,请耐心等待片刻后刷新。`;
|
|
|
|
|
|
msg.hidden = false;
|
|
|
|
|
|
cava.hidden = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
msg.hidden = true;
|
|
|
|
|
|
cava.hidden = false;
|
|
|
|
|
|
|
|
|
|
|
|
const train_losses = data.train_losses;
|
|
|
|
|
|
const test_losses = data.test_losses;
|
|
|
|
|
|
|
|
|
|
|
|
const epochs = Array.from({ length: Math.max(train_losses.length, test_losses.length) }, (_, i) => i + 1);
|
|
|
|
|
|
|
|
|
|
|
|
const ctx = document.getElementById('lossChart').getContext('2d');
|
|
|
|
|
|
new Chart(ctx, {
|
|
|
|
|
|
type: 'line',
|
|
|
|
|
|
data: {
|
|
|
|
|
|
labels: epochs,
|
|
|
|
|
|
datasets: [
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Train Loss',
|
|
|
|
|
|
data: train_losses,
|
|
|
|
|
|
borderColor: 'blue',
|
|
|
|
|
|
fill: false,
|
|
|
|
|
|
tension: 0.2,
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
label: 'Test Loss',
|
|
|
|
|
|
data: test_losses,
|
|
|
|
|
|
borderColor: 'red',
|
|
|
|
|
|
fill: false,
|
|
|
|
|
|
tension: 0.2,
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
},
|
|
|
|
|
|
options: {
|
|
|
|
|
|
responsive: true,
|
|
|
|
|
|
interaction: { mode: 'index', intersect: false },
|
|
|
|
|
|
scales: {
|
|
|
|
|
|
x: { title: { display: true, text: 'Epoch' } },
|
|
|
|
|
|
y: { title: { display: true, text: 'Loss' } }
|
2025-09-24 19:09:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 12:41:34 +00:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
msg.textContent = "请求失败";
|
|
|
|
|
|
msg.hidden = false;
|
|
|
|
|
|
console.error(e);
|
2025-09-24 19:09:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-17 12:41:34 +00:00
|
|
|
|
|
|
|
|
|
|
window.downloadTif = function () {
|
|
|
|
|
|
const a = document.createElement("a");
|
|
|
|
|
|
a.href = API_DL + "?id=" + encodeURIComponent(ID);
|
|
|
|
|
|
a.download = ID; // 留空:文件名由服务端决定;可写具体名称
|
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
|
a.click();
|
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
|
|
|
|
|
|
</html>
|