266 lines
8.1 KiB
JavaScript
266 lines
8.1 KiB
JavaScript
const state = {
|
|
images: [],
|
|
capabilities: null,
|
|
selectedImage: null,
|
|
currentJob: null,
|
|
pollTimer: null,
|
|
};
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
function assetUrl(path) {
|
|
return `/asset?path=${encodeURIComponent(path)}&t=${Date.now()}`;
|
|
}
|
|
|
|
async function api(path, options = {}) {
|
|
const response = await fetch(path, options);
|
|
const payload = await response.json();
|
|
if (!response.ok) {
|
|
throw new Error(payload.error || response.statusText);
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
function formatBytes(bytes) {
|
|
if (!Number.isFinite(bytes)) return "";
|
|
if (bytes < 1024) return `${bytes} B`;
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
}
|
|
|
|
function renderImages() {
|
|
const box = $("imageList");
|
|
box.innerHTML = "";
|
|
state.images.forEach((image) => {
|
|
const item = document.createElement("button");
|
|
item.className = `image-item ${state.selectedImage === image.name ? "active" : ""}`;
|
|
item.type = "button";
|
|
item.innerHTML = `
|
|
<span>${image.name}</span>
|
|
<span class="image-meta">${image.width || "?"}x${image.height || "?"} · ${formatBytes(image.size)}</span>
|
|
`;
|
|
item.addEventListener("click", () => selectImage(image.name));
|
|
box.appendChild(item);
|
|
});
|
|
}
|
|
|
|
function renderMethods() {
|
|
const box = $("methodList");
|
|
box.innerHTML = "";
|
|
const methods = state.capabilities?.methods || {};
|
|
Object.entries(methods).forEach(([key, info]) => {
|
|
const label = document.createElement("label");
|
|
label.className = `check-item ${info.available ? "" : "disabled"}`;
|
|
label.innerHTML = `
|
|
<span>
|
|
<strong>${info.label}</strong>
|
|
<span class="method-meta">${info.available ? "ready" : `缺 ${info.module_ok ? "模型" : info.module}`}</span>
|
|
</span>
|
|
<input type="checkbox" value="${key}" ${info.available || key === "Baidu_API" || key === "DCP" ? "" : "disabled"} ${key === "DCP" ? "checked" : ""} />
|
|
`;
|
|
box.appendChild(label);
|
|
});
|
|
}
|
|
|
|
function renderPostprocessors() {
|
|
const box = $("postList");
|
|
box.innerHTML = "";
|
|
const processors = state.capabilities?.postprocessors || {};
|
|
Object.entries(processors).forEach(([key, info]) => {
|
|
const label = document.createElement("label");
|
|
label.className = "check-item";
|
|
label.innerHTML = `
|
|
<span><strong>${info.label}</strong></span>
|
|
<input type="checkbox" value="${key}" ${key === "manual_sv" ? "checked" : ""} />
|
|
`;
|
|
box.appendChild(label);
|
|
});
|
|
}
|
|
|
|
function renderReferenceOptions() {
|
|
const select = $("referenceImage");
|
|
select.innerHTML = "";
|
|
state.images.forEach((image) => {
|
|
const option = document.createElement("option");
|
|
option.value = image.name;
|
|
option.textContent = image.name;
|
|
option.selected = image.name === state.selectedImage;
|
|
select.appendChild(option);
|
|
});
|
|
}
|
|
|
|
function setJobState(text, status = "") {
|
|
const box = $("jobState");
|
|
box.textContent = text;
|
|
box.className = `job-state ${status}`;
|
|
}
|
|
|
|
async function selectImage(name) {
|
|
state.selectedImage = name;
|
|
$("currentTitle").textContent = name;
|
|
renderImages();
|
|
renderReferenceOptions();
|
|
await refreshResults();
|
|
}
|
|
|
|
function resultCard(title, path, exists = true) {
|
|
const card = document.createElement("article");
|
|
card.className = "result-card";
|
|
const badge = exists ? '<span class="badge ok">ready</span>' : '<span class="badge pending">未生成</span>';
|
|
const body = exists
|
|
? `<div class="image-frame"><img src="${assetUrl(path)}" alt="${title}" loading="lazy" /></div>`
|
|
: '<div class="image-frame"><span class="missing">暂无结果</span></div>';
|
|
card.innerHTML = `<header><h3>${title}</h3>${badge}</header>${body}`;
|
|
return card;
|
|
}
|
|
|
|
function updatePostSourceOptions(results) {
|
|
const select = $("postSource");
|
|
const previous = select.value;
|
|
select.innerHTML = "";
|
|
const original = document.createElement("option");
|
|
original.value = "original";
|
|
original.textContent = "原图";
|
|
select.appendChild(original);
|
|
results.dehaze.filter((item) => item.exists).forEach((item) => {
|
|
const option = document.createElement("option");
|
|
option.value = item.method;
|
|
option.textContent = item.label;
|
|
select.appendChild(option);
|
|
});
|
|
if ([...select.options].some((option) => option.value === previous)) {
|
|
select.value = previous;
|
|
} else if (results.dehaze.some((item) => item.method === "DCP" && item.exists)) {
|
|
select.value = "DCP";
|
|
}
|
|
}
|
|
|
|
async function refreshResults() {
|
|
if (!state.selectedImage) return;
|
|
const results = await api(`/api/results?image=${encodeURIComponent(state.selectedImage)}`);
|
|
const grid = $("resultGrid");
|
|
grid.innerHTML = "";
|
|
grid.appendChild(resultCard(results.original.label, results.original.path, true));
|
|
results.dehaze.forEach((item) => {
|
|
grid.appendChild(resultCard(item.label, item.path, item.exists));
|
|
});
|
|
results.postprocess.forEach((item) => {
|
|
grid.appendChild(resultCard(item.name, item.path, true));
|
|
});
|
|
updatePostSourceOptions(results);
|
|
}
|
|
|
|
function selectedValues(containerId) {
|
|
return [...$(containerId).querySelectorAll("input[type=checkbox]:checked")].map((input) => input.value);
|
|
}
|
|
|
|
function postParams() {
|
|
return {
|
|
manual_sv: {
|
|
s_gain: Number($("sGain").value),
|
|
v_gain: Number($("vGain").value),
|
|
},
|
|
hsv_hist: {
|
|
match_hue: $("matchHue").checked,
|
|
},
|
|
hist_auto_sv: {
|
|
match_hue: $("matchHue").checked,
|
|
},
|
|
};
|
|
}
|
|
|
|
async function startJob(endpoint, payload) {
|
|
const response = await api(endpoint, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
state.currentJob = response.job.id;
|
|
$("logBox").textContent = "";
|
|
setJobState("Running", "running");
|
|
pollJob();
|
|
}
|
|
|
|
async function pollJob() {
|
|
if (!state.currentJob) return;
|
|
clearTimeout(state.pollTimer);
|
|
try {
|
|
const job = await api(`/api/job?id=${encodeURIComponent(state.currentJob)}`);
|
|
$("logBox").textContent = (job.logs || []).join("\n");
|
|
$("logBox").scrollTop = $("logBox").scrollHeight;
|
|
if (job.status === "running") {
|
|
setJobState("Running", "running");
|
|
state.pollTimer = setTimeout(pollJob, 1000);
|
|
} else {
|
|
setJobState(job.status === "done" ? "Done" : "Error", job.status);
|
|
await refreshResults();
|
|
}
|
|
} catch (error) {
|
|
setJobState("Error", "error");
|
|
$("logBox").textContent = error.message;
|
|
}
|
|
}
|
|
|
|
async function runSelectedModels() {
|
|
if (!state.selectedImage) return;
|
|
const methods = selectedValues("methodList");
|
|
const payload = {
|
|
image: state.selectedImage,
|
|
methods,
|
|
options: {
|
|
DCP: {
|
|
sz: Number($("dcpSz").value),
|
|
tx: Number($("dcpTx").value),
|
|
},
|
|
},
|
|
};
|
|
await startJob("/api/run", payload);
|
|
}
|
|
|
|
async function runPostprocess() {
|
|
if (!state.selectedImage) return;
|
|
const processors = selectedValues("postList");
|
|
const payload = {
|
|
image: state.selectedImage,
|
|
source: $("postSource").value,
|
|
reference: $("referenceImage").value || state.selectedImage,
|
|
processors,
|
|
params: postParams(),
|
|
};
|
|
await startJob("/api/postprocess", payload);
|
|
}
|
|
|
|
function bindControls() {
|
|
$("runBtn").addEventListener("click", runSelectedModels);
|
|
$("postBtn").addEventListener("click", runPostprocess);
|
|
$("refreshBtn").addEventListener("click", refreshResults);
|
|
$("sGain").addEventListener("input", () => {
|
|
$("sGainValue").textContent = `${Math.round(Number($("sGain").value) * 100)}%`;
|
|
});
|
|
$("vGain").addEventListener("input", () => {
|
|
$("vGainValue").textContent = `${Math.round(Number($("vGain").value) * 100)}%`;
|
|
});
|
|
}
|
|
|
|
async function init() {
|
|
bindControls();
|
|
const [capabilities, images] = await Promise.all([api("/api/capabilities"), api("/api/images")]);
|
|
state.capabilities = capabilities;
|
|
state.images = images.images || [];
|
|
$("envLine").textContent = capabilities.results_dir;
|
|
renderMethods();
|
|
renderPostprocessors();
|
|
renderImages();
|
|
if (state.images.length) {
|
|
await selectImage(state.images[0].name);
|
|
} else {
|
|
$("currentTitle").textContent = "待去雾图片为空";
|
|
}
|
|
setJobState("Idle");
|
|
}
|
|
|
|
init().catch((error) => {
|
|
setJobState("Error", "error");
|
|
$("logBox").textContent = error.stack || error.message;
|
|
});
|