feat: Retry-Funktion + idempotente Pipeline (Transkript bleibt erhalten)

Backend:
- POST /api/jobs/{id}/retry — löscht summary/protocol/docx, queued den Job
  neu und startet Pipeline. Transkript bleibt erhalten (teurer Schritt).
- Pipeline überspringt Steps, deren Output-Datei schon existiert →
  retry rennt direkt in summarize ohne erneute Transkription.

Frontend:
- Kleiner gelber '↻ Erneut versuchen'-Button in jeder Job-Zeile/-Karte
  wenn Status != done
- Confirm-Dialog erklärt was passiert
- Event-Delegation, funktioniert für Tabelle (Desktop) und Cards (Mobile)
This commit is contained in:
root
2026-05-14 04:11:13 +00:00
parent 24ac3ac058
commit 220b7ccbc3
4 changed files with 148 additions and 26 deletions
+27
View File
@@ -290,9 +290,13 @@ function row(job) {
? `<span class="tag-profile" title="Verarbeitungs-Profil">${escapeHtml(profileLabelOf(job.profile || "meeting"))}</span>`
: "";
const retryBtn = job.status !== "done"
? `<button type="button" class="btn-link btn-retry" data-id="${job.id}" title="Verarbeitung wiederholen (Transkript bleibt erhalten)">↻ Erneut versuchen</button>`
: "";
const statusCell = `
<span class="status-pill status-${job.status}">${escapeHtml(job.status)}</span>
${job.error ? `<div class="status-error" title="${escapeHtml(job.error)}">${escapeHtml(job.error)}</div>` : ""}
${retryBtn}
`;
tr.innerHTML = `
@@ -337,6 +341,7 @@ function card(job) {
<small class="muted">${job.progress}%</small>
</div>
${job.error ? `<div class="jc-error" title="${escapeHtml(job.error)}">${escapeHtml(job.error)}</div>` : ""}
${job.status !== "done" ? `<button type="button" class="btn-link btn-retry" data-id="${job.id}">↻ Erneut versuchen</button>` : ""}
<div class="jc-sub">aktualisiert: ${escapeHtml(fmtDate(job.updated_at))}</div>
<div class="jc-dl">${dlButtons}</div>
`;
@@ -666,6 +671,28 @@ adminToggle.addEventListener("click", () => {
}
});
// Retry-Button: funktioniert für Tabelle und Karten gleichzeitig (Event-Delegation)
document.addEventListener("click", async (ev) => {
const btn = ev.target.closest(".btn-retry");
if (!btn) return;
ev.preventDefault();
const id = btn.dataset.id;
if (!confirm("Verarbeitung erneut starten?\nDas Transkript bleibt erhalten — nur Zusammenfassung, Protokoll und DOCX werden neu erzeugt.")) return;
btn.disabled = true;
btn.textContent = "wird gestartet …";
try {
const r = await api(`/api/jobs/${id}/retry`, { method: "POST" });
if (!r.ok) {
const d = await r.json().catch(() => ({}));
alert(d.detail || "Konnte nicht neu starten.");
return;
}
loadJobs();
} catch (e) {
alert("Netzwerkfehler beim Neustart.");
}
});
// ─── Init ──────────────────────────────────────────────────────────────────
bootstrap();
checkMac();
+17
View File
@@ -530,6 +530,23 @@ select {
main { padding-bottom: max(20px, env(safe-area-inset-bottom)); }
}
/* Retry-Button in der Status-Spalte */
.btn-retry {
display: inline-block;
margin-top: 6px;
padding: 3px 8px;
font-size: 11px;
font-weight: 500;
background: rgba(241,196,15,.12);
color: var(--warn);
border: 1px solid rgba(241,196,15,.4);
border-radius: 5px;
text-decoration: none;
cursor: pointer;
}
.btn-retry:hover { background: rgba(241,196,15,.22); text-decoration: none; }
.btn-retry:disabled { opacity: .55; cursor: not-allowed; }
/* Profile-Tag in der Job-Zeile */
.tag-profile {
display: inline-block;