mirror of
https://github.com/ClovertaTheTrilobita/cellpose-web.git
synced 2026-04-01 23:14:50 +00:00
353 lines
No EOL
12 KiB
HTML
353 lines
No EOL
12 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Bootstrap demo</title>
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"
|
||
integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous">
|
||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
|
||
</head>
|
||
|
||
<body>
|
||
<!-- <div style="padding: 1rem 1rem;">
|
||
<div class="mb-3 border" style="padding: 1rem 1rem;">
|
||
<div class="input-group mb-3">
|
||
<input id="fileInput" type="file" class="form-control" multiple />
|
||
</div>
|
||
|
||
<hr>
|
||
<div>
|
||
<div style="padding-right: 50rem;">
|
||
<div class="input-group mb-3">
|
||
<span class="input-group-text">flow threshold:</span>
|
||
<input id="flow" type="text" class="form-control" placeholder="" />
|
||
</div>
|
||
|
||
<div class="input-group mb-3">
|
||
<span class="input-group-text">cellprob threshold:</span>
|
||
<input id="cellprob" type="text" class="form-control" placeholder="" />
|
||
</div>
|
||
|
||
<div class="input-group mb-3">
|
||
<span class="input-group-text">diameter:</span>
|
||
<input id="diameter" type="text" class="form-control" placeholder="" />
|
||
</div>
|
||
|
||
<label>
|
||
<select id="model" class="form-select"></select>
|
||
</label>
|
||
|
||
</div>
|
||
</div>
|
||
<br>
|
||
<div>
|
||
<button id="uploadBtn" class="btn btn-success">Upload</button>
|
||
<progress id="bar" max="100" value="0" style="width:300px;"></progress>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<br><br><br> -->
|
||
|
||
<div class="container py-4">
|
||
<div class="glass-card rounded-4 shadow-lg p-4 p-md-5">
|
||
|
||
<!-- 上传区 -->
|
||
<div class="mb-4">
|
||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||
<h2 class="h5 section-title mb-0"><i class="bi bi-folder2-open me-2"></i>选择文件</h2>
|
||
<span class="hint">可多选</span>
|
||
</div>
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-cloud-arrow-up"></i></span>
|
||
<input id="fileInput" type="file" class="form-control" multiple />
|
||
</div>
|
||
</div>
|
||
|
||
<hr class="my-4">
|
||
|
||
<!-- 参数区 -->
|
||
<div class="row g-3">
|
||
<div class="col-12 col-lg-8">
|
||
<div class="row g-3">
|
||
<div class="col-12 col-md-6">
|
||
<div class="input-group">
|
||
<span class="input-group-text"><i class="bi bi-speedometer2 me-1"></i>flow threshold</span>
|
||
<input id="flow" 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-bezier2 me-1"></i>cellprob threshold</span>
|
||
<input id="cellprob" 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-bounding-box-circles me-1"></i>diameter</span>
|
||
<input id="diameter" type="text" class="form-control" placeholder="" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-12 col-md-6">
|
||
<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>
|
||
• 数值可使用小数(如 0.4)。<br>
|
||
• 选择合适的模型以获得更稳健的分割效果。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 行动区 -->
|
||
<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-upload 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>
|
||
|
||
|
||
<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 + "run_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 flow = (document.getElementById('flow')?.value || '').trim();
|
||
const cellp = (document.getElementById('cellprob')?.value || '').trim();
|
||
const diameter = (document.getElementById('diameter')?.value || '').trim();
|
||
|
||
// 用 URLSearchParams 组装查询串
|
||
const qs = new URLSearchParams({
|
||
model: model,
|
||
flow_threshold: flow,
|
||
cellprob_threshold: cellp,
|
||
diameter: diameter
|
||
});
|
||
|
||
return `${API_UPLOAD}?${qs.toString()}`;
|
||
}
|
||
|
||
document.getElementById("uploadBtn").addEventListener("click", async () => {
|
||
const input = document.getElementById("fileInput");
|
||
if (!input.files.length) return alert("请选择文件");
|
||
|
||
const fd = new FormData();
|
||
for (const f of input.files) fd.append("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 = `preview.html?id=${encodeURIComponent(res.data['id'])}`;
|
||
}
|
||
}, 1000);
|
||
} catch (e) {
|
||
alert("上传失败:" + (e.response?.data?.message || e.message));
|
||
} finally {
|
||
bar.value = 0;
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<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, 0.65);
|
||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
[data-bs-theme="dark"] .glass-card {
|
||
background: rgba(17, 20, 34, 0.6);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
/* 区块与控件 */
|
||
.section-title {
|
||
font-weight: 600;
|
||
letter-spacing: .02em;
|
||
}
|
||
.input-group-text i {
|
||
opacity: .7;
|
||
}
|
||
.hint {
|
||
font-size: .875rem;
|
||
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;
|
||
}
|
||
/* Chrome / Edge / Safari */
|
||
#bar::-webkit-progress-bar {
|
||
background-color: transparent;
|
||
}
|
||
#bar::-webkit-progress-value {
|
||
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||
border-radius: .75rem;
|
||
}
|
||
/* Firefox */
|
||
#bar::-moz-progress-bar {
|
||
background: linear-gradient(90deg, #0d6efd, #6ea8fe);
|
||
border-radius: .75rem;
|
||
}
|
||
|
||
/* 上传按钮动效 */
|
||
#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>
|
||
|
||
</body>
|
||
|
||
</html> |