整合去雾网页工具

This commit is contained in:
admin
2026-06-10 17:42:11 +08:00
commit 6db15ebc3f
101 changed files with 10167 additions and 0 deletions

265
web_dehaze/static/app.js Normal file
View File

@@ -0,0 +1,265 @@
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;
});