Fotos: Gesichtssuche FH-weit (Studierende + Lektoren) als Job mit Live-Progress, Person-Info-Seiten, Scope-Auswahl

This commit is contained in:
Nicolas Gruber
2026-10-19 14:43:09 +02:00
parent 567bcb40ee
commit 6763faf9c1
7 changed files with 405 additions and 72 deletions
+54
View File
@@ -424,10 +424,64 @@ async function reloadVerbands() {
} catch (e) { /* Keep static options */ }
}
/* ================= Gesichtssuche: Job-Polling ================= */
function initSearchJob() {
const job = window.__SEARCH_JOB__;
if (!job) return;
const state = $("#search-state");
const count = $("#search-count");
const progress = $("#search-progress");
const timeBadge = $("#search-time");
const bar = $("#search-bar");
const grid = $("#match-grid");
const hint = $("#search-hint");
const t0 = Date.now();
function tile(r) {
const name = `${esc(r.nachname)}${r.vorname ? ", " + esc(r.vorname) : ""}`;
const pct = Math.max(0, Math.round(r.sim * 100));
const cls = r.sim >= 0.5 ? "match-strong" : (r.sim >= 0.35 ? "match-mid" : "");
const badge = r.kind === "lektor" ? ` <span class="badge badge-muted">Lektor</span>` : "";
return `<div class="match-tile ${cls}">
<a href="/person/${encodeURIComponent(r.uid)}"><img src="${esc(r.img)}" alt="${name}"></a>
<div class="match-name"><a href="/person/${encodeURIComponent(r.uid)}">${name}</a>${badge}</div>
<div class="match-sim">
<div class="sim-bar"><div style="width:${pct}%"></div></div>
<span class="sim-num">${pct} %</span>
</div>
</div>`;
}
function poll() {
fetch(`/api/suche/status/${job.id}`)
.then(r => { if (!r.ok) throw new Error("status"); return r.json(); })
.then(d => {
if (d.total) bar.style.width = (d.done / d.total * 100).toFixed(1) + "%";
progress.textContent = `${d.done}/${d.total}`;
grid.innerHTML = (d.top || []).map(tile).join("");
count.textContent = `${(d.top || []).filter(r => r.sim >= 0.35).length} Treffer`;
if (d.status === "done") {
clearInterval(timer);
state.textContent = d.error ? "Suche fehlgeschlagen" : "Fertig";
hint.style.display = "block";
if (d.error) hint.innerHTML = `<span style="color:#b3261e">${esc(d.error)}</span>`;
const secs = d.took ? `${d.took} s` : `${((Date.now() - t0) / 1000).toFixed(0)} s`;
timeBadge.textContent = secs;
timeBadge.style.display = "";
}
})
.catch(() => { /* retry on next tick */ });
}
const timer = setInterval(poll, 2000);
poll();
}
/* ================= boot ================= */
document.addEventListener("DOMContentLoaded", () => {
if (window.__PLAN_DATA__) initPlan();
if (window.__STGS__) decodeMat();
if (window.__SEARCH_JOB__) initSearchJob();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeDetail();