Files
trueref-legacy/src/lib/server/services/embedding-settings.service.ts

138 lines
4.2 KiB
TypeScript

import type Database from 'better-sqlite3';
import type { EmbeddingSettingsUpdateDto } from '$lib/dtos/embedding-settings.js';
import {
createProviderFromProfile,
getDefaultLocalProfile
} from '$lib/server/embeddings/registry.js';
import { EmbeddingProfileMapper } from '$lib/server/mappers/embedding-profile.mapper.js';
import { EmbeddingProfile, EmbeddingProfileEntity } from '$lib/server/models/embedding-profile.js';
import { EmbeddingSettings } from '$lib/server/models/embedding-settings.js';
import { InvalidInputError } from '$lib/server/utils/validation.js';
export class EmbeddingSettingsService {
constructor(private readonly db: Database.Database) {}
getSettings(): EmbeddingSettings {
const profiles = this.loadProfiles();
const activeProfile = profiles.find((profile) => profile.isDefault && profile.enabled) ?? null;
return new EmbeddingSettings({ profiles, activeProfile });
}
async updateSettings(input: EmbeddingSettingsUpdateDto): Promise<EmbeddingSettings> {
const now = Math.floor(Date.now() / 1000);
this.db.prepare('UPDATE embedding_profiles SET is_default = 0, updated_at = ?').run(now);
if (input.activeProfileId === null) {
return this.getSettings();
}
const profile =
input.activeProfileId === 'local-default'
? this.buildDefaultLocalProfile(now)
: this.buildCustomProfile(input, now);
const available = await createProviderFromProfile(profile).isAvailable();
if (!available) {
throw new InvalidInputError(
`Could not connect to the "${profile.providerKind}" provider. Check your configuration.`
);
}
this.persistProfile(profile);
return this.getSettings();
}
private loadProfiles(): EmbeddingProfile[] {
return this.db
.prepare('SELECT * FROM embedding_profiles ORDER BY is_default DESC, created_at ASC')
.all()
.map((row) => EmbeddingProfileMapper.fromEntity(new EmbeddingProfileEntity(row as never)));
}
private buildDefaultLocalProfile(now: number): EmbeddingProfile {
const defaultLocal = getDefaultLocalProfile();
return new EmbeddingProfile({
id: defaultLocal.id,
providerKind: defaultLocal.providerKind,
title: defaultLocal.title,
enabled: true,
isDefault: true,
model: defaultLocal.model,
dimensions: defaultLocal.dimensions,
config: {},
createdAt: this.getCreatedAt(defaultLocal.id, now),
updatedAt: now
});
}
private buildCustomProfile(input: EmbeddingSettingsUpdateDto, now: number): EmbeddingProfile {
const candidate = input.profile;
if (!candidate) {
throw new InvalidInputError('profile is required for custom embedding providers');
}
if (candidate.id !== input.activeProfileId) {
throw new InvalidInputError('activeProfileId must match profile.id');
}
if (!candidate.title || !candidate.model) {
throw new InvalidInputError('profile title and model are required');
}
return new EmbeddingProfile({
id: candidate.id,
providerKind: candidate.providerKind,
title: candidate.title,
enabled: true,
isDefault: true,
model: candidate.model,
dimensions: candidate.dimensions,
config: candidate.config,
createdAt: this.getCreatedAt(candidate.id, now),
updatedAt: now
});
}
private getCreatedAt(id: string, fallback: number): number {
return (
this.db
.prepare<
[string],
{ created_at: number }
>('SELECT created_at FROM embedding_profiles WHERE id = ?')
.get(id)?.created_at ?? fallback
);
}
private persistProfile(profile: EmbeddingProfile): void {
this.db
.prepare(
`INSERT INTO embedding_profiles
(id, provider_kind, title, enabled, is_default, model, dimensions, config, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
provider_kind = excluded.provider_kind,
title = excluded.title,
enabled = excluded.enabled,
is_default = excluded.is_default,
model = excluded.model,
dimensions = excluded.dimensions,
config = excluded.config,
updated_at = excluded.updated_at`
)
.run(
profile.id,
profile.providerKind,
profile.title,
profile.enabled ? 1 : 0,
profile.isDefault ? 1 : 0,
profile.model,
profile.dimensions,
JSON.stringify(profile.config),
profile.createdAt,
profile.updatedAt
);
}
}