feat(EMBEDDINGS-0001): enable local embedder by default and overhaul settings page

- Wire local embedding provider as the default on startup when no profile is configured
- Refactor embedding settings into dedicated service, DTOs, mappers and models
- Rebuild settings page with profile management UI and live test feedback
- Expose index summary (indexed versions + embedding count) on repo endpoints
- Harden indexing pipeline and context search with additional test coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Giancarmine Salucci
2026-03-28 09:28:01 +01:00
parent d1381f7fc0
commit 781d224adc
30 changed files with 1419 additions and 313 deletions

View File

@@ -408,6 +408,36 @@ describe('EmbeddingService', () => {
expect(embedding![2]).toBeCloseTo(0.2, 5);
});
it('stores embeddings under the configured profile ID', async () => {
client
.prepare(
`INSERT INTO embedding_profiles
(id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, unixepoch(), unixepoch())`
)
.run(
'openai-custom',
'openai-compatible',
'OpenAI Custom',
1,
0,
'test-model',
4,
'{}'
);
const snippetId = seedSnippet(db, client);
const provider = makeProvider(4, 'test-model');
const service = new EmbeddingService(client, provider, 'openai-custom');
await service.embedSnippets([snippetId]);
const row = client
.prepare('SELECT profile_id FROM snippet_embeddings WHERE snippet_id = ?')
.get(snippetId) as { profile_id: string };
expect(row.profile_id).toBe('openai-custom');
});
it('is idempotent — re-embedding replaces the existing row', async () => {
const snippetId = seedSnippet(db, client);
const provider = makeProvider(2);
@@ -469,6 +499,19 @@ describe('EmbeddingService', () => {
};
expect(rows.cnt).toBe(0);
});
it('finds snippets missing embeddings for the active profile', async () => {
const firstSnippetId = seedSnippet(db, client);
const secondSnippetId = seedSnippet(db, client, { content: 'Second snippet content' });
const provider = makeProvider(4);
const service = new EmbeddingService(client, provider, 'local-default');
await service.embedSnippets([firstSnippetId]);
expect(service.findSnippetIdsMissingEmbeddings('/test/embed-repo', null)).toEqual([
secondSnippetId
]);
});
});
// ---------------------------------------------------------------------------