From cffd1cec9069aa03d7f8361b64b72610432f541f Mon Sep 17 00:00:00 2001 From: altair823 Date: Thu, 7 May 2026 02:35:43 +0900 Subject: [PATCH] =?UTF-8?q?refactor(v027):=20OllamaSettingsModal=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20+=20onOpenOllamaSettings=20=EC=B1=84?= =?UTF-8?q?=EB=84=90=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/preload/index.ts | 5 - src/renderer/inbox/App.tsx | 11 +- .../inbox/components/OllamaSettingsModal.tsx | 140 ------------------ src/shared/types.ts | 1 - tests/unit/App.test.tsx | 1 - 5 files changed, 2 insertions(+), 156 deletions(-) delete mode 100644 src/renderer/inbox/components/OllamaSettingsModal.tsx diff --git a/src/preload/index.ts b/src/preload/index.ts index 76ae853..e80982b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -47,11 +47,6 @@ const api: InklingApi = { emitRecallSnoozed: (id: string) => ipcRenderer.invoke('inbox:emitRecallSnoozed', id), loadOllamaSettings: () => ipcRenderer.invoke('inbox:loadOllamaSettings'), saveOllamaSettings: (v: { endpoint: string; model: string }) => ipcRenderer.invoke('inbox:saveOllamaSettings', v), - onOpenOllamaSettings: (cb: () => void) => { - const handler = () => cb(); - ipcRenderer.on('inbox:openOllamaSettings', handler); - return () => ipcRenderer.removeListener('inbox:openOllamaSettings', handler); - }, // v0.2.7 Task 13 — 외부 (트레이) 에서 view 전환 요청 listener. onNavigate: (cb: (view: 'inbox' | 'trash' | 'settings') => void) => { const listener = (_e: unknown, view: 'inbox' | 'trash' | 'settings') => cb(view); diff --git a/src/renderer/inbox/App.tsx b/src/renderer/inbox/App.tsx index 85ed039..8421d73 100644 --- a/src/renderer/inbox/App.tsx +++ b/src/renderer/inbox/App.tsx @@ -12,7 +12,6 @@ import { TagUndoToast } from './components/TagUndoToast.js'; import { ExpiryBanner } from './components/ExpiryBanner.js'; import { FailedBanner } from './components/FailedBanner.js'; import { RecallBanner } from './components/RecallBanner.js'; -import { OllamaSettingsModal } from './components/OllamaSettingsModal.js'; import { SettingsPage } from './components/SettingsPage.js'; export function App(): React.ReactElement { @@ -25,7 +24,6 @@ export function App(): React.ReactElement { const showSettings = useInbox((s) => s.showSettings); const setShowSettings = useInbox((s) => s.setShowSettings); const [recoveryDismissed, setRecoveryDismissed] = useState(isRecoveryDismissedToday()); - const [ollamaSettingsOpen, setOllamaSettingsOpen] = useState(false); useEffect(() => { void loadInitial(); @@ -36,7 +34,6 @@ export function App(): React.ReactElement { const unsubOllama = inboxApi.onOllamaStatus((status) => { useInbox.setState({ ollamaStatus: status }); }); - const unsubOllamaSettings = inboxApi.onOpenOllamaSettings(() => setOllamaSettingsOpen(true)); const unsubNav = inboxApi.onNavigate((view) => { if (view === 'settings') { useInbox.getState().setShowSettings(true); @@ -50,7 +47,7 @@ export function App(): React.ReactElement { }); const onFocus = () => { void refreshMeta(); }; window.addEventListener('focus', onFocus); - return () => { unsubNote(); unsubOllama(); unsubOllamaSettings(); unsubNav(); window.removeEventListener('focus', onFocus); }; + return () => { unsubNote(); unsubOllama(); unsubNav(); window.removeEventListener('focus', onFocus); }; // onOllamaStatus 콜백은 useInbox.setState 직접 호출 — store reference 가 안정적이라 // deps array 에 추가 불필요. mount 시 1회 구독 + unmount 시 해제. }, [loadInitial, refreshMeta, upsertNote]); @@ -112,7 +109,7 @@ export function App(): React.ReactElement {
{!showTrash && ( <> - setOllamaSettingsOpen(true)} /> + setShowSettings(true)} /> { markRecoveryDismissed(); setRecoveryDismissed(true); }} @@ -187,10 +184,6 @@ export function App(): React.ReactElement { )}
- setOllamaSettingsOpen(false)} - /> ); } diff --git a/src/renderer/inbox/components/OllamaSettingsModal.tsx b/src/renderer/inbox/components/OllamaSettingsModal.tsx deleted file mode 100644 index 8400e59..0000000 --- a/src/renderer/inbox/components/OllamaSettingsModal.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { z } from 'zod'; -import { inboxApi } from '../api.js'; -import { DEFAULT_OLLAMA_ENDPOINT, DEFAULT_OLLAMA_MODEL } from '../../../shared/constants.js'; - -const EndpointSchema = z.string().url(); - -interface Props { - open: boolean; - onClose: () => void; -} - -export function OllamaSettingsModal({ open, onClose }: Props): React.ReactElement | null { - const [endpoint, setEndpoint] = useState(DEFAULT_OLLAMA_ENDPOINT); - const [model, setModel] = useState(DEFAULT_OLLAMA_MODEL); - const [error, setError] = useState(null); - const [saving, setSaving] = useState(false); - - // 마운트/open 시 현재 설정 fetch - useEffect(() => { - if (!open) return; - void inboxApi.loadOllamaSettings().then((s) => { - if (s) { - setEndpoint(s.endpoint); - setModel(s.model); - } - setError(null); - }); - }, [open]); - - if (!open) return null; - - async function handleSave() { - if (saving) return; // m4 fix: synchronous double-click 가드 - setSaving(true); - setError(null); - try { - // v0.2.6 #42 — client-side URL validation, server-side healthCheck 전에 명확한 메시지 - const parseResult = EndpointSchema.safeParse(endpoint); - if (!parseResult.success) { - setError('유효한 URL 형식이 아닙니다 (예: http://localhost:11434)'); - return; - } - if (model.trim().length === 0) { - setError('모델명을 입력하세요'); - return; - } - const r = await inboxApi.saveOllamaSettings({ endpoint, model }); - if (r.ok) { - onClose(); - } else { - setError(r.reason); - } - } catch (e) { - setError(String(e)); - } finally { - setSaving(false); - } - } - - 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 - }} - > -
-

Ollama 설정

-
- - 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} - /> -
-
- - setModel(e.target.value)} - placeholder="gemma4:e4b" - style={{ width: '100%', padding: '6px 8px', fontSize: 13, border: '1px solid #ccc', borderRadius: 4 }} - disabled={saving} - /> -
- {error && ( -
- 저장 실패: {error} -
- )} -
- - -
-
-
- ); -} diff --git a/src/shared/types.ts b/src/shared/types.ts index fe47154..cb1eb9f 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -105,7 +105,6 @@ export interface InboxApi { emitRecallSnoozed(id: string): Promise; loadOllamaSettings(): Promise<{ endpoint: string; model: string } | null>; saveOllamaSettings(v: { endpoint: string; model: string }): Promise<{ ok: true } | { ok: false; reason: string }>; - onOpenOllamaSettings(cb: () => void): () => void; // v0.2.7 Task 13 — 외부 (트레이 등) 에서 view 전환 요청 구독. onNavigate(cb: (view: 'inbox' | 'trash' | 'settings') => void): () => void; // v0.2.7 자동 실행 (Task 22 통일) — 진단 정보 포함 응답 diff --git a/tests/unit/App.test.tsx b/tests/unit/App.test.tsx index 317e59e..8e077f5 100644 --- a/tests/unit/App.test.tsx +++ b/tests/unit/App.test.tsx @@ -19,7 +19,6 @@ vi.mock('../../src/renderer/inbox/api.js', () => ({ listRecallCandidate: vi.fn(async () => null), onNoteUpdated: vi.fn(() => () => undefined), onOllamaStatus: vi.fn(() => () => undefined), - onOpenOllamaSettings: vi.fn(() => () => undefined), onNavigate: vi.fn(() => () => undefined), // 4 섹션 mounted 시 호출되는 stub loadOllamaSettings: vi.fn(async () => ({ endpoint: '', model: '' })),