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

@@ -7,35 +7,24 @@
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { getClient } from '$lib/server/db/client';
import { LocalEmbeddingProvider } from '$lib/server/embeddings/local.provider';
import { createProviderFromProfile } from '$lib/server/embeddings/registry';
import type { EmbeddingProfile } from '$lib/server/db/schema';
import { EmbeddingProfileEntity } from '$lib/server/models/embedding-profile';
import { EmbeddingProfileMapper } from '$lib/server/mappers/embedding-profile.mapper';
import { handleServiceError } from '$lib/server/utils/validation';
export const GET: RequestHandler = async () => {
try {
const db = getClient();
const profile = db
.prepare<
[],
EmbeddingProfile
>('SELECT * FROM embedding_profiles WHERE is_default = 1 AND enabled = 1 LIMIT 1')
.get();
if (!profile) {
return json({ available: false, error: 'No active embedding profile configured' });
}
const provider = createProviderFromProfile(profile);
const provider = new LocalEmbeddingProvider();
const available = await provider.isAvailable();
return json({
available,
profile: {
id: profile.id,
providerKind: profile.providerKind,
model: profile.model,
dimensions: profile.dimensions
id: 'local-default',
providerKind: 'local-transformers',
model: provider.model,
dimensions: provider.dimensions
}
});
} catch (err) {
@@ -46,19 +35,43 @@ export const GET: RequestHandler = async () => {
export const POST: RequestHandler = async ({ request }) => {
try {
const body = await request.json();
const config = validateConfig(body);
if (config.provider === 'none') {
throw new InvalidInputError('Cannot test the "none" provider — no backend is configured.');
if (typeof body !== 'object' || body === null) {
throw new Error('Request body must be a JSON object');
}
const provider = createProviderFromConfig(config);
const candidate = body as Record<string, unknown>;
if (candidate.providerKind !== 'openai-compatible') {
throw new Error('Only openai-compatible providers can be tested via this endpoint');
}
if (typeof candidate.model !== 'string' || typeof candidate.dimensions !== 'number') {
throw new Error('model and dimensions are required');
}
const provider = createProviderFromProfile(
EmbeddingProfileMapper.fromEntity(
new EmbeddingProfileEntity({
id: typeof candidate.id === 'string' ? candidate.id : 'test-openai-profile',
provider_kind: 'openai-compatible',
title: typeof candidate.title === 'string' ? candidate.title : 'Test Provider',
enabled: true,
is_default: false,
model: candidate.model,
dimensions: candidate.dimensions,
config:
typeof candidate.config === 'object' && candidate.config !== null
? (candidate.config as Record<string, unknown>)
: {},
created_at: Date.now(),
updated_at: Date.now()
})
)
);
const available = await provider.isAvailable();
if (!available) {
return new Response(
JSON.stringify({
error: `Provider "${config.provider}" is not available. Check your configuration.`
error: 'Provider is not available. Check your configuration.'
}),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);