Polish search and embedding UX
- add semantic text search with toolbar mode switching and sqlite-vec query support - improve embedding progress visibility, failure recovery, and similar-image affordances - add search clearing and keyboard controls for filename vs semantic search modes - refine background task interactions and gallery/lightbox embedding states Refs: #4
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
use crate::db::{self, DbPool, Folder, FolderJobProgress, ImageRecord};
|
||||
use crate::embedder::ClipImageEmbedder;
|
||||
use crate::indexer;
|
||||
use crate::vector;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -44,6 +45,15 @@ pub struct RetryFailedEmbeddingsParams {
|
||||
pub folder_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct SemanticSearchParams {
|
||||
pub query: String,
|
||||
pub folder_id: Option<i64>,
|
||||
pub media_kind: Option<String>,
|
||||
pub favorites_only: Option<bool>,
|
||||
pub limit: Option<usize>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn add_folder(
|
||||
app: AppHandle,
|
||||
@@ -188,3 +198,52 @@ pub async fn retry_failed_embeddings(
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
db::retry_failed_embedding_jobs(&conn, params.folder_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn semantic_search_images(
|
||||
db: State<'_, DbState>,
|
||||
params: SemanticSearchParams,
|
||||
) -> Result<Vec<ImageRecord>, String> {
|
||||
let embedder = ClipImageEmbedder::new().map_err(|e| e.to_string())?;
|
||||
let embedding = embedder.embed_text(¶ms.query).map_err(|e| e.to_string())?;
|
||||
|
||||
let conn = db.get().map_err(|e| e.to_string())?;
|
||||
let limit = params.limit.unwrap_or(64);
|
||||
let ids = vector::search_image_ids_by_embedding(&conn, &embedding, limit).map_err(|e| e.to_string())?;
|
||||
let mut images = db::get_images_by_ids(&conn, &ids).map_err(|e| e.to_string())?;
|
||||
|
||||
if let Some(folder_id) = params.folder_id {
|
||||
images.retain(|image| image.folder_id == folder_id);
|
||||
}
|
||||
if let Some(media_kind) = params.media_kind.as_deref() {
|
||||
images.retain(|image| image.media_kind == media_kind);
|
||||
}
|
||||
if params.favorites_only.unwrap_or(false) {
|
||||
images.retain(|image| image.favorite);
|
||||
}
|
||||
|
||||
Ok(images)
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct WorkerStates {
|
||||
pub thumbnail_paused: bool,
|
||||
pub metadata_paused: bool,
|
||||
pub embedding_paused: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_worker_paused(worker: String, paused: bool) -> Result<(), String> {
|
||||
indexer::set_worker_paused(&worker, paused);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_worker_states() -> Result<WorkerStates, String> {
|
||||
let states = indexer::get_worker_paused_states();
|
||||
Ok(WorkerStates {
|
||||
thumbnail_paused: states[0],
|
||||
metadata_paused: states[1],
|
||||
embedding_paused: states[2],
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user