feat: expand media discovery and AI workflows #8
+17
-7
@@ -349,7 +349,11 @@ pub fn upsert_image(conn: &Connection, img: &ImageRecord) -> Result<i64> {
|
|||||||
generated_caption = excluded.generated_caption,
|
generated_caption = excluded.generated_caption,
|
||||||
caption_model = excluded.caption_model,
|
caption_model = excluded.caption_model,
|
||||||
caption_updated_at = excluded.caption_updated_at,
|
caption_updated_at = excluded.caption_updated_at,
|
||||||
caption_error = excluded.caption_error
|
caption_error = excluded.caption_error,
|
||||||
|
ai_rating = NULL,
|
||||||
|
ai_tagger_model = NULL,
|
||||||
|
ai_tagged_at = NULL,
|
||||||
|
ai_tagger_error = NULL
|
||||||
RETURNING id",
|
RETURNING id",
|
||||||
params![
|
params![
|
||||||
img.folder_id,
|
img.folder_id,
|
||||||
@@ -385,6 +389,12 @@ pub fn upsert_image(conn: &Connection, img: &ImageRecord) -> Result<i64> {
|
|||||||
],
|
],
|
||||||
|row| row.get(0),
|
|row| row.get(0),
|
||||||
)?;
|
)?;
|
||||||
|
// Remove stale AI tags when content changes so they aren't shown for the
|
||||||
|
// new file. User-sourced tags are preserved.
|
||||||
|
conn.execute(
|
||||||
|
"DELETE FROM image_tags WHERE image_id = ?1 AND source = 'ai'",
|
||||||
|
[id],
|
||||||
|
)?;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1678,14 +1688,17 @@ fn get_pending_tagging_jobs_excluding(
|
|||||||
paused_folder_ids: &std::collections::HashSet<i64>,
|
paused_folder_ids: &std::collections::HashSet<i64>,
|
||||||
limit: usize,
|
limit: usize,
|
||||||
) -> Result<Vec<TaggingJob>> {
|
) -> Result<Vec<TaggingJob>> {
|
||||||
let mut stmt = conn.prepare(
|
let sql = format!(
|
||||||
"SELECT j.image_id, i.folder_id, i.path
|
"SELECT j.image_id, i.folder_id, i.path
|
||||||
FROM tagging_jobs j
|
FROM tagging_jobs j
|
||||||
JOIN images i ON i.id = j.image_id
|
JOIN images i ON i.id = j.image_id
|
||||||
WHERE j.status = 'pending'
|
WHERE j.status = 'pending'
|
||||||
|
{}
|
||||||
ORDER BY j.created_at ASC
|
ORDER BY j.created_at ASC
|
||||||
LIMIT ?1",
|
LIMIT ?1",
|
||||||
)?;
|
folder_exclusion_clause("i", paused_folder_ids)
|
||||||
|
);
|
||||||
|
let mut stmt = conn.prepare(&sql)?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map([limit as i64], |row| {
|
.query_map([limit as i64], |row| {
|
||||||
Ok(TaggingJob {
|
Ok(TaggingJob {
|
||||||
@@ -1695,10 +1708,7 @@ fn get_pending_tagging_jobs_excluding(
|
|||||||
})
|
})
|
||||||
})?
|
})?
|
||||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||||
Ok(rows
|
Ok(rows)
|
||||||
.into_iter()
|
|
||||||
.filter(|job| !paused_folder_ids.contains(&job.folder_id))
|
|
||||||
.collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_ai_tags(
|
pub fn update_ai_tags(
|
||||||
|
|||||||
+12
-10
@@ -365,8 +365,10 @@ const PAGE_SIZE = 200;
|
|||||||
const AI_CAPTIONS_ENABLED_KEY = "phokus.aiCaptionsEnabled";
|
const AI_CAPTIONS_ENABLED_KEY = "phokus.aiCaptionsEnabled";
|
||||||
const SIMILAR_DISTANCE_THRESHOLD = 0.24;
|
const SIMILAR_DISTANCE_THRESHOLD = 0.24;
|
||||||
|
|
||||||
|
// Single token shared by all gallery-producing requests (folder loads, searches,
|
||||||
|
// similarity, region search). Any new request increments it so a stale response
|
||||||
|
// from a previous collection type cannot overwrite newer results.
|
||||||
let galleryRequestToken = 0;
|
let galleryRequestToken = 0;
|
||||||
let similarRequestToken = 0;
|
|
||||||
let tagCloudRequestToken = 0;
|
let tagCloudRequestToken = 0;
|
||||||
let exploreTagRequestToken = 0;
|
let exploreTagRequestToken = 0;
|
||||||
|
|
||||||
@@ -902,7 +904,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
showVisualCluster: async (imageIds) => {
|
showVisualCluster: async (imageIds) => {
|
||||||
const requestToken = ++similarRequestToken;
|
const requestToken = ++galleryRequestToken;
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
activeView: "gallery",
|
activeView: "gallery",
|
||||||
search: "",
|
search: "",
|
||||||
@@ -923,7 +925,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
const images = await invoke<ImageRecord[]>("get_images_by_ids", {
|
const images = await invoke<ImageRecord[]>("get_images_by_ids", {
|
||||||
params: { image_ids: imageIds },
|
params: { image_ids: imageIds },
|
||||||
});
|
});
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
set({
|
set({
|
||||||
images,
|
images,
|
||||||
totalImages: images.length,
|
totalImages: images.length,
|
||||||
@@ -933,7 +935,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
collectionTitle: "Explore Cluster",
|
collectionTitle: "Explore Cluster",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
set({
|
set({
|
||||||
images: [],
|
images: [],
|
||||||
totalImages: 0,
|
totalImages: 0,
|
||||||
@@ -951,7 +953,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadSimilarImages: async (imageId, folderId = get().selectedFolderId, reset = true, sourceFolderId = folderId ?? null) => {
|
loadSimilarImages: async (imageId, folderId = get().selectedFolderId, reset = true, sourceFolderId = folderId ?? null) => {
|
||||||
const requestToken = ++similarRequestToken;
|
const requestToken = ++galleryRequestToken;
|
||||||
const offset = reset ? 0 : get().loadedCount;
|
const offset = reset ? 0 : get().loadedCount;
|
||||||
const similarScope = folderId === null ? "all_media" : "current_folder";
|
const similarScope = folderId === null ? "all_media" : "current_folder";
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
@@ -978,7 +980,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const nextImages = reset ? result.images : [...state.images, ...result.images];
|
const nextImages = reset ? result.images : [...state.images, ...result.images];
|
||||||
@@ -999,7 +1001,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
console.error("Failed to load similar images:", error);
|
console.error("Failed to load similar images:", error);
|
||||||
set({
|
set({
|
||||||
images: [],
|
images: [],
|
||||||
@@ -1019,7 +1021,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
loadSimilarByRegion: async (imageId, crop, folderId = get().selectedFolderId, sourceFolderId = folderId ?? null) => {
|
loadSimilarByRegion: async (imageId, crop, folderId = get().selectedFolderId, sourceFolderId = folderId ?? null) => {
|
||||||
const requestToken = ++similarRequestToken;
|
const requestToken = ++galleryRequestToken;
|
||||||
const similarScope = folderId === null ? "all_media" : "current_folder";
|
const similarScope = folderId === null ? "all_media" : "current_folder";
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
images: [],
|
images: [],
|
||||||
@@ -1050,7 +1052,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
|
|
||||||
set({
|
set({
|
||||||
images: result.images,
|
images: result.images,
|
||||||
@@ -1067,7 +1069,7 @@ export const useGalleryStore = create<GalleryState>((set, get) => ({
|
|||||||
similarScope,
|
similarScope,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (requestToken !== similarRequestToken) return;
|
if (requestToken !== galleryRequestToken) return;
|
||||||
console.error("Failed to load region search results:", error);
|
console.error("Failed to load region search results:", error);
|
||||||
set({
|
set({
|
||||||
images: [],
|
images: [],
|
||||||
|
|||||||
Reference in New Issue
Block a user