markupr 2.1.8 → 2.5.0

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.
Files changed (300) hide show
  1. package/README.md +292 -15
  2. package/dist/cli/index.mjs +3593 -0
  3. package/dist/main/index.mjs +743 -220
  4. package/dist/mcp/index.mjs +4053 -0
  5. package/package.json +32 -7
  6. package/.claude/commands/review-feedback.md +0 -47
  7. package/.eslintrc.json +0 -35
  8. package/.github/CODEOWNERS +0 -16
  9. package/.github/FUNDING.yml +0 -1
  10. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -56
  11. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -54
  12. package/.github/PULL_REQUEST_TEMPLATE.md +0 -89
  13. package/.github/dependabot.yml +0 -70
  14. package/.github/workflows/ci.yml +0 -184
  15. package/.github/workflows/deploy-landing.yml +0 -134
  16. package/.github/workflows/nightly.yml +0 -288
  17. package/.github/workflows/release.yml +0 -318
  18. package/CHANGELOG.md +0 -127
  19. package/CLAUDE.md +0 -137
  20. package/CODE_OF_CONDUCT.md +0 -9
  21. package/CONTRIBUTING.md +0 -390
  22. package/PRODUCT_VISION.md +0 -277
  23. package/SECURITY.md +0 -51
  24. package/SIGNING_INSTRUCTIONS.md +0 -284
  25. package/assets/DMG_BACKGROUND_INSTRUCTIONS.md +0 -130
  26. package/assets/svg-source/dmg-background.svg +0 -70
  27. package/assets/svg-source/icon.svg +0 -20
  28. package/assets/svg-source/tray-icon-processing.svg +0 -7
  29. package/assets/svg-source/tray-icon-recording.svg +0 -7
  30. package/assets/svg-source/tray-icon.svg +0 -6
  31. package/assets/tray-complete.png +0 -0
  32. package/assets/tray-complete@2x.png +0 -0
  33. package/assets/tray-completeTemplate.png +0 -0
  34. package/assets/tray-completeTemplate@2x.png +0 -0
  35. package/assets/tray-error.png +0 -0
  36. package/assets/tray-error@2x.png +0 -0
  37. package/assets/tray-errorTemplate.png +0 -0
  38. package/assets/tray-errorTemplate@2x.png +0 -0
  39. package/assets/tray-icon-processing.png +0 -0
  40. package/assets/tray-icon-processing@2x.png +0 -0
  41. package/assets/tray-icon-processingTemplate.png +0 -0
  42. package/assets/tray-icon-processingTemplate@2x.png +0 -0
  43. package/assets/tray-icon-recording.png +0 -0
  44. package/assets/tray-icon-recording@2x.png +0 -0
  45. package/assets/tray-icon-recordingTemplate.png +0 -0
  46. package/assets/tray-icon-recordingTemplate@2x.png +0 -0
  47. package/assets/tray-icon.png +0 -0
  48. package/assets/tray-icon@2x.png +0 -0
  49. package/assets/tray-iconTemplate.png +0 -0
  50. package/assets/tray-iconTemplate@2x.png +0 -0
  51. package/assets/tray-idle.png +0 -0
  52. package/assets/tray-idle@2x.png +0 -0
  53. package/assets/tray-idleTemplate.png +0 -0
  54. package/assets/tray-idleTemplate@2x.png +0 -0
  55. package/assets/tray-processing-0.png +0 -0
  56. package/assets/tray-processing-0@2x.png +0 -0
  57. package/assets/tray-processing-0Template.png +0 -0
  58. package/assets/tray-processing-0Template@2x.png +0 -0
  59. package/assets/tray-processing-1.png +0 -0
  60. package/assets/tray-processing-1@2x.png +0 -0
  61. package/assets/tray-processing-1Template.png +0 -0
  62. package/assets/tray-processing-1Template@2x.png +0 -0
  63. package/assets/tray-processing-2.png +0 -0
  64. package/assets/tray-processing-2@2x.png +0 -0
  65. package/assets/tray-processing-2Template.png +0 -0
  66. package/assets/tray-processing-2Template@2x.png +0 -0
  67. package/assets/tray-processing-3.png +0 -0
  68. package/assets/tray-processing-3@2x.png +0 -0
  69. package/assets/tray-processing-3Template.png +0 -0
  70. package/assets/tray-processing-3Template@2x.png +0 -0
  71. package/assets/tray-processing.png +0 -0
  72. package/assets/tray-processing@2x.png +0 -0
  73. package/assets/tray-processingTemplate.png +0 -0
  74. package/assets/tray-processingTemplate@2x.png +0 -0
  75. package/assets/tray-recording.png +0 -0
  76. package/assets/tray-recording@2x.png +0 -0
  77. package/assets/tray-recordingTemplate.png +0 -0
  78. package/assets/tray-recordingTemplate@2x.png +0 -0
  79. package/build/DMG_BACKGROUND_SPEC.md +0 -50
  80. package/build/dmg-background.png +0 -0
  81. package/build/dmg-background@2x.png +0 -0
  82. package/build/entitlements.mac.inherit.plist +0 -27
  83. package/build/entitlements.mac.plist +0 -41
  84. package/build/favicon-16.png +0 -0
  85. package/build/favicon-180.png +0 -0
  86. package/build/favicon-192.png +0 -0
  87. package/build/favicon-32.png +0 -0
  88. package/build/favicon-48.png +0 -0
  89. package/build/favicon-512.png +0 -0
  90. package/build/favicon-64.png +0 -0
  91. package/build/icon-128.png +0 -0
  92. package/build/icon-16.png +0 -0
  93. package/build/icon-24.png +0 -0
  94. package/build/icon-256.png +0 -0
  95. package/build/icon-32.png +0 -0
  96. package/build/icon-48.png +0 -0
  97. package/build/icon-64.png +0 -0
  98. package/build/icon.icns +0 -0
  99. package/build/icon.ico +0 -0
  100. package/build/icon.iconset/icon_128x128.png +0 -0
  101. package/build/icon.iconset/icon_128x128@2x.png +0 -0
  102. package/build/icon.iconset/icon_16x16.png +0 -0
  103. package/build/icon.iconset/icon_16x16@2x.png +0 -0
  104. package/build/icon.iconset/icon_256x256.png +0 -0
  105. package/build/icon.iconset/icon_256x256@2x.png +0 -0
  106. package/build/icon.iconset/icon_32x32.png +0 -0
  107. package/build/icon.iconset/icon_32x32@2x.png +0 -0
  108. package/build/icon.iconset/icon_512x512.png +0 -0
  109. package/build/icon.iconset/icon_512x512@2x.png +0 -0
  110. package/build/icon.png +0 -0
  111. package/build/installer-header.bmp +0 -0
  112. package/build/installer-header.png +0 -0
  113. package/build/installer-sidebar.bmp +0 -0
  114. package/build/installer-sidebar.png +0 -0
  115. package/build/installer.nsh +0 -45
  116. package/build/overlay-processing.png +0 -0
  117. package/build/overlay-recording.png +0 -0
  118. package/build/toolbar-record.png +0 -0
  119. package/build/toolbar-screenshot.png +0 -0
  120. package/build/toolbar-settings.png +0 -0
  121. package/build/toolbar-stop.png +0 -0
  122. package/dist/preload/index.mjs +0 -907
  123. package/dist/renderer/assets/index-CCmUjl9K.js +0 -19495
  124. package/dist/renderer/assets/index-CUqz_Gs6.css +0 -2270
  125. package/dist/renderer/index.html +0 -27
  126. package/docs/AI_AGENT_QUICKSTART.md +0 -42
  127. package/docs/AI_PIPELINE_DESIGN.md +0 -595
  128. package/docs/API.md +0 -514
  129. package/docs/ARCHITECTURE.md +0 -460
  130. package/docs/CONFIGURATION.md +0 -336
  131. package/docs/DEVELOPMENT.md +0 -508
  132. package/docs/EXPORT_FORMATS.md +0 -451
  133. package/docs/GETTING_STARTED.md +0 -236
  134. package/docs/KEYBOARD_SHORTCUTS.md +0 -334
  135. package/docs/TROUBLESHOOTING.md +0 -418
  136. package/docs/landing/index.html +0 -672
  137. package/docs/landing/script.js +0 -342
  138. package/docs/landing/styles.css +0 -1543
  139. package/electron-builder.yml +0 -140
  140. package/electron.vite.config.ts +0 -63
  141. package/railway.json +0 -12
  142. package/scripts/build.mjs +0 -51
  143. package/scripts/generate-icons.mjs +0 -314
  144. package/scripts/generate-installer-images.cjs +0 -253
  145. package/scripts/generate-tray-icons.mjs +0 -258
  146. package/scripts/notarize.cjs +0 -180
  147. package/scripts/one-click-clean-test.sh +0 -147
  148. package/scripts/postinstall.mjs +0 -36
  149. package/scripts/setup-markupr.sh +0 -55
  150. package/setup +0 -17
  151. package/site/index.html +0 -1835
  152. package/site/package.json +0 -11
  153. package/site/railway.json +0 -12
  154. package/site/server.js +0 -31
  155. package/src/main/AutoUpdater.ts +0 -392
  156. package/src/main/CrashRecovery.ts +0 -655
  157. package/src/main/ErrorHandler.ts +0 -703
  158. package/src/main/HotkeyManager.ts +0 -399
  159. package/src/main/MenuManager.ts +0 -529
  160. package/src/main/PermissionManager.ts +0 -420
  161. package/src/main/SessionController.ts +0 -1465
  162. package/src/main/TrayManager.ts +0 -540
  163. package/src/main/ai/AIPipelineManager.ts +0 -199
  164. package/src/main/ai/ClaudeAnalyzer.ts +0 -339
  165. package/src/main/ai/ImageOptimizer.ts +0 -176
  166. package/src/main/ai/StructuredMarkdownBuilder.ts +0 -379
  167. package/src/main/ai/index.ts +0 -16
  168. package/src/main/ai/types.ts +0 -258
  169. package/src/main/analysis/ClarificationGenerator.ts +0 -385
  170. package/src/main/analysis/FeedbackAnalyzer.ts +0 -531
  171. package/src/main/analysis/index.ts +0 -19
  172. package/src/main/audio/AudioCapture.ts +0 -978
  173. package/src/main/audio/audioUtils.ts +0 -100
  174. package/src/main/audio/index.ts +0 -20
  175. package/src/main/capture/index.ts +0 -1
  176. package/src/main/index.ts +0 -1693
  177. package/src/main/ipc/captureHandlers.ts +0 -272
  178. package/src/main/ipc/index.ts +0 -45
  179. package/src/main/ipc/outputHandlers.ts +0 -302
  180. package/src/main/ipc/sessionHandlers.ts +0 -56
  181. package/src/main/ipc/settingsHandlers.ts +0 -471
  182. package/src/main/ipc/types.ts +0 -56
  183. package/src/main/ipc/windowHandlers.ts +0 -277
  184. package/src/main/output/ClipboardService.ts +0 -369
  185. package/src/main/output/ExportService.ts +0 -539
  186. package/src/main/output/FileManager.ts +0 -416
  187. package/src/main/output/MarkdownGenerator.ts +0 -791
  188. package/src/main/output/MarkdownPatcher.ts +0 -299
  189. package/src/main/output/index.ts +0 -186
  190. package/src/main/output/sessionAdapter.ts +0 -207
  191. package/src/main/output/templates/html-template.ts +0 -553
  192. package/src/main/pipeline/FrameExtractor.ts +0 -330
  193. package/src/main/pipeline/PostProcessor.ts +0 -399
  194. package/src/main/pipeline/TranscriptAnalyzer.ts +0 -226
  195. package/src/main/pipeline/index.ts +0 -36
  196. package/src/main/platform/WindowsTaskbar.ts +0 -600
  197. package/src/main/platform/index.ts +0 -16
  198. package/src/main/settings/SettingsManager.ts +0 -730
  199. package/src/main/settings/index.ts +0 -19
  200. package/src/main/transcription/ModelDownloadManager.ts +0 -494
  201. package/src/main/transcription/TierManager.ts +0 -219
  202. package/src/main/transcription/TranscriptionRecoveryService.ts +0 -340
  203. package/src/main/transcription/WhisperService.ts +0 -748
  204. package/src/main/transcription/index.ts +0 -56
  205. package/src/main/transcription/types.ts +0 -135
  206. package/src/main/windows/PopoverManager.ts +0 -284
  207. package/src/main/windows/TaskbarIntegration.ts +0 -452
  208. package/src/main/windows/index.ts +0 -23
  209. package/src/preload/index.ts +0 -1047
  210. package/src/renderer/App.tsx +0 -515
  211. package/src/renderer/AppWrapper.tsx +0 -28
  212. package/src/renderer/assets/logo-dark.svg +0 -7
  213. package/src/renderer/assets/logo.svg +0 -7
  214. package/src/renderer/audio/AudioCaptureRenderer.ts +0 -454
  215. package/src/renderer/capture/ScreenRecordingRenderer.ts +0 -492
  216. package/src/renderer/components/AnnotationOverlay.tsx +0 -836
  217. package/src/renderer/components/AudioWaveform.tsx +0 -811
  218. package/src/renderer/components/ClarificationQuestions.tsx +0 -656
  219. package/src/renderer/components/CountdownTimer.tsx +0 -495
  220. package/src/renderer/components/CrashRecoveryDialog.tsx +0 -632
  221. package/src/renderer/components/DonateButton.tsx +0 -127
  222. package/src/renderer/components/ErrorBoundary.tsx +0 -308
  223. package/src/renderer/components/ExportDialog.tsx +0 -872
  224. package/src/renderer/components/HotkeyHint.tsx +0 -261
  225. package/src/renderer/components/KeyboardShortcuts.tsx +0 -787
  226. package/src/renderer/components/ModelDownloadDialog.tsx +0 -844
  227. package/src/renderer/components/Onboarding.tsx +0 -1830
  228. package/src/renderer/components/ProcessingOverlay.tsx +0 -157
  229. package/src/renderer/components/RecordingOverlay.tsx +0 -423
  230. package/src/renderer/components/SessionHistory.tsx +0 -1746
  231. package/src/renderer/components/SessionReview.tsx +0 -1321
  232. package/src/renderer/components/SettingsPanel.tsx +0 -217
  233. package/src/renderer/components/Skeleton.tsx +0 -347
  234. package/src/renderer/components/StatusIndicator.tsx +0 -86
  235. package/src/renderer/components/ThemeProvider.tsx +0 -429
  236. package/src/renderer/components/Tooltip.tsx +0 -370
  237. package/src/renderer/components/TranscriptionPreview.tsx +0 -183
  238. package/src/renderer/components/TranscriptionTierSelector.tsx +0 -640
  239. package/src/renderer/components/UpdateNotification.tsx +0 -377
  240. package/src/renderer/components/WindowSelector.tsx +0 -947
  241. package/src/renderer/components/index.ts +0 -99
  242. package/src/renderer/components/primitives/ApiKeyInput.tsx +0 -98
  243. package/src/renderer/components/primitives/ColorPicker.tsx +0 -65
  244. package/src/renderer/components/primitives/DangerButton.tsx +0 -45
  245. package/src/renderer/components/primitives/DirectoryPicker.tsx +0 -41
  246. package/src/renderer/components/primitives/Dropdown.tsx +0 -34
  247. package/src/renderer/components/primitives/KeyRecorder.tsx +0 -117
  248. package/src/renderer/components/primitives/SettingsSection.tsx +0 -32
  249. package/src/renderer/components/primitives/Slider.tsx +0 -43
  250. package/src/renderer/components/primitives/Toggle.tsx +0 -36
  251. package/src/renderer/components/primitives/index.ts +0 -10
  252. package/src/renderer/components/settings/AdvancedTab.tsx +0 -174
  253. package/src/renderer/components/settings/AppearanceTab.tsx +0 -77
  254. package/src/renderer/components/settings/GeneralTab.tsx +0 -40
  255. package/src/renderer/components/settings/HotkeysTab.tsx +0 -79
  256. package/src/renderer/components/settings/RecordingTab.tsx +0 -84
  257. package/src/renderer/components/settings/index.ts +0 -9
  258. package/src/renderer/components/settings/settingsStyles.ts +0 -673
  259. package/src/renderer/components/settings/tabConfig.tsx +0 -85
  260. package/src/renderer/components/settings/useSettingsPanel.ts +0 -447
  261. package/src/renderer/contexts/ProcessingContext.tsx +0 -227
  262. package/src/renderer/contexts/RecordingContext.tsx +0 -683
  263. package/src/renderer/contexts/UIContext.tsx +0 -326
  264. package/src/renderer/contexts/index.ts +0 -24
  265. package/src/renderer/donateMessages.ts +0 -69
  266. package/src/renderer/hooks/index.ts +0 -75
  267. package/src/renderer/hooks/useAnimation.tsx +0 -544
  268. package/src/renderer/hooks/useTheme.ts +0 -313
  269. package/src/renderer/index.html +0 -26
  270. package/src/renderer/main.tsx +0 -52
  271. package/src/renderer/styles/animations.css +0 -1093
  272. package/src/renderer/styles/app-shell.css +0 -662
  273. package/src/renderer/styles/globals.css +0 -515
  274. package/src/renderer/styles/theme.ts +0 -578
  275. package/src/renderer/types/electron.d.ts +0 -385
  276. package/src/shared/hotkeys.ts +0 -283
  277. package/src/shared/types.ts +0 -809
  278. package/tests/clipboard.test.ts +0 -228
  279. package/tests/e2e/criticalPaths.test.ts +0 -594
  280. package/tests/feedbackAnalyzer.test.ts +0 -303
  281. package/tests/integration/sessionFlow.test.ts +0 -583
  282. package/tests/markdownGenerator.test.ts +0 -418
  283. package/tests/output.test.ts +0 -96
  284. package/tests/setup.ts +0 -486
  285. package/tests/unit/appIntegration.test.ts +0 -676
  286. package/tests/unit/appViewState.test.ts +0 -281
  287. package/tests/unit/audioIpcChannels.test.ts +0 -17
  288. package/tests/unit/exportService.test.ts +0 -492
  289. package/tests/unit/hotkeys.test.ts +0 -92
  290. package/tests/unit/navigationPreload.test.ts +0 -94
  291. package/tests/unit/onboardingFlow.test.ts +0 -345
  292. package/tests/unit/permissionManager.test.ts +0 -175
  293. package/tests/unit/permissionManagerExpanded.test.ts +0 -296
  294. package/tests/unit/screenRecordingRenderer.test.ts +0 -368
  295. package/tests/unit/sessionController.test.ts +0 -515
  296. package/tests/unit/tierManager.test.ts +0 -61
  297. package/tests/unit/tierManagerExpanded.test.ts +0 -142
  298. package/tests/unit/transcriptAnalyzer.test.ts +0 -64
  299. package/tsconfig.json +0 -25
  300. package/vitest.config.ts +0 -46
