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

@@ -34,6 +34,7 @@ vi.mock('$lib/server/embeddings/registry.js', () => ({
}));
import { POST as postLibraries } from './libs/+server.js';
import { GET as getLibraries } from './libs/+server.js';
import { GET as getLibrary } from './libs/[id]/+server.js';
import { GET as getJobs } from './jobs/+server.js';
import { GET as getJob } from './jobs/[id]/+server.js';
@@ -186,6 +187,16 @@ function seedSnippet(
return snippetId;
}
function seedEmbedding(client: Database.Database, snippetId: string, values: number[]): void {
client
.prepare(
`INSERT INTO snippet_embeddings
(snippet_id, profile_id, model, dimensions, embedding, created_at)
VALUES (?, 'local-default', 'Xenova/all-MiniLM-L6-v2', ?, ?, ?)`
)
.run(snippetId, values.length, Buffer.from(Float32Array.from(values).buffer), NOW_S);
}
function seedRules(client: Database.Database, repositoryId: string, rules: string[]) {
client
.prepare(
@@ -249,6 +260,36 @@ describe('API contract integration', () => {
expect(body).not.toHaveProperty('total_snippets');
});
it('GET /api/v1/libs includes embedding counts and indexed versions per repository', async () => {
const repositoryId = seedRepo(db);
const versionId = seedVersion(db, repositoryId, 'v18.3.0');
const baseDocId = seedDocument(db, repositoryId);
const versionDocId = seedDocument(db, repositoryId, versionId);
const baseSnippetId = seedSnippet(db, {
documentId: baseDocId,
repositoryId,
content: 'Base branch snippet'
});
const versionSnippetId = seedSnippet(db, {
documentId: versionDocId,
repositoryId,
versionId,
content: 'Versioned snippet'
});
seedEmbedding(db, baseSnippetId, [1, 0]);
seedEmbedding(db, versionSnippetId, [0, 1]);
const response = await getLibraries({
url: new URL('http://test/api/v1/libs')
} as never);
expect(response.status).toBe(200);
const body = await response.json();
expect(body.libraries).toHaveLength(1);
expect(body.libraries[0].embeddingCount).toBe(2);
expect(body.libraries[0].indexedVersions).toEqual(['main', 'v18.3.0']);
});
it('GET /api/v1/jobs and /api/v1/jobs/:id return job DTOs in camelCase', async () => {
const repoService = new RepositoryService(db);
repoService.add({ source: 'github', sourceUrl: 'https://github.com/facebook/react' });