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:
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user