diff --git a/src/main/ai/AiWorker.ts b/src/main/ai/AiWorker.ts index f7469ad..17e9206 100644 --- a/src/main/ai/AiWorker.ts +++ b/src/main/ai/AiWorker.ts @@ -6,6 +6,9 @@ import { parseAllCandidates } from '../services/dueDateParser.js'; import { ZodError } from 'zod'; import { kstTodayAsDate, kstTodayIso } from '../../shared/util/kstDate.js'; +// v0.2.6 #29 — backlog 의 top-N 튜닝은 dogfood telemetry 후 (현재 magic 만 추출). +const VOCAB_TOP_N = 20; + function classifyReason(err: unknown): AiFailedReason { if (err instanceof ZodError) return 'schema'; const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase(); @@ -124,7 +127,7 @@ export class AiWorker { const todayDate = kstTodayAsDate(nowDate); const todayIso = kstTodayIso(nowDate); const candidates = parseAllCandidates(note.rawText, todayDate); - const vocab = this.repo.getTopUsedTags(20); + const vocab = this.repo.getTopUsedTags(VOCAB_TOP_N); const res = await this.holder.get().generate({ text: note.rawText, todayKst: todayIso, diff --git a/src/main/ipc/inboxApi.ts b/src/main/ipc/inboxApi.ts index 97a9819..16d1b81 100644 --- a/src/main/ipc/inboxApi.ts +++ b/src/main/ipc/inboxApi.ts @@ -39,7 +39,7 @@ export function registerInboxApi(deps: InboxIpcDeps): void { deps.repo.setDueDate(arg.noteId, arg.date); }); - ipcMain.handle('inbox:delete', async (_e, noteId: string) => { + ipcMain.handle('inbox:trash', async (_e, noteId: string) => { await deps.capture.deleteNote(noteId); }); diff --git a/src/main/services/telemetryStats.ts b/src/main/services/telemetryStats.ts index 5222797..8c99cc3 100644 --- a/src/main/services/telemetryStats.ts +++ b/src/main/services/telemetryStats.ts @@ -139,6 +139,8 @@ export function aggregateStats(events: TelemetryEvent[], generatedAt: Date): Sta const aiTotal = aiSucceeded + aiFailed; const successRate = aiTotal === 0 ? 'N/A' : `${(aiSucceeded / aiTotal * 100).toFixed(1)}% (${aiSucceeded}/${aiTotal})`; const avgDuration = durationN === 0 ? 'N/A' : `${Math.round(durationSum / durationN)}`; + // v0.2.6 #9 — 회수율 = restore / trash event 비율 (event-level — 한 노트 trash-restore 반복 시 + // 100% 가능, unique-note 회수율 아님. spec §6.2 "회수 도구 동작?" 질문에 충분). const trashRecoveryRate = trashCount === 0 ? 'N/A' : `${(restoreCount / trashCount * 100).toFixed(1)}% (${restoreCount}/${trashCount})`; diff --git a/src/preload/index.ts b/src/preload/index.ts index 1ba817c..4281f5a 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -12,7 +12,7 @@ const api: InklingApi = { updateAiFields: (noteId, fields) => ipcRenderer.invoke('inbox:updateAi', { noteId, fields }), setDueDate: (noteId, date) => ipcRenderer.invoke('inbox:setDueDate', { noteId, date }), - deleteNote: (noteId) => ipcRenderer.invoke('inbox:delete', noteId), + deleteNote: (noteId) => ipcRenderer.invoke('inbox:trash', noteId), setIntent: (noteId, text) => ipcRenderer.invoke('inbox:setIntent', { noteId, text }), dismissIntent: (noteId) => ipcRenderer.invoke('inbox:dismissIntent', noteId), getContinuity: () => ipcRenderer.invoke('inbox:continuity'), diff --git a/src/renderer/inbox/components/OllamaSettingsModal.tsx b/src/renderer/inbox/components/OllamaSettingsModal.tsx index 5eadc22..8400e59 100644 --- a/src/renderer/inbox/components/OllamaSettingsModal.tsx +++ b/src/renderer/inbox/components/OllamaSettingsModal.tsx @@ -1,7 +1,10 @@ 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; @@ -32,6 +35,16 @@ export function OllamaSettingsModal({ open, onClose }: Props): React.ReactElemen 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();