@@ -1,277 +0,0 @@
1
- /**
2
- * Window IPC Handlers
3
- *
4
- * Registers IPC handlers for window control, popover management,
5
- * app version, Windows taskbar, transcription tiers, and Whisper model management.
6
- */
7
-
8
- import { ipcMain, app } from 'electron';
9
- import { tierManager } from '../transcription/TierManager';
10
- import { modelDownloadManager } from '../transcription/ModelDownloadManager';
11
- import type { WhisperModel } from '../transcription/types';
12
- import { POPOVER_SIZES } from '../windows';
13
- import {
14
- IPC_CHANNELS,
15
- type TranscriptionTier as UiTranscriptionTier,
16
- type TranscriptionTierStatus,
17
- } from '../../shared/types';
18
- import type { IpcContext } from './types';
19
-
20
- export function registerWindowHandlers(ctx: IpcContext): void {
21
- const { getMainWindow, getPopover, getWindowsTaskbar } = ctx;
22
-
23
- // -------------------------------------------------------------------------
24
- // App Version
25
- // -------------------------------------------------------------------------
26
-
27
- ipcMain.handle(IPC_CHANNELS.APP_VERSION, () => {
28
- return app.getVersion();
29
- });
30
-
31
- // -------------------------------------------------------------------------
32
- // Window Control
33
- // -------------------------------------------------------------------------
34
-
35
- ipcMain.handle(IPC_CHANNELS.WINDOW_MINIMIZE, () => {
36
- getMainWindow()?.minimize();
37
- return { success: true };
38
- });
39
-
40
- ipcMain.handle(IPC_CHANNELS.WINDOW_HIDE, () => {
41
- const popover = getPopover();
42
- if (popover) {
43
- popover.hide();
44
- } else {
45
- getMainWindow()?.hide();
46
- }
47
- return { success: true };
48
- });
49
-
50
- ipcMain.handle(IPC_CHANNELS.WINDOW_CLOSE, () => {
51
- getMainWindow()?.close();
52
- return { success: true };
53
- });
54
-
55
- // -------------------------------------------------------------------------
56
- // Popover Control (Menu Bar Mode)
57
- // -------------------------------------------------------------------------
58
-
59
- ipcMain.handle(IPC_CHANNELS.POPOVER_RESIZE, (_, width: number, height: number) => {
60
- const popover = getPopover();
61
- if (popover) {
62
- popover.resize(width, height);
63
- return { success: true };
64
- }
65
- return { success: false, error: 'Popover not initialized' };
66
- });
67
-
68
- ipcMain.handle(IPC_CHANNELS.POPOVER_RESIZE_TO_STATE, (_, state: string) => {
69
- const popover = getPopover();
70
- if (popover && state in POPOVER_SIZES) {
71
- popover.resizeToState(state as keyof typeof POPOVER_SIZES);
72
- return { success: true };
73
- }
74
- return { success: false, error: 'Popover not initialized or invalid state' };
75
- });
76
-
77
- ipcMain.handle(IPC_CHANNELS.POPOVER_SHOW, () => {
78
- getPopover()?.show();
79
- return { success: true };
80
- });
81
-
82
- ipcMain.handle(IPC_CHANNELS.POPOVER_HIDE, () => {
83
- getPopover()?.hide();
84
- return { success: true };
85
- });
86
-
87
- ipcMain.handle(IPC_CHANNELS.POPOVER_TOGGLE, () => {
88
- getPopover()?.toggle();
89
- return { success: true };
90
- });
91
-
92
- // -------------------------------------------------------------------------
93
- // Windows Taskbar (Windows-specific)
94
- // -------------------------------------------------------------------------
95
-
96
- ipcMain.handle(
97
- IPC_CHANNELS.TASKBAR_SET_PROGRESS,
98
- (_, progress: number) => {
99
- getWindowsTaskbar()?.setProgress(progress);
100
- return { success: true };
101
- }
102
- );
103
-
104
- ipcMain.handle(
105
- IPC_CHANNELS.TASKBAR_FLASH_FRAME,
106
- (_, count?: number) => {
107
- getWindowsTaskbar()?.flashFrame(count);
108
- return { success: true };
109
- }
110
- );
111
-
112
- ipcMain.handle(
113
- IPC_CHANNELS.TASKBAR_SET_OVERLAY,
114
- (_, state: 'recording' | 'processing' | 'none') => {
115
- getWindowsTaskbar()?.setOverlayIcon(state);
116
- return { success: true };
117
- }
118
- );
119
-
120
- // -------------------------------------------------------------------------
121
- // Transcription Tier Control
122
- // -------------------------------------------------------------------------
123
-
124
- ipcMain.handle(
125
- IPC_CHANNELS.TRANSCRIPTION_GET_TIER_STATUSES,
126
- async (): Promise<TranscriptionTierStatus[]> => {
127
- const statuses = await tierManager.getTierStatuses();
128
-
129
- return statuses.map((status) => {
130
- if (tierManager.tierProvidesTranscription(status.tier)) {
131
- return status;
132
- }
133
-
134
- return {
135
- ...status,
136
- available: false,
137
- reason: 'Not supported for narrated feedback reports',
138
- };
139
- });
140
- }
141
- );
142
-
143
- ipcMain.handle(
144
- IPC_CHANNELS.TRANSCRIPTION_GET_CURRENT_TIER,
145
- async (): Promise<UiTranscriptionTier | null> => {
146
- const preferred = tierManager.getPreferredTier();
147
- if (preferred !== 'auto') {
148
- return preferred;
149
- }
150
-
151
- const active = tierManager.getCurrentTier();
152
- if (active) {
153
- return active;
154
- }
155
-
156
- const best = await tierManager.selectBestTier();
157
- if (tierManager.tierProvidesTranscription(best)) {
158
- return best;
159
- }
160
-
161
- return null;
162
- }
163
- );
164
-
165
- ipcMain.handle(
166
- IPC_CHANNELS.TRANSCRIPTION_SET_TIER,
167
- (_, tier: UiTranscriptionTier): { success: boolean; error?: string } => {
168
- try {
169
- const validTiers = new Set(['auto', 'whisper', 'timer-only']);
170
- if (!validTiers.has(tier)) {
171
- return { success: false, error: `Tier "${tier}" is no longer supported.` };
172
- }
173
- tierManager.setPreferredTier(tier as 'auto' | 'whisper' | 'timer-only');
174
- return { success: true };
175
- } catch (error) {
176
- return {
177
- success: false,
178
- error: error instanceof Error ? error.message : 'Failed to set transcription tier.',
179
- };
180
- }
181
- }
182
- );
183
-
184
- // -------------------------------------------------------------------------
185
- // Whisper Model Channels
186
- // -------------------------------------------------------------------------
187
-
188
- ipcMain.handle(IPC_CHANNELS.WHISPER_CHECK_MODEL, () => {
189
- const hasAnyModel = modelDownloadManager.hasAnyModel();
190
- const downloadedModels: string[] = [];
191
- const models: WhisperModel[] = ['tiny', 'base', 'small', 'medium', 'large'];
192
-
193
- for (const model of models) {
194
- if (modelDownloadManager.isModelDownloaded(model)) {
195
- downloadedModels.push(model);
196
- }
197
- }
198
-
199
- const defaultModel = hasAnyModel ? modelDownloadManager.getDefaultModel() : null;
200
- const recommendedModel = 'tiny';
201
- const recommendedInfo = modelDownloadManager.getModelInfo('tiny');
202
-
203
- return {
204
- hasAnyModel,
205
- defaultModel,
206
- downloadedModels,
207
- recommendedModel,
208
- recommendedModelSizeMB: recommendedInfo.sizeMB,
209
- };
210
- });
211
-
212
- ipcMain.handle(IPC_CHANNELS.WHISPER_HAS_TRANSCRIPTION_CAPABILITY, async () => {
213
- return tierManager.hasTranscriptionCapability();
214
- });
215
-
216
- ipcMain.handle(IPC_CHANNELS.WHISPER_GET_AVAILABLE_MODELS, () => {
217
- const models = modelDownloadManager.getAvailableModels();
218
- return models.map((info) => ({
219
- name: info.name,
220
- filename: info.filename,
221
- sizeMB: info.sizeMB,
222
- ramRequired: info.ramRequired,
223
- quality: info.quality,
224
- isDownloaded: modelDownloadManager.isModelDownloaded(info.name as WhisperModel),
225
- }));
226
- });
227
-
228
- ipcMain.handle(IPC_CHANNELS.WHISPER_DOWNLOAD_MODEL, async (_, model: WhisperModel) => {
229
- try {
230
- const unsubProgress = modelDownloadManager.onProgress((progress) => {
231
- getMainWindow()?.webContents.send(IPC_CHANNELS.WHISPER_DOWNLOAD_PROGRESS, {
232
- model: progress.model,
233
- downloadedBytes: progress.downloadedBytes,
234
- totalBytes: progress.totalBytes,
235
- percent: progress.percent,
236
- speedBps: progress.speedBps,
237
- estimatedSecondsRemaining: progress.estimatedSecondsRemaining,
238
- });
239
- });
240
-
241
- const unsubComplete = modelDownloadManager.onComplete((result) => {
242
- getMainWindow()?.webContents.send(IPC_CHANNELS.WHISPER_DOWNLOAD_COMPLETE, {
243
- model: result.model,
244
- path: result.path,
245
- });
246
- unsubProgress();
247
- unsubComplete();
248
- unsubError();
249
- });
250
-
251
- const unsubError = modelDownloadManager.onError((error, errorModel) => {
252
- getMainWindow()?.webContents.send(IPC_CHANNELS.WHISPER_DOWNLOAD_ERROR, {
253
- model: errorModel,
254
- error: error.message,
255
- });
256
- unsubProgress();
257
- unsubComplete();
258
- unsubError();
259
- });
260
-
261
- const result = await modelDownloadManager.downloadModel(model);
262
-
263
- return { success: result.success };
264
- } catch (error) {
265
- console.error('[Main] Failed to download Whisper model:', error);
266
- return {
267
- success: false,
268
- error: error instanceof Error ? error.message : 'Unknown error'
269
- };
270
- }
271
- });
272
-
273
- ipcMain.handle(IPC_CHANNELS.WHISPER_CANCEL_DOWNLOAD, (_, model: WhisperModel) => {
274
- modelDownloadManager.cancelDownload(model);
275
- return { success: true };
276
- });
277
- }
@@ -1,369 +0,0 @@
1
- /**
2
- * ClipboardService - Auto-copy on save with native notifications
3
- *
4
- * Features:
5
- * - Copy generated summary to clipboard automatically
6
- * - Toast notification confirms copy
7
- * - Summary truncation to <1500 chars
8
- * - Include path to full report
9
- * - Support both full and compact modes
10
- */
11
-
12
- import { clipboard, Notification, app, BrowserWindow } from 'electron';
13
- import path from 'path';
14
- import { Session, FeedbackItem } from '../SessionController';
15
-
16
- // =============================================================================
17
- // Types
18
- // =============================================================================
19
-
20
- export interface ClipboardService {
21
- copy(content: string): Promise<boolean>;
22
- copyWithNotification(content: string, title?: string): Promise<boolean>;
23
- estimateSize(content: string): number;
24
- generateClipboardSummary(session: Session, options?: SummaryOptions): string;
25
- }
26
-
27
- export interface SummaryOptions {
28
- mode: 'full' | 'compact';
29
- maxLength: number;
30
- includeReportPath: boolean;
31
- reportPath?: string;
32
- }
33
-
34
- export const DEFAULT_SUMMARY_OPTIONS: SummaryOptions = {
35
- mode: 'compact',
36
- maxLength: 1500,
37
- includeReportPath: true,
38
- };
39
-
40
- // =============================================================================
41
- // ClipboardService Implementation
42
- // =============================================================================
43
-
44
- class ClipboardServiceImpl implements ClipboardService {
45
- private lastNotificationTime = 0;
46
- private readonly NOTIFICATION_DEBOUNCE_MS = 1000;
47
-
48
- /**
49
- * Copy content to system clipboard
50
- */
51
- async copy(content: string): Promise<boolean> {
52
- try {
53
- clipboard.writeText(content);
54
- console.log(`[Clipboard] Copied ${content.length} characters`);
55
- return true;
56
- } catch (error) {
57
- console.error('[Clipboard] Failed to copy:', error);
58
- return false;
59
- }
60
- }
61
-
62
- /**
63
- * Copy content to clipboard and show native notification
64
- */
65
- async copyWithNotification(content: string, title?: string): Promise<boolean> {
66
- const success = await this.copy(content);
67
-
68
- // Debounce notifications
69
- const now = Date.now();
70
- if (now - this.lastNotificationTime < this.NOTIFICATION_DEBOUNCE_MS) {
71
- return success;
72
- }
73
- this.lastNotificationTime = now;
74
-
75
- if (success) {
76
- this.showNotification(
77
- title || 'markupr',
78
- 'Summary copied to clipboard!',
79
- 'Paste into your AI coding assistant.'
80
- );
81
- } else {
82
- this.showNotification(
83
- 'markupr',
84
- 'Failed to copy',
85
- 'Please try again or copy manually.'
86
- );
87
- }
88
-
89
- return success;
90
- }
91
-
92
- /**
93
- * Estimate byte size for UTF-8 content
94
- */
95
- estimateSize(content: string): number {
96
- // Calculate UTF-8 byte size
97
- return Buffer.byteLength(content, 'utf8');
98
- }
99
-
100
- /**
101
- * Generate a clipboard-friendly summary from a session
102
- * Follows llms.txt format for AI consumption
103
- */
104
- generateClipboardSummary(
105
- session: Session,
106
- options: Partial<SummaryOptions> = {}
107
- ): string {
108
- const opts: SummaryOptions = { ...DEFAULT_SUMMARY_OPTIONS, ...options };
109
-
110
- const duration = session.endTime
111
- ? Math.round((session.endTime - session.startTime) / 1000)
112
- : Math.round((Date.now() - session.startTime) / 1000);
113
-
114
- const feedbackItems = session.feedbackItems;
115
-
116
- // Build summary based on mode
117
- let summary: string;
118
-
119
- if (opts.mode === 'full') {
120
- summary = this.generateFullSummary(feedbackItems, duration);
121
- } else {
122
- summary = this.generateCompactSummary(feedbackItems, duration);
123
- }
124
-
125
- // Add report path if available
126
- if (opts.includeReportPath && opts.reportPath) {
127
- summary += `\n\n---\nFull report: ${opts.reportPath}`;
128
- }
129
-
130
- // Truncate if needed
131
- if (summary.length > opts.maxLength) {
132
- summary = this.truncateSummary(summary, opts.maxLength);
133
- }
134
-
135
- return summary;
136
- }
137
-
138
- /**
139
- * Generate full summary with all feedback items
140
- */
141
- private generateFullSummary(items: FeedbackItem[], durationSec: number): string {
142
- const lines: string[] = [
143
- '# Feedback Session',
144
- '',
145
- `**Duration:** ${this.formatDuration(durationSec)}`,
146
- `**Items:** ${items.length}`,
147
- '',
148
- '---',
149
- '',
150
- ];
151
-
152
- for (const item of items) {
153
- const time = new Date(item.timestamp).toLocaleTimeString('en-US', {
154
- hour: '2-digit',
155
- minute: '2-digit',
156
- second: '2-digit',
157
- });
158
-
159
- lines.push(`## [${time}]`);
160
- lines.push('');
161
- lines.push(item.text);
162
-
163
- if (item.screenshot) {
164
- lines.push('');
165
- lines.push(`_[Screenshot captured: ${item.screenshot.width}x${item.screenshot.height}]_`);
166
- }
167
-
168
- lines.push('');
169
- }
170
-
171
- return lines.join('\n');
172
- }
173
-
174
- /**
175
- * Generate compact summary optimized for clipboard
176
- */
177
- private generateCompactSummary(items: FeedbackItem[], durationSec: number): string {
178
- const lines: string[] = [
179
- '# Feedback Summary',
180
- '',
181
- `Duration: ${this.formatDuration(durationSec)} | ${items.length} items | ${items.filter((i) => i.screenshot).length} screenshots`,
182
- '',
183
- ];
184
-
185
- // Group similar items and extract key points
186
- const keyPoints = this.extractKeyPoints(items);
187
-
188
- if (keyPoints.length > 0) {
189
- lines.push('## Key Points');
190
- lines.push('');
191
-
192
- for (const point of keyPoints) {
193
- lines.push(`- ${point}`);
194
- }
195
-
196
- lines.push('');
197
- }
198
-
199
- // Add timeline summary
200
- lines.push('## Timeline');
201
- lines.push('');
202
-
203
- for (const item of items) {
204
- const time = new Date(item.timestamp).toLocaleTimeString('en-US', {
205
- hour: '2-digit',
206
- minute: '2-digit',
207
- });
208
-
209
- // Truncate text for compact view
210
- const text =
211
- item.text.length > 100 ? item.text.substring(0, 97) + '...' : item.text;
212
-
213
- const screenshotIndicator = item.screenshot ? ' [img]' : '';
214
- lines.push(`- **${time}:** ${text}${screenshotIndicator}`);
215
- }
216
-
217
- return lines.join('\n');
218
- }
219
-
220
- /**
221
- * Extract key points from feedback items
222
- * Simple heuristics for now, could be enhanced with NLP
223
- */
224
- private extractKeyPoints(items: FeedbackItem[]): string[] {
225
- const keyPoints: string[] = [];
226
-
227
- // Filter out very short or low-confidence items
228
- const significantItems = items.filter(
229
- (item) => item.text.length > 20 && item.confidence > 0.7
230
- );
231
-
232
- // Extract unique key phrases
233
- const seen = new Set<string>();
234
-
235
- for (const item of significantItems) {
236
- // Look for action words and important phrases
237
- const text = item.text.toLowerCase();
238
-
239
- // Skip if too similar to existing points
240
- const normalized = text.substring(0, 50);
241
- if (seen.has(normalized)) {
242
- continue;
243
- }
244
- seen.add(normalized);
245
-
246
- // Capitalize first letter and add
247
- const point = item.text.charAt(0).toUpperCase() + item.text.slice(1);
248
- keyPoints.push(point);
249
-
250
- // Limit to 5 key points
251
- if (keyPoints.length >= 5) {
252
- break;
253
- }
254
- }
255
-
256
- return keyPoints;
257
- }
258
-
259
- /**
260
- * Truncate summary while keeping it readable
261
- */
262
- private truncateSummary(summary: string, maxLength: number): string {
263
- if (summary.length <= maxLength) {
264
- return summary;
265
- }
266
-
267
- // Find a good break point (end of line or sentence)
268
- const targetLength = maxLength - 50; // Leave room for truncation notice
269
- let breakPoint = summary.lastIndexOf('\n', targetLength);
270
-
271
- if (breakPoint === -1 || breakPoint < targetLength * 0.7) {
272
- // Try sentence boundary
273
- breakPoint = summary.lastIndexOf('. ', targetLength);
274
-
275
- if (breakPoint === -1 || breakPoint < targetLength * 0.5) {
276
- breakPoint = targetLength;
277
- } else {
278
- breakPoint += 1; // Include the period
279
- }
280
- }
281
-
282
- return (
283
- summary.substring(0, breakPoint) +
284
- '\n\n_[Truncated - see full report for details]_'
285
- );
286
- }
287
-
288
- /**
289
- * Format duration in human-readable form
290
- */
291
- private formatDuration(seconds: number): string {
292
- if (seconds < 60) {
293
- return `${seconds}s`;
294
- }
295
-
296
- const minutes = Math.floor(seconds / 60);
297
- const remainingSeconds = seconds % 60;
298
-
299
- if (minutes < 60) {
300
- return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
301
- }
302
-
303
- const hours = Math.floor(minutes / 60);
304
- const remainingMinutes = minutes % 60;
305
-
306
- return `${hours}h ${remainingMinutes}m`;
307
- }
308
-
309
- /**
310
- * Show native notification
311
- */
312
- private showNotification(title: string, body: string, subtitle?: string): void {
313
- // Check if notifications are supported
314
- if (!Notification.isSupported()) {
315
- console.log(`[Notification] (unsupported) ${title}: ${body}`);
316
- return;
317
- }
318
-
319
- const notification = new Notification({
320
- title,
321
- body,
322
- subtitle,
323
- silent: false,
324
- icon: this.getIconPath(),
325
- timeoutType: 'default', // Auto-dismiss
326
- });
327
-
328
- notification.on('click', () => {
329
- // Focus the app when notification is clicked
330
- const windows = BrowserWindow.getAllWindows();
331
- if (windows.length > 0) {
332
- windows[0].focus();
333
- }
334
- console.log('[Notification] Clicked - focusing app');
335
- });
336
-
337
- notification.on('close', () => {
338
- console.log('[Notification] Dismissed');
339
- });
340
-
341
- notification.show();
342
- }
343
-
344
- /**
345
- * Get app icon path for notification
346
- */
347
- private getIconPath(): string | undefined {
348
- // macOS uses the app icon automatically
349
- if (process.platform === 'darwin') {
350
- return undefined;
351
- }
352
-
353
- // For Windows/Linux, try to find the icon
354
- // This would be in the app resources after packaging
355
- try {
356
- const iconPath = path.join(app.getAppPath(), 'assets', 'icon.png');
357
- return iconPath;
358
- } catch {
359
- return undefined;
360
- }
361
- }
362
- }
363
-
364
- // =============================================================================
365
- // Singleton Export
366
- // =============================================================================
367
-
368
- export const clipboardService = new ClipboardServiceImpl();
369
- export default ClipboardService;