feat(V2-1a): Monorepo 구조 전환 — apps/desktop으로 V1 이동
- npm workspaces 루트 (apps/*, packages/*) 세팅 - V1 전체를 apps/desktop/으로 git mv (src, resources, tests, sidecar, scripts, electron.vite.config.ts, electron-builder.yml, vitest.config.ts, tsconfig.node.json, tsconfig.web.json) - apps/desktop/package.json 신규 (name=@d3ro/desktop) - productName: 'd3ro-voice' 명시 — app.getName()을 고정하여 userData 경로 %APPDATA%\d3ro-voice\ 그대로 유지 (기존 DB/설정 연속성 보장) - 루트 package.json을 workspace 루트로 재구성, 공통 devDep만 유지 (typescript, eslint, prettier) - turbo.json, tsconfig.base.json 추가 (Turborepo 자체 설치는 별도 sub-phase) - memory/project_status.md 생성 (규칙 13) 검증: - npm run typecheck 통과 - npm run build 통과 (electron-vite main+preload+renderer) - npm run dev 실제 실행 → DB/핫키/Ollama 자동 실행 모두 정상
This commit is contained in:
parent
3a160b9032
commit
45a580878a
178 changed files with 214 additions and 0 deletions
683
apps/desktop/src/preload/index.ts
Normal file
683
apps/desktop/src/preload/index.ts
Normal file
|
|
@ -0,0 +1,683 @@
|
|||
// src/preload/index.ts
|
||||
// contextBridge로 렌더러에 노출할 API 정의
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
import { IPC_CHANNELS } from '@shared/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,
|
||||
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,
|
||||
StatsSummary,
|
||||
PermissionStatus,
|
||||
// Phase 10
|
||||
MemoTag,
|
||||
AddTagParams,
|
||||
RemoveTagParams,
|
||||
GetTagsParams,
|
||||
SearchByTagParams,
|
||||
ExportMemoParams,
|
||||
TagCount,
|
||||
VoiceCommandRule,
|
||||
VoiceCommandMatch,
|
||||
SetVoiceCommandKeywordsParams,
|
||||
SetVoiceCommandEnabledParams,
|
||||
CaptureContextResult,
|
||||
ScreenContext,
|
||||
LLMChain,
|
||||
CreateChainParams,
|
||||
UpdateChainParams,
|
||||
DeleteChainParams,
|
||||
ExecuteChainParams,
|
||||
ChainExecutionResult,
|
||||
ChainProgress,
|
||||
CaptionState,
|
||||
CaptionSegment,
|
||||
CaptionConfig,
|
||||
CaptionSessionSummary,
|
||||
// Phase 11
|
||||
LicenseInfo,
|
||||
ActivateLicenseParams,
|
||||
ActivateLicenseResult,
|
||||
FeatureAccess,
|
||||
UsageQuota,
|
||||
UpgradePromptEvent,
|
||||
TierComparison,
|
||||
// Phase 12
|
||||
FileTranscriptionStartParams,
|
||||
// Phase 13.1
|
||||
ConversationSessionInfo,
|
||||
ConversationMessage,
|
||||
ConversationSendParams,
|
||||
ConversationAssistantDelta,
|
||||
ConversationAssistantMessage,
|
||||
ConversationError,
|
||||
// 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,
|
||||
CaptionSegment,
|
||||
MeetingDocument,
|
||||
MeetingDocTemplate,
|
||||
MeetingGenerateDocParams,
|
||||
MeetingGenerateDocResult,
|
||||
MeetingUpdateDocParams,
|
||||
MeetingDeleteDocParams,
|
||||
MeetingGetDocsParams,
|
||||
MeetingUpdateTranscriptParams,
|
||||
MeetingExportDocParams,
|
||||
MeetingExportFormat,
|
||||
MeetingDocGeneratingProgress,
|
||||
CreateMeetingDocTemplateParams,
|
||||
UpdateMeetingDocTemplateParams,
|
||||
DeleteMeetingDocTemplateParams,
|
||||
MeetingChatMessage,
|
||||
MeetingChatSendParams,
|
||||
MeetingChatDelta,
|
||||
DiarizeSessionParams,
|
||||
} from '@shared/types'
|
||||
import { Feature } from '@shared/types'
|
||||
import type { IPCResult } from '@shared/errors'
|
||||
|
||||
type Unsubscribe = () => void
|
||||
|
||||
function invoke<TResult>(channel: string, ...args: unknown[]): Promise<IPCResult<TResult>> {
|
||||
return ipcRenderer.invoke(channel, ...args)
|
||||
}
|
||||
|
||||
function send(channel: string, ...args: unknown[]): void {
|
||||
ipcRenderer.send(channel, ...args)
|
||||
}
|
||||
|
||||
function on<T>(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 = {
|
||||
// ── Audio ──────────────────────────────────────────────
|
||||
audio: {
|
||||
getDevices: () => invoke<AudioDevice[]>(IPC_CHANNELS.AUDIO.GET_DEVICES),
|
||||
getSelectedDevice: () => invoke<string | null>(IPC_CHANNELS.AUDIO.GET_SELECTED_DEVICE),
|
||||
setSelectedDevice: (params: SetDeviceParams) =>
|
||||
invoke<void>(IPC_CHANNELS.AUDIO.SET_SELECTED_DEVICE, params),
|
||||
testDevice: (params: TestDeviceParams) =>
|
||||
invoke<TestDeviceResult>(IPC_CHANNELS.AUDIO.TEST_DEVICE, params),
|
||||
onDeviceChanged: (cb: (e: AudioDeviceChangedEvent) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.AUDIO.DEVICE_CHANGED, cb),
|
||||
onTestLevel: (cb: (e: { level: number }) => void): Unsubscribe =>
|
||||
on('audio:testLevel', cb),
|
||||
},
|
||||
|
||||
// ── Config ─────────────────────────────────────────────
|
||||
config: {
|
||||
get: (params: ConfigGetParams) => invoke<unknown>(IPC_CHANNELS.CONFIG.GET, params),
|
||||
set: (params: ConfigSetParams) => invoke<void>(IPC_CHANNELS.CONFIG.SET, params),
|
||||
getAll: () => invoke<AppConfig>(IPC_CHANNELS.CONFIG.GET_ALL),
|
||||
reset: (params: ConfigResetParams) => invoke<void>(IPC_CHANNELS.CONFIG.RESET, params),
|
||||
getTheme: () => invoke<ThemeMode>(IPC_CHANNELS.CONFIG.GET_THEME),
|
||||
setTheme: (params: SetThemeParams) => invoke<void>(IPC_CHANNELS.CONFIG.SET_THEME, params),
|
||||
getLanguage: () => invoke<string>(IPC_CHANNELS.CONFIG.GET_LANGUAGE),
|
||||
setLanguage: (params: SetLanguageParams) =>
|
||||
invoke<void>(IPC_CHANNELS.CONFIG.SET_LANGUAGE, params),
|
||||
onChanged: (cb: (e: ConfigChangedEvent) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.CONFIG.CHANGED, cb)
|
||||
},
|
||||
|
||||
// ── Voice ──────────────────────────────────────────────
|
||||
voice: {
|
||||
startRecording: (params: StartRecordingParams) =>
|
||||
invoke<StartRecordingResult>(IPC_CHANNELS.VOICE.START_RECORDING, params),
|
||||
stopRecording: (params: StopRecordingParams) =>
|
||||
invoke<StopRecordingResult>(IPC_CHANNELS.VOICE.STOP_RECORDING, params),
|
||||
cancelRecording: (params: CancelRecordingParams) =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE.CANCEL_RECORDING, params),
|
||||
getState: () => invoke<VoiceState>(IPC_CHANNELS.VOICE.GET_STATE),
|
||||
setMode: (params: SetVoiceModeParams) =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE.SET_MODE, params),
|
||||
getMode: () => invoke<VoiceMode>(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<STTStatus>(IPC_CHANNELS.STT.GET_STATUS),
|
||||
getModels: () => invoke<STTModel[]>(IPC_CHANNELS.STT.GET_MODELS),
|
||||
getActiveModel: () => invoke<string | null>(IPC_CHANNELS.STT.GET_ACTIVE_MODEL),
|
||||
setModel: (params: SetSTTModelParams) =>
|
||||
invoke<void>(IPC_CHANNELS.STT.SET_MODEL, params),
|
||||
getLanguage: () => invoke<string>(IPC_CHANNELS.STT.GET_LANGUAGE),
|
||||
setLanguage: (params: SetSTTLanguageParams) =>
|
||||
invoke<void>(IPC_CHANNELS.STT.SET_LANGUAGE, params),
|
||||
onStatusChanged: (cb: (e: STTStatusChangedEvent) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.STT.STATUS_CHANGED, cb)
|
||||
},
|
||||
|
||||
// ── Hotkey ─────────────────────────────────────────────
|
||||
hotkey: {
|
||||
getDictationShortcut: () =>
|
||||
invoke<HotkeyBinding>(IPC_CHANNELS.HOTKEY.GET_DICTATION_SHORTCUT),
|
||||
setDictationShortcut: (params: SetHotkeyParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HOTKEY.SET_DICTATION_SHORTCUT, params),
|
||||
getHandsFreeShortcut: () =>
|
||||
invoke<HotkeyBinding>(IPC_CHANNELS.HOTKEY.GET_HANDS_FREE_SHORTCUT),
|
||||
setHandsFreeShortcut: (params: SetHotkeyParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HOTKEY.SET_HANDS_FREE_SHORTCUT, params),
|
||||
getCommandShortcut: () =>
|
||||
invoke<HotkeyBinding>(IPC_CHANNELS.HOTKEY.GET_COMMAND_SHORTCUT),
|
||||
setCommandShortcut: (params: SetHotkeyParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HOTKEY.SET_COMMAND_SHORTCUT, params),
|
||||
getCaptionShortcut: () =>
|
||||
invoke<HotkeyBinding>(IPC_CHANNELS.HOTKEY.GET_CAPTION_SHORTCUT),
|
||||
setCaptionShortcut: (params: SetHotkeyParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HOTKEY.SET_CAPTION_SHORTCUT, params),
|
||||
isEnabled: () => invoke<boolean>(IPC_CHANNELS.HOTKEY.IS_ENABLED),
|
||||
setEnabled: (params: SetEnabledParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HOTKEY.SET_ENABLED, params),
|
||||
onTriggered: (cb: (e: HotkeyTriggeredEvent) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.HOTKEY.TRIGGERED, cb)
|
||||
},
|
||||
|
||||
// ── LLM ────────────────────────────────────────────────
|
||||
llm: {
|
||||
getStatus: () => invoke<LLMStatus>(IPC_CHANNELS.LLM.GET_STATUS),
|
||||
getModels: () => invoke<LLMModel[]>(IPC_CHANNELS.LLM.GET_MODELS),
|
||||
getActiveModel: () => invoke<string | null>(IPC_CHANNELS.LLM.GET_ACTIVE_MODEL),
|
||||
setModel: (params: SetLLMModelParams) =>
|
||||
invoke<void>(IPC_CHANNELS.LLM.SET_MODEL, params),
|
||||
process: (params: LLMProcessParams) =>
|
||||
invoke<LLMProcessResult>(IPC_CHANNELS.LLM.PROCESS, params),
|
||||
cancelProcess: () => invoke<void>(IPC_CHANNELS.LLM.CANCEL_PROCESS),
|
||||
getServerUrl: () => invoke<string>(IPC_CHANNELS.LLM.GET_SERVER_URL),
|
||||
setServerUrl: (params: SetServerUrlParams) =>
|
||||
invoke<void>(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)
|
||||
},
|
||||
|
||||
// ── History ────────────────────────────────────────────
|
||||
history: {
|
||||
getAll: (params: HistoryQueryParams) =>
|
||||
invoke<HistoryPage>(IPC_CHANNELS.HISTORY.GET_ALL, params),
|
||||
getById: (params: HistoryGetByIdParams) =>
|
||||
invoke<HistoryEntry | null>(IPC_CHANNELS.HISTORY.GET_BY_ID, params),
|
||||
delete: (params: HistoryDeleteParams) =>
|
||||
invoke<void>(IPC_CHANNELS.HISTORY.DELETE, params),
|
||||
deleteAll: () => invoke<void>(IPC_CHANNELS.HISTORY.DELETE_ALL),
|
||||
search: (params: HistorySearchParams) =>
|
||||
invoke<HistoryPage>(IPC_CHANNELS.HISTORY.SEARCH, params),
|
||||
onAdded: (cb: (e: HistoryEntry) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.HISTORY.ADDED, cb)
|
||||
},
|
||||
|
||||
// ── Dictionary ─────────────────────────────────────────
|
||||
dictionary: {
|
||||
getAll: (params: DictionaryQueryParams) =>
|
||||
invoke<DictionaryPage>(IPC_CHANNELS.DICTIONARY.GET_ALL, params),
|
||||
add: (params: DictionaryAddParams) =>
|
||||
invoke<DictionaryEntry>(IPC_CHANNELS.DICTIONARY.ADD, params),
|
||||
update: (params: DictionaryUpdateParams) =>
|
||||
invoke<DictionaryEntry>(IPC_CHANNELS.DICTIONARY.UPDATE, params),
|
||||
delete: (params: DictionaryDeleteParams) =>
|
||||
invoke<void>(IPC_CHANNELS.DICTIONARY.DELETE, params),
|
||||
search: (params: DictionarySearchParams) =>
|
||||
invoke<DictionaryPage>(IPC_CHANNELS.DICTIONARY.SEARCH, params)
|
||||
},
|
||||
|
||||
// ── Stats ──────────────────────────────────────────────
|
||||
stats: {
|
||||
getSummary: () => invoke<StatsSummary>(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<boolean>(IPC_CHANNELS.WINDOW.IS_MAXIMIZED)
|
||||
},
|
||||
|
||||
// ── System ─────────────────────────────────────────────
|
||||
system: {
|
||||
getPlatform: () => invoke<NodeJS.Platform>(IPC_CHANNELS.SYSTEM.GET_PLATFORM),
|
||||
getVersion: () => invoke<string>(IPC_CHANNELS.SYSTEM.GET_VERSION),
|
||||
checkMicPermission: () =>
|
||||
invoke<PermissionStatus>(IPC_CHANNELS.SYSTEM.CHECK_MIC_PERMISSION),
|
||||
openExternal: (params: { url: string }) =>
|
||||
invoke<void>(IPC_CHANNELS.SYSTEM.OPEN_EXTERNAL, params),
|
||||
},
|
||||
|
||||
// ── Instruction (커스텀 명령어) ────────────────────────
|
||||
instruction: {
|
||||
getAll: () => invoke<unknown[]>('instruction:getAll'),
|
||||
getById: (params: { id: string }) => invoke<unknown>('instruction:getById', params),
|
||||
create: (params: { name: string; description: string; prompt: string }) =>
|
||||
invoke<unknown>('instruction:create', params),
|
||||
update: (params: { id: string; name?: string; description?: string; prompt?: string }) =>
|
||||
invoke<unknown>('instruction:update', params),
|
||||
delete: (params: { id: string }) => invoke<void>('instruction:delete', params),
|
||||
reorder: (params: { ids: string[] }) => invoke<void>('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<MemoTag[]>(IPC_CHANNELS.MEMO.GET_TAGS, params),
|
||||
addTag: (params: AddTagParams) => invoke<MemoTag>(IPC_CHANNELS.MEMO.ADD_TAG, params),
|
||||
removeTag: (params: RemoveTagParams) => invoke<void>(IPC_CHANNELS.MEMO.REMOVE_TAG, params),
|
||||
getAllTags: () => invoke<TagCount[]>(IPC_CHANNELS.MEMO.GET_ALL_TAGS),
|
||||
searchByTag: (params: SearchByTagParams) => invoke<HistoryPage>(IPC_CHANNELS.MEMO.SEARCH_BY_TAG, params),
|
||||
export: (params: ExportMemoParams) => invoke<string>(IPC_CHANNELS.MEMO.EXPORT, params),
|
||||
},
|
||||
|
||||
// ── Phase 10: Voice Commands ────────────────────────────
|
||||
voiceCommand: {
|
||||
getAll: () => invoke<VoiceCommandRule[]>(IPC_CHANNELS.VOICE_COMMAND.GET_ALL),
|
||||
setKeywords: (params: SetVoiceCommandKeywordsParams) => invoke<void>(IPC_CHANNELS.VOICE_COMMAND.SET_KEYWORDS, params),
|
||||
setEnabled: (params: SetVoiceCommandEnabledParams) => invoke<void>(IPC_CHANNELS.VOICE_COMMAND.SET_ENABLED, params),
|
||||
isEnabled: () => invoke<boolean>(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<CaptureContextResult>(IPC_CHANNELS.CONTEXT.CAPTURE, { captureSelectedText }),
|
||||
isEnabled: () => invoke<boolean>(IPC_CHANNELS.CONTEXT.IS_ENABLED),
|
||||
setEnabled: (params: { enabled: boolean }) => invoke<void>(IPC_CHANNELS.CONTEXT.SET_ENABLED, params),
|
||||
},
|
||||
|
||||
// ── Phase 10: LLM Chain ─────────────────────────────────
|
||||
chain: {
|
||||
getAll: () => invoke<LLMChain[]>(IPC_CHANNELS.CHAIN.GET_ALL),
|
||||
create: (params: CreateChainParams) => invoke<LLMChain>(IPC_CHANNELS.CHAIN.CREATE, params),
|
||||
update: (params: UpdateChainParams) => invoke<LLMChain>(IPC_CHANNELS.CHAIN.UPDATE, params),
|
||||
delete: (params: DeleteChainParams) => invoke<void>(IPC_CHANNELS.CHAIN.DELETE, params),
|
||||
execute: (params: ExecuteChainParams) => invoke<ChainExecutionResult>(IPC_CHANNELS.CHAIN.EXECUTE, params),
|
||||
onProgress: (cb: (data: ChainProgress) => void): Unsubscribe =>
|
||||
on(IPC_CHANNELS.CHAIN.PROGRESS, cb),
|
||||
},
|
||||
|
||||
// ── Phase 10: Live Caption ──────────────────────────────
|
||||
caption: {
|
||||
start: () => invoke<void>(IPC_CHANNELS.CAPTION.START),
|
||||
stop: () => invoke<void>(IPC_CHANNELS.CAPTION.STOP),
|
||||
getState: () => invoke<CaptionState>(IPC_CHANNELS.CAPTION.GET_STATE),
|
||||
setConfig: (params: Partial<CaptionConfig>) => invoke<void>(IPC_CHANNELS.CAPTION.SET_CONFIG, params),
|
||||
getConfig: () => invoke<CaptionConfig>(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<void>('system-audio:enable-loopback'),
|
||||
/** 시스템 오디오 루프백 비활성화 */
|
||||
disableLoopback: () => invoke<void>('system-audio:disable-loopback'),
|
||||
},
|
||||
|
||||
// ── License (Phase 11) ────────────────────────────────
|
||||
license: {
|
||||
getInfo: () =>
|
||||
invoke<LicenseInfo>(IPC_CHANNELS.LICENSE.GET_INFO),
|
||||
activate: (params: ActivateLicenseParams) =>
|
||||
invoke<ActivateLicenseResult>(IPC_CHANNELS.LICENSE.ACTIVATE, params),
|
||||
deactivate: () =>
|
||||
invoke<void>(IPC_CHANNELS.LICENSE.DEACTIVATE),
|
||||
checkFeature: (feature: Feature) =>
|
||||
invoke<FeatureAccess>(IPC_CHANNELS.LICENSE.CHECK_FEATURE, { feature }),
|
||||
getUsage: (feature: Feature) =>
|
||||
invoke<UsageQuota>(IPC_CHANNELS.LICENSE.GET_USAGE, { feature }),
|
||||
getAllUsage: () =>
|
||||
invoke<UsageQuota[]>(IPC_CHANNELS.LICENSE.GET_ALL_USAGE),
|
||||
getTierComparison: () =>
|
||||
invoke<TierComparison[]>(IPC_CHANNELS.LICENSE.GET_TIER_COMPARISON),
|
||||
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<FileTranscriptionResult>(IPC_CHANNELS.FILE_TRANSCRIPTION.START, params),
|
||||
cancel: () =>
|
||||
invoke<void>(IPC_CHANNELS.FILE_TRANSCRIPTION.CANCEL),
|
||||
getState: () =>
|
||||
invoke<FileTranscriptionStateInfo>(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<MeetingSummaryResult>(IPC_CHANNELS.MEETING_SUMMARY.SUMMARIZE, params),
|
||||
getSummary: (params: MeetingSummaryGetParams) =>
|
||||
invoke<MeetingSummaryResult | null>(IPC_CHANNELS.MEETING_SUMMARY.GET_SUMMARY, params),
|
||||
exportMarkdown: (params: MeetingSummaryExportParams) =>
|
||||
invoke<string>(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<DictationTemplate[]>(IPC_CHANNELS.DICTATION_TEMPLATE.GET_ALL),
|
||||
create: (params: CreateTemplateParams) =>
|
||||
invoke<DictationTemplate>(IPC_CHANNELS.DICTATION_TEMPLATE.CREATE, params),
|
||||
update: (params: UpdateTemplateParams) =>
|
||||
invoke<DictationTemplate>(IPC_CHANNELS.DICTATION_TEMPLATE.UPDATE, params),
|
||||
delete: (params: DeleteTemplateParams) =>
|
||||
invoke<void>(IPC_CHANNELS.DICTATION_TEMPLATE.DELETE, params),
|
||||
startSession: (params: StartTemplateSessionParams) =>
|
||||
invoke<void>(IPC_CHANNELS.DICTATION_TEMPLATE.START_SESSION, params),
|
||||
cancelSession: () =>
|
||||
invoke<void>(IPC_CHANNELS.DICTATION_TEMPLATE.CANCEL_SESSION),
|
||||
getSessionState: () =>
|
||||
invoke<TemplateSessionInfo | null>(IPC_CHANNELS.DICTATION_TEMPLATE.GET_SESSION_STATE),
|
||||
setFieldValue: (params: SetFieldValueParams) =>
|
||||
invoke<void>(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<RAGDocument>(IPC_CHANNELS.RAG.ADD_DOCUMENT, params ?? {}),
|
||||
removeDocument: (params: RAGRemoveDocumentParams) =>
|
||||
invoke<void>(IPC_CHANNELS.RAG.REMOVE_DOCUMENT, params),
|
||||
getDocuments: () =>
|
||||
invoke<RAGDocument[]>(IPC_CHANNELS.RAG.GET_DOCUMENTS),
|
||||
query: (params: RAGQueryParams) =>
|
||||
invoke<RAGQueryResult>(IPC_CHANNELS.RAG.QUERY, params),
|
||||
getState: () =>
|
||||
invoke<RAGStateInfo>(IPC_CHANNELS.RAG.GET_STATE),
|
||||
reindex: (documentId: string) =>
|
||||
invoke<void>(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<void>(IPC_CHANNELS.VOICE_ACTION.EXECUTE, params),
|
||||
getPresets: () =>
|
||||
invoke<VoiceActionPreset[]>(IPC_CHANNELS.VOICE_ACTION.GET_PRESETS),
|
||||
getHistory: () =>
|
||||
invoke<VoiceActionHistoryEntry[]>(IPC_CHANNELS.VOICE_ACTION.GET_HISTORY),
|
||||
clearHistory: () =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_ACTION.CLEAR_HISTORY),
|
||||
setEnabled: (enabled: boolean) =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_ACTION.SET_ENABLED, { enabled }),
|
||||
isEnabled: () =>
|
||||
invoke<boolean>(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<void>(IPC_CHANNELS.VOICE_CONVERSATION.START_SESSION),
|
||||
stopSession: () =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_CONVERSATION.STOP_SESSION),
|
||||
sendMessage: (params: ConversationSendParams) =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_CONVERSATION.SEND_MESSAGE, params),
|
||||
getState: () =>
|
||||
invoke<ConversationSessionInfo>(IPC_CHANNELS.VOICE_CONVERSATION.GET_STATE),
|
||||
getHistory: () =>
|
||||
invoke<ConversationMessage[]>(IPC_CHANNELS.VOICE_CONVERSATION.GET_HISTORY),
|
||||
clearHistory: () =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_CONVERSATION.CLEAR_HISTORY),
|
||||
cancelResponse: () =>
|
||||
invoke<void>(IPC_CHANNELS.VOICE_CONVERSATION.CANCEL_RESPONSE),
|
||||
finishListening: () =>
|
||||
invoke<void>('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),
|
||||
},
|
||||
|
||||
// ── Meeting Mode (Phase 14) ───────────────────────────
|
||||
meetingMode: {
|
||||
startRecording: () =>
|
||||
invoke<MeetingStartResult>(IPC_CHANNELS.MEETING_MODE.START_RECORDING),
|
||||
stopRecording: () =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_MODE.STOP_RECORDING),
|
||||
addMemo: (params: MeetingAddMemoParams) =>
|
||||
invoke<MeetingMemo>(IPC_CHANNELS.MEETING_MODE.ADD_MEMO, params),
|
||||
getState: () =>
|
||||
invoke<MeetingModeStateInfo>(IPC_CHANNELS.MEETING_MODE.GET_STATE),
|
||||
getSessions: (params: MeetingGetSessionsParams) =>
|
||||
invoke<MeetingSessionPage>(IPC_CHANNELS.MEETING_MODE.GET_SESSIONS, params),
|
||||
getSession: (params: MeetingGetSessionParams) =>
|
||||
invoke<MeetingSessionDetail>(IPC_CHANNELS.MEETING_MODE.GET_SESSION, params),
|
||||
deleteSession: (params: MeetingDeleteSessionParams) =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_MODE.DELETE_SESSION, params),
|
||||
updateTitle: (params: { sessionId: string; title: string }) =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_MODE.UPDATE_TITLE, params),
|
||||
exportPdf: (params: MeetingExportParams) =>
|
||||
invoke<string>(IPC_CHANNELS.MEETING_MODE.EXPORT_PDF, params),
|
||||
exportMarkdown: (params: MeetingExportParams) =>
|
||||
invoke<string>(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<void>(IPC_CHANNELS.MEETING_MODE.UPDATE_TRANSCRIPT, params),
|
||||
generateDocument: (params: MeetingGenerateDocParams) =>
|
||||
invoke<MeetingDocument>(IPC_CHANNELS.MEETING_MODE.GENERATE_DOCUMENT, params),
|
||||
getDocuments: (params: MeetingGetDocsParams) =>
|
||||
invoke<MeetingDocument[]>(IPC_CHANNELS.MEETING_MODE.GET_DOCUMENTS, params),
|
||||
updateDocument: (params: MeetingUpdateDocParams) =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_MODE.UPDATE_DOCUMENT, params),
|
||||
deleteDocument: (params: MeetingDeleteDocParams) =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_MODE.DELETE_DOCUMENT, params),
|
||||
exportDocument: (params: MeetingExportDocParams) =>
|
||||
invoke<string>(IPC_CHANNELS.MEETING_MODE.EXPORT_DOCUMENT, params),
|
||||
exportTranscript: (params: { sessionId: string; format: MeetingExportFormat }) =>
|
||||
invoke<string>(IPC_CHANNELS.MEETING_MODE.EXPORT_TRANSCRIPT, params),
|
||||
editSegment: (params: { sessionId: string; segmentId: string; text: string }) =>
|
||||
invoke<void>(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<string>(IPC_CHANNELS.MEETING_MODE.POLISH_TRANSCRIPT, params),
|
||||
// Phase 15.5
|
||||
diarize: (params: DiarizeSessionParams) =>
|
||||
invoke<void>(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<void>(IPC_CHANNELS.MEETING_CHAT.SEND, params),
|
||||
cancel: () =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_CHAT.CANCEL),
|
||||
clear: () =>
|
||||
invoke<void>(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<MeetingDocTemplate[]>(IPC_CHANNELS.MEETING_DOC_TEMPLATE.GET_ALL),
|
||||
create: (params: CreateMeetingDocTemplateParams) =>
|
||||
invoke<MeetingDocTemplate>(IPC_CHANNELS.MEETING_DOC_TEMPLATE.CREATE, params),
|
||||
update: (params: UpdateMeetingDocTemplateParams) =>
|
||||
invoke<MeetingDocTemplate>(IPC_CHANNELS.MEETING_DOC_TEMPLATE.UPDATE, params),
|
||||
delete: (params: DeleteMeetingDocTemplateParams) =>
|
||||
invoke<void>(IPC_CHANNELS.MEETING_DOC_TEMPLATE.DELETE, params),
|
||||
},
|
||||
} as const
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
|
||||
|
||||
export type ElectronAPI = typeof electronAPI
|
||||
21
apps/desktop/src/preload/popup.ts
Normal file
21
apps/desktop/src/preload/popup.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// src/preload/popup.ts
|
||||
// 팝업 윈도우(RecordingTip, ResultPopup)용 최소 preload
|
||||
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
type Unsubscribe = () => void
|
||||
|
||||
const popupAPI = {
|
||||
send: (channel: string, ...args: unknown[]): void => {
|
||||
ipcRenderer.send(channel, ...args)
|
||||
},
|
||||
on: (channel: string, callback: (...args: unknown[]) => void): Unsubscribe => {
|
||||
const handler = (_event: Electron.IpcRendererEvent, ...args: unknown[]) => callback(...args)
|
||||
ipcRenderer.on(channel, handler)
|
||||
return () => ipcRenderer.removeListener(channel, handler)
|
||||
}
|
||||
} as const
|
||||
|
||||
contextBridge.exposeInMainWorld('popupAPI', popupAPI)
|
||||
|
||||
export type PopupAPI = typeof popupAPI
|
||||
Loading…
Add table
Add a link
Reference in a new issue