mirror of
https://github.com/perstarkse/minne.git
synced 2026-05-30 03:10:45 +02:00
chore: centralize embedding errors, retrieval strategy, and test DB helpers.
Replace anyhow in embedding production code with EmbeddingError, move RetrievalStrategy into common config, and deduplicate Surreal test setup via common::test_utils.
This commit is contained in:
@@ -2,8 +2,11 @@ use common::storage::types::conversation::SidebarConversation;
|
||||
use common::storage::{db::SurrealDbClient, store::StorageManager};
|
||||
use common::utils::embedding::EmbeddingProvider;
|
||||
use common::utils::template_engine::{ProvidesTemplateEngine, TemplateEngine};
|
||||
use common::{create_template_engine, storage::db::ProvidesDb, utils::config::AppConfig};
|
||||
use retrieval_pipeline::{reranking::RerankerPool, RetrievalStrategy};
|
||||
use common::{
|
||||
create_template_engine, storage::db::ProvidesDb,
|
||||
utils::config::{AppConfig, RetrievalStrategy},
|
||||
};
|
||||
use retrieval_pipeline::reranking::RerankerPool;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -16,6 +19,7 @@ use tracing::debug;
|
||||
use crate::{OpenAIClientType, SessionStoreType};
|
||||
|
||||
#[derive(Clone)]
|
||||
/// Shared application state for HTML handlers and middleware.
|
||||
pub struct HtmlState {
|
||||
pub db: Arc<SurrealDbClient>,
|
||||
pub openai_client: Arc<OpenAIClientType>,
|
||||
@@ -31,7 +35,7 @@ pub struct HtmlState {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ConversationArchiveCacheEntry {
|
||||
conversations: Vec<SidebarConversation>,
|
||||
conversations: Arc<[SidebarConversation]>,
|
||||
expires_at: Instant,
|
||||
}
|
||||
|
||||
@@ -72,23 +76,19 @@ impl HtmlState {
|
||||
}
|
||||
|
||||
pub fn retrieval_strategy(&self) -> RetrievalStrategy {
|
||||
self.config
|
||||
.retrieval_strategy
|
||||
.as_deref()
|
||||
.and_then(|value| value.parse().ok())
|
||||
.unwrap_or(RetrievalStrategy::Default)
|
||||
self.config.resolved_retrieval_strategy()
|
||||
}
|
||||
|
||||
pub async fn get_cached_conversation_archive(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Option<Vec<SidebarConversation>> {
|
||||
) -> Option<Arc<[SidebarConversation]>> {
|
||||
let now = Instant::now();
|
||||
let should_evict_expired = {
|
||||
let cache = self.conversation_archive_cache.read().await;
|
||||
if let Some(entry) = cache.get(user_id) {
|
||||
if entry.expires_at > now {
|
||||
return Some(entry.conversations.clone());
|
||||
return Some(Arc::clone(&entry.conversations));
|
||||
}
|
||||
true
|
||||
} else {
|
||||
@@ -107,7 +107,7 @@ impl HtmlState {
|
||||
pub async fn set_cached_conversation_archive(
|
||||
&self,
|
||||
user_id: &str,
|
||||
conversations: Vec<SidebarConversation>,
|
||||
conversations: Arc<[SidebarConversation]>,
|
||||
) {
|
||||
let now = Instant::now();
|
||||
let mut cache = self.conversation_archive_cache.write().await;
|
||||
@@ -235,10 +235,10 @@ mod tests {
|
||||
cache.insert(
|
||||
user_id.to_string(),
|
||||
ConversationArchiveCacheEntry {
|
||||
conversations: vec![SidebarConversation {
|
||||
conversations: Arc::from([SidebarConversation {
|
||||
id: "conv-1".to_string(),
|
||||
title: "A stale chat".to_string(),
|
||||
}],
|
||||
}]),
|
||||
expires_at: Instant::now() - Duration::from_secs(1),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ use crate::{
|
||||
auth_middleware::RequireUser,
|
||||
response_middleware::{HtmlError, TemplateResponse},
|
||||
},
|
||||
utils::truncate::{first_non_empty_line, truncate_with_ellipsis},
|
||||
};
|
||||
|
||||
/// Serde deserialization decorator to map empty Strings to None,
|
||||
@@ -41,31 +42,6 @@ fn source_id_suffix(source_id: &str) -> String {
|
||||
source_id[start..].to_string()
|
||||
}
|
||||
|
||||
fn truncate_label(value: &str, max_chars: usize) -> String {
|
||||
let mut end = None;
|
||||
for (count, (idx, _)) in value.char_indices().enumerate() {
|
||||
if count == max_chars {
|
||||
end = Some(idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match end {
|
||||
Some(idx) => format!("{}...", &value[..idx]),
|
||||
None => value.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn first_non_empty_line(text: &str, max_chars: usize) -> Option<String> {
|
||||
for line in text.lines() {
|
||||
let trimmed = line.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(truncate_label(trimmed, max_chars));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UrlInfoLabel {
|
||||
#[serde(default)]
|
||||
@@ -121,7 +97,7 @@ fn build_source_label(row: &SourceLabelRow) -> String {
|
||||
if let Some(context) = row.context.as_ref() {
|
||||
let trimmed = context.trim();
|
||||
if !trimmed.is_empty() {
|
||||
return truncate_label(trimmed, MAX_LABEL_CHARS);
|
||||
return truncate_with_ellipsis(trimmed, MAX_LABEL_CHARS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +107,7 @@ fn build_source_label(row: &SourceLabelRow) -> String {
|
||||
|
||||
let category = row.category.trim();
|
||||
if !category.is_empty() {
|
||||
return truncate_label(category, MAX_LABEL_CHARS);
|
||||
return truncate_with_ellipsis(category, MAX_LABEL_CHARS);
|
||||
}
|
||||
|
||||
format!("Text snippet: {}", source_id_suffix(&row.id))
|
||||
|
||||
Reference in New Issue
Block a user