use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct Memory {
pub id: Uuid,
pub content: String,
pub content_hash: String, pub tags: Vec<String>,
pub context: String, pub summary: String, pub chunk_index: Option<i32>, pub total_chunks: Option<i32>, pub parent_id: Option<Uuid>, pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Memory {
pub fn new(
content: String,
context: String,
summary: String,
tags: Option<Vec<String>>,
) -> Self {
use sha2::{Digest, Sha256};
let mut content_hasher = Sha256::new();
content_hasher.update(content.as_bytes());
let content_hash = hex::encode(content_hasher.finalize());
let now = Utc::now();
Self {
id: Uuid::new_v4(),
content,
content_hash,
tags: tags.unwrap_or_default(),
context,
summary,
chunk_index: None,
total_chunks: None,
parent_id: None,
created_at: now,
updated_at: now,
}
}
pub fn new_chunk(
content: String,
context: String,
summary: String,
tags: Option<Vec<String>>,
chunk_index: i32,
total_chunks: i32,
parent_id: Uuid,
) -> Self {
use sha2::{Digest, Sha256};
let mut content_hasher = Sha256::new();
content_hasher.update(content.as_bytes());
let content_hash = hex::encode(content_hasher.finalize());
let now = Utc::now();
Self {
id: Uuid::new_v4(),
content,
content_hash,
tags: tags.unwrap_or_default(),
context,
summary,
chunk_index: Some(chunk_index),
total_chunks: Some(total_chunks),
parent_id: Some(parent_id),
created_at: now,
updated_at: now,
}
}
pub fn is_semantically_similar(&self, other: &Memory, similarity_threshold: f64) -> bool {
let content_similarity = self.simple_text_similarity(&self.content, &other.content);
let context_similarity = self.simple_text_similarity(&self.context, &other.context);
let combined_similarity = (content_similarity * 0.6) + (context_similarity * 0.4);
combined_similarity >= similarity_threshold
}
fn simple_text_similarity(&self, text1: &str, text2: &str) -> f64 {
use std::collections::HashSet;
let words1: HashSet<&str> = text1.split_whitespace().collect();
let words2: HashSet<&str> = text2.split_whitespace().collect();
let intersection = words1.intersection(&words2).count();
let union = words1.union(&words2).count();
if union == 0 {
0.0
} else {
intersection as f64 / union as f64
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct StorageStats {
pub total_memories: i64,
pub table_size: String,
pub last_memory_created: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchMetadata {
pub stage_used: u8,
pub stage_description: String,
pub threshold_used: f64,
pub total_results: usize,
}
#[derive(Debug, Clone)]
pub struct SearchResultWithMetadata {
pub results: Vec<SearchResult>,
pub metadata: SearchMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchParams {
pub query: String,
pub tag_filter: Option<Vec<String>>,
pub use_tag_embedding: bool,
pub use_content_embedding: bool,
pub similarity_threshold: f64,
pub max_results: usize,
pub search_strategy: SearchStrategy,
pub boost_recent: bool,
pub tag_weight: f64,
pub content_weight: f64,
}
impl Default for SearchParams {
fn default() -> Self {
Self {
query: String::new(),
tag_filter: None,
use_tag_embedding: true,
use_content_embedding: true,
similarity_threshold: 0.7,
max_results: 10,
search_strategy: SearchStrategy::Hybrid,
boost_recent: false,
tag_weight: 0.4,
content_weight: 0.6,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SearchStrategy {
TagsFirst,
ContentFirst,
Hybrid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub memory: Memory,
pub tag_similarity: Option<f64>,
pub content_similarity: Option<f64>,
pub combined_score: f64,
pub semantic_cluster: Option<i32>,
}
impl SearchResult {
pub fn new(
memory: Memory,
tag_similarity: Option<f64>,
content_similarity: Option<f64>,
semantic_cluster: Option<i32>,
tag_weight: f64,
content_weight: f64,
) -> Self {
let combined_score = Self::calculate_combined_score(
tag_similarity,
content_similarity,
tag_weight,
content_weight,
);
Self {
memory,
tag_similarity,
content_similarity,
combined_score,
semantic_cluster,
}
}
fn calculate_combined_score(
tag_similarity: Option<f64>,
content_similarity: Option<f64>,
tag_weight: f64,
content_weight: f64,
) -> f64 {
let tag_score = tag_similarity.unwrap_or(0.0) * tag_weight;
let content_score = content_similarity.unwrap_or(0.0) * content_weight;
tag_score + content_score
}
}