diff --git a/src/clip_search.rs b/src/clip_search.rs index 5e8d5b9..d91e490 100644 --- a/src/clip_search.rs +++ b/src/clip_search.rs @@ -48,10 +48,17 @@ pub struct SearchQuery { /// CLIP; tunable per call when sweeping. Defaults to 0.20. #[serde(default = "default_threshold")] pub threshold: f32, - /// Optional single-library scope. When omitted, every enabled - /// library is searched. Multi-select isn't supported yet — the - /// frontend wires through one or all. + /// Optional single-library scope. Legacy param — new clients pass + /// `library_ids` instead so multi-select scopes (Apollo's HUD library + /// chips, FileViewer-React's library picker) actually filter. Kept + /// for back-compat; `library_ids` wins when both are supplied. pub library: Option, + /// Optional multi-library scope, comma-separated id list + /// (`?library_ids=1,3`). Empty / omitted = every enabled library + /// (the historical default). Apollo and FileViewer-React both send + /// this when 2+ libraries are selected; the single-library case + /// works through either param interchangeably. + pub library_ids: Option, /// Optional model-version filter. Defaults to the live engine's /// version (queried lazily). Forces a strict join so mid-flight /// model swaps can't mix geometries in a single response. @@ -172,10 +179,34 @@ pub async fn search_photos( } }; - // 2. Decide which library scope to search. - let library_ids: Vec = match query.library { - Some(id) => vec![id], - None => Vec::new(), // empty = all libraries + // 2. Decide which library scope to search. `library_ids` (multi) + // wins over the legacy `library` (single) when both are present; + // either / both empty falls back to "every enabled library". + let library_ids: Vec = if let Some(raw) = query.library_ids.as_deref() { + let mut out: Vec = Vec::new(); + for piece in raw.split(',') { + let trimmed = piece.trim(); + if trimmed.is_empty() { + continue; + } + match trimmed.parse::() { + Ok(id) => { + if !out.contains(&id) { + out.push(id); + } + } + Err(_) => { + return Ok(HttpResponse::BadRequest().json(SearchError { + error: format!("invalid library_ids entry: {trimmed:?}"), + })); + } + } + } + out + } else if let Some(id) = query.library { + vec![id] + } else { + Vec::new() }; // 3. Pull the (hash, embedding) matrix. Lock contention here is