From 04ade3d5d67349c81e8ec3b4e7f13fd024ca51e2 Mon Sep 17 00:00:00 2001 From: LyAhn Date: Sun, 7 Jun 2026 10:20:28 +0100 Subject: [PATCH] fix: address 3 P2 review issues from PR #8 round 5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit captioner: set_caption_detail now marks CAPTION_SESSION_DIRTY so the cached FlorenceCaptioner session is torn down and rebuilt with the new prompt and token limit; previously only the file was updated on disk db: reset_generated_captions now cancels in-flight caption jobs instead of deleting them; the worker checks for a cancelled row before writing back, so deleting the row allowed results to be written immediately after a reset — now mirrors the clear_caption_jobs cancellation protocol BackgroundTasks: caption_pending/ready/failed counts are now included in pendingMediaWork, task stages, hasFailed checks, and the dismiss snapshot; previously a folder with only caption work caused the panel to disappear entirely and exposed no caption progress or failure status --- src-tauri/src/captioner.rs | 1 + src-tauri/src/db.rs | 24 +++++++++++++++++-- src/components/BackgroundTasks.tsx | 38 ++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src-tauri/src/captioner.rs b/src-tauri/src/captioner.rs index 27bb5e5..aea7833 100644 --- a/src-tauri/src/captioner.rs +++ b/src-tauri/src/captioner.rs @@ -238,6 +238,7 @@ pub fn set_caption_detail(app_data_dir: &Path, detail: CaptionDetail) -> Result< std::fs::create_dir_all(parent)?; } std::fs::write(path, detail.as_str())?; + CAPTION_SESSION_DIRTY.store(true, Ordering::Relaxed); Ok(detail) } diff --git a/src-tauri/src/db.rs b/src-tauri/src/db.rs index e36c3bb..980f871 100644 --- a/src-tauri/src/db.rs +++ b/src-tauri/src/db.rs @@ -693,9 +693,19 @@ pub fn reset_generated_captions(conn: &Connection, folder_id: Option) -> Re WHERE folder_id = ?1", [folder_id], )?; + // Mark in-flight jobs cancelled so the worker skips writing back; + // delete all others. + tx.execute( + "UPDATE caption_jobs + SET status = 'cancelled', last_error = NULL, updated_at = datetime('now') + WHERE status = 'processing' + AND image_id IN (SELECT id FROM images WHERE folder_id = ?1)", + [folder_id], + )?; tx.execute( "DELETE FROM caption_jobs - WHERE image_id IN (SELECT id FROM images WHERE folder_id = ?1)", + WHERE status NOT IN ('processing', 'cancelled') + AND image_id IN (SELECT id FROM images WHERE folder_id = ?1)", [folder_id], )?; } @@ -708,7 +718,17 @@ pub fn reset_generated_captions(conn: &Connection, folder_id: Option) -> Re caption_error = NULL", [], )?; - tx.execute("DELETE FROM caption_jobs", [])?; + // Same cancellation protocol as clear_caption_jobs. + tx.execute( + "UPDATE caption_jobs + SET status = 'cancelled', last_error = NULL, updated_at = datetime('now') + WHERE status = 'processing'", + [], + )?; + tx.execute( + "DELETE FROM caption_jobs WHERE status NOT IN ('processing', 'cancelled')", + [], + )?; } } diff --git a/src/components/BackgroundTasks.tsx b/src/components/BackgroundTasks.tsx index 46d1861..d34ceb2 100644 --- a/src/components/BackgroundTasks.tsx +++ b/src/components/BackgroundTasks.tsx @@ -24,6 +24,7 @@ interface Task { stages: TaskStage[]; hasFailedEmbeddings: boolean; hasFailedTagging: boolean; + hasFailedCaptions: boolean; pendingMediaWork: number; embeddingProcessed: number; embeddingTotal: number; @@ -148,16 +149,22 @@ export function BackgroundTasks() { const taggingPending = jobs?.tagging_pending ?? 0; const taggingReady = jobs?.tagging_ready ?? 0; const taggingFailed = jobs?.tagging_failed ?? 0; + const captionPending = jobs?.caption_pending ?? 0; + const captionReady = jobs?.caption_ready ?? 0; + const captionFailed = jobs?.caption_failed ?? 0; - const pendingMediaWork = thumbnailPending + metadataPending + embeddingPending + taggingPending; + const pendingMediaWork = thumbnailPending + metadataPending + embeddingPending + taggingPending + captionPending; const embeddingProcessed = embeddingReady + embeddingFailed; const embeddingTotal = embeddingProcessed + embeddingPending; const taggingProcessed = taggingReady + taggingFailed; const taggingTotal = taggingProcessed + taggingPending; + const captionProcessed = captionReady + captionFailed; + const captionTotal = captionProcessed + captionPending; const hasFailedEmbeddings = embeddingFailed > 0; const hasFailedTagging = taggingFailed > 0; + const hasFailedCaptions = captionFailed > 0; - if (!index && pendingMediaWork === 0 && !hasFailedEmbeddings && !hasFailedTagging) return null; + if (!index && pendingMediaWork === 0 && !hasFailedEmbeddings && !hasFailedTagging && !hasFailedCaptions) return null; const stages: TaskStage[] = []; @@ -209,6 +216,16 @@ export function BackgroundTasks() { }); } + if (captionPending > 0) { + const pct = captionTotal > 0 ? (captionProcessed / captionTotal) * 100 : 0; + stages.push({ + label: "Captions", + detail: `${captionProcessed.toLocaleString()} / ${captionTotal.toLocaleString()}`, + progress: pct, + failed: false, + }); + } + if (hasFailedEmbeddings && pendingMediaWork === 0) { stages.push({ label: "Failed", @@ -227,7 +244,16 @@ export function BackgroundTasks() { }); } - const snapshot = `${pendingMediaWork}:${embeddingFailed}:${taggingFailed}`; + if (hasFailedCaptions && pendingMediaWork === 0) { + stages.push({ + label: "Failed", + detail: `${captionFailed.toLocaleString()} captions`, + progress: null, + failed: true, + }); + } + + const snapshot = `${pendingMediaWork}:${embeddingFailed}:${taggingFailed}:${captionFailed}`; return { id: folder.id, @@ -235,6 +261,7 @@ export function BackgroundTasks() { stages, hasFailedEmbeddings, hasFailedTagging, + hasFailedCaptions, pendingMediaWork, embeddingProcessed, embeddingTotal, @@ -262,6 +289,7 @@ export function BackgroundTasks() { }], hasFailedEmbeddings: false, hasFailedTagging: false, + hasFailedCaptions: false, pendingMediaWork: 1, embeddingProcessed: 0, embeddingTotal: 0, @@ -275,7 +303,7 @@ export function BackgroundTasks() { const primary = allTasks[0]; const extraCount = allTasks.length - 1; - const hasFailed = tasks.some((t) => (t.hasFailedEmbeddings || t.hasFailedTagging) && t.pendingMediaWork === 0); + const hasFailed = tasks.some((t) => (t.hasFailedEmbeddings || t.hasFailedTagging || t.hasFailedCaptions) && t.pendingMediaWork === 0); // Best progress bar value: use embedding progress if available (most informative), // otherwise tagging progress, otherwise fall back to scanning progress, otherwise indeterminate. @@ -407,7 +435,7 @@ export function BackgroundTasks() { const taskTaggingStage = task.stages.find((s) => s.label === "Tags"); const taskScanningStage = task.stages.find((s) => s.label === "Scanning"); const taskBarProgress = taskEmbeddingStage?.progress ?? taskTaggingStage?.progress ?? taskScanningStage?.progress ?? null; - const taskHasFailed = (task.hasFailedEmbeddings || task.hasFailedTagging) && task.pendingMediaWork === 0; + const taskHasFailed = (task.hasFailedEmbeddings || task.hasFailedTagging || task.hasFailedCaptions) && task.pendingMediaWork === 0; return (