use anyhow::Result;
use async_trait::async_trait;
use chrono::Utc;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use vectoria_core::{
embedding::EmbeddingProvider,
model::{Product, ProductStatus},
search::SearchEngine,
SearchEngineBuilder,
};
pub struct StubEmbedding {
pub dims: usize,
pub calls: Arc<AtomicUsize>,
}
impl StubEmbedding {
pub fn new(dims: usize) -> Self {
Self { dims, calls: Arc::new(AtomicUsize::new(0)) }
}
pub fn call_count(&self) -> usize {
self.calls.load(Ordering::SeqCst)
}
fn hash_vec(text: &str, dims: usize) -> Vec<f32> {
let bytes = text.as_bytes();
(0..dims)
.map(|i| {
let b1 = bytes.get(i % bytes.len().max(1)).copied().unwrap_or(0) as f32;
let b2 = bytes.get((i * 7) % bytes.len().max(1)).copied().unwrap_or(0) as f32;
(b1 * 3.14159 + b2 * 2.71828 + i as f32).sin()
})
.collect()
}
}
#[async_trait]
impl EmbeddingProvider for StubEmbedding {
async fn embed(&self, text: &str) -> Result<Vec<f32>> {
self.calls.fetch_add(1, Ordering::SeqCst);
Ok(Self::hash_vec(text, self.dims))
}
async fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
self.calls.fetch_add(texts.len(), Ordering::SeqCst);
Ok(texts.iter().map(|t| Self::hash_vec(t, self.dims)).collect())
}
fn model_id(&self) -> &str { "stub" }
fn dims(&self) -> usize { self.dims }
}
pub async fn make_engine(dims: usize) -> (SearchEngine, Arc<StubEmbedding>) {
let embed = Arc::new(StubEmbedding::new(dims));
let embed_dyn: Arc<dyn EmbeddingProvider> = Arc::clone(&embed) as Arc<dyn EmbeddingProvider>;
let engine = SearchEngineBuilder::new()
.embedding(embed_dyn)
.build()
.await
.unwrap();
(engine, embed)
}
pub async fn make_engine_with_cache(dims: usize) -> (SearchEngine, Arc<StubEmbedding>) {
let embed = Arc::new(StubEmbedding::new(dims));
let embed_dyn: Arc<dyn EmbeddingProvider> = Arc::clone(&embed) as Arc<dyn EmbeddingProvider>;
let engine = SearchEngineBuilder::new()
.embedding(embed_dyn)
.query_cache(60, 1000)
.build()
.await
.unwrap();
(engine, embed)
}
pub fn make_product(id: &str, title: &str) -> Product {
let now = Utc::now();
Product {
id: id.to_string(),
text: Some(title.to_string()),
vector: None,
metadata: serde_json::json!({"title": title, "in_stock": true, "price": 9.99}),
model_id: None,
dims: None,
status: ProductStatus::PendingVector,
created_at: now,
updated_at: now,
}
}