clip-search: accept library_ids (multi-select whitelist) on /photos/search #97
@@ -48,10 +48,17 @@ pub struct SearchQuery {
|
|||||||
/// CLIP; tunable per call when sweeping. Defaults to 0.20.
|
/// CLIP; tunable per call when sweeping. Defaults to 0.20.
|
||||||
#[serde(default = "default_threshold")]
|
#[serde(default = "default_threshold")]
|
||||||
pub threshold: f32,
|
pub threshold: f32,
|
||||||
/// Optional single-library scope. When omitted, every enabled
|
/// Optional single-library scope. Legacy param — new clients pass
|
||||||
/// library is searched. Multi-select isn't supported yet — the
|
/// `library_ids` instead so multi-select scopes (Apollo's HUD library
|
||||||
/// frontend wires through one or all.
|
/// chips, FileViewer-React's library picker) actually filter. Kept
|
||||||
|
/// for back-compat; `library_ids` wins when both are supplied.
|
||||||
pub library: Option<i32>,
|
pub library: Option<i32>,
|
||||||
|
/// 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<String>,
|
||||||
/// Optional model-version filter. Defaults to the live engine's
|
/// Optional model-version filter. Defaults to the live engine's
|
||||||
/// version (queried lazily). Forces a strict join so mid-flight
|
/// version (queried lazily). Forces a strict join so mid-flight
|
||||||
/// model swaps can't mix geometries in a single response.
|
/// 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.
|
// 2. Decide which library scope to search. `library_ids` (multi)
|
||||||
let library_ids: Vec<i32> = match query.library {
|
// wins over the legacy `library` (single) when both are present;
|
||||||
Some(id) => vec![id],
|
// either / both empty falls back to "every enabled library".
|
||||||
None => Vec::new(), // empty = all libraries
|
let library_ids: Vec<i32> = if let Some(raw) = query.library_ids.as_deref() {
|
||||||
|
let mut out: Vec<i32> = Vec::new();
|
||||||
|
for piece in raw.split(',') {
|
||||||
|
let trimmed = piece.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match trimmed.parse::<i32>() {
|
||||||
|
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
|
// 3. Pull the (hash, embedding) matrix. Lock contention here is
|
||||||
|
|||||||
Reference in New Issue
Block a user