fix: add failed tag locate and filter controls

Add a failed-tag discovery flow for background worker failures.

Changes:
- Add a Failed Tags toolbar filter that appears when tag failures exist.
- Add Locate buttons for failed tag tasks in the background worker prompt.
- Route Locate to the affected folder and filter the gallery to images with tagger errors.
- Fetch and display failed tag filenames/errors in the expanded worker details.
- Add a backend query and gallery filter flag for images with failed AI tagging.
- Improve subtle-light contrast for failed worker chips, filenames, and Locate/Retry buttons.
- Also slightly increases the title bar update indicator pulse size for better visibility.
This commit is contained in:
2026-06-18 00:36:02 +01:00
parent c97fec2eb3
commit ca58c2ddd4
7 changed files with 238 additions and 68 deletions
+39 -4
View File
@@ -324,7 +324,10 @@ pub fn migrate(conn: &Connection) -> Result<()> {
conn.execute_batch("CREATE INDEX IF NOT EXISTS idx_images_taken_at ON images(taken_at);")?;
ensure_column(conn, "folders", "scan_error", "TEXT")?;
ensure_column(conn, "folders", "sort_order", "INTEGER NOT NULL DEFAULT 0")?;
conn.execute("UPDATE folders SET sort_order = id WHERE sort_order = 0", [])?;
conn.execute(
"UPDATE folders SET sort_order = id WHERE sort_order = 0",
[],
)?;
vector::migrate(conn)?;
Ok(())
@@ -1629,6 +1632,7 @@ pub fn get_images(
favorites_only: bool,
rating_min: i64,
embedding_failed_only: bool,
tagging_failed_only: bool,
sort: &str,
offset: i64,
limit: i64,
@@ -1652,6 +1656,7 @@ pub fn get_images(
let search_pattern = search.map(|value| format!("%{value}%"));
let favorites_flag = i64::from(favorites_only);
let embedding_failed_flag = i64::from(embedding_failed_only);
let tagging_failed_flag = i64::from(tagging_failed_only);
let sql = format!(
"SELECT id, folder_id, path, filename, thumbnail_path, width, height, file_size, created_at, modified_at, taken_at, mime_type,
media_kind, duration_ms, video_codec, audio_codec, metadata_updated_at, metadata_error,
@@ -1665,8 +1670,9 @@ pub fn get_images(
AND (?4 = 0 OR favorite = 1)
AND rating >= ?5
AND (?6 = 0 OR embedding_status = 'failed')
AND (?7 = 0 OR ai_tagger_error IS NOT NULL)
ORDER BY {order}
LIMIT ?7 OFFSET ?8"
LIMIT ?8 OFFSET ?9"
);
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map(
@@ -1677,6 +1683,7 @@ pub fn get_images(
favorites_flag,
rating_min,
embedding_failed_flag,
tagging_failed_flag,
limit,
offset
],
@@ -1693,11 +1700,13 @@ pub fn count_images(
favorites_only: bool,
rating_min: i64,
embedding_failed_only: bool,
tagging_failed_only: bool,
) -> Result<i64> {
let search_pattern = search.map(|value| format!("%{value}%"));
let favorites_flag = i64::from(favorites_only);
let embedding_failed_flag = i64::from(embedding_failed_only);
let tagging_failed_flag = i64::from(tagging_failed_only);
let count = conn.query_row(
"SELECT COUNT(*) FROM images
WHERE (?1 IS NULL OR folder_id = ?1)
@@ -1705,14 +1714,16 @@ pub fn count_images(
AND (?3 IS NULL OR media_kind = ?3)
AND (?4 = 0 OR favorite = 1)
AND rating >= ?5
AND (?6 = 0 OR embedding_status = 'failed')",
AND (?6 = 0 OR embedding_status = 'failed')
AND (?7 = 0 OR ai_tagger_error IS NOT NULL)",
params![
folder_id,
search_pattern,
media_kind,
favorites_flag,
rating_min,
embedding_failed_flag
embedding_failed_flag,
tagging_failed_flag
],
|row| row.get(0),
)?;
@@ -1947,6 +1958,30 @@ pub fn get_failed_embedding_images(
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
}
type FailedTaggingRow = (i64, String, String, Option<String>);
pub fn get_failed_tagging_images(
conn: &Connection,
folder_id: i64,
) -> Result<Vec<FailedTaggingRow>> {
let mut stmt = conn.prepare(
"SELECT i.id, i.filename, i.path, COALESCE(j.last_error, i.ai_tagger_error)
FROM images i
LEFT JOIN tagging_jobs j ON j.image_id = i.id AND j.status = 'failed'
WHERE i.folder_id = ?1 AND i.ai_tagger_error IS NOT NULL
ORDER BY i.filename",
)?;
let rows = stmt.query_map([folder_id], |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, Option<String>>(3)?,
))
})?;
Ok(rows.collect::<rusqlite::Result<Vec<_>>>()?)
}
pub fn update_generated_caption(
conn: &Connection,
image_id: i64,