138 lines
4.2 KiB
TypeScript
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
|
|
);
|
|
}
|
|
}
|