cellpose-web/frontend/train.html
2025-10-17 11:58:14 +00:00

310 lines
No EOL
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN" data-bs-theme="auto">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>训练任务</title>
<!-- Bootstrap 5.3 与 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);
}
}
.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-bottom:0;
}
.hint{font-size:.9rem; opacity:.75}
/* 原生 progress 美化(保持 id 不变) */
#bar{
width:100%; height:1rem; border:none; border-radius:.75rem;
background-color:rgba(13,110,253,.15); overflow:hidden; vertical-align:middle;
}
#bar::-webkit-progress-bar{background:transparent}
#bar::-webkit-progress-value{background:linear-gradient(90deg,#0d6efd,#6ea8fe); border-radius:.75rem}
#bar::-moz-progress-bar{background:linear-gradient(90deg,#0d6efd,#6ea8fe); border-radius:.75rem}
/* 上传按钮动效(保持 id 不变) */
#uploadBtn{
padding:.65rem 1.1rem; border-radius:.8rem;
transition:transform .12s ease, box-shadow .12s ease;
}
#uploadBtn:hover, #uploadBtn:focus-visible{
transform:translateY(-1px);
box-shadow:0 .75rem 1.25rem rgba(0,0,0,.12);
}
</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-cpu"></i> 训练配置</h1>
<div class="hint">选择训练/测试数据并设置模型参数</div>
</div>
<!-- 选择文件 -->
<div class="mb-4">
<div class="row g-3">
<div class="col-12 col-lg-6">
<label class="form-label fw-semibold"><i class="bi bi-folder2-open me-1"></i>选择训练文件</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
<input id="trainFileInput" type="file" class="form-control" multiple />
</div>
</div>
<div class="col-12 col-lg-6">
<label class="form-label fw-semibold"><i class="bi bi-folder2 me-1"></i>选择测试文件</label>
<div class="input-group">
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
<input id="testFileInput" type="file" class="form-control" multiple />
</div>
</div>
</div>
</div>
<hr class="my-4">
<!-- 参数区(仅美化,不改 id -->
<div class="row g-3">
<div class="col-12 col-lg-8">
<div class="row g-3">
<div class="col-12">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-type me-1"></i>model name</span>
<input id="name" type="text" class="form-control" placeholder="为你的模型命名" />
</div>
</div>
<div class="col-12 col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-image me-1"></i>image filter</span>
<input id="image_filter" type="text" class="form-control" placeholder="_img" />
</div>
</div>
<div class="col-12 col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-brush me-1"></i>masks filter</span>
<input id="masks_filter" type="text" class="form-control" placeholder="_masks" />
</div>
</div>
<div class="col-12">
<label class="w-100">
<div class="input-group">
<span class="input-group-text"><i class="bi bi-cpu-fill me-1"></i>model</span>
<select id="model" class="form-select"></select>
</div>
</label>
</div>
</div>
</div>
<!-- 右侧提示(可删,不影响逻辑) -->
<div class="col-12 col-lg-4">
<div class="p-3 rounded-3 border h-100">
<div class="d-flex align-items-center mb-2">
<i class="bi bi-info-circle me-2"></i><strong>提示</strong>
</div>
<div class="hint">
• 名称用于区分训练产物(日志/权重)。<br>
• filter 用于匹配文件名,如 <code>*_img.png</code><code>*_masks.png</code><br>
• 选择合适的预训练模型可加速收敛。
</div>
</div>
</div>
</div>
<!-- 行动与进度(保持 id 与机制不变) -->
<div class="mt-4 d-flex flex-column flex-md-row align-items-stretch gap-3">
<button id="uploadBtn" class="btn btn-success d-inline-flex align-items-center">
<i class="bi bi-play-circle me-2"></i> Upload
</button>
<div class="flex-grow-1">
<div class="d-flex align-items-center justify-content-between mb-1">
<span class="hint"><i class="bi bi-activity me-1"></i>进度</span>
<span class="hint" id="barText"></span><!-- 如需 JS 写入文字可用;不用也不影响 -->
</div>
<progress id="bar" max="100" value="0"></progress>
</div>
</div>
</div>
</div>
<!-- 只需引入 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/@popperjs/core@2.11.6/dist/umd/popper.min.js"
integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.min.js"
integrity="sha384-ep+dxp/oz2RKF89ALMPGc7Z89QFa32C8Uv1A3TcEK8sMzXVysblLA3+eJWTzPJzT"
crossorigin="anonymous"></script>
<script src="api.js"></script>
<script>
const API_UPLOAD = API_BASE + "train_upload";
const API_MODEL = API_BASE + "models";
async function loadModels() {
const select = document.getElementById('model');
// 占位 & 禁用,避免用户在加载中点选
select.disabled = true;
select.innerHTML = '<option value="">加载中…</option>';
// 可选超时控制5 秒)
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 5000);
try {
const resp = await fetch(API_MODEL, {
headers: { 'Accept': 'application/json' },
signal: controller.signal
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
const list = Array.isArray(data.models) ? data.models : [];
// 记住之前的选择(如果有)
const prev = select.value;
// 重新渲染选项
select.innerHTML = '<option value="" disabled selected>请选择基础模型</option>';
const seen = new Set();
for (const name of list) {
if (!name || seen.has(name)) continue;
seen.add(name);
const opt = document.createElement('option');
opt.value = String(name);
opt.textContent = String(name);
select.appendChild(opt);
}
// 如果原选择仍然存在,则恢复它
if (prev && seen.has(prev)) {
select.value = prev;
}
} catch (err) {
console.error('加载模型列表失败:', err);
select.innerHTML = '<option value="">加载失败(点击重试)</option>';
// 点击下拉框时重试
select.addEventListener('click', loadModels, { once: true });
} finally {
clearTimeout(t);
select.disabled = false;
}
}
document.addEventListener('DOMContentLoaded', loadModels);
function buildUrl() {
// 获取参数
const model = document.getElementById('model')?.value;
const model_name = (document.getElementById('model_name')?.value || '').trim();
const image_filter = (document.getElementById('image_filter')?.value || '').trim();
const masks_filter = (document.getElementById('masks_filter')?.value || '').trim();
// 用 URLSearchParams 组装查询串
const qs = new URLSearchParams({
model: model,
model_name: model_name,
image_filter: image_filter,
masks_filter: masks_filter
});
return `${API_UPLOAD}?${qs.toString()}`;
}
document.getElementById("uploadBtn").addEventListener("click", async () => {
const input_train = document.getElementById("trainFileInput");
const input_test = document.getElementById("testFileInput");
if (!input_train.files.length) return alert("请选择训练文件");
if (!input_test.files.length) return alert("请选择训练文件");
const fd = new FormData();
for (const f of input_train.files) fd.append("train_files", f);
for (const f of input_test.files) fd.append("test_files", f);
const bar = document.getElementById("bar");
try {
const URL = buildUrl();
const res = await axios.post(URL, fd, {
onUploadProgress: (e) => {
if (e.total) bar.value = Math.round((e.loaded * 100) / e.total);
},
});
// alert("上传成功:" + JSON.stringify(res.data));
// 创建一个提示元素
const notice = document.createElement("div");
notice.style.position = "fixed";
notice.style.top = "20px";
notice.style.left = "50%";
notice.style.transform = "translateX(-50%)";
notice.style.padding = "10px 20px";
notice.style.background = "#4caf50";
notice.style.color = "white";
notice.style.borderRadius = "8px";
notice.style.fontSize = "16px";
notice.style.zIndex = "9999";
let seconds = 3;
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
document.body.appendChild(notice);
const timer = setInterval(() => {
seconds--;
if (seconds > 0) {
notice.textContent = `上传成功!${seconds} 秒后跳转预览页面…`;
} else {
clearInterval(timer);
document.body.removeChild(notice);
window.location.href = `train_result.html?id=${encodeURIComponent(res.data['id'])}`;
}
}, 1000);
} catch (e) {
alert("上传失败:" + (e.response?.data?.message || e.message));
} finally {
bar.value = 0;
}
});
</script>
</body>
</html>