Hintergrundverarbeitung, Bildaufbereitung, Sammelexport entfernt
All checks were successful
Build & Deploy / deploy (push) Successful in 1m24s
All checks were successful
Build & Deploy / deploy (push) Successful in 1m24s
Auslesen im Hintergrund: /api/scan antwortet, sobald die Karten freigestellt und gespeichert sind, und stößt die Extraktion als Task an. Ein Zwanzigerstapel blockierte vorher den Upload für die ganze Dauer aller Modellaufrufe. Die Oberfläche zeigt "wird gelesen" und lädt nach, solange etwas offen ist. Zwei Folgen davon sind mitbehandelt: Beim Schreiben der Ergebnisse steht COALESCE, damit ein Handeintrag während des Lesens nicht überschrieben wird, und offene Karten werden beim Start nachgeholt, statt dauerhaft in der Warteschlange zu hängen. Bildaufbereitung des Zuschnitts: - Der Einzug zieht die erkannten Ecken um 1,5 % zur Mitte, damit kein Untergrund im Zuschnitt bleibt. - Die Beleuchtung wird ausgeglichen (Division durch eine weichgezeichnete Fassung), damit das Papier weiß wird statt grau. Dunkle Karten bleiben unangetastet - bei ihnen ist das Dunkle das Papier, kein Schatten. - Das Modell meldet die nötige Drehung im Schema; das gespeicherte Bild wird entsprechend gedreht. Geometrisch ist die Lage nicht bestimmbar. Sammelexport entfernt: Der Button "Alle als vCard" ist weg, mit ihm der Endpunkt /vcf sowie vcard.build_many und db.execute_many, die dadurch keinen Aufrufer mehr hatten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -116,10 +116,14 @@
|
||||
function cardTags(card) {
|
||||
const tags = [];
|
||||
if (card.place) tags.push(["", card.place]);
|
||||
tags.push(
|
||||
card.exported_at ? ["pill-solid", "exportiert"] : ["", "nicht exportiert"]
|
||||
);
|
||||
if (card.extract_status !== "ok") tags.push(["", "Lesefehler"]);
|
||||
if (card.extract_status === "offen") {
|
||||
tags.push(["pill-solid", "wird gelesen"]);
|
||||
} else {
|
||||
tags.push(
|
||||
card.exported_at ? ["pill-solid", "exportiert"] : ["", "nicht exportiert"]
|
||||
);
|
||||
if (card.extract_status === "fehler") tags.push(["", "Lesefehler"]);
|
||||
}
|
||||
return tags
|
||||
.map(([extra, text]) => `<span class="pill ${extra}">${esc(text)}</span>`)
|
||||
.join("");
|
||||
@@ -166,7 +170,9 @@
|
||||
const result = await api("/api/scan", { method: "POST", body });
|
||||
renderScanResult(result);
|
||||
await refreshStats();
|
||||
toast(`${result.count} Karte${result.count === 1 ? "" : "n"} erfasst`);
|
||||
toast(
|
||||
`${result.count} Karte${result.count === 1 ? "" : "n"} erfasst, wird gelesen`
|
||||
);
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
} finally {
|
||||
@@ -181,9 +187,9 @@
|
||||
<button class="row" data-card="${esc(card.id)}">
|
||||
<span class="row-head">
|
||||
<span class="row-title">${esc(displayName(card))}</span>
|
||||
<span class="row-meta">${card.has_contact ? "Gelesen" : "Leer"}</span>
|
||||
<span class="row-meta">wird gelesen</span>
|
||||
</span>
|
||||
<span class="row-sub">${esc(card.company || card.email || "Keine Daten erkannt")}</span>
|
||||
<span class="row-sub">${esc(card.company || card.email || "Zuschnitt gespeichert")}</span>
|
||||
</button>`
|
||||
)
|
||||
.join("");
|
||||
@@ -212,11 +218,24 @@
|
||||
state.stats = data.stats;
|
||||
updateCounters();
|
||||
renderCards();
|
||||
schedulePoll();
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Das Auslesen laeuft auf dem Server weiter, nachdem der Scan geantwortet
|
||||
* hat. Solange noch etwas offen ist, holen wir den Stand nach. */
|
||||
function schedulePoll() {
|
||||
clearTimeout(schedulePoll.timer);
|
||||
if (!state.stats.laeuft) return;
|
||||
schedulePoll.timer = setTimeout(() => {
|
||||
if (state.route === "karten") loadCards();
|
||||
else if (state.route === "karte") loadCard(state.cardId);
|
||||
else refreshStats();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function renderCards() {
|
||||
const list = $("cards-list");
|
||||
if (!state.cards.length) {
|
||||
@@ -230,11 +249,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const selectable = state.cards.map((c) => c.id).join(",");
|
||||
list.innerHTML = `
|
||||
<div class="strip" style="border-top:3px solid #000">
|
||||
<div class="strip">
|
||||
<span class="label">${state.cards.length} Karte${state.cards.length === 1 ? "" : "n"}</span>
|
||||
<a class="badge" href="/vcf?ids=${esc(selectable)}" download>Alle als vCard</a>
|
||||
${state.stats.laeuft ? `<span class="pill">${state.stats.laeuft} werden gelesen</span>` : ""}
|
||||
</div>
|
||||
<div class="rows">
|
||||
${state.cards
|
||||
@@ -261,6 +279,10 @@
|
||||
try {
|
||||
const card = await api(`/api/cards/${cardId}`);
|
||||
renderCard(card);
|
||||
if (card.extract_status === "offen") {
|
||||
state.stats = { ...state.stats, laeuft: 1 };
|
||||
schedulePoll();
|
||||
}
|
||||
} catch (error) {
|
||||
screens.karte.innerHTML = `<div class="empty"><p class="label">${esc(error.message)}</p></div>`;
|
||||
}
|
||||
@@ -341,7 +363,10 @@
|
||||
In Kontakte speichern <span class="arrow">→</span>
|
||||
</a>
|
||||
|
||||
${card.extract_status !== "ok"
|
||||
${card.extract_status === "offen"
|
||||
? `<p class="hint">Die Karte wird gerade ausgelesen – die Felder erscheinen gleich.</p>`
|
||||
: ""}
|
||||
${card.extract_status === "fehler"
|
||||
? `<p class="hint">Lesefehler: ${esc(card.extract_error || "unbekannt")}</p>`
|
||||
: ""}
|
||||
|
||||
@@ -460,6 +485,7 @@
|
||||
const data = await api("/api/cards?filter=alle");
|
||||
state.stats = data.stats;
|
||||
updateCounters();
|
||||
schedulePoll();
|
||||
} catch (_) { /* Zähler sind nicht kritisch */ }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user