feat: Implement semantic search and smart note linking with embedding generation, vector storage, and link persistence.

This commit is contained in:
2025-12-25 23:59:11 +01:00
parent 4cb398869d
commit 178d59540e
19 changed files with 2501 additions and 49 deletions

View File

@@ -0,0 +1,68 @@
use async_trait::async_trait;
use sqlx::SqlitePool;
use uuid::Uuid;
use notes_domain::entities::NoteLink;
use notes_domain::errors::{DomainError, DomainResult};
use notes_domain::ports::LinkRepository;
pub struct SqliteLinkRepository {
pool: SqlitePool,
}
impl SqliteLinkRepository {
pub fn new(pool: SqlitePool) -> Self {
Self { pool }
}
}
#[async_trait]
impl LinkRepository for SqliteLinkRepository {
async fn save_links(&self, links: &[NoteLink]) -> DomainResult<()> {
let mut tx = self
.pool
.begin()
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
for link in links {
let source = link.source_note_id.to_string();
let target = link.target_note_id.to_string();
let created_at = link.created_at.to_rfc3339();
sqlx::query(
r#"
INSERT INTO note_links (source_note_id, target_note_id, score, created_at)
VALUES (?, ?, ?, ?)
ON CONFLICT(source_note_id, target_note_id) DO UPDATE SET
score = excluded.score,
created_at = excluded.created_at
"#,
)
.bind(source)
.bind(target)
.bind(link.score)
.bind(created_at)
.execute(&mut *tx)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
}
tx.commit()
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(())
}
async fn delete_links_for_source(&self, source_note_id: Uuid) -> DomainResult<()> {
let source_str = source_note_id.to_string();
sqlx::query("DELETE FROM note_links WHERE source_note_id = ?")
.bind(source_str)
.execute(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(())
}
}