feat: expand media discovery and AI workflows #8
@@ -1153,9 +1153,9 @@ pub async fn invalidate_duplicate_scan_cache(
|
||||
pub async fn delete_images_from_disk(
|
||||
db: State<'_, DbState>,
|
||||
params: DeleteImagesFromDiskParams,
|
||||
) -> Result<usize, String> {
|
||||
) -> Result<Vec<i64>, String> {
|
||||
if params.image_ids.is_empty() {
|
||||
return Ok(0);
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
let records = db::get_all_image_paths(&conn, None).map_err(|e| e.to_string())?;
|
||||
@@ -1170,11 +1170,11 @@ pub async fn delete_images_from_disk(
|
||||
succeeded_ids.push(r.id);
|
||||
}
|
||||
}
|
||||
let deleted = succeeded_ids.len();
|
||||
if !succeeded_ids.is_empty() {
|
||||
db::delete_images_by_ids(&conn, &succeeded_ids).map_err(|e| e.to_string())?;
|
||||
}
|
||||
Ok(deleted)
|
||||
// Return the IDs that were actually removed so the caller can update state precisely.
|
||||
Ok(succeeded_ids)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -175,9 +175,13 @@ export function Lightbox() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedImage) return;
|
||||
// Capture the ID so a stale response for image A cannot overwrite B's tags
|
||||
// when the user navigates before the request resolves.
|
||||
let cancelled = false;
|
||||
void getImageTags(selectedImage.id)
|
||||
.then(setImageTags)
|
||||
.catch(() => setImageTags([]));
|
||||
.then((tags) => { if (!cancelled) setImageTags(tags); })
|
||||
.catch(() => { if (!cancelled) setImageTags([]); });
|
||||
return () => { cancelled = true; };
|
||||
}, [selectedImage?.id, selectedImage?.ai_tagged_at, getImageTags]);
|
||||
|
||||
// Reset the queued state once the worker finishes so the button is usable again
|
||||
|
||||
+20
-7
@@ -1513,20 +1513,33 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
||||
clearDuplicateSelection: () => set({ duplicateSelectedIds: new Set() }),
|
||||
|
||||
deleteSelectedDuplicates: async () => {
|
||||
const { duplicateSelectedIds, duplicateScanFolderId } = get();
|
||||
const { duplicateSelectedIds, duplicateGroups } = get();
|
||||
const ids = Array.from(duplicateSelectedIds);
|
||||
if (ids.length === 0) return 0;
|
||||
const deleted = await invoke<number>("delete_images_from_disk", { params: { image_ids: ids } });
|
||||
// Remove deleted images from groups and drop now-trivial groups
|
||||
// Backend returns only the IDs that were actually removed from disk.
|
||||
const succeededIds = await invoke<number[]>("delete_images_from_disk", { params: { image_ids: ids } });
|
||||
const succeededSet = new Set(succeededIds);
|
||||
// Only remove images confirmed deleted — failed files remain visible so the user can retry.
|
||||
set((state) => ({
|
||||
duplicateSelectedIds: new Set(),
|
||||
duplicateGroups: state.duplicateGroups
|
||||
.map((g) => ({ ...g, images: g.images.filter((img) => !duplicateSelectedIds.has(img.id)) }))
|
||||
.map((g) => ({ ...g, images: g.images.filter((img) => !succeededSet.has(img.id)) }))
|
||||
.filter((g) => g.images.length > 1),
|
||||
}));
|
||||
// Invalidate the persisted cache so a restart doesn't reload stale entries
|
||||
await invoke("invalidate_duplicate_scan_cache", { folderId: duplicateScanFolderId ?? null });
|
||||
return deleted;
|
||||
// Invalidate the persisted cache for every affected scope:
|
||||
// - global "all" cache (always, since a folder-scoped deletion still makes the global result stale)
|
||||
// - each folder that contained a deleted image (so a folder-scoped scan is also evicted)
|
||||
const affectedFolderIds = new Set<number>(
|
||||
duplicateGroups
|
||||
.flatMap((g) => g.images)
|
||||
.filter((img) => succeededSet.has(img.id))
|
||||
.map((img) => img.folder_id),
|
||||
);
|
||||
await invoke("invalidate_duplicate_scan_cache", { folderId: null }); // global
|
||||
for (const folderId of affectedFolderIds) {
|
||||
await invoke("invalidate_duplicate_scan_cache", { folderId });
|
||||
}
|
||||
return succeededIds.length;
|
||||
},
|
||||
|
||||
retryFailedEmbeddings: async (folderId) => {
|
||||
|
||||
Reference in New Issue
Block a user