- #15: IPC channel inbox:delete → inbox:trash (semantic = soft delete) channel name 만 변경, InboxApi method name (deleteNote) 은 backward compat 유지 - #29: getTopUsedTags(20) → VOCAB_TOP_N const (튜닝 자체는 dogfood telemetry 후) - #42: OllamaSettingsModal client-side URL validation (zod safeParse pre-check) + model 빈 문자열 가드. server-side healthCheck 전에 친화적 에러 메시지. - #9: 휴지통 회수율 ratio 의미 1줄 코멘트 (event-level, unique-note 아님) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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})`;
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user