markupr 2.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/review-feedback.md +47 -0
- package/.eslintrc.json +35 -0
- package/.github/CODEOWNERS +16 -0
- package/.github/FUNDING.yml +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +56 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +54 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +89 -0
- package/.github/dependabot.yml +70 -0
- package/.github/workflows/ci.yml +184 -0
- package/.github/workflows/deploy-landing.yml +134 -0
- package/.github/workflows/nightly.yml +288 -0
- package/.github/workflows/release.yml +318 -0
- package/CHANGELOG.md +127 -0
- package/CLAUDE.md +137 -0
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +390 -0
- package/LICENSE +21 -0
- package/PRODUCT_VISION.md +277 -0
- package/README.md +517 -0
- package/SECURITY.md +51 -0
- package/SIGNING_INSTRUCTIONS.md +284 -0
- package/assets/DMG_BACKGROUND_INSTRUCTIONS.md +130 -0
- package/assets/svg-source/dmg-background.svg +70 -0
- package/assets/svg-source/icon.svg +20 -0
- package/assets/svg-source/tray-icon-processing.svg +7 -0
- package/assets/svg-source/tray-icon-recording.svg +7 -0
- package/assets/svg-source/tray-icon.svg +6 -0
- package/assets/tray-complete.png +0 -0
- package/assets/tray-complete@2x.png +0 -0
- package/assets/tray-completeTemplate.png +0 -0
- package/assets/tray-completeTemplate@2x.png +0 -0
- package/assets/tray-error.png +0 -0
- package/assets/tray-error@2x.png +0 -0
- package/assets/tray-errorTemplate.png +0 -0
- package/assets/tray-errorTemplate@2x.png +0 -0
- package/assets/tray-icon-processing.png +0 -0
- package/assets/tray-icon-processing@2x.png +0 -0
- package/assets/tray-icon-processingTemplate.png +0 -0
- package/assets/tray-icon-processingTemplate@2x.png +0 -0
- package/assets/tray-icon-recording.png +0 -0
- package/assets/tray-icon-recording@2x.png +0 -0
- package/assets/tray-icon-recordingTemplate.png +0 -0
- package/assets/tray-icon-recordingTemplate@2x.png +0 -0
- package/assets/tray-icon.png +0 -0
- package/assets/tray-icon@2x.png +0 -0
- package/assets/tray-iconTemplate.png +0 -0
- package/assets/tray-iconTemplate@2x.png +0 -0
- package/assets/tray-idle.png +0 -0
- package/assets/tray-idle@2x.png +0 -0
- package/assets/tray-idleTemplate.png +0 -0
- package/assets/tray-idleTemplate@2x.png +0 -0
- package/assets/tray-processing-0.png +0 -0
- package/assets/tray-processing-0@2x.png +0 -0
- package/assets/tray-processing-0Template.png +0 -0
- package/assets/tray-processing-0Template@2x.png +0 -0
- package/assets/tray-processing-1.png +0 -0
- package/assets/tray-processing-1@2x.png +0 -0
- package/assets/tray-processing-1Template.png +0 -0
- package/assets/tray-processing-1Template@2x.png +0 -0
- package/assets/tray-processing-2.png +0 -0
- package/assets/tray-processing-2@2x.png +0 -0
- package/assets/tray-processing-2Template.png +0 -0
- package/assets/tray-processing-2Template@2x.png +0 -0
- package/assets/tray-processing-3.png +0 -0
- package/assets/tray-processing-3@2x.png +0 -0
- package/assets/tray-processing-3Template.png +0 -0
- package/assets/tray-processing-3Template@2x.png +0 -0
- package/assets/tray-processing.png +0 -0
- package/assets/tray-processing@2x.png +0 -0
- package/assets/tray-processingTemplate.png +0 -0
- package/assets/tray-processingTemplate@2x.png +0 -0
- package/assets/tray-recording.png +0 -0
- package/assets/tray-recording@2x.png +0 -0
- package/assets/tray-recordingTemplate.png +0 -0
- package/assets/tray-recordingTemplate@2x.png +0 -0
- package/build/DMG_BACKGROUND_SPEC.md +50 -0
- package/build/dmg-background.png +0 -0
- package/build/dmg-background@2x.png +0 -0
- package/build/entitlements.mac.inherit.plist +27 -0
- package/build/entitlements.mac.plist +41 -0
- package/build/favicon-16.png +0 -0
- package/build/favicon-180.png +0 -0
- package/build/favicon-192.png +0 -0
- package/build/favicon-32.png +0 -0
- package/build/favicon-48.png +0 -0
- package/build/favicon-512.png +0 -0
- package/build/favicon-64.png +0 -0
- package/build/icon-128.png +0 -0
- package/build/icon-16.png +0 -0
- package/build/icon-24.png +0 -0
- package/build/icon-256.png +0 -0
- package/build/icon-32.png +0 -0
- package/build/icon-48.png +0 -0
- package/build/icon-64.png +0 -0
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.iconset/icon_128x128.png +0 -0
- package/build/icon.iconset/icon_128x128@2x.png +0 -0
- package/build/icon.iconset/icon_16x16.png +0 -0
- package/build/icon.iconset/icon_16x16@2x.png +0 -0
- package/build/icon.iconset/icon_256x256.png +0 -0
- package/build/icon.iconset/icon_256x256@2x.png +0 -0
- package/build/icon.iconset/icon_32x32.png +0 -0
- package/build/icon.iconset/icon_32x32@2x.png +0 -0
- package/build/icon.iconset/icon_512x512.png +0 -0
- package/build/icon.iconset/icon_512x512@2x.png +0 -0
- package/build/icon.png +0 -0
- package/build/installer-header.bmp +0 -0
- package/build/installer-header.png +0 -0
- package/build/installer-sidebar.bmp +0 -0
- package/build/installer-sidebar.png +0 -0
- package/build/installer.nsh +45 -0
- package/build/overlay-processing.png +0 -0
- package/build/overlay-recording.png +0 -0
- package/build/toolbar-record.png +0 -0
- package/build/toolbar-screenshot.png +0 -0
- package/build/toolbar-settings.png +0 -0
- package/build/toolbar-stop.png +0 -0
- package/dist/main/index.mjs +12612 -0
- package/dist/preload/index.mjs +907 -0
- package/dist/renderer/assets/index-CCmUjl9K.js +19495 -0
- package/dist/renderer/assets/index-CUqz_Gs6.css +2270 -0
- package/dist/renderer/index.html +27 -0
- package/docs/AI_AGENT_QUICKSTART.md +42 -0
- package/docs/AI_PIPELINE_DESIGN.md +595 -0
- package/docs/API.md +514 -0
- package/docs/ARCHITECTURE.md +460 -0
- package/docs/CONFIGURATION.md +336 -0
- package/docs/DEVELOPMENT.md +508 -0
- package/docs/EXPORT_FORMATS.md +451 -0
- package/docs/GETTING_STARTED.md +236 -0
- package/docs/KEYBOARD_SHORTCUTS.md +334 -0
- package/docs/TROUBLESHOOTING.md +418 -0
- package/docs/landing/index.html +672 -0
- package/docs/landing/script.js +342 -0
- package/docs/landing/styles.css +1543 -0
- package/electron-builder.yml +140 -0
- package/electron.vite.config.ts +63 -0
- package/package.json +108 -0
- package/railway.json +12 -0
- package/scripts/build.mjs +51 -0
- package/scripts/generate-icons.mjs +314 -0
- package/scripts/generate-installer-images.cjs +253 -0
- package/scripts/generate-tray-icons.mjs +258 -0
- package/scripts/notarize.cjs +180 -0
- package/scripts/one-click-clean-test.sh +147 -0
- package/scripts/postinstall.mjs +36 -0
- package/scripts/setup-markupr.sh +55 -0
- package/setup +17 -0
- package/site/index.html +1835 -0
- package/site/package.json +11 -0
- package/site/railway.json +12 -0
- package/site/server.js +31 -0
- package/src/main/AutoUpdater.ts +392 -0
- package/src/main/CrashRecovery.ts +655 -0
- package/src/main/ErrorHandler.ts +703 -0
- package/src/main/HotkeyManager.ts +399 -0
- package/src/main/MenuManager.ts +529 -0
- package/src/main/PermissionManager.ts +420 -0
- package/src/main/SessionController.ts +1465 -0
- package/src/main/TrayManager.ts +540 -0
- package/src/main/ai/AIPipelineManager.ts +199 -0
- package/src/main/ai/ClaudeAnalyzer.ts +339 -0
- package/src/main/ai/ImageOptimizer.ts +176 -0
- package/src/main/ai/StructuredMarkdownBuilder.ts +379 -0
- package/src/main/ai/index.ts +16 -0
- package/src/main/ai/types.ts +258 -0
- package/src/main/analysis/ClarificationGenerator.ts +385 -0
- package/src/main/analysis/FeedbackAnalyzer.ts +531 -0
- package/src/main/analysis/index.ts +19 -0
- package/src/main/audio/AudioCapture.ts +978 -0
- package/src/main/audio/audioUtils.ts +100 -0
- package/src/main/audio/index.ts +20 -0
- package/src/main/capture/index.ts +1 -0
- package/src/main/index.ts +1693 -0
- package/src/main/ipc/captureHandlers.ts +272 -0
- package/src/main/ipc/index.ts +45 -0
- package/src/main/ipc/outputHandlers.ts +302 -0
- package/src/main/ipc/sessionHandlers.ts +56 -0
- package/src/main/ipc/settingsHandlers.ts +471 -0
- package/src/main/ipc/types.ts +56 -0
- package/src/main/ipc/windowHandlers.ts +277 -0
- package/src/main/output/ClipboardService.ts +369 -0
- package/src/main/output/ExportService.ts +539 -0
- package/src/main/output/FileManager.ts +416 -0
- package/src/main/output/MarkdownGenerator.ts +791 -0
- package/src/main/output/MarkdownPatcher.ts +299 -0
- package/src/main/output/index.ts +186 -0
- package/src/main/output/sessionAdapter.ts +207 -0
- package/src/main/output/templates/html-template.ts +553 -0
- package/src/main/pipeline/FrameExtractor.ts +330 -0
- package/src/main/pipeline/PostProcessor.ts +399 -0
- package/src/main/pipeline/TranscriptAnalyzer.ts +226 -0
- package/src/main/pipeline/index.ts +36 -0
- package/src/main/platform/WindowsTaskbar.ts +600 -0
- package/src/main/platform/index.ts +16 -0
- package/src/main/settings/SettingsManager.ts +730 -0
- package/src/main/settings/index.ts +19 -0
- package/src/main/transcription/ModelDownloadManager.ts +494 -0
- package/src/main/transcription/TierManager.ts +219 -0
- package/src/main/transcription/TranscriptionRecoveryService.ts +340 -0
- package/src/main/transcription/WhisperService.ts +748 -0
- package/src/main/transcription/index.ts +56 -0
- package/src/main/transcription/types.ts +135 -0
- package/src/main/windows/PopoverManager.ts +284 -0
- package/src/main/windows/TaskbarIntegration.ts +452 -0
- package/src/main/windows/index.ts +23 -0
- package/src/preload/index.ts +1047 -0
- package/src/renderer/App.tsx +515 -0
- package/src/renderer/AppWrapper.tsx +28 -0
- package/src/renderer/assets/logo-dark.svg +7 -0
- package/src/renderer/assets/logo.svg +7 -0
- package/src/renderer/audio/AudioCaptureRenderer.ts +454 -0
- package/src/renderer/capture/ScreenRecordingRenderer.ts +492 -0
- package/src/renderer/components/AnnotationOverlay.tsx +836 -0
- package/src/renderer/components/AudioWaveform.tsx +811 -0
- package/src/renderer/components/ClarificationQuestions.tsx +656 -0
- package/src/renderer/components/CountdownTimer.tsx +495 -0
- package/src/renderer/components/CrashRecoveryDialog.tsx +632 -0
- package/src/renderer/components/DonateButton.tsx +127 -0
- package/src/renderer/components/ErrorBoundary.tsx +308 -0
- package/src/renderer/components/ExportDialog.tsx +872 -0
- package/src/renderer/components/HotkeyHint.tsx +261 -0
- package/src/renderer/components/KeyboardShortcuts.tsx +787 -0
- package/src/renderer/components/ModelDownloadDialog.tsx +844 -0
- package/src/renderer/components/Onboarding.tsx +1830 -0
- package/src/renderer/components/ProcessingOverlay.tsx +157 -0
- package/src/renderer/components/RecordingOverlay.tsx +423 -0
- package/src/renderer/components/SessionHistory.tsx +1746 -0
- package/src/renderer/components/SessionReview.tsx +1321 -0
- package/src/renderer/components/SettingsPanel.tsx +217 -0
- package/src/renderer/components/Skeleton.tsx +347 -0
- package/src/renderer/components/StatusIndicator.tsx +86 -0
- package/src/renderer/components/ThemeProvider.tsx +429 -0
- package/src/renderer/components/Tooltip.tsx +370 -0
- package/src/renderer/components/TranscriptionPreview.tsx +183 -0
- package/src/renderer/components/TranscriptionTierSelector.tsx +640 -0
- package/src/renderer/components/UpdateNotification.tsx +377 -0
- package/src/renderer/components/WindowSelector.tsx +947 -0
- package/src/renderer/components/index.ts +99 -0
- package/src/renderer/components/primitives/ApiKeyInput.tsx +98 -0
- package/src/renderer/components/primitives/ColorPicker.tsx +65 -0
- package/src/renderer/components/primitives/DangerButton.tsx +45 -0
- package/src/renderer/components/primitives/DirectoryPicker.tsx +41 -0
- package/src/renderer/components/primitives/Dropdown.tsx +34 -0
- package/src/renderer/components/primitives/KeyRecorder.tsx +117 -0
- package/src/renderer/components/primitives/SettingsSection.tsx +32 -0
- package/src/renderer/components/primitives/Slider.tsx +43 -0
- package/src/renderer/components/primitives/Toggle.tsx +36 -0
- package/src/renderer/components/primitives/index.ts +10 -0
- package/src/renderer/components/settings/AdvancedTab.tsx +174 -0
- package/src/renderer/components/settings/AppearanceTab.tsx +77 -0
- package/src/renderer/components/settings/GeneralTab.tsx +40 -0
- package/src/renderer/components/settings/HotkeysTab.tsx +79 -0
- package/src/renderer/components/settings/RecordingTab.tsx +84 -0
- package/src/renderer/components/settings/index.ts +9 -0
- package/src/renderer/components/settings/settingsStyles.ts +673 -0
- package/src/renderer/components/settings/tabConfig.tsx +85 -0
- package/src/renderer/components/settings/useSettingsPanel.ts +447 -0
- package/src/renderer/contexts/ProcessingContext.tsx +227 -0
- package/src/renderer/contexts/RecordingContext.tsx +683 -0
- package/src/renderer/contexts/UIContext.tsx +326 -0
- package/src/renderer/contexts/index.ts +24 -0
- package/src/renderer/donateMessages.ts +69 -0
- package/src/renderer/hooks/index.ts +75 -0
- package/src/renderer/hooks/useAnimation.tsx +544 -0
- package/src/renderer/hooks/useTheme.ts +313 -0
- package/src/renderer/index.html +26 -0
- package/src/renderer/main.tsx +52 -0
- package/src/renderer/styles/animations.css +1093 -0
- package/src/renderer/styles/app-shell.css +662 -0
- package/src/renderer/styles/globals.css +515 -0
- package/src/renderer/styles/theme.ts +578 -0
- package/src/renderer/types/electron.d.ts +385 -0
- package/src/shared/hotkeys.ts +283 -0
- package/src/shared/types.ts +809 -0
- package/tests/clipboard.test.ts +228 -0
- package/tests/e2e/criticalPaths.test.ts +594 -0
- package/tests/feedbackAnalyzer.test.ts +303 -0
- package/tests/integration/sessionFlow.test.ts +583 -0
- package/tests/markdownGenerator.test.ts +418 -0
- package/tests/output.test.ts +96 -0
- package/tests/setup.ts +486 -0
- package/tests/unit/appIntegration.test.ts +676 -0
- package/tests/unit/appViewState.test.ts +281 -0
- package/tests/unit/audioIpcChannels.test.ts +17 -0
- package/tests/unit/exportService.test.ts +492 -0
- package/tests/unit/hotkeys.test.ts +92 -0
- package/tests/unit/navigationPreload.test.ts +94 -0
- package/tests/unit/onboardingFlow.test.ts +345 -0
- package/tests/unit/permissionManager.test.ts +175 -0
- package/tests/unit/permissionManagerExpanded.test.ts +296 -0
- package/tests/unit/screenRecordingRenderer.test.ts +368 -0
- package/tests/unit/sessionController.test.ts +515 -0
- package/tests/unit/tierManager.test.ts +61 -0
- package/tests/unit/tierManagerExpanded.test.ts +142 -0
- package/tests/unit/transcriptAnalyzer.test.ts +64 -0
- package/tsconfig.json +25 -0
- package/vitest.config.ts +46 -0
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* markupr - Preload Script
|
|
3
|
+
*
|
|
4
|
+
* Exposes a safe API to the renderer process via contextBridge.
|
|
5
|
+
* This is the ONLY way the renderer can communicate with the main process.
|
|
6
|
+
*
|
|
7
|
+
* API Design:
|
|
8
|
+
* - Organized by domain (session, capture, audio, transcript, settings, permissions, output)
|
|
9
|
+
* - invoke() for request/response patterns
|
|
10
|
+
* - on/send for event streams (returns cleanup function)
|
|
11
|
+
* - Type-safe channel names from shared types
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
15
|
+
import {
|
|
16
|
+
IPC_CHANNELS,
|
|
17
|
+
type AppSettings,
|
|
18
|
+
type CaptureSource,
|
|
19
|
+
type AudioDevice,
|
|
20
|
+
type PermissionType,
|
|
21
|
+
type PermissionStatus,
|
|
22
|
+
type SessionStatusPayload,
|
|
23
|
+
type SessionPayload,
|
|
24
|
+
type FeedbackItemPayload,
|
|
25
|
+
type TranscriptChunkPayload,
|
|
26
|
+
type ScreenshotCapturedPayload,
|
|
27
|
+
type OutputReadyPayload,
|
|
28
|
+
type SaveResult,
|
|
29
|
+
type HotkeyConfig,
|
|
30
|
+
type SessionState,
|
|
31
|
+
type UpdateStatusPayload,
|
|
32
|
+
type WhisperDownloadProgressPayload,
|
|
33
|
+
type WhisperModelInfoPayload,
|
|
34
|
+
type WhisperModelCheckResult,
|
|
35
|
+
type TranscriptionTier,
|
|
36
|
+
type TranscriptionTierStatus,
|
|
37
|
+
type ApiKeyValidationResult,
|
|
38
|
+
type ProcessingProgressPayload,
|
|
39
|
+
} from '../shared/types';
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// Type Definitions for Event Handlers
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
type Unsubscribe = () => void;
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// Helper: Create typed event subscriber
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
function createEventSubscriber<T>(channel: string) {
|
|
52
|
+
return (callback: (data: T) => void): Unsubscribe => {
|
|
53
|
+
const handler = (_: Electron.IpcRendererEvent, data: T) => callback(data);
|
|
54
|
+
ipcRenderer.on(channel, handler);
|
|
55
|
+
return () => ipcRenderer.removeListener(channel, handler);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// markupr API
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
const markuprApi = {
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// Session API
|
|
66
|
+
// ===========================================================================
|
|
67
|
+
session: {
|
|
68
|
+
/**
|
|
69
|
+
* Start a recording session
|
|
70
|
+
* @param sourceId - ID of the capture source (screen or window)
|
|
71
|
+
*/
|
|
72
|
+
start: (
|
|
73
|
+
sourceId?: string,
|
|
74
|
+
sourceName?: string
|
|
75
|
+
): Promise<{ success: boolean; sessionId?: string; error?: string }> => {
|
|
76
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_START, sourceId, sourceName);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Stop the current recording session
|
|
81
|
+
*/
|
|
82
|
+
stop: (): Promise<{ success: boolean; session?: SessionPayload; error?: string }> => {
|
|
83
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_STOP);
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Pause the current recording session
|
|
88
|
+
*/
|
|
89
|
+
pause: (): Promise<{ success: boolean; error?: string }> => {
|
|
90
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_PAUSE);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resume a paused recording session
|
|
95
|
+
*/
|
|
96
|
+
resume: (): Promise<{ success: boolean; error?: string }> => {
|
|
97
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_RESUME);
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cancel the current session without saving
|
|
102
|
+
*/
|
|
103
|
+
cancel: (): Promise<{ success: boolean }> => {
|
|
104
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_CANCEL);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get current session status
|
|
109
|
+
*/
|
|
110
|
+
getStatus: (): Promise<SessionStatusPayload> => {
|
|
111
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_GET_STATUS);
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get current session data
|
|
116
|
+
*/
|
|
117
|
+
getCurrent: (): Promise<SessionPayload | null> => {
|
|
118
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SESSION_GET_CURRENT);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Subscribe to session state changes
|
|
123
|
+
*/
|
|
124
|
+
onStateChange: createEventSubscriber<{
|
|
125
|
+
state: SessionState;
|
|
126
|
+
session: SessionPayload | null;
|
|
127
|
+
}>(IPC_CHANNELS.SESSION_STATE_CHANGED),
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Subscribe to session status updates (periodic updates during recording)
|
|
131
|
+
*/
|
|
132
|
+
onStatusUpdate: createEventSubscriber<SessionStatusPayload>(IPC_CHANNELS.SESSION_STATUS),
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Subscribe to session completion
|
|
136
|
+
*/
|
|
137
|
+
onComplete: createEventSubscriber<SessionPayload>(IPC_CHANNELS.SESSION_COMPLETE),
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Subscribe to new feedback items
|
|
141
|
+
*/
|
|
142
|
+
onFeedbackItem: createEventSubscriber<FeedbackItemPayload>(IPC_CHANNELS.SESSION_FEEDBACK_ITEM),
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Subscribe to voice activity changes
|
|
146
|
+
*/
|
|
147
|
+
onVoiceActivity: createEventSubscriber<{ active: boolean }>(IPC_CHANNELS.SESSION_VOICE_ACTIVITY),
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Subscribe to session errors
|
|
151
|
+
*/
|
|
152
|
+
onError: createEventSubscriber<{ message: string }>(IPC_CHANNELS.SESSION_ERROR),
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ===========================================================================
|
|
156
|
+
// Capture API
|
|
157
|
+
// ===========================================================================
|
|
158
|
+
capture: {
|
|
159
|
+
/**
|
|
160
|
+
* Get available capture sources (screens and windows)
|
|
161
|
+
*/
|
|
162
|
+
getSources: (): Promise<CaptureSource[]> => {
|
|
163
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CAPTURE_GET_SOURCES);
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Trigger a manual screenshot during recording
|
|
168
|
+
*/
|
|
169
|
+
manualScreenshot: (): Promise<{ success: boolean; error?: string }> => {
|
|
170
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CAPTURE_MANUAL_SCREENSHOT);
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Subscribe to screenshot captured events
|
|
175
|
+
*/
|
|
176
|
+
onScreenshot: createEventSubscriber<ScreenshotCapturedPayload>(IPC_CHANNELS.SCREENSHOT_CAPTURED),
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Subscribe to manual screenshot trigger events (from hotkey)
|
|
180
|
+
*/
|
|
181
|
+
onManualTrigger: createEventSubscriber<{ timestamp: number }>(IPC_CHANNELS.MANUAL_SCREENSHOT),
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// ===========================================================================
|
|
185
|
+
// Audio API
|
|
186
|
+
// ===========================================================================
|
|
187
|
+
audio: {
|
|
188
|
+
/**
|
|
189
|
+
* Get available audio input devices
|
|
190
|
+
* Note: Device enumeration happens in renderer via Web Audio API
|
|
191
|
+
*/
|
|
192
|
+
getDevices: (): Promise<AudioDevice[]> => {
|
|
193
|
+
return ipcRenderer.invoke(IPC_CHANNELS.AUDIO_GET_DEVICES);
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Set the preferred audio input device
|
|
198
|
+
*/
|
|
199
|
+
setDevice: (deviceId: string): Promise<{ success: boolean }> => {
|
|
200
|
+
return ipcRenderer.invoke(IPC_CHANNELS.AUDIO_SET_DEVICE, deviceId);
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Subscribe to audio level updates (for visualization)
|
|
205
|
+
*/
|
|
206
|
+
onLevel: createEventSubscriber<number>(IPC_CHANNELS.AUDIO_LEVEL),
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Subscribe to voice activity detection updates
|
|
210
|
+
*/
|
|
211
|
+
onVoiceActivity: createEventSubscriber<boolean>(IPC_CHANNELS.AUDIO_VOICE_ACTIVITY),
|
|
212
|
+
|
|
213
|
+
// -------------------------------------------------------------------------
|
|
214
|
+
// Audio Capture Bridge (Renderer -> Main communication)
|
|
215
|
+
// These are used by the AudioCaptureRenderer to communicate with AudioCapture in main
|
|
216
|
+
// -------------------------------------------------------------------------
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Respond to device enumeration request from main
|
|
220
|
+
*/
|
|
221
|
+
onRequestDevices: (callback: () => void): Unsubscribe => {
|
|
222
|
+
const handler = () => callback();
|
|
223
|
+
ipcRenderer.on(IPC_CHANNELS.AUDIO_REQUEST_DEVICES, handler);
|
|
224
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.AUDIO_REQUEST_DEVICES, handler);
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Send device list back to main
|
|
229
|
+
*/
|
|
230
|
+
sendDevices: (devices: AudioDevice[]): void => {
|
|
231
|
+
ipcRenderer.send(IPC_CHANNELS.AUDIO_DEVICES_RESPONSE, devices);
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Handle start capture command from main
|
|
236
|
+
*/
|
|
237
|
+
onStartCapture: (
|
|
238
|
+
callback: (config: {
|
|
239
|
+
deviceId: string | null;
|
|
240
|
+
sampleRate: number;
|
|
241
|
+
channels: number;
|
|
242
|
+
chunkDurationMs: number;
|
|
243
|
+
}) => void
|
|
244
|
+
): Unsubscribe => {
|
|
245
|
+
const handler = (
|
|
246
|
+
_: Electron.IpcRendererEvent,
|
|
247
|
+
config: {
|
|
248
|
+
deviceId: string | null;
|
|
249
|
+
sampleRate: number;
|
|
250
|
+
channels: number;
|
|
251
|
+
chunkDurationMs: number;
|
|
252
|
+
}
|
|
253
|
+
) => callback(config);
|
|
254
|
+
ipcRenderer.on(IPC_CHANNELS.AUDIO_START_CAPTURE, handler);
|
|
255
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.AUDIO_START_CAPTURE, handler);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Handle stop capture command from main
|
|
260
|
+
*/
|
|
261
|
+
onStopCapture: (callback: () => void): Unsubscribe => {
|
|
262
|
+
const handler = () => callback();
|
|
263
|
+
ipcRenderer.on(IPC_CHANNELS.AUDIO_STOP_CAPTURE, handler);
|
|
264
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.AUDIO_STOP_CAPTURE, handler);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Handle device change command from main
|
|
269
|
+
*/
|
|
270
|
+
onSetDevice: (callback: (deviceId: string) => void): Unsubscribe => {
|
|
271
|
+
const handler = (_: Electron.IpcRendererEvent, deviceId: string) => callback(deviceId);
|
|
272
|
+
ipcRenderer.on(IPC_CHANNELS.AUDIO_SET_DEVICE, handler);
|
|
273
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.AUDIO_SET_DEVICE, handler);
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Send audio chunk to main for transcription
|
|
278
|
+
*/
|
|
279
|
+
sendAudioChunk: (data: {
|
|
280
|
+
timestamp: number;
|
|
281
|
+
duration: number;
|
|
282
|
+
samples?: number[];
|
|
283
|
+
encodedChunk?: Uint8Array;
|
|
284
|
+
mimeType?: string;
|
|
285
|
+
audioLevel?: number;
|
|
286
|
+
rms?: number;
|
|
287
|
+
}): void => {
|
|
288
|
+
ipcRenderer.send(IPC_CHANNELS.AUDIO_CHUNK, data);
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Notify main that capture started
|
|
293
|
+
*/
|
|
294
|
+
notifyCaptureStarted: (): void => {
|
|
295
|
+
ipcRenderer.send(IPC_CHANNELS.AUDIO_CAPTURE_STARTED);
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Notify main that capture stopped
|
|
300
|
+
*/
|
|
301
|
+
notifyCaptureStopped: (): void => {
|
|
302
|
+
ipcRenderer.send(IPC_CHANNELS.AUDIO_CAPTURE_STOPPED);
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Send capture error to main
|
|
307
|
+
*/
|
|
308
|
+
sendCaptureError: (error: string): void => {
|
|
309
|
+
ipcRenderer.send(IPC_CHANNELS.AUDIO_CAPTURE_ERROR, error);
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
// ===========================================================================
|
|
314
|
+
// Screen Recording API
|
|
315
|
+
// ===========================================================================
|
|
316
|
+
screenRecording: {
|
|
317
|
+
/**
|
|
318
|
+
* Start persisted screen recording for a session
|
|
319
|
+
*/
|
|
320
|
+
start: (
|
|
321
|
+
sessionId: string,
|
|
322
|
+
mimeType: string,
|
|
323
|
+
startTime?: number
|
|
324
|
+
): Promise<{ success: boolean; path?: string; error?: string }> => {
|
|
325
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SCREEN_RECORDING_START, sessionId, mimeType, startTime);
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Append a video chunk to the active recording file
|
|
330
|
+
*/
|
|
331
|
+
appendChunk: (
|
|
332
|
+
sessionId: string,
|
|
333
|
+
chunk: Uint8Array
|
|
334
|
+
): Promise<{ success: boolean; error?: string }> => {
|
|
335
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SCREEN_RECORDING_CHUNK, sessionId, chunk);
|
|
336
|
+
},
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Finalize persisted recording for a session
|
|
340
|
+
*/
|
|
341
|
+
stop: (
|
|
342
|
+
sessionId: string
|
|
343
|
+
): Promise<{ success: boolean; path?: string; bytes?: number; mimeType?: string; error?: string }> => {
|
|
344
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SCREEN_RECORDING_STOP, sessionId);
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// ===========================================================================
|
|
349
|
+
// Transcription API
|
|
350
|
+
// ===========================================================================
|
|
351
|
+
transcript: {
|
|
352
|
+
/**
|
|
353
|
+
* Subscribe to transcription chunks (interim and final)
|
|
354
|
+
*/
|
|
355
|
+
onChunk: createEventSubscriber<TranscriptChunkPayload>(IPC_CHANNELS.TRANSCRIPTION_UPDATE),
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Subscribe to final transcription results
|
|
359
|
+
*/
|
|
360
|
+
onFinal: createEventSubscriber<{
|
|
361
|
+
text: string;
|
|
362
|
+
confidence: number;
|
|
363
|
+
timestamp: number;
|
|
364
|
+
}>(IPC_CHANNELS.TRANSCRIPTION_FINAL),
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
// ===========================================================================
|
|
368
|
+
// Processing Pipeline API (Main -> Renderer events)
|
|
369
|
+
// ===========================================================================
|
|
370
|
+
processing: {
|
|
371
|
+
/**
|
|
372
|
+
* Subscribe to post-process pipeline progress updates
|
|
373
|
+
*/
|
|
374
|
+
onProgress: createEventSubscriber<ProcessingProgressPayload>(IPC_CHANNELS.PROCESSING_PROGRESS),
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Subscribe to post-process pipeline completion
|
|
378
|
+
*/
|
|
379
|
+
onComplete: createEventSubscriber<OutputReadyPayload>(IPC_CHANNELS.PROCESSING_COMPLETE),
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
// ===========================================================================
|
|
383
|
+
// Transcription Control API
|
|
384
|
+
// ===========================================================================
|
|
385
|
+
transcription: {
|
|
386
|
+
/**
|
|
387
|
+
* Get runtime availability for all transcription tiers.
|
|
388
|
+
*/
|
|
389
|
+
getTierStatuses: (): Promise<TranscriptionTierStatus[]> => {
|
|
390
|
+
return ipcRenderer.invoke(IPC_CHANNELS.TRANSCRIPTION_GET_TIER_STATUSES);
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Get current preferred/active transcription tier.
|
|
395
|
+
*/
|
|
396
|
+
getCurrentTier: (): Promise<TranscriptionTier | null> => {
|
|
397
|
+
return ipcRenderer.invoke(IPC_CHANNELS.TRANSCRIPTION_GET_CURRENT_TIER);
|
|
398
|
+
},
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Set preferred transcription tier.
|
|
402
|
+
*/
|
|
403
|
+
setTier: (tier: TranscriptionTier): Promise<{ success: boolean; error?: string }> => {
|
|
404
|
+
return ipcRenderer.invoke(IPC_CHANNELS.TRANSCRIPTION_SET_TIER, tier);
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Download a specific Whisper model via transcription controls.
|
|
409
|
+
*/
|
|
410
|
+
downloadModel: (model: string): Promise<{ success: boolean; error?: string }> => {
|
|
411
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_DOWNLOAD_MODEL, model);
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Cancel Whisper model download via transcription controls.
|
|
416
|
+
*/
|
|
417
|
+
cancelDownload: (model: string): Promise<{ success: boolean }> => {
|
|
418
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_CANCEL_DOWNLOAD, model);
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Subscribe to Whisper model progress updates.
|
|
423
|
+
*/
|
|
424
|
+
onModelProgress: createEventSubscriber<WhisperDownloadProgressPayload>(
|
|
425
|
+
IPC_CHANNELS.WHISPER_DOWNLOAD_PROGRESS
|
|
426
|
+
),
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
// ===========================================================================
|
|
430
|
+
// Settings API
|
|
431
|
+
// ===========================================================================
|
|
432
|
+
settings: {
|
|
433
|
+
/**
|
|
434
|
+
* Get a specific setting
|
|
435
|
+
*/
|
|
436
|
+
get: <K extends keyof AppSettings>(key: K): Promise<AppSettings[K]> => {
|
|
437
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_GET, key);
|
|
438
|
+
},
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Get all settings
|
|
442
|
+
*/
|
|
443
|
+
getAll: (): Promise<AppSettings> => {
|
|
444
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_GET_ALL);
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Set a specific setting
|
|
449
|
+
*/
|
|
450
|
+
set: <K extends keyof AppSettings>(key: K, value: AppSettings[K]): Promise<AppSettings> => {
|
|
451
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_SET, key, value);
|
|
452
|
+
},
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get an API key from secure storage
|
|
456
|
+
*/
|
|
457
|
+
getApiKey: (service: string): Promise<string | null> => {
|
|
458
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_GET_API_KEY, service);
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Set an API key in secure storage
|
|
463
|
+
*/
|
|
464
|
+
setApiKey: (service: string, key: string): Promise<boolean> => {
|
|
465
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_SET_API_KEY, service, key);
|
|
466
|
+
},
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Delete an API key from secure storage
|
|
470
|
+
*/
|
|
471
|
+
deleteApiKey: (service: string): Promise<boolean> => {
|
|
472
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_DELETE_API_KEY, service);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Check if an API key exists in secure storage
|
|
477
|
+
*/
|
|
478
|
+
hasApiKey: (service: string): Promise<boolean> => {
|
|
479
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_HAS_API_KEY, service);
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Validate an API key by performing a provider request from main process.
|
|
484
|
+
*/
|
|
485
|
+
testApiKey: (
|
|
486
|
+
service: 'openai' | 'anthropic',
|
|
487
|
+
key: string
|
|
488
|
+
): Promise<ApiKeyValidationResult> => {
|
|
489
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_TEST_API_KEY, service, key);
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Open native directory picker for output path selection
|
|
494
|
+
*/
|
|
495
|
+
selectDirectory: (): Promise<string | null> => {
|
|
496
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_SELECT_DIRECTORY);
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Clear app data and reset settings
|
|
501
|
+
*/
|
|
502
|
+
clearAllData: (): Promise<void> => {
|
|
503
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_CLEAR_ALL_DATA);
|
|
504
|
+
},
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Export settings to a JSON file
|
|
508
|
+
*/
|
|
509
|
+
export: (): Promise<void> => {
|
|
510
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_EXPORT);
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Import settings from a JSON file
|
|
515
|
+
*/
|
|
516
|
+
import: (): Promise<AppSettings | null> => {
|
|
517
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SETTINGS_IMPORT);
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
|
|
521
|
+
// ===========================================================================
|
|
522
|
+
// Hotkey API
|
|
523
|
+
// ===========================================================================
|
|
524
|
+
hotkeys: {
|
|
525
|
+
/**
|
|
526
|
+
* Get current hotkey configuration
|
|
527
|
+
*/
|
|
528
|
+
getConfig: (): Promise<HotkeyConfig> => {
|
|
529
|
+
return ipcRenderer.invoke(IPC_CHANNELS.HOTKEY_CONFIG);
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Update hotkey configuration
|
|
534
|
+
*/
|
|
535
|
+
updateConfig: (
|
|
536
|
+
config: Partial<HotkeyConfig>
|
|
537
|
+
): Promise<{ config: HotkeyConfig; results: unknown[] }> => {
|
|
538
|
+
return ipcRenderer.invoke(IPC_CHANNELS.HOTKEY_UPDATE, config);
|
|
539
|
+
},
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Subscribe to hotkey triggered events
|
|
543
|
+
*/
|
|
544
|
+
onTriggered: createEventSubscriber<{ action: string; accelerator: string }>(
|
|
545
|
+
IPC_CHANNELS.HOTKEY_TRIGGERED
|
|
546
|
+
),
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
// ===========================================================================
|
|
550
|
+
// Permissions API
|
|
551
|
+
// ===========================================================================
|
|
552
|
+
permissions: {
|
|
553
|
+
/**
|
|
554
|
+
* Check if a permission is granted
|
|
555
|
+
*/
|
|
556
|
+
check: (type: PermissionType): Promise<boolean> => {
|
|
557
|
+
return ipcRenderer.invoke(IPC_CHANNELS.PERMISSIONS_CHECK, type);
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Request a permission
|
|
562
|
+
*/
|
|
563
|
+
request: (type: PermissionType): Promise<boolean> => {
|
|
564
|
+
return ipcRenderer.invoke(IPC_CHANNELS.PERMISSIONS_REQUEST, type);
|
|
565
|
+
},
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get all permission statuses
|
|
569
|
+
*/
|
|
570
|
+
getAll: (): Promise<PermissionStatus> => {
|
|
571
|
+
return ipcRenderer.invoke(IPC_CHANNELS.PERMISSIONS_GET_ALL);
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
// ===========================================================================
|
|
576
|
+
// Output API
|
|
577
|
+
// ===========================================================================
|
|
578
|
+
output: {
|
|
579
|
+
/**
|
|
580
|
+
* Save the current session to disk
|
|
581
|
+
*/
|
|
582
|
+
save: (session?: SessionPayload): Promise<SaveResult> => {
|
|
583
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_SAVE, session);
|
|
584
|
+
},
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Copy session summary to clipboard
|
|
588
|
+
*/
|
|
589
|
+
copyClipboard: (): Promise<boolean> => {
|
|
590
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_COPY_CLIPBOARD);
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Open the session output folder in file explorer
|
|
595
|
+
*/
|
|
596
|
+
openFolder: (sessionDir?: string): Promise<{ success: boolean; error?: string }> => {
|
|
597
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_OPEN_FOLDER, sessionDir);
|
|
598
|
+
},
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Subscribe to output ready events
|
|
602
|
+
*/
|
|
603
|
+
onReady: createEventSubscriber<OutputReadyPayload>(
|
|
604
|
+
IPC_CHANNELS.OUTPUT_READY
|
|
605
|
+
),
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Subscribe to output error events
|
|
609
|
+
*/
|
|
610
|
+
onError: createEventSubscriber<{ message: string }>(IPC_CHANNELS.OUTPUT_ERROR),
|
|
611
|
+
|
|
612
|
+
// -------------------------------------------------------------------------
|
|
613
|
+
// Session History Browser API
|
|
614
|
+
// -------------------------------------------------------------------------
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* List all saved sessions
|
|
618
|
+
*/
|
|
619
|
+
listSessions: (): Promise<Array<{
|
|
620
|
+
id: string;
|
|
621
|
+
startTime: number;
|
|
622
|
+
endTime: number;
|
|
623
|
+
itemCount: number;
|
|
624
|
+
screenshotCount: number;
|
|
625
|
+
sourceName: string;
|
|
626
|
+
firstThumbnail?: string;
|
|
627
|
+
folder: string;
|
|
628
|
+
transcriptionPreview?: string;
|
|
629
|
+
}>> => {
|
|
630
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_LIST_SESSIONS);
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Get metadata for a specific session
|
|
635
|
+
*/
|
|
636
|
+
getSessionMetadata: (sessionId: string): Promise<{
|
|
637
|
+
id: string;
|
|
638
|
+
startTime: number;
|
|
639
|
+
endTime: number;
|
|
640
|
+
itemCount: number;
|
|
641
|
+
screenshotCount: number;
|
|
642
|
+
sourceName: string;
|
|
643
|
+
firstThumbnail?: string;
|
|
644
|
+
folder: string;
|
|
645
|
+
transcriptionPreview?: string;
|
|
646
|
+
} | null> => {
|
|
647
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_GET_SESSION_METADATA, sessionId);
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Delete a single session
|
|
652
|
+
*/
|
|
653
|
+
deleteSession: (sessionId: string): Promise<{ success: boolean; error?: string }> => {
|
|
654
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_DELETE_SESSION, sessionId);
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Delete multiple sessions
|
|
659
|
+
*/
|
|
660
|
+
deleteSessions: (sessionIds: string[]): Promise<{ success: boolean; deleted: string[]; failed: string[] }> => {
|
|
661
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_DELETE_SESSIONS, sessionIds);
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Export a single session
|
|
666
|
+
*/
|
|
667
|
+
exportSession: (
|
|
668
|
+
sessionId: string,
|
|
669
|
+
format: 'markdown' | 'json' | 'pdf' = 'markdown'
|
|
670
|
+
): Promise<{ success: boolean; path?: string; error?: string }> => {
|
|
671
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_EXPORT_SESSION, sessionId, format);
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Export multiple sessions
|
|
676
|
+
*/
|
|
677
|
+
exportSessions: (
|
|
678
|
+
sessionIds: string[],
|
|
679
|
+
format: 'markdown' | 'json' | 'pdf' = 'markdown'
|
|
680
|
+
): Promise<{ success: boolean; path?: string; error?: string }> => {
|
|
681
|
+
return ipcRenderer.invoke(IPC_CHANNELS.OUTPUT_EXPORT_SESSIONS, sessionIds, format);
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
// ===========================================================================
|
|
686
|
+
// Crash Recovery API
|
|
687
|
+
// ===========================================================================
|
|
688
|
+
crashRecovery: {
|
|
689
|
+
/**
|
|
690
|
+
* Check for incomplete sessions from crashes
|
|
691
|
+
*/
|
|
692
|
+
check: (): Promise<{
|
|
693
|
+
hasIncomplete: boolean;
|
|
694
|
+
session: {
|
|
695
|
+
id: string;
|
|
696
|
+
startTime: number;
|
|
697
|
+
lastSaveTime: number;
|
|
698
|
+
feedbackItems: Array<{
|
|
699
|
+
id: string;
|
|
700
|
+
timestamp: number;
|
|
701
|
+
text: string;
|
|
702
|
+
confidence: number;
|
|
703
|
+
hasScreenshot: boolean;
|
|
704
|
+
screenshotId?: string;
|
|
705
|
+
}>;
|
|
706
|
+
sourceName: string;
|
|
707
|
+
screenshotCount: number;
|
|
708
|
+
metadata?: {
|
|
709
|
+
appVersion: string;
|
|
710
|
+
platform: string;
|
|
711
|
+
sessionDurationMs: number;
|
|
712
|
+
};
|
|
713
|
+
} | null;
|
|
714
|
+
}> => {
|
|
715
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_CHECK);
|
|
716
|
+
},
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Recover an incomplete session
|
|
720
|
+
*/
|
|
721
|
+
recover: (
|
|
722
|
+
sessionId: string
|
|
723
|
+
): Promise<{
|
|
724
|
+
success: boolean;
|
|
725
|
+
session?: {
|
|
726
|
+
id: string;
|
|
727
|
+
feedbackItems: Array<{
|
|
728
|
+
id: string;
|
|
729
|
+
timestamp: number;
|
|
730
|
+
text: string;
|
|
731
|
+
confidence: number;
|
|
732
|
+
hasScreenshot: boolean;
|
|
733
|
+
}>;
|
|
734
|
+
};
|
|
735
|
+
error?: string;
|
|
736
|
+
}> => {
|
|
737
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_RECOVER, sessionId);
|
|
738
|
+
},
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Discard an incomplete session
|
|
742
|
+
*/
|
|
743
|
+
discard: (): Promise<{ success: boolean }> => {
|
|
744
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_DISCARD);
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Get recent crash logs for debugging
|
|
749
|
+
*/
|
|
750
|
+
getLogs: (
|
|
751
|
+
limit?: number
|
|
752
|
+
): Promise<
|
|
753
|
+
Array<{
|
|
754
|
+
timestamp: string;
|
|
755
|
+
error: { name: string; message: string; stack?: string };
|
|
756
|
+
appVersion: string;
|
|
757
|
+
platform: string;
|
|
758
|
+
sessionId?: string;
|
|
759
|
+
}>
|
|
760
|
+
> => {
|
|
761
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_GET_LOGS, limit);
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Clear crash logs
|
|
766
|
+
*/
|
|
767
|
+
clearLogs: (): Promise<{ success: boolean }> => {
|
|
768
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_CLEAR_LOGS);
|
|
769
|
+
},
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Update crash recovery settings
|
|
773
|
+
*/
|
|
774
|
+
updateSettings: (settings: {
|
|
775
|
+
enableAutoSave?: boolean;
|
|
776
|
+
autoSaveIntervalMs?: number;
|
|
777
|
+
enableCrashReporting?: boolean;
|
|
778
|
+
maxCrashLogs?: number;
|
|
779
|
+
}): Promise<{ success: boolean }> => {
|
|
780
|
+
return ipcRenderer.invoke(IPC_CHANNELS.CRASH_RECOVERY_UPDATE_SETTINGS, settings);
|
|
781
|
+
},
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Subscribe to incomplete session found events (on startup)
|
|
785
|
+
*/
|
|
786
|
+
onIncompleteFound: createEventSubscriber<{
|
|
787
|
+
session: {
|
|
788
|
+
id: string;
|
|
789
|
+
startTime: number;
|
|
790
|
+
lastSaveTime: number;
|
|
791
|
+
feedbackItems: Array<{
|
|
792
|
+
id: string;
|
|
793
|
+
timestamp: number;
|
|
794
|
+
text: string;
|
|
795
|
+
confidence: number;
|
|
796
|
+
hasScreenshot: boolean;
|
|
797
|
+
}>;
|
|
798
|
+
sourceName: string;
|
|
799
|
+
screenshotCount: number;
|
|
800
|
+
};
|
|
801
|
+
}>(IPC_CHANNELS.CRASH_RECOVERY_FOUND),
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
// ===========================================================================
|
|
805
|
+
// Updates API
|
|
806
|
+
// ===========================================================================
|
|
807
|
+
updates: {
|
|
808
|
+
/**
|
|
809
|
+
* Check for available updates
|
|
810
|
+
*/
|
|
811
|
+
check: (): Promise<unknown> => {
|
|
812
|
+
return ipcRenderer.invoke(IPC_CHANNELS.UPDATE_CHECK);
|
|
813
|
+
},
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Download the available update
|
|
817
|
+
*/
|
|
818
|
+
download: (): Promise<void> => {
|
|
819
|
+
return ipcRenderer.invoke(IPC_CHANNELS.UPDATE_DOWNLOAD);
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Install the downloaded update (quits and restarts app)
|
|
824
|
+
*/
|
|
825
|
+
install: (): Promise<void> => {
|
|
826
|
+
return ipcRenderer.invoke(IPC_CHANNELS.UPDATE_INSTALL);
|
|
827
|
+
},
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Subscribe to update status changes
|
|
831
|
+
*/
|
|
832
|
+
onStatus: (callback: (status: UpdateStatusPayload) => void): Unsubscribe => {
|
|
833
|
+
const handler = (_: Electron.IpcRendererEvent, status: UpdateStatusPayload) => callback(status);
|
|
834
|
+
ipcRenderer.on(IPC_CHANNELS.UPDATE_STATUS, handler);
|
|
835
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.UPDATE_STATUS, handler);
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
// ===========================================================================
|
|
840
|
+
// Whisper Model API
|
|
841
|
+
// ===========================================================================
|
|
842
|
+
whisper: {
|
|
843
|
+
/**
|
|
844
|
+
* Check if any Whisper model is downloaded and get recommended model
|
|
845
|
+
*/
|
|
846
|
+
checkModel: (): Promise<WhisperModelCheckResult> => {
|
|
847
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_CHECK_MODEL);
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Check if we have any tier that can actually transcribe
|
|
852
|
+
* (OpenAI key or Whisper with model)
|
|
853
|
+
*/
|
|
854
|
+
hasTranscriptionCapability: (): Promise<boolean> => {
|
|
855
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_HAS_TRANSCRIPTION_CAPABILITY);
|
|
856
|
+
},
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Get available models with their info
|
|
860
|
+
*/
|
|
861
|
+
getAvailableModels: (): Promise<WhisperModelInfoPayload[]> => {
|
|
862
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_GET_AVAILABLE_MODELS);
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Download a specific Whisper model
|
|
867
|
+
* @param model - Model name: 'tiny', 'base', 'small', 'medium', or 'large'
|
|
868
|
+
*/
|
|
869
|
+
downloadModel: (model: string): Promise<{ success: boolean; error?: string }> => {
|
|
870
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_DOWNLOAD_MODEL, model);
|
|
871
|
+
},
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Cancel an active download
|
|
875
|
+
* @param model - Model name to cancel
|
|
876
|
+
*/
|
|
877
|
+
cancelDownload: (model: string): Promise<{ success: boolean }> => {
|
|
878
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WHISPER_CANCEL_DOWNLOAD, model);
|
|
879
|
+
},
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Subscribe to download progress events
|
|
883
|
+
*/
|
|
884
|
+
onDownloadProgress: createEventSubscriber<WhisperDownloadProgressPayload>(
|
|
885
|
+
IPC_CHANNELS.WHISPER_DOWNLOAD_PROGRESS
|
|
886
|
+
),
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Subscribe to download complete events
|
|
890
|
+
*/
|
|
891
|
+
onDownloadComplete: createEventSubscriber<{ model: string; path: string }>(
|
|
892
|
+
IPC_CHANNELS.WHISPER_DOWNLOAD_COMPLETE
|
|
893
|
+
),
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* Subscribe to download error events
|
|
897
|
+
*/
|
|
898
|
+
onDownloadError: createEventSubscriber<{ model: string; error: string }>(
|
|
899
|
+
IPC_CHANNELS.WHISPER_DOWNLOAD_ERROR
|
|
900
|
+
),
|
|
901
|
+
},
|
|
902
|
+
|
|
903
|
+
// ===========================================================================
|
|
904
|
+
// App Version
|
|
905
|
+
// ===========================================================================
|
|
906
|
+
version: (): Promise<string> => {
|
|
907
|
+
return ipcRenderer.invoke(IPC_CHANNELS.APP_VERSION);
|
|
908
|
+
},
|
|
909
|
+
|
|
910
|
+
// ===========================================================================
|
|
911
|
+
// Legacy API (for backwards compatibility)
|
|
912
|
+
// ===========================================================================
|
|
913
|
+
startSession: (): Promise<{ success: boolean; sessionId?: string }> => {
|
|
914
|
+
return ipcRenderer.invoke(IPC_CHANNELS.START_SESSION);
|
|
915
|
+
},
|
|
916
|
+
|
|
917
|
+
stopSession: (): Promise<{ success: boolean }> => {
|
|
918
|
+
return ipcRenderer.invoke(IPC_CHANNELS.STOP_SESSION);
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
getSettings: (): Promise<AppSettings> => {
|
|
922
|
+
return ipcRenderer.invoke(IPC_CHANNELS.GET_SETTINGS);
|
|
923
|
+
},
|
|
924
|
+
|
|
925
|
+
setSettings: (settings: Partial<AppSettings>): Promise<AppSettings> => {
|
|
926
|
+
return ipcRenderer.invoke(IPC_CHANNELS.SET_SETTINGS, settings);
|
|
927
|
+
},
|
|
928
|
+
|
|
929
|
+
copyToClipboard: (text: string): Promise<{ success: boolean }> => {
|
|
930
|
+
return ipcRenderer.invoke(IPC_CHANNELS.COPY_TO_CLIPBOARD, text);
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
// Window controls
|
|
934
|
+
window: {
|
|
935
|
+
minimize: (): Promise<{ success: boolean }> => {
|
|
936
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WINDOW_MINIMIZE);
|
|
937
|
+
},
|
|
938
|
+
hide: (): Promise<{ success: boolean }> => {
|
|
939
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WINDOW_HIDE);
|
|
940
|
+
},
|
|
941
|
+
close: (): Promise<{ success: boolean }> => {
|
|
942
|
+
return ipcRenderer.invoke(IPC_CHANNELS.WINDOW_CLOSE);
|
|
943
|
+
},
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
// Popover controls
|
|
947
|
+
popover: {
|
|
948
|
+
resize: (width: number, height: number): Promise<{ success: boolean }> => {
|
|
949
|
+
return ipcRenderer.invoke(IPC_CHANNELS.POPOVER_RESIZE, width, height);
|
|
950
|
+
},
|
|
951
|
+
resizeToState: (state: string): Promise<{ success: boolean }> => {
|
|
952
|
+
return ipcRenderer.invoke(IPC_CHANNELS.POPOVER_RESIZE_TO_STATE, state);
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
|
|
956
|
+
// ===========================================================================
|
|
957
|
+
// Navigation API (Main -> Renderer navigation events)
|
|
958
|
+
// ===========================================================================
|
|
959
|
+
navigation: {
|
|
960
|
+
onShowSettings: (callback: () => void): Unsubscribe => {
|
|
961
|
+
const handler = () => callback();
|
|
962
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_SETTINGS, handler);
|
|
963
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_SETTINGS, handler);
|
|
964
|
+
},
|
|
965
|
+
onShowHistory: (callback: () => void): Unsubscribe => {
|
|
966
|
+
const handler = () => callback();
|
|
967
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_HISTORY, handler);
|
|
968
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_HISTORY, handler);
|
|
969
|
+
},
|
|
970
|
+
onShowShortcuts: (callback: () => void): Unsubscribe => {
|
|
971
|
+
const handler = () => callback();
|
|
972
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_SHORTCUTS, handler);
|
|
973
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_SHORTCUTS, handler);
|
|
974
|
+
},
|
|
975
|
+
onShowOnboarding: (callback: () => void): Unsubscribe => {
|
|
976
|
+
const handler = () => callback();
|
|
977
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_ONBOARDING, handler);
|
|
978
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_ONBOARDING, handler);
|
|
979
|
+
},
|
|
980
|
+
onShowExport: (callback: () => void): Unsubscribe => {
|
|
981
|
+
const handler = () => callback();
|
|
982
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_EXPORT, handler);
|
|
983
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_EXPORT, handler);
|
|
984
|
+
},
|
|
985
|
+
onShowWindowSelector: (callback: () => void): Unsubscribe => {
|
|
986
|
+
const handler = () => callback();
|
|
987
|
+
ipcRenderer.on(IPC_CHANNELS.SHOW_WINDOW_SELECTOR, handler);
|
|
988
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SHOW_WINDOW_SELECTOR, handler);
|
|
989
|
+
},
|
|
990
|
+
},
|
|
991
|
+
|
|
992
|
+
onSessionStatus: (
|
|
993
|
+
callback: (status: { action: string; status?: SessionStatusPayload }) => void
|
|
994
|
+
): Unsubscribe => {
|
|
995
|
+
const handler = (
|
|
996
|
+
_: Electron.IpcRendererEvent,
|
|
997
|
+
data: { action: string; status?: SessionStatusPayload }
|
|
998
|
+
) => callback(data);
|
|
999
|
+
ipcRenderer.on(IPC_CHANNELS.SESSION_STATUS, handler);
|
|
1000
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SESSION_STATUS, handler);
|
|
1001
|
+
},
|
|
1002
|
+
|
|
1003
|
+
onTranscriptionUpdate: (callback: (data: { text: string; isFinal: boolean }) => void): Unsubscribe => {
|
|
1004
|
+
const handler = (_: Electron.IpcRendererEvent, data: { text: string; isFinal: boolean }) =>
|
|
1005
|
+
callback(data);
|
|
1006
|
+
ipcRenderer.on(IPC_CHANNELS.TRANSCRIPTION_UPDATE, handler);
|
|
1007
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.TRANSCRIPTION_UPDATE, handler);
|
|
1008
|
+
},
|
|
1009
|
+
|
|
1010
|
+
onScreenshotCaptured: (callback: (data: { id: string; timestamp: number }) => void): Unsubscribe => {
|
|
1011
|
+
const handler = (_: Electron.IpcRendererEvent, data: { id: string; timestamp: number }) =>
|
|
1012
|
+
callback(data);
|
|
1013
|
+
ipcRenderer.on(IPC_CHANNELS.SCREENSHOT_CAPTURED, handler);
|
|
1014
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.SCREENSHOT_CAPTURED, handler);
|
|
1015
|
+
},
|
|
1016
|
+
|
|
1017
|
+
onOutputReady: (callback: (data: OutputReadyPayload) => void): Unsubscribe => {
|
|
1018
|
+
const handler = (_: Electron.IpcRendererEvent, data: OutputReadyPayload) =>
|
|
1019
|
+
callback(data);
|
|
1020
|
+
ipcRenderer.on(IPC_CHANNELS.OUTPUT_READY, handler);
|
|
1021
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.OUTPUT_READY, handler);
|
|
1022
|
+
},
|
|
1023
|
+
|
|
1024
|
+
onOutputError: (callback: (error: { message: string }) => void): Unsubscribe => {
|
|
1025
|
+
const handler = (_: Electron.IpcRendererEvent, error: { message: string }) => callback(error);
|
|
1026
|
+
ipcRenderer.on(IPC_CHANNELS.OUTPUT_ERROR, handler);
|
|
1027
|
+
return () => ipcRenderer.removeListener(IPC_CHANNELS.OUTPUT_ERROR, handler);
|
|
1028
|
+
},
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
// =============================================================================
|
|
1032
|
+
// Expose API to Renderer
|
|
1033
|
+
// =============================================================================
|
|
1034
|
+
|
|
1035
|
+
contextBridge.exposeInMainWorld('markupr', markuprApi);
|
|
1036
|
+
|
|
1037
|
+
// =============================================================================
|
|
1038
|
+
// Type Exports
|
|
1039
|
+
// =============================================================================
|
|
1040
|
+
|
|
1041
|
+
export type MarkuprAPI = typeof markuprApi;
|
|
1042
|
+
|
|
1043
|
+
declare global {
|
|
1044
|
+
interface Window {
|
|
1045
|
+
markupr: MarkuprAPI;
|
|
1046
|
+
}
|
|
1047
|
+
}
|