From 5a42ad9e372cd6a9810f9423184b24be38207276 Mon Sep 17 00:00:00 2001 From: Alice Hernandez Date: Wed, 10 Jun 2026 12:09:44 +1000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20custom=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/aiService.ts | 14 ++++++++++---- src/main/globalConfig.ts | 24 ++++++++++++++++++------ src/main/ipcHandlers.ts | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/aiService.ts b/src/main/aiService.ts index 65c19fe..009c988 100644 --- a/src/main/aiService.ts +++ b/src/main/aiService.ts @@ -1,5 +1,5 @@ import Anthropic from '@anthropic-ai/sdk' -import { getApiKey } from './globalConfig' +import { getApiKey, getAIConfig } from './globalConfig' import type { Market } from './fileSystem' export type AnalysisMode = 'compression' | 'ending' | 'tone' | 'market_fit' | 'chat' @@ -17,6 +17,9 @@ export interface AIPayload { userMessage: string } +const DEFAULT_MODEL = 'claude-sonnet-4-6' +const DEFAULT_PROMPT_MODEL = 'claude-haiku-4-5-20251001' + let _client: Anthropic | null = null export function resetClient(): void { @@ -29,7 +32,8 @@ function getClient(): Anthropic { if (!apiKey || apiKey === 'your-api-key-here') { throw new Error('API key not set. Open Borges → Preferences to configure it.') } - _client = new Anthropic({ apiKey }) + const { baseURL } = getAIConfig() + _client = new Anthropic({ apiKey, ...(baseURL ? { baseURL } : {}) }) } return _client } @@ -97,8 +101,9 @@ Be candid. If the fit is poor, say so plainly.` export async function streamPrompt(onChunk: (chunk: string) => void): Promise { const client = getClient() + const { promptModel } = getAIConfig() const stream = client.messages.stream({ - model: 'claude-haiku-4-5-20251001', + model: promptModel ?? DEFAULT_PROMPT_MODEL, max_tokens: 120, messages: [{ role: 'user', @@ -118,6 +123,7 @@ export async function streamMessage( onChunk: (chunk: string) => void ): Promise { const client = getClient() + const { model } = getAIConfig() const messages: Anthropic.MessageParam[] = [ ...payload.conversationHistory.slice(-10), @@ -125,7 +131,7 @@ export async function streamMessage( ] const stream = client.messages.stream({ - model: 'claude-sonnet-4-6', + model: model ?? DEFAULT_MODEL, max_tokens: 4096, system: buildSystemPrompt(payload), messages diff --git a/src/main/globalConfig.ts b/src/main/globalConfig.ts index fd8dbed..7ecceec 100644 --- a/src/main/globalConfig.ts +++ b/src/main/globalConfig.ts @@ -2,31 +2,43 @@ import { join } from 'path' import { homedir } from 'os' import { readFileSync, writeFileSync, mkdirSync } from 'fs' +export interface AIProviderConfig { + baseURL?: string + model?: string + promptModel?: string +} + export interface GlobalConfig { apiKey?: string collectionPath?: string fontSize?: number theme?: 'dark' | 'light' defaultWordCountTarget?: number + ai?: AIProviderConfig } const CONFIG_DIR = join(homedir(), '.borges') const CONFIG_FILE = join(CONFIG_DIR, 'config.json') -let _config: GlobalConfig = {} - -try { - _config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')) -} catch { - // first run +function loadFromDisk(): GlobalConfig { + try { + return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')) + } catch { + return {} + } } +let _config: GlobalConfig = loadFromDisk() + export const getCollectionRoot = (): string => _config.collectionPath ?? join(homedir(), 'Documents', 'borges-collection') export const getApiKey = (): string | undefined => _config.apiKey ?? process.env.ANTHROPIC_API_KEY +export const getAIConfig = (): AIProviderConfig => + _config.ai ?? {} + export function readGlobalConfig(): GlobalConfig { return { ..._config } } diff --git a/src/main/ipcHandlers.ts b/src/main/ipcHandlers.ts index e887e11..aaad20c 100644 --- a/src/main/ipcHandlers.ts +++ b/src/main/ipcHandlers.ts @@ -57,7 +57,7 @@ export function registerIpcHandlers(): void { ipcMain.handle('config:read', async () => readGlobalConfig()) ipcMain.handle('config:write', async (_e, updates: Partial) => { writeGlobalConfig(updates) - if (updates.apiKey !== undefined) resetClient() + resetClient() // always reset — baseURL or model may have changed }) ipcMain.handle('config:pickFolder', async (event): Promise => { const win = BrowserWindow.fromWebContents(event.sender)