Per-workspace AI provider config previously shared a single base URL and a single API key between the chat model and the embedding model. Add dedicated, optional embedding endpoint/token that fall back to the chat values when empty, preserving backward compatibility. - db: new migration adds nullable `embedding_api_key_enc` to `ai_provider_credentials`; chat key stays in `api_key_enc` - repo: add `upsertEmbeddingKey` / `clearEmbeddingKey` (on-conflict touches only its own column, so chat/embedding keys never overwrite) - ai-settings.service: store non-secret `embeddingBaseUrl`; resolve() applies fallback (embeddingBaseUrl || baseUrl; embedding key || chat key); getMasked() exposes raw `embeddingBaseUrl` + `hasEmbeddingApiKey`, never the key; update() handles the embedding key write-only - ai.service: getEmbeddingModel() builds openai/gemini/ollama with the embedding-specific URL/key; chat path unchanged - client: new "Embedding base URL" and "Embedding API key" fields with fallback hints and a clear-key action Requires running the DB migration on deploy.
19 lines
651 B
TypeScript
19 lines
651 B
TypeScript
import { type Kysely } from 'kysely';
|
|
|
|
export async function up(db: Kysely<any>): Promise<void> {
|
|
// Encrypted, embedding-specific provider key. Separate from `api_key_enc`
|
|
// (the chat key) so the chat model and the embedding model can use different
|
|
// tokens. When NULL, the embedding model falls back to `api_key_enc`.
|
|
await db.schema
|
|
.alterTable('ai_provider_credentials')
|
|
.addColumn('embedding_api_key_enc', 'text', (col) => col)
|
|
.execute();
|
|
}
|
|
|
|
export async function down(db: Kysely<any>): Promise<void> {
|
|
await db.schema
|
|
.alterTable('ai_provider_credentials')
|
|
.dropColumn('embedding_api_key_enc')
|
|
.execute();
|
|
}
|