// dwg-counting frontend
(() => {
const $ = (id) => document.getElementById(id);
const sections = {
upload: $("s-upload"),
processing: $("s-processing"),
symbols: $("s-symbols"),
results: $("s-results"),
};
const show = (name) => {
for (const [k, el] of Object.entries(sections)) el.classList.toggle("hidden", k !== name);
};
let state = { jobId: null, symbols: [], selected: new Set(), results: [] };
// Upload handlers
const fileInput = $("file-input");
const dropZone = $("drop-zone");
$("browse-btn").addEventListener("click", () => fileInput.click());
fileInput.addEventListener("change", (e) => e.target.files[0] && upload(e.target.files[0]));
["dragenter", "dragover"].forEach((ev) =>
dropZone.addEventListener(ev, (e) => { e.preventDefault(); dropZone.classList.add("drag-over"); }));
["dragleave", "drop"].forEach((ev) =>
dropZone.addEventListener(ev, (e) => { e.preventDefault(); dropZone.classList.remove("drag-over"); }));
dropZone.addEventListener("drop", (e) => e.dataTransfer.files[0] && upload(e.dataTransfer.files[0]));
async function upload(file) {
show("processing");
$("processing-title").textContent = "Připravuji výkres…";
const fd = new FormData();
fd.append("file", file);
try {
// Skip auto-detection by default — user defines symbols manually
const r = await fetch("/api/upload?auto_detect=false", {
method: "POST", body: fd,
});
if (!r.ok) throw new Error((await r.json()).detail || r.statusText);
const data = await r.json();
state.jobId = data.job_id;
state.symbols = data.symbols || [];
state.selected = new Set();
renderSymbols();
show("symbols");
} catch (err) {
alert("Chyba: " + err.message);
show("upload");
}
}
$("auto-detect-btn").addEventListener("click", async () => {
if (!state.jobId) return;
show("processing");
$("processing-title").textContent = "Hledám legendu pomocí AI…";
try {
const r = await fetch(`/api/auto-detect/${state.jobId}`, { method: "POST" });
if (!r.ok) throw new Error((await r.json()).detail || r.statusText);
const data = await r.json();
state.symbols = data.symbols || [];
renderSymbols();
show("symbols");
} catch (err) {
alert("Chyba: " + err.message);
show("symbols");
}
});
$("add-symbol-btn").addEventListener("click", () => {
openAddSymbolModal();
});
$("upload-symbol-btn").addEventListener("click", () => {
$("symbol-file-input").click();
});
$("symbol-file-input").addEventListener("change", async (e) => {
const f = e.target.files[0];
if (!f) return;
const name = prompt("Název symbolu:", f.name.replace(/\.[a-z]+$/i, ""));
if (!name) return;
const fd = new FormData();
fd.append("file", f);
fd.append("description", name);
try {
const r = await fetch(`/api/symbols/${state.jobId}/upload`, { method: "POST", body: fd });
if (!r.ok) throw new Error((await r.json()).detail || r.statusText);
const sym = await r.json();
state.symbols.push(sym);
renderSymbols();
} catch (err) { alert("Chyba: " + err.message); }
e.target.value = "";
});
function renderSymbols() {
const meta = $("symbols-meta");
if (!state.symbols.length) {
meta.textContent = 'Klikněte „+ Přidat symbol" a vyznačte ve výkresu, co chcete počítat. Nebo zkuste „Auto-detekce z legendy" pro automatický návrh.';
$("symbols-grid").innerHTML = "";
return;
}
meta.textContent = `${state.symbols.length} symbolů. Zaškrtněte k spočítání, ✎ upravit, 🗑 smazat.`;
const grid = $("symbols-grid");
grid.innerHTML = "";
for (const s of state.symbols) {
const card = document.createElement("div");
card.className = "symbol-card";
card.innerHTML = `
Načítám diagnostiku…
'; modal.classList.remove("hidden"); try { const r = await fetch(`/api/debug/${state.jobId}/${sym.id}`); if (!r.ok) throw new Error(await r.text()); const info = await r.json(); const tmplURL = `/api/symbol/${state.jobId}/${sym.id}?v=${sym._v||0}`; const procURL = `/api/debug-template/${state.jobId}/${sym.id}?v=${sym._v||0}&t=${Date.now()}`; const drawURL = `/api/drawing/${state.jobId}`; const matches = info.matches_at_threshold || {}; const maxMatchCount = Math.max(1, ...Object.values(matches)); const threshRows = Object.entries(matches).map(([t, n]) => `Aktuálně používaný práh pro počítání: 0.75. Skutečné počty (s rotacemi 0/90/180/270° a třemi scale faktory) jsou typicky nižší kvůli deduplikaci.
${t}
`).join(""); } catch (err) { body.innerHTML = `Chyba: ${err.message}
`; } } $("debug-close").addEventListener("click", () => $("debug-modal").classList.add("hidden")); $("debug-ok").addEventListener("click", () => $("debug-modal").classList.add("hidden")); $("crop-close").addEventListener("click", closeCropModal); $("crop-cancel").addEventListener("click", closeCropModal); $("crop-save").addEventListener("click", async () => { if (!cropState || !cropState.bbox) return; try { if (cropState.mode === "create") { const name = ($("crop-name").value || "").trim(); if (!name) { alert("Zadejte název symbolu."); return; } const r = await fetch(`/api/symbols/${state.jobId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ bbox: cropState.bbox, description: name, source: "drawing", }), }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); const newSym = await r.json(); state.symbols.push(newSym); } else { const r = await fetch(`/api/symbol/${state.jobId}/${cropState.sym.id}/recrop`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ bbox: cropState.bbox }), }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); cropState.sym._v = (cropState.sym._v || 0) + 1; } renderSymbols(); closeCropModal(); } catch (err) { alert("Chyba: " + err.message); } }); // Threshold slider live readout const thrSlider = $("threshold-slider"); const thrValue = $("threshold-value"); thrSlider.addEventListener("input", () => { thrValue.textContent = parseFloat(thrSlider.value).toFixed(2); }); $("count-btn").addEventListener("click", async () => { show("processing"); $("processing-title").textContent = `Počítám ${state.selected.size} symbolů…`; try { const r = await fetch(`/api/count/${state.jobId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ symbol_ids: [...state.selected], threshold: parseFloat(thrSlider.value), }), }); if (!r.ok) throw new Error((await r.json()).detail || r.statusText); const data = await r.json(); state.results = data.results || []; renderResults(); show("results"); } catch (err) { alert("Chyba: " + err.message); show("symbols"); } }); function renderResults() { const total = state.results.reduce((a, r) => a + (r.count || 0), 0); $("results-meta").textContent = `Celkem ${total} symbolů ve ${state.results.length} kategoriích.`; const tb = $("results-tbody"); tb.innerHTML = ""; for (const r of state.results) { const tr = document.createElement("tr"); const conf = r.confidence || ""; tr.innerHTML = `