From 21c815dc974b5f1e689b09b67fd0d2b5538e725a Mon Sep 17 00:00:00 2001 From: Swift Ugandan Date: Sun, 1 Dec 2024 18:09:33 +0000 Subject: [PATCH 1/2] Add custom API key support in AI configuration --- packages/api/ai/config.mts | 2 +- packages/api/db/schema.mts | 1 + .../api/drizzle/0014_add_custom_api_key.sql | 1 + packages/api/drizzle/meta/_journal.json | 7 +++ packages/web/src/routes/settings.tsx | 47 ++++++++++++------- packages/web/src/types.ts | 1 + 6 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 packages/api/drizzle/0014_add_custom_api_key.sql diff --git a/packages/api/ai/config.mts b/packages/api/ai/config.mts index faf25af9..8f8d90ec 100644 --- a/packages/api/ai/config.mts +++ b/packages/api/ai/config.mts @@ -55,7 +55,7 @@ export async function getModel(): Promise { } const openaiCompatible = createOpenAI({ compatibility: 'compatible', - apiKey: 'bogus', // required but unused + apiKey: config.customApiKey || 'bogus', // Use custom API key if provided, otherwise use bogus key baseURL: aiBaseUrl, }); return openaiCompatible(model); diff --git a/packages/api/db/schema.mts b/packages/api/db/schema.mts index eba6b7d5..512bbe4a 100644 --- a/packages/api/db/schema.mts +++ b/packages/api/db/schema.mts @@ -10,6 +10,7 @@ export const configs = sqliteTable('config', { anthropicKey: text('anthropic_api_key'), xaiKey: text('xai_api_key'), geminiKey: text('gemini_api_key'), + customApiKey: text('custom_api_key'), // TODO: This is deprecated in favor of SRCBOOK_DISABLE_ANALYTICS env variable. Remove this. enabledAnalytics: integer('enabled_analytics', { mode: 'boolean' }).notNull().default(true), // Stable ID for posthog diff --git a/packages/api/drizzle/0014_add_custom_api_key.sql b/packages/api/drizzle/0014_add_custom_api_key.sql new file mode 100644 index 00000000..6a4e6193 --- /dev/null +++ b/packages/api/drizzle/0014_add_custom_api_key.sql @@ -0,0 +1 @@ +ALTER TABLE `config` ADD `custom_api_key` text; diff --git a/packages/api/drizzle/meta/_journal.json b/packages/api/drizzle/meta/_journal.json index fb21da73..321904a8 100644 --- a/packages/api/drizzle/meta/_journal.json +++ b/packages/api/drizzle/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1732197490638, "tag": "0014_Gemini_Integration", "breakpoints": true + }, + { + "idx": 15, + "version": "6", + "when": 1733076427000, + "tag": "0015_add_custom_api_key", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/web/src/routes/settings.tsx b/packages/web/src/routes/settings.tsx index 8f3079c1..ade17d45 100644 --- a/packages/web/src/routes/settings.tsx +++ b/packages/web/src/routes/settings.tsx @@ -161,7 +161,7 @@ function AiInfoBanner() { case 'custom': return (
-

Base URL required

+

Base URL required, API key optional

); } @@ -243,6 +243,7 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) { anthropicKey: configAnthropicKey, xaiKey: configXaiKey, geminiKey: configGeminiKey, + customApiKey: configCustomApiKey, updateConfig: updateConfigContext, } = useSettings(); @@ -250,6 +251,7 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) { const [anthropicKey, setAnthropicKey] = useState(configAnthropicKey ?? ''); const [xaiKey, setXaiKey] = useState(configXaiKey ?? ''); const [geminiKey, setGeminiKey] = useState(configGeminiKey ?? ''); + const [customApiKey, setCustomApiKey] = useState(configCustomApiKey ?? ''); const [model, setModel] = useState(aiModel); const [baseUrl, setBaseUrl] = useState(aiBaseUrl || ''); @@ -283,6 +285,8 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) { model !== aiModel; const customModelSaveEnabled = + (typeof configCustomApiKey === 'string' && customApiKey !== configCustomApiKey) || + ((configCustomApiKey === null || configCustomApiKey === undefined) && customApiKey.length > 0) || (typeof aiBaseUrl === 'string' && baseUrl !== aiBaseUrl) || ((aiBaseUrl === null || aiBaseUrl === undefined) && baseUrl.length > 0) || model !== aiModel; @@ -394,22 +398,33 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {

If you want to use an openai-compatible model (for example when running local models - with Ollama), choose this option and set the baseUrl. + with Ollama), choose this option and set the baseUrl and API key.

-
- setBaseUrl(e.target.value)} - /> - +
+
+ setBaseUrl(e.target.value)} + /> + setCustomApiKey(e.target.value)} + /> +
+
+ +
)} diff --git a/packages/web/src/types.ts b/packages/web/src/types.ts index 19c6d7a5..4ce764d6 100644 --- a/packages/web/src/types.ts +++ b/packages/web/src/types.ts @@ -14,6 +14,7 @@ export type SettingsType = { anthropicKey?: string | null; xaiKey?: string | null; geminiKey?: string | null; + customApiKey?: string | null; aiProvider: AiProviderType; aiModel: string; aiBaseUrl?: string | null; From 950df6deacb2bdee295172334897babe029c7478 Mon Sep 17 00:00:00 2001 From: swiftugandan Date: Tue, 3 Dec 2024 09:43:39 +0000 Subject: [PATCH 2/2] migration file --- ...pi_key.sql => 0015_add_custom_api_key.sql} | 0 packages/api/drizzle/meta/0015_snapshot.json | 42 +++++++++++++++++++ packages/web/src/routes/settings.tsx | 7 +++- 3 files changed, 47 insertions(+), 2 deletions(-) rename packages/api/drizzle/{0014_add_custom_api_key.sql => 0015_add_custom_api_key.sql} (100%) create mode 100644 packages/api/drizzle/meta/0015_snapshot.json diff --git a/packages/api/drizzle/0014_add_custom_api_key.sql b/packages/api/drizzle/0015_add_custom_api_key.sql similarity index 100% rename from packages/api/drizzle/0014_add_custom_api_key.sql rename to packages/api/drizzle/0015_add_custom_api_key.sql diff --git a/packages/api/drizzle/meta/0015_snapshot.json b/packages/api/drizzle/meta/0015_snapshot.json new file mode 100644 index 00000000..140c34bc --- /dev/null +++ b/packages/api/drizzle/meta/0015_snapshot.json @@ -0,0 +1,42 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "0015_add_custom_api_key", + "prevId": "0014_Gemini_Integration", + "tables": { + "config": { + "name": "config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "openai_api_key": { + "name": "openai_api_key", + "type": "text" + }, + "posthog_api_key": { + "name": "posthog_api_key", + "type": "text" + }, + "subscription_email": { + "name": "subscription_email", + "type": "text" + }, + "custom_api_key": { + "name": "custom_api_key", + "type": "text" + } + } + } + }, + "enums": {}, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } + } \ No newline at end of file diff --git a/packages/web/src/routes/settings.tsx b/packages/web/src/routes/settings.tsx index ade17d45..8ff2b7e5 100644 --- a/packages/web/src/routes/settings.tsx +++ b/packages/web/src/routes/settings.tsx @@ -286,7 +286,8 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) { const customModelSaveEnabled = (typeof configCustomApiKey === 'string' && customApiKey !== configCustomApiKey) || - ((configCustomApiKey === null || configCustomApiKey === undefined) && customApiKey.length > 0) || + ((configCustomApiKey === null || configCustomApiKey === undefined) && + customApiKey.length > 0) || (typeof aiBaseUrl === 'string' && baseUrl !== aiBaseUrl) || ((aiBaseUrl === null || aiBaseUrl === undefined) && baseUrl.length > 0) || model !== aiModel; @@ -419,7 +420,9 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {