use super::*;
#[async_trait]
impl crate::traits::EpisodeStore for SqliteStateStore {
async fn get_relevant_episodes(
&self,
query: &str,
limit: usize,
) -> anyhow::Result<Vec<Episode>> {
SqliteStateStore::get_relevant_episodes(self, query, limit).await
}
async fn get_relevant_episodes_for_channel(
&self,
query: &str,
limit: usize,
channel_id: Option<&str>,
) -> anyhow::Result<Vec<Episode>> {
let rows = sqlx::query(
"SELECT id, session_id, summary, topics, emotional_tone, outcome, importance, recall_count, last_recalled_at, message_count, start_time, end_time, created_at, channel_id, embedding
FROM episodes WHERE embedding IS NOT NULL ORDER BY created_at DESC LIMIT 500"
)
.fetch_all(&self.pool)
.await?;
if rows.is_empty() || query.trim().is_empty() {
return Ok(vec![]);
}
let query_vec = match self.embedding_service.embed(query.to_string()).await {
Ok(v) => v,
Err(_) => return Ok(vec![]),
};
let mut scored: Vec<(Episode, f32)> = Vec::new();
for row in rows {
let ep_channel_id: Option<String> = row.try_get("channel_id").unwrap_or(None);
let include = match (&ep_channel_id, channel_id) {
(None, _) => true, (Some(ep_ch), Some(current_ch)) => {
crate::session::stored_channel_matches_current(ep_ch, current_ch)
}
(Some(_), None) => false, };
if !include {
continue;
}
let embedding: Option<Vec<u8>> = row.get("embedding");
if let Some(blob) = embedding {
if let Ok(vec) = decode_embedding(&blob) {
let similarity = crate::memory::math::cosine_similarity(&query_vec, &vec);
let episode = self.row_to_episode(&row)?;
let score = crate::memory::scoring::memory_score(
similarity,
episode.created_at,
episode.recall_count,
episode.last_recalled_at,
);
if score > 0.3 {
scored.push((episode, score));
}
}
}
}
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let episodes: Vec<Episode> = scored.into_iter().take(limit).map(|(e, _)| e).collect();
Ok(episodes)
}
}