diff --git a/src/preload/index.ts b/src/preload/index.ts new file mode 100644 index 0000000..7024fc6 --- /dev/null +++ b/src/preload/index.ts @@ -0,0 +1,27 @@ +import { contextBridge, ipcRenderer } from 'electron'; +import type { InklingApi, Note } from '@shared/types'; + +const api: InklingApi = { + capture: { + submit: (payload) => ipcRenderer.invoke('capture:submit', payload), + hide: () => ipcRenderer.send('capture:hide') + }, + inbox: { + listNotes: (opts) => ipcRenderer.invoke('inbox:list', opts), + updateAiFields: (noteId, fields) => + ipcRenderer.invoke('inbox:updateAi', { noteId, fields }), + deleteNote: (noteId) => ipcRenderer.invoke('inbox:delete', noteId), + setIntent: (noteId, text) => ipcRenderer.invoke('inbox:setIntent', { noteId, text }), + dismissIntent: (noteId) => ipcRenderer.invoke('inbox:dismissIntent', noteId), + getContinuity: () => ipcRenderer.invoke('inbox:continuity'), + getPendingCount: () => ipcRenderer.invoke('inbox:pendingCount'), + getOllamaStatus: () => ipcRenderer.invoke('inbox:ollamaStatus'), + onNoteUpdated: (cb) => { + const listener = (_e: unknown, note: Note) => cb(note); + ipcRenderer.on('note:updated', listener); + return () => ipcRenderer.off('note:updated', listener); + } + } +}; + +contextBridge.exposeInMainWorld('inkling', api); diff --git a/src/shared/types.ts b/src/shared/types.ts index 431d541..3029e63 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,6 +1,74 @@ declare global { - namespace Electron { - interface App { isQuitting?: boolean; } - } + namespace Electron { interface App { isQuitting?: boolean; } } + interface Window { inkling: InklingApi; } } + +export interface NoteMedia { + id: string; + kind: 'image'; + relPath: string; + mime: string; + bytes: number; +} + +export type AiStatus = 'pending' | 'done' | 'failed'; + +export interface NoteTag { + name: string; + source: 'ai' | 'user'; +} + +export interface Note { + id: string; + rawText: string; + aiTitle: string | null; + aiSummary: string | null; + aiStatus: AiStatus; + aiError: string | null; + aiProvider: string | null; + aiGeneratedAt: string | null; + titleEditedByUser: boolean; + summaryEditedByUser: boolean; + userIntent: string | null; + intentPromptedAt: string | null; + createdAt: string; + updatedAt: string; + tags: NoteTag[]; + media: NoteMedia[]; +} + +export interface WeeklyContinuity { + weekStart: string; // ISO date (KST 월요일) + weekCount: number; + weekTarget: number; // 7 + consecutiveCompleteWeeks: number; + showRecoveryToast: boolean; + lastNoteAt: string | null; +} + +export interface CaptureApi { + submit(payload: { text: string; images: ArrayBuffer[] }): Promise<{ noteId: string }>; + hide(): void; +} + +export interface InboxApi { + listNotes(opts: { limit: number; cursor?: string }): Promise; + updateAiFields( + noteId: string, + fields: { title?: string; summary?: string; tags?: string[] } + ): Promise; + deleteNote(noteId: string): Promise; + setIntent(noteId: string, text: string): Promise; + dismissIntent(noteId: string): Promise; + getContinuity(): Promise; + getPendingCount(): Promise; + getOllamaStatus(): Promise<{ ok: boolean; reason?: string }>; + onNoteUpdated(cb: (note: Note) => void): () => void; +} + +export interface InklingApi { + capture: CaptureApi; + inbox: InboxApi; +} + export {};