custom models

This commit is contained in:
2026-06-10 12:09:44 +10:00
parent 264d28a965
commit 5a42ad9e37
3 changed files with 29 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
import Anthropic from '@anthropic-ai/sdk' import Anthropic from '@anthropic-ai/sdk'
import { getApiKey } from './globalConfig' import { getApiKey, getAIConfig } from './globalConfig'
import type { Market } from './fileSystem' import type { Market } from './fileSystem'
export type AnalysisMode = 'compression' | 'ending' | 'tone' | 'market_fit' | 'chat' export type AnalysisMode = 'compression' | 'ending' | 'tone' | 'market_fit' | 'chat'
@@ -17,6 +17,9 @@ export interface AIPayload {
userMessage: string userMessage: string
} }
const DEFAULT_MODEL = 'claude-sonnet-4-6'
const DEFAULT_PROMPT_MODEL = 'claude-haiku-4-5-20251001'
let _client: Anthropic | null = null let _client: Anthropic | null = null
export function resetClient(): void { export function resetClient(): void {
@@ -29,7 +32,8 @@ function getClient(): Anthropic {
if (!apiKey || apiKey === 'your-api-key-here') { if (!apiKey || apiKey === 'your-api-key-here') {
throw new Error('API key not set. Open Borges → Preferences to configure it.') 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 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<void> { export async function streamPrompt(onChunk: (chunk: string) => void): Promise<void> {
const client = getClient() const client = getClient()
const { promptModel } = getAIConfig()
const stream = client.messages.stream({ const stream = client.messages.stream({
model: 'claude-haiku-4-5-20251001', model: promptModel ?? DEFAULT_PROMPT_MODEL,
max_tokens: 120, max_tokens: 120,
messages: [{ messages: [{
role: 'user', role: 'user',
@@ -118,6 +123,7 @@ export async function streamMessage(
onChunk: (chunk: string) => void onChunk: (chunk: string) => void
): Promise<void> { ): Promise<void> {
const client = getClient() const client = getClient()
const { model } = getAIConfig()
const messages: Anthropic.MessageParam[] = [ const messages: Anthropic.MessageParam[] = [
...payload.conversationHistory.slice(-10), ...payload.conversationHistory.slice(-10),
@@ -125,7 +131,7 @@ export async function streamMessage(
] ]
const stream = client.messages.stream({ const stream = client.messages.stream({
model: 'claude-sonnet-4-6', model: model ?? DEFAULT_MODEL,
max_tokens: 4096, max_tokens: 4096,
system: buildSystemPrompt(payload), system: buildSystemPrompt(payload),
messages messages

View File

@@ -2,24 +2,33 @@ import { join } from 'path'
import { homedir } from 'os' import { homedir } from 'os'
import { readFileSync, writeFileSync, mkdirSync } from 'fs' import { readFileSync, writeFileSync, mkdirSync } from 'fs'
export interface AIProviderConfig {
baseURL?: string
model?: string
promptModel?: string
}
export interface GlobalConfig { export interface GlobalConfig {
apiKey?: string apiKey?: string
collectionPath?: string collectionPath?: string
fontSize?: number fontSize?: number
theme?: 'dark' | 'light' theme?: 'dark' | 'light'
defaultWordCountTarget?: number defaultWordCountTarget?: number
ai?: AIProviderConfig
} }
const CONFIG_DIR = join(homedir(), '.borges') const CONFIG_DIR = join(homedir(), '.borges')
const CONFIG_FILE = join(CONFIG_DIR, 'config.json') const CONFIG_FILE = join(CONFIG_DIR, 'config.json')
let _config: GlobalConfig = {} function loadFromDisk(): GlobalConfig {
try { try {
_config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8')) return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'))
} catch { } catch {
// first run return {}
} }
}
let _config: GlobalConfig = loadFromDisk()
export const getCollectionRoot = (): string => export const getCollectionRoot = (): string =>
_config.collectionPath ?? join(homedir(), 'Documents', 'borges-collection') _config.collectionPath ?? join(homedir(), 'Documents', 'borges-collection')
@@ -27,6 +36,9 @@ export const getCollectionRoot = (): string =>
export const getApiKey = (): string | undefined => export const getApiKey = (): string | undefined =>
_config.apiKey ?? process.env.ANTHROPIC_API_KEY _config.apiKey ?? process.env.ANTHROPIC_API_KEY
export const getAIConfig = (): AIProviderConfig =>
_config.ai ?? {}
export function readGlobalConfig(): GlobalConfig { export function readGlobalConfig(): GlobalConfig {
return { ..._config } return { ..._config }
} }

View File

@@ -57,7 +57,7 @@ export function registerIpcHandlers(): void {
ipcMain.handle('config:read', async () => readGlobalConfig()) ipcMain.handle('config:read', async () => readGlobalConfig())
ipcMain.handle('config:write', async (_e, updates: Partial<GlobalConfig>) => { ipcMain.handle('config:write', async (_e, updates: Partial<GlobalConfig>) => {
writeGlobalConfig(updates) writeGlobalConfig(updates)
if (updates.apiKey !== undefined) resetClient() resetClient() // always reset — baseURL or model may have changed
}) })
ipcMain.handle('config:pickFolder', async (event): Promise<string | null> => { ipcMain.handle('config:pickFolder', async (event): Promise<string | null> => {
const win = BrowserWindow.fromWebContents(event.sender) const win = BrowserWindow.fromWebContents(event.sender)