// src/preload/index.ts // contextBridge로 렌더러에 노출할 API 정의 import { contextBridge, ipcRenderer } from 'electron' import { IPC_CHANNELS } from '@d3ro/core/ipc-channels' import type { AudioDevice, SetDeviceParams, TestDeviceParams, TestDeviceResult, AudioDeviceChangedEvent, AppConfig, ConfigGetParams, ConfigSetParams, ConfigResetParams, SetThemeParams, SetLanguageParams, ThemeMode, ConfigChangedEvent, VoiceState, VoiceMode, AudioLevelEvent, VoiceStateChangedEvent, TranscriptionDeltaEvent, TranscriptionCompleteEvent, VoiceErrorEvent, StartRecordingParams, StartRecordingResult, StopRecordingParams, StopRecordingResult, CancelRecordingParams, SetVoiceModeParams, STTStatus, STTModel, SetSTTModelParams, SetSTTLanguageParams, STTStatusChangedEvent, DownloadModelParams, DownloadProgressEvent, STTProviderType, STTProviderInfo, STTProviderConfig, SetSTTProviderParams, SetSTTProviderConfigParams, TestSTTConnectionParams, TestSTTConnectionResult, HotkeyBinding, SetHotkeyParams, SetEnabledParams, HotkeyTriggeredEvent, LLMStatus, LLMModel, LLMProcessParams, LLMProcessResult, SetLLMModelParams, SetServerUrlParams, LLMStatusChangedEvent, LLMProcessProgressEvent, HistoryQueryParams, HistoryPage, HistoryGetByIdParams, HistoryEntry, HistoryDeleteParams, HistorySearchParams, DictionaryQueryParams, DictionaryPage, DictionaryEntry, DictionaryAddParams, DictionaryUpdateParams, DictionaryDeleteParams, DictionarySearchParams, DictionaryImportParams, DictionaryImportResult, DictionaryExportParams, StatsSummary, PermissionStatus, // Phase 10 MemoTag, AddTagParams, RemoveTagParams, GetTagsParams, SearchByTagParams, ExportMemoParams, TagCount, VoiceCommandRule, VoiceCommandMatch, SetVoiceCommandKeywordsParams, SetVoiceCommandEnabledParams, CaptureContextResult, LLMChain, CreateChainParams, UpdateChainParams, DeleteChainParams, ExecuteChainParams, ChainExecutionResult, ChainProgress, CaptionState, CaptionSegment, CaptionConfig, // Phase 11 LicenseInfo, ActivateLicenseParams, ActivateLicenseResult, FeatureAccess, UsageQuota, UpgradePromptEvent, TierComparison, // Phase 12 FileTranscriptionStartParams, // Phase 13.1 ConversationSessionInfo, ConversationMessage, ConversationSendParams, ConversationAssistantDelta, ConversationAssistantMessage, ConversationError, RealtimeTokenParams, RealtimeTokenResult, // Phase 13.2 RAGDocument, RAGQueryParams, RAGQueryResult, RAGStateInfo, RAGIndexProgress, RAGAddDocumentParams, RAGRemoveDocumentParams, // Phase 13.3 VoiceActionExecuteParams, VoiceActionPreset, VoiceActionHistoryEntry, VoiceActionPlannedEvent, VoiceActionExecutedEvent, VoiceActionErrorEvent, FileTranscriptionProgress, FileTranscriptionResult, FileTranscriptionStateInfo, MeetingSummarizeParams, MeetingSummaryResult, MeetingSummaryGetParams, MeetingSummaryExportParams, MeetingSummaryProgress, DictationTemplate, CreateTemplateParams, UpdateTemplateParams, DeleteTemplateParams, StartTemplateSessionParams, SetFieldValueParams, TemplateSessionInfo, TemplateFieldCompletedEvent, TemplateSessionCompletedEvent, MeetingStartResult, MeetingMemo, MeetingAddMemoParams, MeetingModeStateInfo, MeetingSessionPage, MeetingGetSessionsParams, MeetingSessionDetail, MeetingGetSessionParams, MeetingDeleteSessionParams, MeetingExportParams, MeetingProcessingProgress, MeetingDocument, MeetingDocTemplate, MeetingGenerateDocParams, MeetingUpdateDocParams, MeetingDeleteDocParams, MeetingGetDocsParams, MeetingUpdateTranscriptParams, MeetingExportDocParams, MeetingExportTranscriptParams, MeetingDocGeneratingProgress, CreateMeetingDocTemplateParams, UpdateMeetingDocTemplateParams, DeleteMeetingDocTemplateParams, MeetingChatMessage, MeetingChatSendParams, MeetingChatDelta, DiarizeSessionParams, } from '@d3ro/core/types' import { Feature } from '@d3ro/core/types' import type { IPCResult } from '@d3ro/core/errors' /** 로컬 AI 런타임(사이드카 엔진/ffmpeg) 상태 — RuntimeProvisioner가 제공한다 */ type RuntimeComponentName = 'sidecar' | 'ffmpeg' interface RuntimeStatusPayload { component: RuntimeComponentName installed: boolean path: string sizeBytes: number } interface RuntimeProgressPayload { component: RuntimeComponentName phase: 'index' | 'downloading' | 'extracting' | 'done' percent: number downloadedBytes: number totalBytes: number bytesPerSecond: number } type Unsubscribe = () => void function invoke(channel: string, ...args: unknown[]): Promise> { return ipcRenderer.invoke(channel, ...args) } function send(channel: string, ...args: unknown[]): void { ipcRenderer.send(channel, ...args) } function on(channel: string, callback: (data: T) => void): Unsubscribe { const listener = (_event: Electron.IpcRendererEvent, data: T) => callback(data) ipcRenderer.on(channel, listener) return () => ipcRenderer.removeListener(channel, listener) } const electronAPI = { // ── Platform ─────────────────────────────────────────── // 'darwin' | 'win32' | 'linux' — 렌더러에서 키바인딩/단축키 표기 분기 platform: process.platform as NodeJS.Platform, // ── Audio ────────────────────────────────────────────── audio: { getDevices: () => invoke(IPC_CHANNELS.AUDIO.GET_DEVICES), getSelectedDevice: () => invoke(IPC_CHANNELS.AUDIO.GET_SELECTED_DEVICE), setSelectedDevice: (params: SetDeviceParams) => invoke(IPC_CHANNELS.AUDIO.SET_SELECTED_DEVICE, params), testDevice: (params: TestDeviceParams) => invoke(IPC_CHANNELS.AUDIO.TEST_DEVICE, params), stopTest: () => invoke(IPC_CHANNELS.AUDIO.STOP_TEST), onDeviceChanged: (cb: (e: AudioDeviceChangedEvent) => void): Unsubscribe => on(IPC_CHANNELS.AUDIO.DEVICE_CHANGED, cb), onTestLevel: (cb: (e: { level: number; done: boolean }) => void): Unsubscribe => on(IPC_CHANNELS.AUDIO.TEST_LEVEL, cb), }, // ── Config ───────────────────────────────────────────── config: { get: (params: ConfigGetParams) => invoke(IPC_CHANNELS.CONFIG.GET, params), set: (params: ConfigSetParams) => invoke(IPC_CHANNELS.CONFIG.SET, params), getAll: () => invoke(IPC_CHANNELS.CONFIG.GET_ALL), reset: (params: ConfigResetParams) => invoke(IPC_CHANNELS.CONFIG.RESET, params), getTheme: () => invoke(IPC_CHANNELS.CONFIG.GET_THEME), setTheme: (params: SetThemeParams) => invoke(IPC_CHANNELS.CONFIG.SET_THEME, params), getLanguage: () => invoke(IPC_CHANNELS.CONFIG.GET_LANGUAGE), setLanguage: (params: SetLanguageParams) => invoke(IPC_CHANNELS.CONFIG.SET_LANGUAGE, params), onChanged: (cb: (e: ConfigChangedEvent) => void): Unsubscribe => on(IPC_CHANNELS.CONFIG.CHANGED, cb) }, // ── Voice ────────────────────────────────────────────── voice: { startRecording: (params: StartRecordingParams) => invoke(IPC_CHANNELS.VOICE.START_RECORDING, params), stopRecording: (params: StopRecordingParams) => invoke(IPC_CHANNELS.VOICE.STOP_RECORDING, params), cancelRecording: (params: CancelRecordingParams) => invoke(IPC_CHANNELS.VOICE.CANCEL_RECORDING, params), getState: () => invoke(IPC_CHANNELS.VOICE.GET_STATE), setMode: (params: SetVoiceModeParams) => invoke(IPC_CHANNELS.VOICE.SET_MODE, params), getMode: () => invoke(IPC_CHANNELS.VOICE.GET_MODE), onStateChanged: (cb: (e: VoiceStateChangedEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE.STATE_CHANGED, cb), onTranscriptionDelta: (cb: (e: TranscriptionDeltaEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE.TRANSCRIPTION_DELTA, cb), onTranscriptionComplete: (cb: (e: TranscriptionCompleteEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE.TRANSCRIPTION_COMPLETE, cb), onError: (cb: (e: VoiceErrorEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE.ERROR, cb), onAudioLevel: (cb: (e: AudioLevelEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE.AUDIO_LEVEL, cb) }, // ── STT ──────────────────────────────────────────────── stt: { getStatus: () => invoke(IPC_CHANNELS.STT.GET_STATUS), getModels: () => invoke(IPC_CHANNELS.STT.GET_MODELS), getActiveModel: () => invoke(IPC_CHANNELS.STT.GET_ACTIVE_MODEL), setModel: (params: SetSTTModelParams) => invoke(IPC_CHANNELS.STT.SET_MODEL, params), downloadModel: (params: DownloadModelParams) => invoke(IPC_CHANNELS.STT.DOWNLOAD_MODEL, params), cancelDownload: () => invoke(IPC_CHANNELS.STT.CANCEL_DOWNLOAD), getLanguage: () => invoke(IPC_CHANNELS.STT.GET_LANGUAGE), setLanguage: (params: SetSTTLanguageParams) => invoke(IPC_CHANNELS.STT.SET_LANGUAGE, params), getProviders: () => invoke(IPC_CHANNELS.STT.GET_PROVIDERS), getActiveProvider: () => invoke(IPC_CHANNELS.STT.GET_ACTIVE_PROVIDER), setProvider: (params: SetSTTProviderParams) => invoke(IPC_CHANNELS.STT.SET_PROVIDER, params), getProviderConfig: (params: { provider: STTProviderType }) => invoke(IPC_CHANNELS.STT.GET_PROVIDER_CONFIG, params), setProviderConfig: (params: SetSTTProviderConfigParams) => invoke(IPC_CHANNELS.STT.SET_PROVIDER_CONFIG, params), testConnection: (params: TestSTTConnectionParams) => invoke(IPC_CHANNELS.STT.TEST_CONNECTION, params), onStatusChanged: (cb: (e: STTStatusChangedEvent) => void): Unsubscribe => on(IPC_CHANNELS.STT.STATUS_CHANGED, cb), onDownloadProgress: (cb: (e: DownloadProgressEvent) => void): Unsubscribe => on(IPC_CHANNELS.STT.DOWNLOAD_PROGRESS, cb) }, // ── Local AI runtime (엔진/ffmpeg — 필요할 때 내려받음) ── runtime: { getStatus: () => invoke(IPC_CHANNELS.RUNTIME.GET_STATUS), ensure: (params: { component: RuntimeComponentName }) => invoke<{ component: RuntimeComponentName; binaryPath: string }>( IPC_CHANNELS.RUNTIME.ENSURE, params, ), onProgress: (cb: (e: RuntimeProgressPayload) => void): Unsubscribe => on(IPC_CHANNELS.RUNTIME.PROGRESS, cb) }, // ── Hotkey ───────────────────────────────────────────── hotkey: { getDictationShortcut: () => invoke(IPC_CHANNELS.HOTKEY.GET_DICTATION_SHORTCUT), setDictationShortcut: (params: SetHotkeyParams) => invoke(IPC_CHANNELS.HOTKEY.SET_DICTATION_SHORTCUT, params), getHandsFreeShortcut: () => invoke(IPC_CHANNELS.HOTKEY.GET_HANDS_FREE_SHORTCUT), setHandsFreeShortcut: (params: SetHotkeyParams) => invoke(IPC_CHANNELS.HOTKEY.SET_HANDS_FREE_SHORTCUT, params), getCommandShortcut: () => invoke(IPC_CHANNELS.HOTKEY.GET_COMMAND_SHORTCUT), setCommandShortcut: (params: SetHotkeyParams) => invoke(IPC_CHANNELS.HOTKEY.SET_COMMAND_SHORTCUT, params), getCaptionShortcut: () => invoke(IPC_CHANNELS.HOTKEY.GET_CAPTION_SHORTCUT), setCaptionShortcut: (params: SetHotkeyParams) => invoke(IPC_CHANNELS.HOTKEY.SET_CAPTION_SHORTCUT, params), isEnabled: () => invoke(IPC_CHANNELS.HOTKEY.IS_ENABLED), setEnabled: (params: SetEnabledParams) => invoke(IPC_CHANNELS.HOTKEY.SET_ENABLED, params), onTriggered: (cb: (e: HotkeyTriggeredEvent) => void): Unsubscribe => on(IPC_CHANNELS.HOTKEY.TRIGGERED, cb) }, // ── LLM ──────────────────────────────────────────────── llm: { getStatus: () => invoke(IPC_CHANNELS.LLM.GET_STATUS), getModels: () => invoke(IPC_CHANNELS.LLM.GET_MODELS), getActiveModel: () => invoke(IPC_CHANNELS.LLM.GET_ACTIVE_MODEL), setModel: (params: SetLLMModelParams) => invoke(IPC_CHANNELS.LLM.SET_MODEL, params), process: (params: LLMProcessParams) => invoke(IPC_CHANNELS.LLM.PROCESS, params), cancelProcess: () => invoke(IPC_CHANNELS.LLM.CANCEL_PROCESS), getServerUrl: () => invoke(IPC_CHANNELS.LLM.GET_SERVER_URL), setServerUrl: (params: SetServerUrlParams) => invoke(IPC_CHANNELS.LLM.SET_SERVER_URL, params), onStatusChanged: (cb: (e: LLMStatusChangedEvent) => void): Unsubscribe => on(IPC_CHANNELS.LLM.STATUS_CHANGED, cb), onProcessProgress: (cb: (e: LLMProcessProgressEvent) => void): Unsubscribe => on(IPC_CHANNELS.LLM.PROCESS_PROGRESS, cb), pullModel: (params: { modelId: string }) => invoke(IPC_CHANNELS.LLM.PULL_MODEL, params), startServer: () => invoke<'running' | 'starting' | 'not-installed' | 'failed'>( IPC_CHANNELS.LLM.START_SERVER ), checkConnection: () => invoke<{ available: boolean; version: string | null; models: LLMModel[] }>( IPC_CHANNELS.LLM.CHECK_CONNECTION ), onPullProgress: ( cb: (e: { modelId: string status: string digest: string | null total: number completed: number percent: number }) => void ): Unsubscribe => on(IPC_CHANNELS.LLM.PULL_PROGRESS, cb), // Phase 3.2: Premium LLM premium: { getStatus: () => invoke<{ available: boolean; backend: 'local' | 'premium' }>( IPC_CHANNELS.LLM.PREMIUM_GET_STATUS ), getQuota: () => invoke<{ tier: 'free' | 'pro' | 'pro_plus' current: number limit: number overageCredits: number } | null>(IPC_CHANNELS.LLM.PREMIUM_GET_QUOTA), onFallback: (cb: (e: { reason: string }) => void): Unsubscribe => on(IPC_CHANNELS.LLM.PREMIUM_FALLBACK, cb), onQuotaWarning: ( cb: (e: { current: number; limit: number; overageCredits: number }) => void ): Unsubscribe => on(IPC_CHANNELS.LLM.PREMIUM_QUOTA_WARNING, cb), onUpgradeRequired: ( cb: (e: { reason: 'quota_exceeded' | 'model_not_allowed' | 'auth_required' }) => void ): Unsubscribe => on(IPC_CHANNELS.LLM.PREMIUM_UPGRADE_REQUIRED, cb), }, }, // ── History ──────────────────────────────────────────── history: { getAll: (params: HistoryQueryParams) => invoke(IPC_CHANNELS.HISTORY.GET_ALL, params), getById: (params: HistoryGetByIdParams) => invoke(IPC_CHANNELS.HISTORY.GET_BY_ID, params), delete: (params: HistoryDeleteParams) => invoke(IPC_CHANNELS.HISTORY.DELETE, params), deleteAll: () => invoke(IPC_CHANNELS.HISTORY.DELETE_ALL), search: (params: HistorySearchParams) => invoke(IPC_CHANNELS.HISTORY.SEARCH, params), onAdded: (cb: (e: HistoryEntry) => void): Unsubscribe => on(IPC_CHANNELS.HISTORY.ADDED, cb) }, // ── Dictionary ───────────────────────────────────────── dictionary: { getAll: (params: DictionaryQueryParams) => invoke(IPC_CHANNELS.DICTIONARY.GET_ALL, params), add: (params: DictionaryAddParams) => invoke(IPC_CHANNELS.DICTIONARY.ADD, params), update: (params: DictionaryUpdateParams) => invoke(IPC_CHANNELS.DICTIONARY.UPDATE, params), delete: (params: DictionaryDeleteParams) => invoke(IPC_CHANNELS.DICTIONARY.DELETE, params), search: (params: DictionarySearchParams) => invoke(IPC_CHANNELS.DICTIONARY.SEARCH, params), import: (params: DictionaryImportParams) => invoke(IPC_CHANNELS.DICTIONARY.IMPORT, params), export: (params: DictionaryExportParams) => invoke(IPC_CHANNELS.DICTIONARY.EXPORT, params) }, // ── Stats ────────────────────────────────────────────── stats: { getSummary: () => invoke(IPC_CHANNELS.STATS.GET_SUMMARY) }, // ── Window ───────────────────────────────────────────── window: { minimize: () => send(IPC_CHANNELS.WINDOW.MINIMIZE), maximize: () => send(IPC_CHANNELS.WINDOW.MAXIMIZE), close: () => send(IPC_CHANNELS.WINDOW.CLOSE), isMaximized: () => invoke(IPC_CHANNELS.WINDOW.IS_MAXIMIZED) }, // ── System ───────────────────────────────────────────── system: { getPlatform: () => invoke(IPC_CHANNELS.SYSTEM.GET_PLATFORM), getVersion: () => invoke(IPC_CHANNELS.SYSTEM.GET_VERSION), checkMicPermission: () => invoke(IPC_CHANNELS.SYSTEM.CHECK_MIC_PERMISSION), openExternal: (params: { url: string }) => invoke(IPC_CHANNELS.SYSTEM.OPEN_EXTERNAL, params), }, // ── Instruction (커스텀 명령어) ──────────────────────── instruction: { getAll: () => invoke('instruction:getAll'), getById: (params: { id: string }) => invoke('instruction:getById', params), create: (params: { name: string; description: string; prompt: string }) => invoke('instruction:create', params), update: (params: { id: string; name?: string; description?: string; prompt?: string }) => invoke('instruction:update', params), delete: (params: { id: string }) => invoke('instruction:delete', params), reorder: (params: { ids: string[] }) => invoke('instruction:reorder', params), }, // ── App 이벤트 (실시간 UI 갱신) ──────────────────────── app: { onDataChanged: (cb: (data: { type: string; activeId?: string }) => void): Unsubscribe => on('app:dataChanged', cb), }, // ── Phase 10: Memo Tags ───────────────────────────────── memo: { getTags: (params: GetTagsParams) => invoke(IPC_CHANNELS.MEMO.GET_TAGS, params), addTag: (params: AddTagParams) => invoke(IPC_CHANNELS.MEMO.ADD_TAG, params), removeTag: (params: RemoveTagParams) => invoke(IPC_CHANNELS.MEMO.REMOVE_TAG, params), getAllTags: () => invoke(IPC_CHANNELS.MEMO.GET_ALL_TAGS), searchByTag: (params: SearchByTagParams) => invoke(IPC_CHANNELS.MEMO.SEARCH_BY_TAG, params), export: (params: ExportMemoParams) => invoke(IPC_CHANNELS.MEMO.EXPORT, params), }, // ── Phase 10: Voice Commands ──────────────────────────── voiceCommand: { getAll: () => invoke(IPC_CHANNELS.VOICE_COMMAND.GET_ALL), setKeywords: (params: SetVoiceCommandKeywordsParams) => invoke(IPC_CHANNELS.VOICE_COMMAND.SET_KEYWORDS, params), setEnabled: (params: SetVoiceCommandEnabledParams) => invoke(IPC_CHANNELS.VOICE_COMMAND.SET_ENABLED, params), isEnabled: () => invoke(IPC_CHANNELS.VOICE_COMMAND.IS_ENABLED), onMatched: (cb: (data: VoiceCommandMatch) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_COMMAND.MATCHED, cb), }, // ── Phase 10: Screen Context ──────────────────────────── context: { capture: (captureSelectedText?: boolean) => invoke(IPC_CHANNELS.CONTEXT.CAPTURE, { captureSelectedText }), isEnabled: () => invoke(IPC_CHANNELS.CONTEXT.IS_ENABLED), setEnabled: (params: { enabled: boolean }) => invoke(IPC_CHANNELS.CONTEXT.SET_ENABLED, params), }, // ── Phase 10: LLM Chain ───────────────────────────────── chain: { getAll: () => invoke(IPC_CHANNELS.CHAIN.GET_ALL), create: (params: CreateChainParams) => invoke(IPC_CHANNELS.CHAIN.CREATE, params), update: (params: UpdateChainParams) => invoke(IPC_CHANNELS.CHAIN.UPDATE, params), delete: (params: DeleteChainParams) => invoke(IPC_CHANNELS.CHAIN.DELETE, params), execute: (params: ExecuteChainParams) => invoke(IPC_CHANNELS.CHAIN.EXECUTE, params), onProgress: (cb: (data: ChainProgress) => void): Unsubscribe => on(IPC_CHANNELS.CHAIN.PROGRESS, cb), }, // ── Phase 10: Live Caption ────────────────────────────── caption: { start: () => invoke(IPC_CHANNELS.CAPTION.START), stop: () => invoke(IPC_CHANNELS.CAPTION.STOP), getState: () => invoke(IPC_CHANNELS.CAPTION.GET_STATE), setConfig: (params: Partial) => invoke(IPC_CHANNELS.CAPTION.SET_CONFIG, params), getConfig: () => invoke(IPC_CHANNELS.CAPTION.GET_CONFIG), onSegment: (cb: (data: CaptionSegment) => void): Unsubscribe => on(IPC_CHANNELS.CAPTION.SEGMENT, cb), onDelta: (cb: (data: { text: string }) => void): Unsubscribe => on(IPC_CHANNELS.CAPTION.DELTA, cb), onStateChanged: (cb: (data: { state: CaptionState }) => void): Unsubscribe => on(IPC_CHANNELS.CAPTION.STATE_CHANGED, cb), /** 시스템 오디오 PCM 데이터를 메인에 전달 (렌더러 → 메인) */ sendSystemAudioData: (data: ArrayBuffer) => send(IPC_CHANNELS.CAPTION.SYSTEM_AUDIO_DATA, data), /** 메인에서 시스템 오디오 캡처 시작 요청 수신 */ onStartSystemAudio: (cb: () => void): Unsubscribe => on(IPC_CHANNELS.CAPTION.START_SYSTEM_AUDIO, cb), /** 메인에서 시스템 오디오 캡처 중지 요청 수신 */ onStopSystemAudio: (cb: () => void): Unsubscribe => on(IPC_CHANNELS.CAPTION.STOP_SYSTEM_AUDIO, cb), /** 시스템 오디오 루프백 활성화 (getDisplayMedia 전에 호출) */ enableLoopback: () => invoke('system-audio:enable-loopback'), /** 시스템 오디오 루프백 비활성화 */ disableLoopback: () => invoke('system-audio:disable-loopback'), }, // ── License (Phase 11) ──────────────────────────────── license: { getInfo: () => invoke(IPC_CHANNELS.LICENSE.GET_INFO), activate: (params: ActivateLicenseParams) => invoke(IPC_CHANNELS.LICENSE.ACTIVATE, params), deactivate: () => invoke(IPC_CHANNELS.LICENSE.DEACTIVATE), checkFeature: (feature: Feature) => invoke(IPC_CHANNELS.LICENSE.CHECK_FEATURE, { feature }), getUsage: (feature: Feature) => invoke(IPC_CHANNELS.LICENSE.GET_USAGE, { feature }), getAllUsage: () => invoke(IPC_CHANNELS.LICENSE.GET_ALL_USAGE), getTierComparison: () => invoke(IPC_CHANNELS.LICENSE.GET_TIER_COMPARISON), openBilling: (params: { tier: 'pro' | 'pro_plus' }) => invoke(IPC_CHANNELS.LICENSE.OPEN_BILLING, params), onUpgradePrompt: (cb: (e: UpgradePromptEvent) => void): Unsubscribe => on(IPC_CHANNELS.LICENSE.UPGRADE_PROMPT, cb), onTierChanged: (cb: (e: LicenseInfo) => void): Unsubscribe => on(IPC_CHANNELS.LICENSE.TIER_CHANGED, cb), }, // ── File Transcription (Phase 12.1) ──────────────────── fileTranscription: { start: (params: FileTranscriptionStartParams) => invoke(IPC_CHANNELS.FILE_TRANSCRIPTION.START, params), cancel: () => invoke(IPC_CHANNELS.FILE_TRANSCRIPTION.CANCEL), getState: () => invoke(IPC_CHANNELS.FILE_TRANSCRIPTION.GET_STATE), onProgress: (cb: (data: FileTranscriptionProgress) => void): Unsubscribe => on(IPC_CHANNELS.FILE_TRANSCRIPTION.PROGRESS, cb), onComplete: (cb: (data: FileTranscriptionResult) => void): Unsubscribe => on(IPC_CHANNELS.FILE_TRANSCRIPTION.COMPLETE, cb), onError: (cb: (data: { message: string }) => void): Unsubscribe => on(IPC_CHANNELS.FILE_TRANSCRIPTION.ERROR, cb), }, // ── Meeting Summary (Phase 12.2) ───────────────────── meetingSummary: { summarize: (params: MeetingSummarizeParams) => invoke(IPC_CHANNELS.MEETING_SUMMARY.SUMMARIZE, params), getSummary: (params: MeetingSummaryGetParams) => invoke(IPC_CHANNELS.MEETING_SUMMARY.GET_SUMMARY, params), exportMarkdown: (params: MeetingSummaryExportParams) => invoke(IPC_CHANNELS.MEETING_SUMMARY.EXPORT_MARKDOWN, params), onSummaryReady: (cb: (data: MeetingSummaryResult) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_SUMMARY.SUMMARY_READY, cb), onProgress: (cb: (data: MeetingSummaryProgress) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_SUMMARY.SUMMARY_PROGRESS, cb), }, // ── Dictation Templates (Phase 12.3) ───────────────── dictationTemplate: { getAll: () => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.GET_ALL), create: (params: CreateTemplateParams) => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.CREATE, params), update: (params: UpdateTemplateParams) => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.UPDATE, params), delete: (params: DeleteTemplateParams) => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.DELETE, params), startSession: (params: StartTemplateSessionParams) => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.START_SESSION, params), cancelSession: () => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.CANCEL_SESSION), getSessionState: () => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.GET_SESSION_STATE), setFieldValue: (params: SetFieldValueParams) => invoke(IPC_CHANNELS.DICTATION_TEMPLATE.SET_FIELD_VALUE, params), onSessionStateChanged: (cb: (data: TemplateSessionInfo) => void): Unsubscribe => on(IPC_CHANNELS.DICTATION_TEMPLATE.SESSION_STATE_CHANGED, cb), onFieldCompleted: (cb: (data: TemplateFieldCompletedEvent) => void): Unsubscribe => on(IPC_CHANNELS.DICTATION_TEMPLATE.FIELD_COMPLETED, cb), onSessionCompleted: (cb: (data: TemplateSessionCompletedEvent) => void): Unsubscribe => on(IPC_CHANNELS.DICTATION_TEMPLATE.SESSION_COMPLETED, cb), }, // ── RAG (Phase 13.2) ────────────────────────────────── rag: { addDocument: (params?: RAGAddDocumentParams) => invoke(IPC_CHANNELS.RAG.ADD_DOCUMENT, params ?? {}), removeDocument: (params: RAGRemoveDocumentParams) => invoke(IPC_CHANNELS.RAG.REMOVE_DOCUMENT, params), getDocuments: () => invoke(IPC_CHANNELS.RAG.GET_DOCUMENTS), query: (params: RAGQueryParams) => invoke(IPC_CHANNELS.RAG.QUERY, params), getState: () => invoke(IPC_CHANNELS.RAG.GET_STATE), reindex: (documentId: string) => invoke(IPC_CHANNELS.RAG.REINDEX, { documentId }), onIndexProgress: (cb: (data: RAGIndexProgress) => void): Unsubscribe => on(IPC_CHANNELS.RAG.INDEX_PROGRESS, cb), onIndexComplete: (cb: (data: { documentId: string; fileName: string }) => void): Unsubscribe => on(IPC_CHANNELS.RAG.INDEX_COMPLETE, cb), }, // ── Voice Action (Phase 13.3) ──────────────────────── voiceAction: { execute: (params: VoiceActionExecuteParams) => invoke(IPC_CHANNELS.VOICE_ACTION.EXECUTE, params), getPresets: () => invoke(IPC_CHANNELS.VOICE_ACTION.GET_PRESETS), getHistory: () => invoke(IPC_CHANNELS.VOICE_ACTION.GET_HISTORY), clearHistory: () => invoke(IPC_CHANNELS.VOICE_ACTION.CLEAR_HISTORY), setEnabled: (enabled: boolean) => invoke(IPC_CHANNELS.VOICE_ACTION.SET_ENABLED, { enabled }), isEnabled: () => invoke(IPC_CHANNELS.VOICE_ACTION.IS_ENABLED), onActionPlanned: (cb: (data: VoiceActionPlannedEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_ACTION.ACTION_PLANNED, cb), onActionExecuted: (cb: (data: VoiceActionExecutedEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_ACTION.ACTION_EXECUTED, cb), onActionError: (cb: (data: VoiceActionErrorEvent) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_ACTION.ACTION_ERROR, cb), }, // ── Voice Conversation (Phase 13.1) ─────────────────── voiceConversation: { startSession: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.START_SESSION), stopSession: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.STOP_SESSION), sendMessage: (params: ConversationSendParams) => invoke(IPC_CHANNELS.VOICE_CONVERSATION.SEND_MESSAGE, params), getState: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.GET_STATE), getHistory: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.GET_HISTORY), clearHistory: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.CLEAR_HISTORY), cancelResponse: () => invoke(IPC_CHANNELS.VOICE_CONVERSATION.CANCEL_RESPONSE), getRealtimeToken: (params: RealtimeTokenParams) => invoke( IPC_CHANNELS.VOICE_CONVERSATION.GET_REALTIME_TOKEN, params ), finishListening: () => invoke('voiceConversation:finishListening'), onStateChanged: (cb: (data: ConversationSessionInfo) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.STATE_CHANGED, cb), onUserMessage: (cb: (data: ConversationMessage) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.USER_MESSAGE, cb), onAssistantDelta: (cb: (data: ConversationAssistantDelta) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.ASSISTANT_DELTA, cb), onAssistantMessage: (cb: (data: ConversationAssistantMessage) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.ASSISTANT_MESSAGE, cb), onTTSStarted: (cb: () => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.TTS_STARTED, cb), onTTSFinished: (cb: () => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.TTS_FINISHED, cb), onError: (cb: (data: ConversationError) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.ERROR, cb), onAudioLevel: (cb: (e: { level: number }) => void): Unsubscribe => on(IPC_CHANNELS.VOICE_CONVERSATION.AUDIO_LEVEL, cb), }, // ── Meeting Mode (Phase 14) ─────────────────────────── meetingMode: { startRecording: (params?: { force?: boolean }) => invoke(IPC_CHANNELS.MEETING_MODE.START_RECORDING, params), forceReset: () => invoke('meetingMode:forceReset'), stopRecording: () => invoke(IPC_CHANNELS.MEETING_MODE.STOP_RECORDING), addMemo: (params: MeetingAddMemoParams) => invoke(IPC_CHANNELS.MEETING_MODE.ADD_MEMO, params), getState: () => invoke(IPC_CHANNELS.MEETING_MODE.GET_STATE), getSessions: (params: MeetingGetSessionsParams) => invoke(IPC_CHANNELS.MEETING_MODE.GET_SESSIONS, params), getSession: (params: MeetingGetSessionParams) => invoke(IPC_CHANNELS.MEETING_MODE.GET_SESSION, params), deleteSession: (params: MeetingDeleteSessionParams) => invoke(IPC_CHANNELS.MEETING_MODE.DELETE_SESSION, params), updateTitle: (params: { sessionId: string; title: string }) => invoke(IPC_CHANNELS.MEETING_MODE.UPDATE_TITLE, params), exportPdf: (params: MeetingExportParams) => invoke(IPC_CHANNELS.MEETING_MODE.EXPORT_PDF, params), exportMarkdown: (params: MeetingExportParams) => invoke(IPC_CHANNELS.MEETING_MODE.EXPORT_MARKDOWN, params), onStateChanged: (cb: (e: MeetingModeStateInfo) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.STATE_CHANGED, cb), onSegment: (cb: (e: CaptionSegment) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.SEGMENT, cb), onProcessingProgress: (cb: (e: MeetingProcessingProgress) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.PROCESSING_PROGRESS, cb), onSessionCompleted: (cb: (e: MeetingSessionDetail) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.SESSION_COMPLETED, cb), onError: (cb: (e: { code: number; message: string }) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.ERROR, cb), onAudioLevel: (cb: (e: { level: number }) => void): Unsubscribe => on('meetingMode:audioLevel', cb), // Phase 14.5 updateTranscript: (params: MeetingUpdateTranscriptParams) => invoke(IPC_CHANNELS.MEETING_MODE.UPDATE_TRANSCRIPT, params), generateDocument: (params: MeetingGenerateDocParams) => invoke(IPC_CHANNELS.MEETING_MODE.GENERATE_DOCUMENT, params), getDocuments: (params: MeetingGetDocsParams) => invoke(IPC_CHANNELS.MEETING_MODE.GET_DOCUMENTS, params), updateDocument: (params: MeetingUpdateDocParams) => invoke(IPC_CHANNELS.MEETING_MODE.UPDATE_DOCUMENT, params), deleteDocument: (params: MeetingDeleteDocParams) => invoke(IPC_CHANNELS.MEETING_MODE.DELETE_DOCUMENT, params), exportDocument: (params: MeetingExportDocParams) => invoke(IPC_CHANNELS.MEETING_MODE.EXPORT_DOCUMENT, params), exportTranscript: (params: MeetingExportTranscriptParams) => invoke(IPC_CHANNELS.MEETING_MODE.EXPORT_TRANSCRIPT, params), editSegment: (params: { sessionId: string; segmentId: string; text: string }) => invoke(IPC_CHANNELS.MEETING_MODE.EDIT_SEGMENT, params), onDocGeneratingProgress: (cb: (e: MeetingDocGeneratingProgress) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.DOC_GENERATING_PROGRESS, cb), // Phase 15 polishTranscript: (params: { sessionId: string }) => invoke(IPC_CHANNELS.MEETING_MODE.POLISH_TRANSCRIPT, params), // Phase 15.5 diarize: (params: DiarizeSessionParams) => invoke(IPC_CHANNELS.MEETING_MODE.DIARIZE, params), onDiarizationProgress: (cb: (e: { sessionId: string; percent: number }) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_MODE.DIARIZATION_PROGRESS, cb), }, // ── Meeting Chat (Phase 15) ─────────────────────────── meetingChat: { send: (params: MeetingChatSendParams) => invoke(IPC_CHANNELS.MEETING_CHAT.SEND, params), cancel: () => invoke(IPC_CHANNELS.MEETING_CHAT.CANCEL), clear: () => invoke(IPC_CHANNELS.MEETING_CHAT.CLEAR), onDelta: (cb: (e: MeetingChatDelta) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_CHAT.DELTA, cb), onMessage: (cb: (e: MeetingChatMessage) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_CHAT.MESSAGE, cb), onError: (cb: (e: { message: string }) => void): Unsubscribe => on(IPC_CHANNELS.MEETING_CHAT.ERROR, cb), }, // ── Meeting Doc Templates (Phase 14.5) ─────────────── meetingDocTemplate: { getAll: () => invoke(IPC_CHANNELS.MEETING_DOC_TEMPLATE.GET_ALL), create: (params: CreateMeetingDocTemplateParams) => invoke(IPC_CHANNELS.MEETING_DOC_TEMPLATE.CREATE, params), update: (params: UpdateMeetingDocTemplateParams) => invoke(IPC_CHANNELS.MEETING_DOC_TEMPLATE.UPDATE, params), delete: (params: DeleteMeetingDocTemplateParams) => invoke(IPC_CHANNELS.MEETING_DOC_TEMPLATE.DELETE, params), }, cloudSync: { getState: () => invoke<{ authenticated: boolean userEmail: string | null lastSyncAt: number | null syncing: boolean }>(IPC_CHANNELS.CLOUD_SYNC.GET_STATE), signIn: (params: { provider: 'google' | 'github' }) => invoke<{ started: boolean }>(IPC_CHANNELS.CLOUD_SYNC.SIGN_IN, params), signOut: () => invoke(IPC_CHANNELS.CLOUD_SYNC.SIGN_OUT), pushAll: () => invoke<{ pushed: number; errors: string[] }>(IPC_CHANNELS.CLOUD_SYNC.PUSH_ALL), pullAll: () => invoke<{ pushed: number; errors: string[] }>(IPC_CHANNELS.CLOUD_SYNC.PULL_ALL), onAuthChanged: (callback: (data: { user: { id: string; email: string | null } | null }) => void) => on<{ user: { id: string; email: string | null } | null }>( IPC_CHANNELS.CLOUD_SYNC.AUTH_CHANGED, callback ), onSyncProgress: (callback: (data: { current: number; total: number; table: string }) => void) => on<{ current: number; total: number; table: string }>( IPC_CHANNELS.CLOUD_SYNC.SYNC_PROGRESS, callback ), onSyncComplete: (callback: (data: { pushed: number; errors: string[] }) => void) => on<{ pushed: number; errors: string[] }>(IPC_CHANNELS.CLOUD_SYNC.SYNC_COMPLETE, callback), onSyncError: (callback: (data: { error: string }) => void) => on<{ error: string }>(IPC_CHANNELS.CLOUD_SYNC.SYNC_ERROR, callback), }, // ── Online Auth ──────────────────────────────────────── onlineAuth: { register: (params: { email: string; password: string }) => invoke(IPC_CHANNELS.ONLINE_AUTH.REGISTER, params), login: (params: { email: string; password: string }) => invoke(IPC_CHANNELS.ONLINE_AUTH.LOGIN, params), logout: () => invoke(IPC_CHANNELS.ONLINE_AUTH.LOGOUT), getUser: () => invoke<{ email: string | null; isAuthenticated: boolean; mode: 'online' | 'local' | null }>( IPC_CHANNELS.ONLINE_AUTH.GET_USER ) }, // ── Ad Monetization & Mediation ──────────────────────── ads: { getConfig: () => invoke(IPC_CHANNELS.ADS.GET_CONFIG), setConfig: (config: Partial) => invoke(IPC_CHANNELS.ADS.SET_CONFIG, config), requestAuction: (request: import('@d3ro/core/types').AdMediationAuctionRequest) => invoke(IPC_CHANNELS.ADS.REQUEST_AUCTION, request), recordImpression: (event: Omit) => invoke(IPC_CHANNELS.ADS.RECORD_IMPRESSION, event), recordClick: (params: { adId: string; networkId: string }) => invoke(IPC_CHANNELS.ADS.RECORD_CLICK, params), claimReward: (params: { adId: string; networkId: string }) => invoke(IPC_CHANNELS.ADS.CLAIM_REWARD, params), getRevenueStats: (params?: { period?: string }) => invoke(IPC_CHANNELS.ADS.GET_REVENUE_STATS, params), getSettlements: () => invoke(IPC_CHANNELS.ADS.GET_SETTLEMENTS), requestPayout: (params: { settlementId: string }) => invoke<{ success: boolean; message: string; settlement?: import('@d3ro/core/types').AdSettlementRecord }>( IPC_CHANNELS.ADS.REQUEST_PAYOUT, params ), getPublisherAccount: () => invoke(IPC_CHANNELS.ADS.GET_PUBLISHER_ACCOUNT), setPublisherAccount: (account: Partial) => invoke(IPC_CHANNELS.ADS.SET_PUBLISHER_ACCOUNT, account), }, // ── Customer Assistance (CA/CS) & Diagnostics ────────── support: { getDiagnostics: () => invoke(IPC_CHANNELS.SUPPORT.GET_DIAGNOSTICS), queryAI: (params: import('@d3ro/core/types').AIAssistQuery) => invoke(IPC_CHANNELS.SUPPORT.QUERY_AI, params), createTicket: (params: Partial) => invoke(IPC_CHANNELS.SUPPORT.CREATE_TICKET, params), checkRefund: () => invoke(IPC_CHANNELS.SUPPORT.CHECK_REFUND), }, // ── Multi-PG Payment & Billing ───────────────────────── payment: { createCheckoutSession: (params: import('@d3ro/core/types').CheckoutSessionParams) => invoke(IPC_CHANNELS.PAYMENT.CREATE_CHECKOUT_SESSION, params), verifyPayment: () => invoke(IPC_CHANNELS.PAYMENT.VERIFY_PAYMENT), getSubscriptionStatus: () => invoke(IPC_CHANNELS.PAYMENT.GET_SUBSCRIPTION_STATUS), }, } as const contextBridge.exposeInMainWorld('electronAPI', electronAPI) export type ElectronAPI = typeof electronAPI