clip-search: fmt + clippy clamp + test AppState arg
Pulls cargo fmt + clippy pass over the new files only — pre-existing files left untouched even though fmt has drift on them. clamp(1,200) swaps a manual min/max chain that clippy flagged. test AppState constructor needed ClipClient::new(None) so the lib-test target compiles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -181,10 +181,7 @@ impl ClipClient {
|
|||||||
|
|
||||||
/// Encode a natural-language query to an embedding. Used by the
|
/// Encode a natural-language query to an embedding. Used by the
|
||||||
/// search route to rank stored image embeddings by cosine sim.
|
/// search route to rank stored image embeddings by cosine sim.
|
||||||
pub async fn encode_text(
|
pub async fn encode_text(&self, text: &str) -> std::result::Result<EncodeResponse, ClipError> {
|
||||||
&self,
|
|
||||||
text: &str,
|
|
||||||
) -> std::result::Result<EncodeResponse, ClipError> {
|
|
||||||
let Some(base) = self.base_url.as_deref() else {
|
let Some(base) = self.base_url.as_deref() else {
|
||||||
return Err(ClipError::Disabled);
|
return Err(ClipError::Disabled);
|
||||||
};
|
};
|
||||||
@@ -206,9 +203,10 @@ impl ClipClient {
|
|||||||
};
|
};
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
if status.is_success() {
|
if status.is_success() {
|
||||||
let body: EncodeResponse = resp.json().await.map_err(|e| {
|
let body: EncodeResponse = resp
|
||||||
ClipError::Transient(anyhow::anyhow!("clip response decode: {e}"))
|
.json()
|
||||||
})?;
|
.await
|
||||||
|
.map_err(|e| ClipError::Transient(anyhow::anyhow!("clip response decode: {e}")))?;
|
||||||
return Ok(body);
|
return Ok(body);
|
||||||
}
|
}
|
||||||
let body_text = resp.text().await.unwrap_or_default();
|
let body_text = resp.text().await.unwrap_or_default();
|
||||||
@@ -246,9 +244,10 @@ impl ClipClient {
|
|||||||
};
|
};
|
||||||
let status = resp.status();
|
let status = resp.status();
|
||||||
if status.is_success() {
|
if status.is_success() {
|
||||||
let body: EncodeResponse = resp.json().await.map_err(|e| {
|
let body: EncodeResponse = resp
|
||||||
ClipError::Transient(anyhow::anyhow!("clip response decode: {e}"))
|
.json()
|
||||||
})?;
|
.await
|
||||||
|
.map_err(|e| ClipError::Transient(anyhow::anyhow!("clip response decode: {e}")))?;
|
||||||
return Ok(body);
|
return Ok(body);
|
||||||
}
|
}
|
||||||
let body_text = resp.text().await.unwrap_or_default();
|
let body_text = resp.text().await.unwrap_or_default();
|
||||||
|
|||||||
@@ -273,10 +273,12 @@ pub fn process_clip_backlog(
|
|||||||
|
|
||||||
let candidates: Vec<crate::clip_watch::ClipCandidate> = rows
|
let candidates: Vec<crate::clip_watch::ClipCandidate> = rows
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(rel_path, content_hash)| crate::clip_watch::ClipCandidate {
|
.map(
|
||||||
rel_path,
|
|(rel_path, content_hash)| crate::clip_watch::ClipCandidate {
|
||||||
content_hash,
|
rel_path,
|
||||||
})
|
content_hash,
|
||||||
|
},
|
||||||
|
)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
crate::clip_watch::run_clip_encoding_pass(
|
crate::clip_watch::run_clip_encoding_pass(
|
||||||
|
|||||||
@@ -122,7 +122,10 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|l| l.id == args.library)
|
.find(|l| l.id == args.library)
|
||||||
.ok_or_else(|| anyhow::anyhow!("library id {} not found", args.library))?;
|
.ok_or_else(|| anyhow::anyhow!("library id {} not found", args.library))?;
|
||||||
info!("probing library #{} ({}) at {}", lib.id, lib.name, lib.root_path);
|
info!(
|
||||||
|
"probing library #{} ({}) at {}",
|
||||||
|
lib.id, lib.name, lib.root_path
|
||||||
|
);
|
||||||
|
|
||||||
let dao: Arc<Mutex<Box<dyn ExifDao>>> = Arc::new(Mutex::new(Box::new(SqliteExifDao::new())));
|
let dao: Arc<Mutex<Box<dyn ExifDao>>> = Arc::new(Mutex::new(Box::new(SqliteExifDao::new())));
|
||||||
let ctx = opentelemetry::Context::new();
|
let ctx = opentelemetry::Context::new();
|
||||||
@@ -136,9 +139,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let query_vec = decode_f32_vec(&query_resp.embedding)?;
|
let query_vec = decode_f32_vec(&query_resp.embedding)?;
|
||||||
info!(
|
info!(
|
||||||
"query encoded ({}d, {}ms): {:?}",
|
"query encoded ({}d, {}ms): {:?}",
|
||||||
query_resp.embedding_dim,
|
query_resp.embedding_dim, query_resp.duration_ms, args.query
|
||||||
query_resp.duration_ms,
|
|
||||||
args.query
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Page through (id, rel_path), filter to images on disk, encode up
|
// Page through (id, rel_path), filter to images on disk, encode up
|
||||||
@@ -245,7 +246,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
scores.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
|
scores.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
let elapsed = started.elapsed();
|
let elapsed = started.elapsed();
|
||||||
println!();
|
println!();
|
||||||
println!("── top {} for query: {:?} ──", args.top.min(scores.len()), args.query);
|
println!(
|
||||||
|
"── top {} for query: {:?} ──",
|
||||||
|
args.top.min(scores.len()),
|
||||||
|
args.query
|
||||||
|
);
|
||||||
for (i, (sim, path)) in scores.iter().take(args.top).enumerate() {
|
for (i, (sim, path)) in scores.iter().take(args.top).enumerate() {
|
||||||
println!("[{:>2}] sim={:.3} {}", i + 1, sim, path);
|
println!("[{:>2}] sim={:.3} {}", i + 1, sim, path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ pub async fn search_photos(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let limit = query.limit.min(200).max(1);
|
let limit = query.limit.clamp(1, 200);
|
||||||
let threshold = query.threshold.clamp(-1.0, 1.0);
|
let threshold = query.threshold.clamp(-1.0, 1.0);
|
||||||
|
|
||||||
// 1. Encode the query text. Fast — Apollo's text encoder is ~50ms
|
// 1. Encode the query text. Fast — Apollo's text encoder is ~50ms
|
||||||
@@ -174,7 +174,10 @@ pub async fn search_photos(
|
|||||||
match dao.list_clip_index(
|
match dao.list_clip_index(
|
||||||
&ctx,
|
&ctx,
|
||||||
&library_ids,
|
&library_ids,
|
||||||
query.model_version.as_deref().or(Some(&query_resp.model_version)),
|
query
|
||||||
|
.model_version
|
||||||
|
.as_deref()
|
||||||
|
.or(Some(&query_resp.model_version)),
|
||||||
) {
|
) {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -257,7 +260,8 @@ pub async fn search_photos(
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"clip_search: find_by_content_hash failed for {}: {:?}",
|
"clip_search: find_by_content_hash failed for {}: {:?}",
|
||||||
hash, e
|
hash,
|
||||||
|
e
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,10 +142,7 @@ async fn process_one(
|
|||||||
let emb_bytes = match resp.decode_embedding() {
|
let emb_bytes = match resp.decode_embedding() {
|
||||||
Ok(b) => b,
|
Ok(b) => b,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(
|
warn!("clip_watch: bad embedding for {}: {:?}", cand.rel_path, e);
|
||||||
"clip_watch: bad embedding for {}: {:?}",
|
|
||||||
cand.rel_path, e
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -450,6 +450,7 @@ impl AppState {
|
|||||||
insight_chat,
|
insight_chat,
|
||||||
preview_dao,
|
preview_dao,
|
||||||
FaceClient::new(None), // disabled in test
|
FaceClient::new(None), // disabled in test
|
||||||
|
ClipClient::new(None), // disabled in test
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user