diff --git a/src/main/ai/LocalOllamaProvider.ts b/src/main/ai/LocalOllamaProvider.ts index 44ce522..b276b23 100644 --- a/src/main/ai/LocalOllamaProvider.ts +++ b/src/main/ai/LocalOllamaProvider.ts @@ -2,6 +2,7 @@ import { request } from 'undici'; import { parseAiResponse, type AiResponse } from './schema.js'; import { buildPrompt } from './prompt.js'; import type { GenerateInput, HealthResult, InferenceProvider } from './InferenceProvider.js'; +import { DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL } from '../../shared/constants.js'; export interface LocalOllamaOptions { endpoint?: string; @@ -21,8 +22,8 @@ export class LocalOllamaProvider implements InferenceProvider { private abortController: AbortController | null = null; constructor(opts: LocalOllamaOptions = {}) { - this.endpoint = opts.endpoint ?? 'http://localhost:11434'; - this.model = opts.model ?? 'gemma4:e4b'; + this.endpoint = opts.endpoint ?? DEFAULT_OLLAMA_ENDPOINT; + this.model = opts.model ?? DEFAULT_OLLAMA_MODEL; this.timeoutMs = opts.timeoutMs ?? 120_000; this.temperature = opts.temperature ?? 0.2; this.numPredict = opts.numPredict ?? 512; diff --git a/src/main/index.ts b/src/main/index.ts index 14a16c1..f4f3932 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -31,6 +31,7 @@ import { ImportService } from './services/ImportService.js'; import { SyncService } from './services/SyncService.js'; import { TelemetryService } from './services/TelemetryService.js'; import { SettingsService } from './services/SettingsService.js'; +import { DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL } from '../shared/constants.js'; const HIDDEN_ARG = '--hidden'; const startedHidden = process.argv.includes(HIDDEN_ARG); @@ -70,8 +71,8 @@ app.whenReady().then(async () => { const resolvedEndpoint = settings.ollama?.endpoint ?? process.env.INKLING_OLLAMA_ENDPOINT - ?? 'http://localhost:11434'; - const resolvedModel = settings.ollama?.model ?? 'gemma4:e4b'; + ?? DEFAULT_OLLAMA_ENDPOINT; + const resolvedModel = settings.ollama?.model ?? DEFAULT_OLLAMA_MODEL; logger.info('ai.endpoint', { endpoint: resolvedEndpoint, diff --git a/src/main/ipc/inboxApi.ts b/src/main/ipc/inboxApi.ts index 644317e..97a9819 100644 --- a/src/main/ipc/inboxApi.ts +++ b/src/main/ipc/inboxApi.ts @@ -158,7 +158,11 @@ export function registerInboxApi(deps: InboxIpcDeps): void { 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); + try { + await deps.settings.setOllama(value); + } catch (e) { + return { ok: false, reason: `persist failed: ${(e as Error).message}` }; + } deps.providerHolder.get().abort?.(); deps.providerHolder.replace(trial); // 즉시 health 재확인 → onUpdate callback 통해 OllamaBanner 자동 갱신 diff --git a/src/renderer/inbox/components/OllamaSettingsModal.tsx b/src/renderer/inbox/components/OllamaSettingsModal.tsx index ac39122..5eadc22 100644 --- a/src/renderer/inbox/components/OllamaSettingsModal.tsx +++ b/src/renderer/inbox/components/OllamaSettingsModal.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { inboxApi } from '../api.js'; +import { DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL } from '../../../shared/constants.js'; interface Props { open: boolean; @@ -7,8 +8,8 @@ interface Props { } export function OllamaSettingsModal({ open, onClose }: Props): React.ReactElement | null { - const [endpoint, setEndpoint] = useState('http://localhost:11434'); - const [model, setModel] = useState('gemma4:e4b'); + const [endpoint, setEndpoint] = useState(DEFAULT_OLLAMA_ENDPOINT); + const [model, setModel] = useState(DEFAULT_OLLAMA_MODEL); const [error, setError] = useState(null); const [saving, setSaving] = useState(false); @@ -27,6 +28,7 @@ export function OllamaSettingsModal({ open, onClose }: Props): React.ReactElemen if (!open) return null; async function handleSave() { + if (saving) return; // m4 fix: synchronous double-click 가드 setSaving(true); setError(null); try { @@ -44,10 +46,17 @@ export function OllamaSettingsModal({ open, onClose }: Props): React.ReactElemen } return ( -
+
{ + if (e.key === 'Escape' && !saving) onClose(); + if (e.key === 'Enter' && !saving) void handleSave(); + }} + tabIndex={-1} + style={{ + position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)', + display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000 + }} + >
setEndpoint(e.target.value)} placeholder="http://localhost:11434" + autoFocus style={{ width: '100%', padding: '6px 8px', fontSize: 13, border: '1px solid #ccc', borderRadius: 4 }} disabled={saving} /> diff --git a/src/shared/constants.ts b/src/shared/constants.ts new file mode 100644 index 0000000..a258531 --- /dev/null +++ b/src/shared/constants.ts @@ -0,0 +1,2 @@ +export const DEFAULT_OLLAMA_MODEL = 'gemma4:e4b'; +export const DEFAULT_OLLAMA_ENDPOINT = 'http://localhost:11434';