"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessagesDAO = void 0; const uuid_1 = require("uuid"); const vector_utils_1 = require("../core/vector-utils"); const vector_search_engine_1 = require("../core/vector-search-engine"); class MessagesDAO { dbManager; embeddingService; searchEngine; constructor(dbManager, embeddingService) { this.dbManager = dbManager; this.embeddingService = embeddingService; this.searchEngine = new vector_search_engine_1.VectorSearchEngine(); } get db() { return this.dbManager.getDatabase(); } async create(input) { const id = input.id || (0, uuid_1.v4)(); const now = input.timestamp || new Date(); // Generate embedding (Async) let embedding; let embeddingBlob = null; let embeddingModel = null; let embeddingGeneratedAt = null; try { embedding = await this.embeddingService.embed(input.content); if (embedding) { embeddingBlob = (0, vector_utils_1.serializeEmbedding)(embedding); const modelInfo = this.embeddingService.getModelInfo(); embeddingModel = modelInfo.name; embeddingGeneratedAt = Date.now(); } } catch (e) { console.error('Failed to generate embedding:', e); // Proceed without embedding } const message = { id, threadId: input.threadId, role: input.role, content: input.content, timestamp: now, metadata: input.metadata || {}, embedding }; const stmt = this.db.prepare(` INSERT INTO messages ( id, thread_id, role, content, timestamp, metadata, embedding_blob, embedding_model, embedding_generated_at ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? ) `); stmt.run(message.id, message.threadId, message.role, message.content, message.timestamp.getTime(), JSON.stringify(message.metadata || {}), embeddingBlob, embeddingModel, embeddingGeneratedAt); // Update thread message count this.updateThreadMessageCount(message.threadId); return message; } findByThreadId(threadId, options) { const { limit = 50, offset = 0 } = options || {}; const stmt = this.db.prepare(` SELECT * FROM messages WHERE thread_id = ? ORDER BY timestamp DESC LIMIT ? OFFSET ? `); const rows = stmt.all(threadId, limit, offset); return rows.map(this.mapRowToMessage); } /** * Semantically search for messages */ async searchSimilar(query, options = {}) { const { threadId, topK = 10, minScore = 0.5 } = options; // 1. Generate query vector const queryVector = await this.embeddingService.embed(query); // 2. Load candidate messages (all messages with embeddings) // Optimization: Filter by threadId in SQL if provided to reduce memory usage let sql = 'SELECT * FROM messages WHERE embedding_blob IS NOT NULL'; const params = []; if (threadId) { sql += ' AND thread_id = ?'; params.push(threadId); } const rows = this.db.prepare(sql).all(...params); // 3. Build corpus const corpus = rows.map(row => ({ id: row.id, vector: (0, vector_utils_1.deserializeEmbedding)(row.embedding_blob), payload: this.mapRowToMessage(row) })); // 4. Perform vector search const results = this.searchEngine.search(queryVector, corpus, { topK, minScore }); // 5. Map results return results.map(result => ({ ...result.payload, score: result.score })); } deleteByThreadId(threadId) { const stmt = this.db.prepare('DELETE FROM messages WHERE thread_id = ?'); stmt.run(threadId); } updateThreadMessageCount(threadId) { const countStmt = this.db.prepare('SELECT COUNT(*) as count FROM messages WHERE thread_id = ?'); const result = countStmt.get(threadId); const updateStmt = this.db.prepare('UPDATE threads SET message_count = ? WHERE id = ?'); updateStmt.run(result.count, threadId); } mapRowToMessage(row) { const message = { id: row.id, threadId: row.thread_id, role: row.role, content: row.content, timestamp: new Date(row.timestamp), metadata: JSON.parse(row.metadata || '{}') }; if (row.embedding_blob) { message.embedding = (0, vector_utils_1.deserializeEmbedding)(row.embedding_blob); } return message; } } exports.MessagesDAO = MessagesDAO;