spa/.claude/skills/thread-manager/dist/database/messages-dao.js

132 lines
4.8 KiB
JavaScript

"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;