- Add database migrations (006-028) for face recognition, identity, file_uuid - Add test scripts for ASR, face, search, processing - Add portal frontend (Tauri) - Add config, benchmark, and monitoring utilities - Add model checkpoints and pretrained model references
111 lines
3.8 KiB
Plaintext
111 lines
3.8 KiB
Plaintext
async fn search_bm25(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<SearchRequest>,
|
|
) -> Result<Json<SearchResponse>, StatusCode> {
|
|
let limit = req.limit.unwrap_or(10);
|
|
let query_hash = generate_query_hash(&req.query, req.uuid.as_deref(), limit);
|
|
let cache_key = keys::bm25_search(&query_hash);
|
|
let ttl = state.mongo_cache.ttl_search();
|
|
|
|
let response = state
|
|
.mongo_cache
|
|
.get_or_fetch(&cache_key, ttl, keys::CATEGORY_SEARCH, || async {
|
|
let pg = PostgresDb::init()
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;
|
|
|
|
let bm25_results = pg
|
|
.search_bm25(&req.query, req.uuid.as_deref(), limit)
|
|
.await?;
|
|
|
|
let results: Vec<SearchResult> = bm25_results
|
|
.into_iter()
|
|
.map(|r| SearchResult {
|
|
uuid: r.uuid,
|
|
chunk_id: r.chunk_id,
|
|
chunk_type: r.chunk_type,
|
|
start_time: r.start_time,
|
|
end_time: r.end_time,
|
|
text: r.text,
|
|
score: r.bm25_score,
|
|
})
|
|
.collect();
|
|
|
|
Ok::<SearchResponse, anyhow::Error>(SearchResponse {
|
|
results,
|
|
query: req.query.clone(),
|
|
})
|
|
})
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(Json(response))
|
|
}
|
|
|
|
async fn n8n_search_bm25(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<SearchRequest>,
|
|
) -> Result<Json<N8nSearchResponse>, StatusCode> {
|
|
let limit = req.limit.unwrap_or(10);
|
|
let query_hash = generate_query_hash(&req.query, req.uuid.as_deref(), limit);
|
|
let cache_key = keys::n8n_bm25_search(&query_hash);
|
|
let ttl = state.mongo_cache.ttl_search();
|
|
|
|
let response = state
|
|
.mongo_cache
|
|
.get_or_fetch(&cache_key, ttl, keys::CATEGORY_N8N_SEARCH, || async {
|
|
let pg = PostgresDb::init()
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?;
|
|
|
|
let bm25_results = pg
|
|
.search_bm25(&req.query, req.uuid.as_deref(), limit)
|
|
.await?;
|
|
|
|
let mut hits = Vec::new();
|
|
|
|
for r in bm25_results {
|
|
if let Some(chunk) = pg
|
|
.get_chunk_by_chunk_id_and_uuid(&r.chunk_id, &r.uuid)
|
|
.await
|
|
.ok()
|
|
.flatten()
|
|
{
|
|
let text = r.text; // Use text from BM25 result
|
|
let title = extract_title_from_content(&chunk.content);
|
|
|
|
let file_path = if chunk.uuid.is_empty() {
|
|
None
|
|
} else {
|
|
let video = pg.get_video_by_uuid(&chunk.uuid).await.ok().flatten();
|
|
video.map(|v| v.file_path)
|
|
};
|
|
|
|
hits.push(N8nSearchHit {
|
|
id: chunk.chunk_id.clone(),
|
|
vid: chunk.uuid.clone(),
|
|
start: chunk.start_time().seconds(),
|
|
end: chunk.end_time().seconds(),
|
|
title: if title.is_empty() {
|
|
format!("Chunk {}", chunk.chunk_id)
|
|
} else {
|
|
title
|
|
},
|
|
text,
|
|
score: r.bm25_score,
|
|
file_path,
|
|
});
|
|
}
|
|
}
|
|
|
|
Ok::<N8nSearchResponse, anyhow::Error>(N8nSearchResponse {
|
|
query: req.query.clone(),
|
|
count: hits.len(),
|
|
hits,
|
|
})
|
|
})
|
|
.await
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
|
|
Ok(Json(response))
|
|
} |