From cee39a90aac390dfaa2e974255f3d542935470e9 Mon Sep 17 00:00:00 2001 From: altair823 Date: Mon, 4 May 2026 23:36:46 +0900 Subject: [PATCH] =?UTF-8?q?feat(ollama):=20index=20=EB=B6=80=ED=8C=85=20+?= =?UTF-8?q?=20IPC=20+=20preload=20+=20types=20(v0.2.3.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - index.ts: SettingsService.load() 후 endpoint/model 결정 (settings > env > default) - IPC: inbox:loadOllamaSettings + inbox:saveOllamaSettings - save: 임시 provider 로 healthCheck 통과 시에만 영속화 + holder.replace - 기존 in-flight generate 는 abort?.() (optional method) - preload + InboxApi shared types Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/index.ts | 20 ++++++++++++++++---- src/main/ipc/inboxApi.ts | 22 ++++++++++++++++++++++ src/preload/index.ts | 4 +++- src/shared/types.ts | 2 ++ 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index a4ccea5..f5c0d56 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -30,6 +30,7 @@ import { ExportService } from './services/ExportService.js'; import { ImportService } from './services/ImportService.js'; import { SyncService } from './services/SyncService.js'; import { TelemetryService } from './services/TelemetryService.js'; +import { SettingsService } from './services/SettingsService.js'; const HIDDEN_ARG = '--hidden'; const startedHidden = process.argv.includes(HIDDEN_ARG); @@ -64,12 +65,23 @@ app.whenReady().then(async () => { const continuity = new ContinuityService(db); const intent = new IntentService(repo); - const resolvedEndpoint = process.env.INKLING_OLLAMA_ENDPOINT ?? 'http://localhost:11434'; + const settingsSvc = new SettingsService(paths.profileDir); + const settings = await settingsSvc.load(); + + const resolvedEndpoint = settings.ollama?.endpoint + ?? process.env.INKLING_OLLAMA_ENDPOINT + ?? 'http://localhost:11434'; + const resolvedModel = settings.ollama?.model ?? 'gemma4:e4b'; + logger.info('ai.endpoint', { endpoint: resolvedEndpoint, - fromEnv: process.env.INKLING_OLLAMA_ENDPOINT !== undefined + model: resolvedModel, + source: settings.ollama?.endpoint + ? 'settings' + : (process.env.INKLING_OLLAMA_ENDPOINT ? 'env' : 'default') }); - const provider = new LocalOllamaProvider({ endpoint: resolvedEndpoint }); + + const provider = new LocalOllamaProvider({ endpoint: resolvedEndpoint, model: resolvedModel }); const providerHolder = new ProviderHolder(provider); const health = new HealthChecker(providerHolder, { onUpdate: (status) => { @@ -116,7 +128,7 @@ app.whenReady().then(async () => { registerCaptureApi(capture, getQuickCaptureWindow); registerInboxApi({ repo, continuity, capture, health, intent, - getInboxWindow + getInboxWindow, settings: settingsSvc, providerHolder }); const hotkeys = new HotkeyService(); diff --git a/src/main/ipc/inboxApi.ts b/src/main/ipc/inboxApi.ts index 5579cb8..312de17 100644 --- a/src/main/ipc/inboxApi.ts +++ b/src/main/ipc/inboxApi.ts @@ -8,6 +8,9 @@ import type { HealthChecker } from '../services/HealthChecker.js'; import type { IntentService } from '../services/IntentService.js'; import type { Note } from '@shared/types'; import type { HealthResult } from '../ai/InferenceProvider.js'; +import { LocalOllamaProvider } from '../ai/LocalOllamaProvider.js'; +import type { SettingsService } from '../services/SettingsService.js'; +import type { ProviderHolder } from '../ai/ProviderHolder.js'; export interface InboxIpcDeps { repo: NoteRepository; @@ -16,6 +19,8 @@ export interface InboxIpcDeps { health: HealthChecker; intent: IntentService; getInboxWindow: () => BrowserWindow | null; + settings: SettingsService; + providerHolder: ProviderHolder; } export function registerInboxApi(deps: InboxIpcDeps): void { @@ -142,6 +147,23 @@ export function registerInboxApi(deps: InboxIpcDeps): void { ipcMain.handle('inbox:dismissRecall', (_e, id: string) => deps.capture.dismissRecall(id)); ipcMain.handle('inbox:emitRecallShown', (_e, id: string) => deps.capture.emitRecallShown(id)); ipcMain.handle('inbox:emitRecallSnoozed', (_e, id: string) => deps.capture.emitRecallSnoozed(id)); + + ipcMain.handle('inbox:loadOllamaSettings', async () => { + const s = await deps.settings.load(); + return s.ollama ?? null; + }); + + ipcMain.handle('inbox:saveOllamaSettings', async (_e, value: { endpoint: string; model: string }) => { + // 검증: 새 인스턴스로 healthCheck + const trial = new LocalOllamaProvider({ endpoint: value.endpoint, model: value.model }); + const r = await trial.healthCheck(); + if (!r.ok) return { ok: false, reason: r.reason ?? 'unknown' }; + await deps.settings.setOllama(value); + deps.providerHolder.get().abort?.(); + deps.providerHolder.replace(trial); + // HealthChecker 의 다음 polling cycle (~60s) 에 새 endpoint 반영 + return { ok: true }; + }); } export function pushNoteUpdated(getWin: () => BrowserWindow | null, note: Note): void { diff --git a/src/preload/index.ts b/src/preload/index.ts index eaa043f..b81e134 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -44,7 +44,9 @@ const api: InklingApi = { markRecallOpened: (id: string) => ipcRenderer.invoke('inbox:markRecallOpened', id), dismissRecall: (id: string) => ipcRenderer.invoke('inbox:dismissRecall', id), emitRecallShown: (id: string) => ipcRenderer.invoke('inbox:emitRecallShown', id), - emitRecallSnoozed: (id: string) => ipcRenderer.invoke('inbox:emitRecallSnoozed', id) + emitRecallSnoozed: (id: string) => ipcRenderer.invoke('inbox:emitRecallSnoozed', id), + loadOllamaSettings: () => ipcRenderer.invoke('inbox:loadOllamaSettings'), + saveOllamaSettings: (v: { endpoint: string; model: string }) => ipcRenderer.invoke('inbox:saveOllamaSettings', v) } }; diff --git a/src/shared/types.ts b/src/shared/types.ts index e1dccea..1ff4146 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -89,6 +89,8 @@ export interface InboxApi { dismissRecall(id: string): Promise<{ note: Note }>; emitRecallShown(id: string): Promise; emitRecallSnoozed(id: string): Promise; + loadOllamaSettings(): Promise<{ endpoint: string; model: string } | null>; + saveOllamaSettings(v: { endpoint: string; model: string }): Promise<{ ok: true } | { ok: false; reason: string }>; } export interface InklingApi {