feat: integrate vectordb for note embeddings

- Added `vectordb` as a dependency in `package.json`.
- Implemented `embedText` function in `src/main/ai/embeddings.ts` to handle text embeddings using GitHub Copilot OAuth token or OpenAI token.
- Created `vectordb.ts` for managing LanceDB connection and embedding notes with upsert strategy.
- Updated `index.ts` to initialize vector database and migrate existing notes on app ready.
- Modified `router/index.ts` to fire-and-forget embedding calls on note creation and updates.
- Enhanced `progress.txt` with detailed implementation notes and learnings regarding the integration.
This commit is contained in:
Roberto Musso
2026-02-24 21:34:48 +01:00
parent e70982c8b6
commit 2cb2f0e4e8
9 changed files with 750 additions and 27 deletions

View File

@@ -7,6 +7,7 @@ import { clients, projects, tasks, checkpoints, notes, taskComments } from '../d
import { getStore } from '../store';
import { saveTokenAndInit, hasActiveToken } from '../ai/provider';
import { orchestrate } from '../ai/orchestrator';
import { upsertNoteEmbedding } from '../db/vectordb';
import type { TRPCContext } from '../ipc';
const t = initTRPC.context<TRPCContext>().create();
@@ -406,7 +407,7 @@ const notesRouter = router({
create: publicProcedure
.input(z.object({ title: z.string(), content: z.string(), projectId: z.string().optional() }))
.mutation(({ input }) => {
.mutation(async ({ input }) => {
const id = crypto.randomUUID();
const now = Date.now();
getDb().insert(notes).values({
@@ -417,18 +418,37 @@ const notesRouter = router({
createdAt: now,
updatedAt: now,
}).run();
// Fire-and-forget: embed the note. Errors are logged, never thrown.
upsertNoteEmbedding(id, input.projectId ?? null, `${input.title}\n\n${input.content}`)
.catch((err) => console.error('[VectorDB] Failed to embed note on create:', err));
return { id };
}),
update: publicProcedure
.input(z.object({ id: z.string(), title: z.string().optional(), content: z.string().optional() }))
.mutation(({ input }) => {
.mutation(async ({ input }) => {
const set: Partial<{ title: string; content: string; updatedAt: number }> = {};
if (input.title !== undefined) set.title = input.title;
if (input.content !== undefined) set.content = input.content;
// Always update updatedAt
set.updatedAt = Date.now();
getDb().update(notes).set(set).where(eq(notes.id, input.id)).run();
// Re-embed if searchable text fields changed.
// Re-fetch from SQLite so the embedding reflects the full current note
// (the update may have changed only one of title or content).
if (input.title !== undefined || input.content !== undefined) {
const updated = getDb()
.select({ id: notes.id, projectId: notes.projectId, title: notes.title, content: notes.content })
.from(notes)
.where(eq(notes.id, input.id))
.all()[0];
if (updated) {
upsertNoteEmbedding(updated.id, updated.projectId ?? null, `${updated.title}\n\n${updated.content}`)
.catch((err) => console.error('[VectorDB] Failed to embed note on update:', err));
}
}
return null;
}),