✨ custom models
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -2,31 +2,43 @@ 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 {
|
return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'))
|
||||||
_config = JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'))
|
} catch {
|
||||||
} catch {
|
return {}
|
||||||
// first run
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
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 }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user