fix: address 3 P2 review issues from PR #8 round 5

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
This commit is contained in:
2026-06-07 10:20:28 +01:00
parent 183cdff6b1
commit 04ade3d5d6
3 changed files with 56 additions and 7 deletions
+1
View File
@@ -238,6 +238,7 @@ pub fn set_caption_detail(app_data_dir: &Path, detail: CaptionDetail) -> Result<
std::fs::create_dir_all(parent)?; std::fs::create_dir_all(parent)?;
} }
std::fs::write(path, detail.as_str())?; std::fs::write(path, detail.as_str())?;
CAPTION_SESSION_DIRTY.store(true, Ordering::Relaxed);
Ok(detail) Ok(detail)
} }
+22 -2
View File
@@ -693,9 +693,19 @@ pub fn reset_generated_captions(conn: &Connection, folder_id: Option<i64>) -> Re
WHERE folder_id = ?1", WHERE folder_id = ?1",
[folder_id], [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( tx.execute(
"DELETE FROM caption_jobs "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], [folder_id],
)?; )?;
} }
@@ -708,7 +718,17 @@ pub fn reset_generated_captions(conn: &Connection, folder_id: Option<i64>) -> Re
caption_error = NULL", 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')",
[],
)?;
} }
} }
+33 -5
View File
@@ -24,6 +24,7 @@ interface Task {
stages: TaskStage[]; stages: TaskStage[];
hasFailedEmbeddings: boolean; hasFailedEmbeddings: boolean;
hasFailedTagging: boolean; hasFailedTagging: boolean;
hasFailedCaptions: boolean;
pendingMediaWork: number; pendingMediaWork: number;
embeddingProcessed: number; embeddingProcessed: number;
embeddingTotal: number; embeddingTotal: number;
@@ -148,16 +149,22 @@ export function BackgroundTasks() {
const taggingPending = jobs?.tagging_pending ?? 0; const taggingPending = jobs?.tagging_pending ?? 0;
const taggingReady = jobs?.tagging_ready ?? 0; const taggingReady = jobs?.tagging_ready ?? 0;
const taggingFailed = jobs?.tagging_failed ?? 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 embeddingProcessed = embeddingReady + embeddingFailed;
const embeddingTotal = embeddingProcessed + embeddingPending; const embeddingTotal = embeddingProcessed + embeddingPending;
const taggingProcessed = taggingReady + taggingFailed; const taggingProcessed = taggingReady + taggingFailed;
const taggingTotal = taggingProcessed + taggingPending; const taggingTotal = taggingProcessed + taggingPending;
const captionProcessed = captionReady + captionFailed;
const captionTotal = captionProcessed + captionPending;
const hasFailedEmbeddings = embeddingFailed > 0; const hasFailedEmbeddings = embeddingFailed > 0;
const hasFailedTagging = taggingFailed > 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[] = []; 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) { if (hasFailedEmbeddings && pendingMediaWork === 0) {
stages.push({ stages.push({
label: "Failed", 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 { return {
id: folder.id, id: folder.id,
@@ -235,6 +261,7 @@ export function BackgroundTasks() {
stages, stages,
hasFailedEmbeddings, hasFailedEmbeddings,
hasFailedTagging, hasFailedTagging,
hasFailedCaptions,
pendingMediaWork, pendingMediaWork,
embeddingProcessed, embeddingProcessed,
embeddingTotal, embeddingTotal,
@@ -262,6 +289,7 @@ export function BackgroundTasks() {
}], }],
hasFailedEmbeddings: false, hasFailedEmbeddings: false,
hasFailedTagging: false, hasFailedTagging: false,
hasFailedCaptions: false,
pendingMediaWork: 1, pendingMediaWork: 1,
embeddingProcessed: 0, embeddingProcessed: 0,
embeddingTotal: 0, embeddingTotal: 0,
@@ -275,7 +303,7 @@ export function BackgroundTasks() {
const primary = allTasks[0]; const primary = allTasks[0];
const extraCount = allTasks.length - 1; 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), // Best progress bar value: use embedding progress if available (most informative),
// otherwise tagging progress, otherwise fall back to scanning progress, otherwise indeterminate. // 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 taskTaggingStage = task.stages.find((s) => s.label === "Tags");
const taskScanningStage = task.stages.find((s) => s.label === "Scanning"); const taskScanningStage = task.stages.find((s) => s.label === "Scanning");
const taskBarProgress = taskEmbeddingStage?.progress ?? taskTaggingStage?.progress ?? taskScanningStage?.progress ?? null; 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 ( return (
<div key={task.id}> <div key={task.id}>