From 9c8ba8ad09354d4aa105ad0e91c021f2945648a6 Mon Sep 17 00:00:00 2001 From: altair823 Date: Thu, 7 May 2026 02:18:32 +0900 Subject: [PATCH] =?UTF-8?q?feat(v027):=20createTray=20wiring=203-callback?= =?UTF-8?q?=20+=20refreshTray=20=ED=98=B8=EC=B6=9C=EB=B6=80=20=EC=8A=AC?= =?UTF-8?q?=EB=A6=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/index.ts | 186 +++------------------------------------------- 1 file changed, 9 insertions(+), 177 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 7942642..264bbb1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -19,7 +19,7 @@ import { ProviderHolder } from './ai/ProviderHolder.js'; import { AiWorker } from './ai/AiWorker.js'; import { registerCaptureApi } from './ipc/captureApi.js'; import { registerInboxApi, pushNoteUpdated, pushOllamaStatus } from './ipc/inboxApi.js'; -import { registerSettingsApi } from './ipc/settingsApi.js'; +import { registerSettingsApi, navigateInbox } from './ipc/settingsApi.js'; import { createInboxWindow, getInboxWindow } from './windows/inboxWindow.js'; import { createQuickCaptureWindow, showQuickCapture, getQuickCaptureWindow @@ -121,7 +121,6 @@ app.whenReady().then(async () => { onUpdate: (status) => { logger.info('ai.health', { ...status } as Record); pushOllamaStatus(getInboxWindow, status); - refreshTray({ ollamaOk: status.ok }); }, onTelemetry: (ev) => { if (ev.kind === 'ollama_unreachable') { @@ -139,7 +138,8 @@ app.whenReady().then(async () => { onUpdate: (note) => { pushNoteUpdated(getInboxWindow, note); // F4-C: AI 처리 완료 = 새 캡처가 inbox 에 합류한 시점, tray 도 즉시 갱신. - refreshTray({ todayCount: repo.countToday(), failedCount: repo.countFailed() }); + // v0.2.7 Phase 3 — failedCount 메뉴 항목 제거됨 → todayCount 만 갱신. + refreshTray({ todayCount: repo.countToday() }); }, logger, telemetry @@ -231,188 +231,20 @@ app.whenReady().then(async () => { }); }); + // v0.2.7 Phase 3 (Task 16) — TrayCallbacks 슬림: 10 → 3. + // 백업/내보내기/복원/동기화/사용 로그/Ollama 재확인/AI 재처리/Ollama 설정/정보 → + // 모두 설정 페이지로 이전 (registerSettingsApi 의 IPC 핸들러가 본문 보유). createTray({ showInbox: () => createInboxWindow(), showCapture: () => showQuickCapture(), - runBackup: async () => { - try { - const r = await backup.runDaily(); - new Notification({ - title: 'Inkling', - body: r.snapshotted - ? `백업 완료 — ${r.removed?.length ?? 0}개 정리` - : `오늘 백업이 이미 있습니다`, - silent: true - }).show(); - } catch (e) { - logger.warn('backup.manual.failed', { reason: String(e) }); - new Notification({ - title: 'Inkling', - body: '백업을 만들지 못했습니다.', - silent: true - }).show(); - } - }, - runExport: async () => { - const win = getInboxWindow(); - const dialogOpts: Electron.OpenDialogOptions = { - title: '내보낼 폴더 선택', - message: '선택한 폴더에 노트를 마크다운으로 내보냅니다. 이미지가 함께 포함됩니다. raw_text 가 평문으로 보관되니 비공개 위치를 권장합니다.', - buttonLabel: '여기에 내보내기', - properties: ['openDirectory', 'createDirectory'] - }; - const result = win - ? await dialog.showOpenDialog(win, dialogOpts) - : await dialog.showOpenDialog(dialogOpts); - if (result.canceled || result.filePaths.length === 0) return; - try { - const r = await exportSvc.export(result.filePaths[0]!, { includeMedia: true }); - logger.info('export.done', { - outDir: r.outDir, - noteCount: r.noteCount, - mediaCount: r.mediaCount, - bytes: r.bytes - }); - new Notification({ - title: 'Inkling', - body: `내보내기 완료 — 노트 ${r.noteCount}개, 이미지 ${r.mediaCount}개`, - silent: true - }).show(); - } catch (e) { - logger.warn('export.failed', { reason: String(e) }); - new Notification({ - title: 'Inkling', - body: '내보내기를 완료하지 못했습니다.', - silent: true - }).show(); - } - }, - runImport: async () => { - const win = getInboxWindow(); - const dirOpts: Electron.OpenDialogOptions = { - title: '복원할 백업 폴더 선택', - message: 'F5 export 형식의 폴더를 선택하세요. notes/ 하위의 마크다운 파일이 적재됩니다.', - buttonLabel: '여기서 복원', - properties: ['openDirectory'] - }; - const dirResult = win - ? await dialog.showOpenDialog(win, dirOpts) - : await dialog.showOpenDialog(dirOpts); - if (dirResult.canceled || dirResult.filePaths.length === 0) return; - const sourceDir = dirResult.filePaths[0]!; - let plan; - try { - plan = await importSvc.preview(sourceDir); - } catch (e) { - logger.warn('import.preview.failed', { reason: String(e) }); - new Notification({ - title: 'Inkling', - body: '백업 폴더를 읽지 못했습니다.', - silent: true - }).show(); - return; - } - const detail = `총 ${plan.total}개 노트\n · 신규 ${plan.newCount}개\n · 동일 (스킵) ${plan.unchangedCount}개\n · 충돌→새 id (${plan.forkedCount}개, raw_text 보존)\n\n이미지 ${plan.mediaCount}개 복사 예정.`; - const confirmOpts: Electron.MessageBoxOptions = { - type: 'question', - buttons: ['복원', '취소'], - defaultId: 0, - cancelId: 1, - title: 'Inkling 복원', - message: '복원 미리보기', - detail - }; - const confirm = win - ? await dialog.showMessageBox(win, confirmOpts) - : await dialog.showMessageBox(confirmOpts); - if (confirm.response !== 0) return; - try { - const r = await importSvc.run(sourceDir); - logger.info('import.done', { - total: r.total, - new: r.newCount, - unchanged: r.unchangedCount, - forked: r.forkedCount, - media: r.mediaCount - }); - new Notification({ - title: 'Inkling', - body: `복원 완료 — 신규 ${r.newCount}개, 스킵 ${r.unchangedCount}개, 충돌 ${r.forkedCount}개`, - silent: true - }).show(); - } catch (e) { - logger.warn('import.run.failed', { reason: String(e) }); - new Notification({ - title: 'Inkling', - body: '복원을 완료하지 못했습니다.', - silent: true - }).show(); - } - }, - runSync: async () => { - // runSync — 트레이 "지금 동기화" - try { - const r = await syncSvc.sync(); - if (!r.ok) { - logger.warn('sync.failed', { reason: r.reason }); - const body = r.reason === 'not_configured' - ? `${syncSvc.getSyncDir()} 에서 git init + remote 설정이 필요합니다.` - : '동기화를 완료하지 못했습니다.'; - new Notification({ title: 'Inkling', body, silent: true }).show(); - return; - } - if (r.changed) { - logger.info('sync.done', { sha: r.sha, pushed: r.pushed }); - new Notification({ title: 'Inkling', body: '동기화 완료', silent: true }).show(); - } else { - new Notification({ title: 'Inkling', body: '변경 사항 없음', silent: true }).show(); - } - } catch (e) { - logger.warn('sync.exception', { reason: String(e) }); - new Notification({ title: 'Inkling', body: '동기화를 완료하지 못했습니다.', silent: true }).show(); - } - }, - runExportTelemetry: async () => { - const win = getInboxWindow(); - const dialogOpts: Electron.OpenDialogOptions = { - title: '사용 로그를 내보낼 폴더 선택', - message: '선택한 폴더에 events.jsonl + stats.md 가 생성됩니다. raw_text/요약/제목/태그 이름은 미포함입니다.', - buttonLabel: '여기로 내보내기', - properties: ['openDirectory', 'createDirectory'] - }; - const result = win - ? await dialog.showOpenDialog(win, dialogOpts) - : await dialog.showOpenDialog(dialogOpts); - if (result.canceled || result.filePaths.length === 0) return; - try { - const r = await telemetry.exportTo(result.filePaths[0]!); - logger.info('telemetry.export', { eventCount: r.eventCount, outDir: result.filePaths[0] }); - new Notification({ - title: 'Inkling', - body: `사용 로그 내보내기 완료 — ${r.eventCount}개 이벤트`, - silent: true - }).show(); - } catch (e) { - logger.warn('telemetry.export.failed', { reason: String(e) }); - new Notification({ - title: 'Inkling', - body: '사용 로그 내보내기를 완료하지 못했습니다.', - silent: true - }).show(); - } - }, - runOllamaRecheck: () => { void health.runOnce({ manual: true }); }, - runRetryAllFailed: () => { void capture.retryAllFailed(); }, - runOpenOllamaSettings: () => { - const win = getInboxWindow(); - if (win) win.webContents.send('inbox:openOllamaSettings'); - } + showSettings: () => navigateInbox('settings') }); // F4-C 환경 앵커 — tray tooltip + 메뉴 첫 항목을 오늘 KST 캡처 수로 갱신. // 초기 1회 + 60s interval. AiWorker.onUpdate 도 별도 갱신 트리거. // cleanup 은 위 통합 before-quit 핸들러에서 처리. - refreshTray({ todayCount: repo.countToday(), failedCount: repo.countFailed() }); + // v0.2.7 Phase 3 — failedCount 메뉴 항목 제거됨 → todayCount 만 갱신. + refreshTray({ todayCount: repo.countToday() }); trayInterval = setInterval(() => { refreshTray({ todayCount: repo.countToday() }); }, 60_000);