返回 oh-my-ppt
ipc.ts
根目录 / src / renderer / src / lib / ipc.ts
1 import type {
2 FontSelection,
3 GenerateAddPagePayload,
4 GenerateChunkEvent,
5 GenerateRetryFailedPayload,
6 GenerateRetrySinglePagePayload,
7 GenerateStartPayload,
8 SessionPageEditAssessment,
9 ParseDocumentPlanPayload,
10 ParseImageReferencePayload,
11 ParsedDocumentPlanResult,
12 PrepareReferenceDocumentPayload,
13 PreparedReferenceDocumentResult,
14 SourceDocumentPlan,
15 SwitchSessionStylePayload,
16 RetrySessionStylePayload,
17 RetryDeckEditPayload,
18 PptxImportPayload,
19 PptxImportProgressPayload,
20 PptxImportResult,
21 UploadedAsset
22 } from '@shared/generation.js'
23 import type { UpdateAvailablePayload } from '@shared/app-update.js'
24 import type { SpeechConfig } from '@shared/speech'
25 import type { HistoryVersion, RollbackHistoryResult } from '@shared/history.js'
26 import type { HtmlThumbnailResourceType } from '@shared/thumbnail'
27 import type { IndexTransitionConfig, IndexTransitionType } from '@shared/index-transition.js'
28 import type { ElementAnimationConfig, ElementAnimationPatch } from '@shared/element-animation.js'
29 import type {
30 ThinkingStage,
31 ThinkingChatMessage,
32 ThinkingWorkspace,
33 ThinkingChatResult,
34 ThinkingPrepareGenerationResult,
35 ThinkingWorkspaceListItem
36 } from '@shared/thinking.js'
37 import type {
38 GeneratedImageAsset,
39 ImageGenerateResult,
40 ImageGeneratePayload,
41 ImagePromptGeneratePayload,
42 ImagePromptGenerateResult,
43 ImageGenerationHistoryRecord,
44 ImageModelConfig,
45 ImageModelProvider,
46 ImageModelVerificationResult
47 } from '@shared/image-generation.js'
48 import type { ThinkingParameterMode } from '@shared/model-config.js'
49 import type { ExportProgressPayload } from '@shared/export-progress.js'
50 import type { PageMergeDisabledReason } from '@shared/page-merge'
51 import type { ModelUsagePeriod, ModelUsageStats } from '@shared/model-usage'
52 import type { SlideSizePresetId } from '@shared/slide-size'
53 import type { ParsedChartDataResult } from '@shared/chart-data'
54 import type { SessionMasterConfig, SessionMasterStatus } from '@shared/master'
55 import type { SessionLayoutLibrary, SessionLayoutLibraryStatus } from '@shared/layout-master'
56
57 type IpcRendererLike = Window['electron']['ipcRenderer']
58
59 function getIpc(): IpcRendererLike {
60 const ipc = window.electron?.ipcRenderer
61 if (!ipc) {
62 const electronKeys = window.electron ? Object.keys(window.electron).join(', ') : 'none'
63 throw new Error(`Electron preload IPC is unavailable. window.electron keys: ${electronKeys}`)
64 }
65 return ipc
66 }
67
68 export interface StyleCategory {
69 name: string
70 styles: Array<{
71 id: string
72 label: string
73 description: string
74 source?: 'builtin' | 'custom' | 'override'
75 editable?: boolean
76 styleCase?: string
77 imageGenerationPrompt?: string
78 }>
79 }
80
81 export type ImageFulfillmentJobView = {
82 id: string
83 session_id: string
84 session_page_id: string
85 page_id: string
86 status: 'pending' | 'running' | 'finalizing' | 'completed' | 'degraded' | 'failed' | 'cancelled'
87 error: string | null
88 created_at: number
89 updated_at: number
90 layout_failed_count: number
91 retryable_intent_count: number
92 }
93
94 export interface StyleDetail {
95 id: string
96 styleKey?: string
97 label: string
98 name?: {
99 zh: string
100 en: string
101 }
102 description: string
103 aliases: string[]
104 styleSkill: string
105 source?: 'builtin' | 'custom' | 'override'
106 editable?: boolean
107 category?: string
108 version?: string
109 styleCase?: string
110 imageGenerationPrompt?: string
111 packageDir?: string
112 }
113
114 export interface StyleListItem {
115 id: string
116 styleKey?: string
117 label: string
118 name?: {
119 zh: string
120 en: string
121 }
122 description: string
123 aliases?: string[]
124 category: string
125 source?: 'builtin' | 'custom' | 'override'
126 editable?: boolean
127 version?: string
128 styleCase?: string
129 imageGenerationPrompt?: string
130 packageDir?: string
131 favoriteAt?: number | null
132 previewPath?: string | null
133 thumbnailPath?: string | null
134 createdAt?: number
135 updatedAt?: number
136 }
137
138 export interface HtmlThumbnailTask {
139 resourceType: HtmlThumbnailResourceType
140 resourceId: string
141 variant: string
142 status: 'queued' | 'running' | 'completed' | 'failed'
143 thumbnailPath: string | null
144 error?: string
145 }
146
147 export interface StyleParseResult {
148 label: string
149 description: string
150 category: string
151 aliases: string[]
152 styleSkill: string
153 styleCase?: string
154 imageGenerationPrompt?: string
155 }
156
157 export interface GenerateRunStateSnapshot {
158 sessionId: string
159 runId: string | null
160 status: 'idle' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled'
161 hasActiveRun: boolean
162 progress: number
163 totalPages: number
164 completedPageCount?: number
165 failedPageCount?: number
166 events: GenerateChunkEvent[]
167 error: string | null
168 startedAt: number | null
169 updatedAt: number | null
170 kind?:
171 | 'standard'
172 | 'template'
173 | 'retry'
174 | 'add-page'
175 | 'single-page-retry'
176 | 'edit'
177 | 'page-edit'
178 | 'deck-edit'
179 | 'style-switch'
180 targetPageId?: string
181 targetPageNumber?: number
182 activityKind?:
183 | 'page-edit'
184 | 'deck-edit'
185 | 'edit'
186 | 'style-switch'
187 | 'single-page-retry'
188 | 'addPage'
189 retryPayload?: GenerateStartPayload
190 }
191
192 export interface StyleSwitchJobSnapshot {
193 sessionId: string
194 runId: string | null
195 status: 'idle' | 'running' | 'completed' | 'partial' | 'failed' | 'cancelled'
196 hasActiveRun: boolean
197 progress: number
198 totalPages: number
199 completedPageCount: number
200 failedPageCount: number
201 targetStyleId: string | null
202 targetStyleName: string | null
203 pages: Array<{
204 pageId: string
205 pageNumber: number
206 title: string
207 status: 'pending' | 'running' | 'completed' | 'failed'
208 error: string | null
209 retryCount: number
210 }>
211 error: string | null
212 startedAt: number | null
213 updatedAt: number | null
214 kind: 'style-switch'
215 }
216
217 export interface ExportDeckResult {
218 success: boolean
219 cancelled?: boolean
220 path?: string
221 warnings?: string[]
222 pageCount?: number
223 durationMs?: number
224 frameCount?: number
225 }
226
227 export interface MergeSourceSessionSummary {
228 id: string
229 title: string
230 pageCount: number
231 slideSizeId: SlideSizePresetId
232 slideWidth: number
233 slideHeight: number
234 updatedAt: number
235 status: string
236 selectable: boolean
237 disabledReason?: PageMergeDisabledReason
238 }
239
240 export interface MergeTemplateSourceSummary {
241 id: string
242 title: string
243 pageCount: number
244 slideSizeId: SlideSizePresetId
245 slideWidth: number
246 slideHeight: number
247 updatedAt: number
248 thumbnailPath: string | null
249 selectable: boolean
250 disabledReason?: PageMergeDisabledReason
251 isSource: boolean
252 }
253
254 export interface MergeSourcePageSummary {
255 id: string
256 pageId: string
257 pageNumber: number
258 title: string
259 contentOutline?: string | null
260 slideSizeId: SlideSizePresetId
261 slideWidth: number
262 slideHeight: number
263 htmlPath?: string
264 sourceUrl?: string
265 status?: string
266 selectable: boolean
267 disabledReason?: PageMergeDisabledReason
268 }
269
270 export interface ImportSessionFileResult {
271 success: boolean
272 cancelled?: boolean
273 sessionId?: string
274 title?: string
275 pageCount?: number
276 warnings?: string[]
277 }
278
279 export interface HtmlEditorImportResult {
280 docId: string
281 title: string
282 htmlPath: string
283 sourcePath: string
284 designWidth: number
285 html: string
286 }
287
288 export interface HtmlEditorAiMessage {
289 role: 'user' | 'assistant'
290 content: string
291 selectedElement?: HtmlEditorAiElementContext
292 }
293
294 export interface HtmlEditorAiElementContext {
295 selector: string
296 label?: string
297 elementTag?: string
298 elementText?: string
299 html?: string
300 }
301
302 export interface HtmlEditorAiEditBatch {
303 propertyEdits: Array<Record<string, unknown>>
304 textEdits: Array<Record<string, unknown>>
305 dragEdits: Array<Record<string, unknown>>
306 deletes: Array<Record<string, unknown>>
307 addElements: Array<Record<string, unknown>>
308 }
309
310 export type HtmlEditorAiIntent = 'inspect' | 'redesign' | 'style' | 'layout' | 'content' | 'other'
311
312 export interface HtmlEditorAiPlan {
313 intent: HtmlEditorAiIntent
314 target: string
315 summary: string
316 changes: string[]
317 confirmationQuestion: string
318 edits: HtmlEditorAiEditBatch
319 }
320
321 export interface HtmlEditorAiHistoryMessage extends HtmlEditorAiMessage {
322 id: string
323 createdAt: number
324 intent?: HtmlEditorAiIntent
325 plan?: HtmlEditorAiPlan | null
326 requiresConfirmation?: boolean
327 }
328
329 export type HtmlEditorFileImportResult =
330 | { cancelled: true; reason?: 'user-cancelled' | 'storage-not-configured' }
331 | ({ cancelled: false } & HtmlEditorImportResult)
332
333 export interface TemplateListItem {
334 id: string
335 name: string
336 description: string
337 source: 'user'
338 pageCount: number
339 tags: string[]
340 slideSizeId?: import('@shared/slide-size').SlideSizePresetId
341 slideWidth?: number
342 slideHeight?: number
343 previewHtmlPath: string | null
344 thumbnailPath: string | null
345 previewPages: Array<{
346 pageNumber: number
347 pageId: string
348 title: string
349 htmlPath: string
350 }>
351 createdAt: number
352 updatedAt: number
353 }
354
355 export interface EnsureElementAnchorPayload {
356 sessionId?: string
357 htmlPath: string
358 pageId: string
359 selector: string
360 elementTag?: string
361 elementText?: string
362 reason?: 'inspect' | 'drag' | 'text-edit'
363 formula?: {
364 latex: string
365 html: string
366 displayMode: boolean
367 }
368 }
369
370 export interface EnsureElementAnchorResult {
371 success: boolean
372 selector: string
373 blockId: string
374 changed: boolean
375 }
376
377 export interface UploadAssetsPayload {
378 sessionId: string
379 files: Array<{
380 path: string
381 name?: string
382 }>
383 }
384
385 export interface UpdateElementLayoutPayload {
386 sessionId: string
387 htmlPath: string
388 pageId: string
389 selector: string
390 x: number
391 y: number
392 width?: number
393 height?: number
394 childUpdates?: Array<{
395 path: number[]
396 width?: number
397 height?: number
398 }>
399 isAbsoluteMode?: boolean
400 }
401
402 export interface UpdateElementPropertiesPayload {
403 sessionId: string
404 htmlPath: string
405 pageId: string
406 selector: string
407 patch: {
408 html?: string
409 text?: string
410 textTarget?: {
411 type: 'text-node'
412 parentSelector: string
413 textNodeIndex: number
414 text: string
415 }
416 style?: {
417 color?: string
418 fontSize?: string
419 fontWeight?: string
420 textAlign?: string
421 }
422 }
423 }
424
425 export interface CreateSessionPayload {
426 topic: string
427 styleId: string
428 modelConfigId?: string
429 visualEnabled?: boolean
430 imageModelConfigId?: string
431 pageCount?: number
432 slideSizeId?: import('@shared/slide-size').SlideSizePresetId
433 referenceDocumentPath?: string
434 fontSelection?: FontSelection
435 sourcePlan?: SourceDocumentPlan
436 }
437
438 export interface SaveSessionAsNewPayload {
439 sessionId: string
440 title: string
441 }
442
443 export interface SaveSessionAsNewResult {
444 sessionId: string
445 }
446
447 export interface ModelConfig {
448 id: string
449 name: string
450 provider: 'anthropic' | 'openai' | 'openai-responses' | 'google'
451 model: string
452 apiKey: string
453 baseUrl: string
454 maxTokens: number
455 disableTemperature: boolean
456 thinkingParameterMode: ThinkingParameterMode
457 active: boolean
458 createdAt: number
459 updatedAt: number
460 }
461
462 export type {
463 GeneratedImageAsset,
464 ImageModelConfig,
465 ImageModelProvider,
466 ImageModelVerificationResult
467 }
468
469 export interface UploadPrerequisitesResult {
470 ready: boolean
471 missing: Array<'storagePath' | 'activeModel' | 'apiKey' | 'model'>
472 message?: string
473 }
474
475 export type FontRole = 'title' | 'body'
476 export type FontScript = 'latin' | 'cjk'
477
478 export interface FontFileEntry {
479 file: string
480 weight: number
481 style: 'normal' | 'italic'
482 size?: number
483 sha256?: string
484 }
485
486 export interface FontListItem {
487 id: string
488 family: string
489 source: 'google' | 'uploaded'
490 category: string
491 role: FontRole[]
492 scripts: FontScript[]
493 files?: FontFileEntry[]
494 createdAt?: number
495 updatedAt?: number
496 }
497
498 export interface FontRegistryResponse {
499 googleFonts: FontListItem[]
500 userFonts: FontListItem[]
501 }
502
503 export interface UploadFontPayload {
504 files: Array<{
505 path: string
506 weight?: number
507 style?: 'normal' | 'italic'
508 }>
509 family: string
510 category?: string
511 role?: FontRole[]
512 scripts?: FontScript[]
513 }
514
515 export const ipc = {
516 getPlatform: (): NodeJS.Platform | 'unknown' => window.electron?.getPlatform?.() ?? 'unknown',
517 getWindowControlState: () =>
518 getIpc().invoke('window:control:getState') as Promise<{ isFullscreen: boolean }>,
519 minimizeWindow: () => getIpc().invoke('window:control:minimize') as Promise<void>,
520 toggleFullscreenWindow: () =>
521 getIpc().invoke('window:control:toggleFullscreen') as Promise<{ isFullscreen: boolean }>,
522 closeWindow: () => getIpc().invoke('window:control:close') as Promise<void>,
523 onWindowControlStateChanged: (
524 callback: (state: { isFullscreen: boolean }) => void
525 ): (() => void) => {
526 const channel = 'window:control:stateChanged'
527 const handler = (_event: unknown, state: unknown): void =>
528 callback(state as { isFullscreen: boolean })
529 getIpc().on(channel, handler)
530 return () => getIpc().removeListener(channel, handler)
531 },
532 createSession: (payload: CreateSessionPayload) =>
533 getIpc().invoke('session:create', payload) as Promise<{ sessionId: string }>,
534 listSessions: () => getIpc().invoke('session:list') as Promise<unknown[]>,
535 listMergeSourceSessions: (payload: { targetSessionId: string }) =>
536 getIpc().invoke('session:listMergeSources', payload) as Promise<MergeSourceSessionSummary[]>,
537 listMergeSourcePages: (payload: { targetSessionId: string; sourceSessionId: string }) =>
538 getIpc().invoke('session:listMergeSourcePages', payload) as Promise<MergeSourcePageSummary[]>,
539 mergeSessionPages: (payload: {
540 targetSessionId: string
541 sourceSessionId: string
542 sourcePageIds: string[]
543 }) =>
544 getIpc().invoke('session:mergePages', payload) as Promise<{
545 ok: true
546 generatedPages: Array<{
547 id: string
548 pageNumber: number
549 pageId: string
550 title: string
551 contentOutline?: string | null
552 html: string
553 htmlPath: string
554 sourceUrl?: string
555 status?: string
556 error?: string | null
557 }>
558 insertedPageIds: string[]
559 selectedPageId: string
560 }>,
561 listMergeSourceTemplates: (payload: { targetSessionId: string }) =>
562 getIpc().invoke('session:listMergeSourceTemplates', payload) as Promise<
563 MergeTemplateSourceSummary[]
564 >,
565 listMergeSourceTemplatePages: (payload: { targetSessionId: string; templateId: string }) =>
566 getIpc().invoke('session:listMergeSourcePages', {
567 targetSessionId: payload.targetSessionId,
568 sourceType: 'template',
569 templateId: payload.templateId
570 }) as Promise<MergeSourcePageSummary[]>,
571 mergeTemplatePages: (payload: {
572 targetSessionId: string
573 templateId: string
574 sourcePageIds: string[]
575 }) =>
576 getIpc().invoke('session:mergePages', {
577 targetSessionId: payload.targetSessionId,
578 sourceType: 'template',
579 templateId: payload.templateId,
580 sourcePageIds: payload.sourcePageIds
581 }) as Promise<{
582 ok: true
583 generatedPages: Array<{
584 id: string
585 pageNumber: number
586 pageId: string
587 title: string
588 contentOutline?: string | null
589 html: string
590 htmlPath: string
591 sourceUrl?: string
592 status?: string
593 error?: string | null
594 }>
595 insertedPageIds: string[]
596 selectedPageId: string
597 }>,
598 saveSessionAsNew: (payload: SaveSessionAsNewPayload): Promise<SaveSessionAsNewResult> =>
599 getIpc().invoke('session:saveAsNew', payload) as Promise<SaveSessionAsNewResult>,
600 getSessionMaster: (payload: { sessionId: string }): Promise<SessionMasterStatus> =>
601 getIpc().invoke('session:getMaster', payload) as Promise<SessionMasterStatus>,
602 saveSessionMaster: (payload: {
603 sessionId: string
604 config: SessionMasterConfig
605 }): Promise<SessionMasterStatus> =>
606 getIpc().invoke('session:saveMaster', payload) as Promise<SessionMasterStatus>,
607 setSessionMasterPageOverride: (payload: {
608 sessionId: string
609 pageId: string
610 disabled: boolean
611 }): Promise<{ disabled: boolean }> =>
612 getIpc().invoke('session:setMasterPageOverride', payload) as Promise<{ disabled: boolean }>,
613 getSessionLayoutLibrary: (payload: { sessionId: string }): Promise<SessionLayoutLibraryStatus> =>
614 getIpc().invoke('session:getLayoutLibrary', payload) as Promise<SessionLayoutLibraryStatus>,
615 saveSessionLayoutLibrary: (payload: {
616 sessionId: string
617 library: SessionLayoutLibrary
618 }): Promise<SessionLayoutLibraryStatus> =>
619 getIpc().invoke('session:saveLayoutLibrary', payload) as Promise<SessionLayoutLibraryStatus>,
620 getSession: (sessionId: string) =>
621 getIpc().invoke('session:get', sessionId) as Promise<{
622 session: unknown
623 messages: unknown[]
624 generatedPages: Array<{
625 id: string
626 pageNumber: number
627 title: string
628 contentOutline?: string | null
629 html: string
630 htmlPath?: string
631 pageId?: string
632 sourceUrl?: string
633 status?: string
634 error?: string | null
635 }>
636 }>,
637 getIndexTransition: (sessionId: string) =>
638 getIpc().invoke('session:getIndexTransition', { sessionId }) as Promise<IndexTransitionConfig>,
639 setIndexTransition: (payload: {
640 sessionId: string
641 type: IndexTransitionType
642 durationMs?: number
643 }) =>
644 getIpc().invoke('session:setIndexTransition', payload) as Promise<{
645 ok: boolean
646 transition: IndexTransitionConfig
647 }>,
648 migratePageOutlinesToSourceSkeletons: (payload: { sessionId: string }) =>
649 getIpc().invoke('session:migratePageOutlinesToSourceSkeletons', payload) as Promise<{
650 migrated: boolean
651 migratedCount: number
652 existingCount: number
653 }>,
654 reorderSessionPages: (payload: {
655 sessionId: string
656 orderedPageIds: string[]
657 selectedPageId?: string
658 }) =>
659 getIpc().invoke('session:reorderPages', payload) as Promise<{
660 ok: boolean
661 generatedPages: Array<{
662 id: string
663 pageNumber: number
664 pageId: string
665 title: string
666 contentOutline?: string | null
667 html: string
668 htmlPath?: string
669 status?: string
670 error?: string | null
671 }>
672 selectedPageId: string | null
673 }>,
674 deleteSessionPages: (payload: {
675 sessionId: string
676 pageIds: string[]
677 selectedPageId?: string
678 }) =>
679 getIpc().invoke('session:deletePages', payload) as Promise<{
680 ok: boolean
681 generatedPages: Array<{
682 id: string
683 pageNumber: number
684 pageId: string
685 title: string
686 contentOutline?: string | null
687 html: string
688 htmlPath?: string
689 status?: string
690 error?: string | null
691 }>
692 selectedPageId: string | null
693 }>,
694 createBlankSessionPage: (payload: { sessionId: string; sourcePageId: string }) =>
695 getIpc().invoke('session:createBlankPage', payload) as Promise<{
696 ok: boolean
697 generatedPages: Array<{
698 id: string
699 pageNumber: number
700 pageId: string
701 title: string
702 contentOutline?: string | null
703 html: string
704 htmlPath?: string
705 status?: string
706 error?: string | null
707 }>
708 selectedPageId: string | null
709 }>,
710 duplicateSessionPage: (payload: { sessionId: string; sourcePageId: string }) =>
711 getIpc().invoke('session:duplicatePage', payload) as Promise<{
712 ok: boolean
713 generatedPages: Array<{
714 id: string
715 pageNumber: number
716 pageId: string
717 title: string
718 contentOutline?: string | null
719 html: string
720 htmlPath?: string
721 status?: string
722 error?: string | null
723 }>
724 selectedPageId: string | null
725 }>,
726 updateSessionPageTitle: (payload: { sessionId: string; pageId: string; title: string }) =>
727 getIpc().invoke('session:updatePageTitle', payload) as Promise<{
728 ok: boolean
729 generatedPages: Array<{
730 id: string
731 pageNumber: number
732 pageId: string
733 title: string
734 contentOutline?: string | null
735 html: string
736 htmlPath?: string
737 status?: string
738 error?: string | null
739 }>
740 selectedPageId: string | null
741 }>,
742 updateSessionPageOutline: (payload: {
743 sessionId: string
744 pageId: string
745 contentOutline: string
746 }) =>
747 getIpc().invoke('session:updatePageOutline', payload) as Promise<{
748 ok: boolean
749 generatedPages: Array<{
750 id: string
751 pageNumber: number
752 pageId: string
753 title: string
754 contentOutline?: string | null
755 html: string
756 htmlPath?: string
757 status?: string
758 error?: string | null
759 }>
760 selectedPageId: string | null
761 }>,
762 getSessionMessages: (payload: {
763 sessionId: string
764 chatType: 'main' | 'page'
765 pageId?: string
766 }) => getIpc().invoke('session:getMessages', payload) as Promise<unknown[]>,
767 deleteSession: (sessionId: string) =>
768 getIpc().invoke('session:delete', sessionId) as Promise<{ success: boolean }>,
769 updateSessionTitle: (payload: { sessionId: string; title: string }) =>
770 getIpc().invoke('session:updateTitle', payload) as Promise<{ ok: boolean }>,
771 importSessionFile: () =>
772 getIpc().invoke('session:importFile') as Promise<ImportSessionFileResult>,
773 importHtmlFile: () =>
774 getIpc().invoke('html-editor:import') as Promise<HtmlEditorFileImportResult>,
775 listHtmlEditorMedia: (payload: { docId: string; mediaType: 'image' | 'video' }) =>
776 getIpc().invoke('html-editor:listMedia', payload) as Promise<{
777 assets: Array<{ fileName: string; filePath: string; relativePath: string; url: string }>
778 }>,
779 chooseAndImportHtmlMedia: (payload: { docId: string; mediaType: 'image' | 'video' }) =>
780 getIpc().invoke('html-editor:chooseAndImportMedia', payload) as Promise<
781 | { cancelled: true }
782 | { cancelled: false; filePath: string; relativePath: string; url: string }
783 >,
784 ensureHtmlAnchor: (payload: {
785 html: string
786 pageId: string
787 selector: string
788 elementTag?: string
789 formula?: { latex?: unknown; html?: unknown; displayMode?: unknown }
790 }) =>
791 getIpc().invoke('html-editor:ensureAnchor', payload) as Promise<{
792 html: string
793 selector: string
794 blockId: string
795 changed: boolean
796 }>,
797 applyHtmlEdits: (payload: {
798 html: string
799 pageId: string
800 dragEdits?: unknown[]
801 textEdits?: unknown[]
802 propertyEdits?: unknown[]
803 deletes?: unknown[]
804 addElements?: unknown[]
805 }) =>
806 getIpc().invoke('html-editor:applyEdits', payload) as Promise<{
807 html: string
808 warnings: string[]
809 }>,
810 exportHtml: (payload: { html: string; suggestedName?: string }) =>
811 getIpc().invoke('html-editor:export', payload) as Promise<
812 { cancelled: true } | { cancelled: false; path: string }
813 >,
814 cleanupHtmlEditor: (payload: { docId: string }) =>
815 getIpc().invoke('html-editor:cleanup', payload) as Promise<{ ok: boolean }>,
816 openHtmlInBrowser: (payload: { docId: string }) =>
817 getIpc().invoke('html-editor:openInBrowser', payload) as Promise<{ ok: boolean }>,
818 revealHtmlFile: (payload: { docId: string }) =>
819 getIpc().invoke('html-editor:revealFile', payload) as Promise<{ ok: boolean }>,
820 listHtmlVersions: (payload: { docId: string }) =>
821 getIpc().invoke('html-editor:listVersions', payload) as Promise<{
822 versions: Array<{ id: string; commitSha: string; message: string; createdAt: number }>
823 }>,
824 restoreHtmlVersion: (payload: { docId: string; versionId: string }) =>
825 getIpc().invoke('html-editor:restoreVersion', payload) as Promise<{ html: string }>,
826 listHtmlDocuments: () =>
827 getIpc().invoke('html-editor:listDocuments') as Promise<{
828 documents: Array<{
829 id: string
830 title: string
831 sourcePath: string | null
832 htmlPath: string
833 designWidth: number
834 updatedAt: number
835 thumbnailPath: string | null
836 }>
837 }>,
838 openHtmlDocument: (payload: { docId: string }) =>
839 getIpc().invoke('html-editor:openDocument', payload) as Promise<HtmlEditorFileImportResult>,
840 htmlEditorAiChat: (payload: {
841 documentId: string
842 documentTitle?: string
843 pageHtml?: string
844 selectedElement?: HtmlEditorAiElementContext
845 recentMessages?: HtmlEditorAiMessage[]
846 pendingPlan?: HtmlEditorAiPlan
847 userMessage: string
848 modelConfigId?: string
849 }) =>
850 getIpc().invoke('html-editor:aiChat', payload) as Promise<{
851 reply: string
852 model: string
853 intent: HtmlEditorAiIntent
854 plan: HtmlEditorAiPlan | null
855 requiresConfirmation: boolean
856 applied: boolean
857 appliedHtml?: string
858 warnings: string[]
859 }>,
860 listHtmlEditorMessages: (payload: { docId: string }) =>
861 getIpc().invoke('html-editor:listMessages', payload) as Promise<{
862 messages: HtmlEditorAiHistoryMessage[]
863 }>,
864 clearHtmlEditorMessages: (payload: { docId: string }) =>
865 getIpc().invoke('html-editor:clearMessages', payload) as Promise<{ ok: boolean }>,
866 listTemplates: () => getIpc().invoke('templates:list') as Promise<{ items: TemplateListItem[] }>,
867 createTemplateFromSession: (payload: {
868 sessionId: string
869 name?: string
870 description?: string
871 tags?: string[]
872 }) =>
873 getIpc().invoke('templates:createFromSession', payload) as Promise<{
874 success: true
875 id: string
876 }>,
877 createSessionFromTemplate: (payload: {
878 templateId: string
879 title?: string
880 modelConfigId?: string
881 pageCount?: number
882 referenceDocumentPath?: string
883 sourcePlan?: SourceDocumentPlan
884 }) =>
885 getIpc().invoke('templates:createSession', payload) as Promise<{
886 success: true
887 sessionId: string
888 }>,
889 createEditableSessionFromTemplate: (payload: {
890 templateId: string
891 title?: string
892 modelConfigId?: string
893 }) =>
894 getIpc().invoke('templates:createEditableSession', payload) as Promise<{
895 success: true
896 sessionId: string
897 }>,
898 importPptxAsTemplate: (payload: { filePath: string; name?: string; modelConfigId?: string }) =>
899 getIpc().invoke('templates:importPptx', payload) as Promise<{
900 success: true
901 id: string
902 pageCount: number
903 warnings: string[]
904 }>,
905 updateTemplateMetadata: (payload: {
906 templateId: string
907 name: string
908 description?: string
909 tags?: string[]
910 }) =>
911 getIpc().invoke('templates:updateMetadata', payload) as Promise<{
912 success: true
913 item: TemplateListItem
914 }>,
915 deleteTemplate: (templateId: string) =>
916 getIpc().invoke('templates:delete', templateId) as Promise<{
917 success: true
918 deleted: boolean
919 }>,
920 startGenerate: (payload: GenerateStartPayload) =>
921 getIpc().invoke('generate:start', payload) as Promise<{
922 success: boolean
923 runId?: string
924 alreadyRunning?: boolean
925 queued?: boolean
926 }>,
927 assessPageEdit: (payload: GenerateStartPayload) =>
928 getIpc().invoke('page-edit:assess', payload) as Promise<
929 SessionPageEditAssessment & {
930 reply: string
931 targetPageId: string
932 targetPageNumber?: number
933 }
934 >,
935 startPageEdit: (payload: GenerateStartPayload) =>
936 getIpc().invoke('page-edit:start', payload) as Promise<{
937 success: boolean
938 runId?: string
939 alreadyRunning?: boolean
940 }>,
941 getPageEditState: (sessionId: string) =>
942 getIpc().invoke('page-edit:state', sessionId) as Promise<GenerateRunStateSnapshot>,
943 listActivePageEditRuns: () =>
944 getIpc().invoke('page-edit:listActive') as Promise<GenerateRunStateSnapshot[]>,
945 cancelPageEdit: (sessionId: string) =>
946 getIpc().invoke('page-edit:cancel', sessionId) as Promise<{ success: boolean }>,
947 startDeckEdit: (payload: GenerateStartPayload) =>
948 getIpc().invoke('deck-edit:start', payload) as Promise<{
949 success: boolean
950 runId?: string
951 alreadyRunning?: boolean
952 }>,
953 getDeckEditState: (sessionId: string) =>
954 getIpc().invoke('deck-edit:state', sessionId) as Promise<GenerateRunStateSnapshot>,
955 listActiveDeckEditRuns: () =>
956 getIpc().invoke('deck-edit:listActive') as Promise<GenerateRunStateSnapshot[]>,
957 cancelDeckEdit: (sessionId: string) =>
958 getIpc().invoke('deck-edit:cancel', sessionId) as Promise<{ success: boolean }>,
959 startStyleSwitch: (payload: SwitchSessionStylePayload) =>
960 getIpc().invoke('style-switch:start', payload) as Promise<{
961 success: boolean
962 runId?: string
963 styleId: string
964 unchanged?: boolean
965 alreadyRunning?: boolean
966 }>,
967 retryStyleSwitchPage: (payload: {
968 sessionId: string
969 failedRunId?: string
970 pageId: string
971 modelConfigId?: string
972 }) =>
973 getIpc().invoke('style-switch:retryPage', payload) as Promise<{
974 success: boolean
975 runId?: string
976 styleId: string
977 alreadyRunning?: boolean
978 }>,
979 retryFailedStyleSwitchPages: (payload: RetrySessionStylePayload) =>
980 getIpc().invoke('style-switch:retryFailed', payload) as Promise<{
981 success: boolean
982 runId?: string
983 styleId: string
984 alreadyRunning?: boolean
985 failedPageCount: number
986 }>,
987 getStyleSwitchState: (sessionId: string) =>
988 getIpc().invoke('style-switch:state', sessionId) as Promise<StyleSwitchJobSnapshot>,
989 listActiveStyleSwitchRuns: () =>
990 getIpc().invoke('style-switch:listActive') as Promise<StyleSwitchJobSnapshot[]>,
991 cancelStyleSwitch: (sessionId: string) =>
992 getIpc().invoke('style-switch:cancel', sessionId) as Promise<{ success: boolean }>,
993 switchSessionStyle: (payload: SwitchSessionStylePayload) =>
994 getIpc().invoke('style-switch:start', payload) as Promise<{
995 success: boolean
996 runId?: string
997 styleId: string
998 unchanged?: boolean
999 alreadyRunning?: boolean
1000 failedPageCount?: number
1001 }>,
1002 retrySessionStyle: (payload: RetrySessionStylePayload) =>
1003 getIpc().invoke('style-switch:retryFailed', payload) as Promise<{
1004 success: boolean
1005 runId?: string
1006 styleId: string
1007 alreadyRunning?: boolean
1008 failedPageCount: number
1009 }>,
1010 retryDeckEdit: (payload: RetryDeckEditPayload) =>
1011 getIpc().invoke('generate:retryDeckEdit', payload) as Promise<{
1012 success: boolean
1013 runId?: string
1014 alreadyRunning?: boolean
1015 failedPageCount: number
1016 }>,
1017 startTemplateGenerate: (payload: GenerateStartPayload & { retry?: boolean }) =>
1018 getIpc().invoke('generate:startTemplate', payload) as Promise<{
1019 success: boolean
1020 runId?: string
1021 alreadyRunning?: boolean
1022 queued?: boolean
1023 }>,
1024 retryFailedPages: (payload: GenerateRetryFailedPayload) =>
1025 getIpc().invoke('generate:retryFailedPages', payload) as Promise<{
1026 success: boolean
1027 runId?: string
1028 alreadyRunning?: boolean
1029 queued?: boolean
1030 }>,
1031 addPage: (payload: GenerateAddPagePayload) =>
1032 getIpc().invoke('generate:addPage', payload) as Promise<{
1033 success: boolean
1034 runId?: string
1035 alreadyRunning?: boolean
1036 queued?: boolean
1037 }>,
1038 retrySinglePage: (payload: GenerateRetrySinglePagePayload) =>
1039 getIpc().invoke('generate:retrySinglePage', payload) as Promise<{
1040 success: boolean
1041 runId?: string
1042 alreadyRunning?: boolean
1043 queued?: boolean
1044 }>,
1045 getGenerateState: (sessionId: string) =>
1046 getIpc().invoke('generate:state', sessionId) as Promise<GenerateRunStateSnapshot>,
1047 listActiveGenerateRuns: () =>
1048 getIpc().invoke('generate:listActive') as Promise<GenerateRunStateSnapshot[]>,
1049 cancelGenerate: (sessionId: string) =>
1050 getIpc().invoke('generate:cancel', sessionId) as Promise<{ success: boolean }>,
1051 listHistoryVersions: (payload: { sessionId: string; limit?: number }) =>
1052 getIpc().invoke('history:listVersions', payload) as Promise<HistoryVersion[]>,
1053 rollbackToHistoryVersion: (payload: { sessionId: string; versionId: string }) =>
1054 getIpc().invoke('history:rollbackToVersion', payload) as Promise<RollbackHistoryResult>,
1055 recordHistorySnapshot: (payload: {
1056 sessionId: string
1057 type?: 'generate' | 'edit' | 'addPage' | 'retry' | 'import' | 'rollback' | 'reorder' | 'delete'
1058 scope?: 'session' | 'deck' | 'page' | 'selector' | 'shell'
1059 prompt?: string
1060 metadata?: Record<string, unknown>
1061 }) => getIpc().invoke('history:recordSnapshot', payload) as Promise<unknown>,
1062 uploadAssets: (payload: UploadAssetsPayload) =>
1063 getIpc().invoke('assets:upload', payload) as Promise<{ assets: UploadedAsset[] }>,
1064 prepareReferenceDocument: (payload: PrepareReferenceDocumentPayload) =>
1065 getIpc().invoke(
1066 'documents:prepareReference',
1067 payload
1068 ) as Promise<PreparedReferenceDocumentResult>,
1069 parseImageReferenceDocument: (payload: ParseImageReferencePayload) =>
1070 getIpc().invoke(
1071 'documents:parseImageReference',
1072 payload
1073 ) as Promise<PreparedReferenceDocumentResult>,
1074 parseDocumentPlan: (payload: ParseDocumentPlanPayload) =>
1075 getIpc().invoke('documents:parsePlan', payload) as Promise<ParsedDocumentPlanResult>,
1076 importPptx: (payload: PptxImportPayload) =>
1077 getIpc().invoke('pptx:import', payload) as Promise<PptxImportResult>,
1078 chooseAndUploadAssets: (sessionId: string, assetType: 'image' | 'video' = 'image') =>
1079 getIpc().invoke('assets:chooseAndUpload', { sessionId, assetType }) as Promise<{
1080 assets: UploadedAsset[]
1081 cancelled?: boolean
1082 }>,
1083 listAssets: (sessionId: string, assetType: 'image' | 'video') =>
1084 getIpc().invoke('assets:list', { sessionId, assetType }) as Promise<{
1085 assets: Array<{ fileName: string; relativePath: string; absolutePath: string }>
1086 }>,
1087 exportPdf: (sessionId: string) =>
1088 getIpc().invoke('export:pdf', { sessionId }) as Promise<ExportDeckResult>,
1089 exportPng: (sessionId: string) =>
1090 getIpc().invoke('export:png', { sessionId }) as Promise<ExportDeckResult>,
1091 exportLongImage: (sessionId: string) =>
1092 getIpc().invoke('export:longImage', { sessionId }) as Promise<ExportDeckResult>,
1093 exportVideo: (sessionId: string, options?: { pageId?: string }) =>
1094 getIpc().invoke('export:video', { sessionId, ...options }) as Promise<ExportDeckResult>,
1095 exportPptx: (
1096 sessionId: string,
1097 options?: {
1098 imageOnly?: boolean
1099 embedFonts?: boolean | 'auto' | 'always' | 'never'
1100 pageId?: string
1101 }
1102 ) => getIpc().invoke('export:pptx', { sessionId, ...options }) as Promise<ExportDeckResult>,
1103 exportSlidePack: (sessionId: string) =>
1104 getIpc().invoke('export:slidePack', { sessionId }) as Promise<ExportDeckResult>,
1105 exportSessionZip: (sessionId: string) =>
1106 getIpc().invoke('export:sessionZip', { sessionId }) as Promise<ExportDeckResult>,
1107 exportOutlinesMarkdown: (sessionId: string) =>
1108 getIpc().invoke('export:outlinesMarkdown', { sessionId }) as Promise<ExportDeckResult>,
1109 onExportProgress: (callback: (payload: ExportProgressPayload) => void): (() => void) => {
1110 const channel = 'export:progress'
1111 const handler = (_event: unknown, payload: unknown): void =>
1112 callback(payload as ExportProgressPayload)
1113 getIpc().on(channel, handler)
1114 return () => getIpc().removeListener(channel, handler)
1115 },
1116 getSettings: () => getIpc().invoke('settings:get') as Promise<Record<string, unknown>>,
1117 getModelUsage: (period: ModelUsagePeriod) =>
1118 getIpc().invoke('settings:getModelUsage', period) as Promise<ModelUsageStats>,
1119 listModelConfigs: () => getIpc().invoke('settings:listModelConfigs') as Promise<ModelConfig[]>,
1120 listImageModelConfigs: () => getIpc().invoke('imageModels:list') as Promise<ImageModelConfig[]>,
1121 validateUploadPrerequisites: () =>
1122 getIpc().invoke('settings:validateUploadPrerequisites') as Promise<UploadPrerequisitesResult>,
1123 listFonts: () => getIpc().invoke('fonts:list') as Promise<FontRegistryResponse>,
1124 uploadFont: (payload: UploadFontPayload) =>
1125 getIpc().invoke('fonts:upload', payload) as Promise<{ success: true; font: FontListItem }>,
1126 updateFont: (payload: {
1127 id: string
1128 family?: string
1129 category?: string
1130 role?: FontRole[]
1131 scripts?: FontScript[]
1132 }) => getIpc().invoke('fonts:update', payload) as Promise<{ success: true; font: FontListItem }>,
1133 deleteFont: (fontId: string) =>
1134 getIpc().invoke('fonts:delete', fontId) as Promise<{ success: true }>,
1135 revealFontsFolder: () => getIpc().invoke('fonts:revealFolder') as Promise<{ success: true }>,
1136 chooseFontFiles: () =>
1137 getIpc().invoke('fonts:chooseFiles') as Promise<{ canceled: boolean; filePaths: string[] }>,
1138 loadFontPreviewCss: () => getIpc().invoke('fonts:previewCss') as Promise<string>,
1139 saveSettings: (settings: Record<string, unknown>) =>
1140 getIpc().invoke('settings:save', settings) as Promise<{ success: boolean }>,
1141 upsertModelConfig: (payload: {
1142 id?: string
1143 name: string
1144 provider: 'anthropic' | 'openai' | 'openai-responses' | 'google'
1145 model: string
1146 apiKey: string
1147 baseUrl: string
1148 maxTokens?: number
1149 disableTemperature?: boolean
1150 thinkingParameterMode?: ThinkingParameterMode
1151 active?: boolean
1152 }) =>
1153 getIpc().invoke('settings:upsertModelConfig', payload) as Promise<{
1154 success: boolean
1155 id: string
1156 }>,
1157 setActiveModelConfig: (id: string) =>
1158 getIpc().invoke('settings:setActiveModelConfig', id) as Promise<{ success: boolean }>,
1159 deleteModelConfig: (id: string) =>
1160 getIpc().invoke('settings:deleteModelConfig', id) as Promise<{ success: boolean }>,
1161 upsertImageModelConfig: (payload: {
1162 id?: string
1163 name: string
1164 provider: ImageModelProvider
1165 active?: boolean
1166 modelConfig: string
1167 }) =>
1168 getIpc().invoke('imageModels:upsert', payload) as Promise<{
1169 success: boolean
1170 id: string
1171 }>,
1172 setActiveImageModelConfig: (id: string) =>
1173 getIpc().invoke('imageModels:setActive', id) as Promise<{ success: boolean }>,
1174 deleteImageModelConfig: (id: string) =>
1175 getIpc().invoke('imageModels:delete', id) as Promise<{ success: boolean }>,
1176 verifyImageModel: (payload: { provider: ImageModelProvider; modelConfig: string }) =>
1177 getIpc().invoke('imageModels:verify', payload) as Promise<ImageModelVerificationResult>,
1178 verifyApiKey: (payload: {
1179 provider: string
1180 apiKey: string
1181 model: string
1182 baseUrl: string
1183 maxTokens?: number
1184 disableTemperature?: boolean
1185 thinkingParameterMode?: ThinkingParameterMode
1186 timeoutMs: number
1187 }) =>
1188 getIpc().invoke('settings:verifyApiKey', payload) as Promise<{
1189 valid: boolean
1190 message?: string
1191 }>,
1192 chooseStoragePath: () =>
1193 getIpc().invoke('settings:chooseStoragePath') as Promise<{
1194 path: string | null
1195 error?: string
1196 }>,
1197 generateImage: (payload: ImageGeneratePayload) =>
1198 getIpc().invoke('images:generate', payload) as Promise<ImageGenerateResult>,
1199 generateImagePrompt: (payload: ImagePromptGeneratePayload) =>
1200 getIpc().invoke('images:generatePrompt', payload) as Promise<ImagePromptGenerateResult>,
1201 listImageGenerationHistory: (payload: { sessionId: string; pageId: string }) =>
1202 getIpc().invoke('images:listHistory', payload) as Promise<ImageGenerationHistoryRecord[]>,
1203 cancelImageGeneration: (sessionId: string) =>
1204 getIpc().invoke('images:cancel', sessionId) as Promise<{ success: boolean }>,
1205 getImageGenerationState: (sessionId: string) =>
1206 getIpc().invoke('images:getState', sessionId) as Promise<{
1207 runId: string
1208 sessionId: string
1209 pageId: string
1210 progress: number
1211 label: string
1212 status: 'running' | 'completed' | 'failed' | 'cancelled'
1213 error: string | null
1214 updatedAt: number
1215 } | null>,
1216 listImageFulfillmentJobs: (payload: { sessionId: string; pageId?: string }) =>
1217 getIpc().invoke('images:listFulfillmentJobs', payload) as Promise<ImageFulfillmentJobView[]>,
1218 cancelImageFulfillment: (payload: { sessionId: string; jobId: string }) =>
1219 getIpc().invoke('images:cancelFulfillment', payload) as Promise<{ success: boolean }>,
1220 retryImageFulfillment: (payload: { sessionId: string; jobId: string }) =>
1221 getIpc().invoke('images:retryFulfillment', payload) as Promise<{
1222 status: 'none' | 'completed' | 'degraded' | 'cancelled'
1223 jobId?: string
1224 error?: string
1225 reused?: boolean
1226 }>,
1227 getStyles: () =>
1228 getIpc().invoke('styles:get') as Promise<{
1229 categories: Record<
1230 string,
1231 Array<{
1232 id: string
1233 label: string
1234 description: string
1235 source?: 'builtin' | 'custom' | 'override'
1236 editable?: boolean
1237 styleCase?: string
1238 }>
1239 >
1240 defaultStyle: string
1241 }>,
1242 getStyleDetail: (styleId: string) =>
1243 getIpc().invoke('styles:getDetail', styleId) as Promise<StyleDetail>,
1244 listStyles: (payload?: { sessionId?: string }) =>
1245 getIpc().invoke('styles:list', payload) as Promise<{ items: StyleListItem[] }>,
1246 recommendStyles: (payload: { topic: string; brief?: string; modelConfigId?: string }) =>
1247 getIpc().invoke('styles:recommend', payload) as Promise<{ styleKeys: string[] }>,
1248 generateStylePreview: (payload: { styleId: string; modelConfigId?: string }) =>
1249 getIpc().invoke('styles:generatePreview', payload) as Promise<{
1250 success: boolean
1251 previewPath: string
1252 thumbnailPath: string
1253 }>,
1254 setStyleFavorite: (payload: { styleId: string; favorite: boolean }) =>
1255 getIpc().invoke('styles:setFavorite', payload) as Promise<{
1256 success: boolean
1257 styleId: string
1258 favoriteAt: number | null
1259 }>,
1260 onHtmlThumbnailChanged: (callback: (task: HtmlThumbnailTask) => void): (() => void) => {
1261 const channel = 'thumbnails:changed'
1262 const handler = (_event: unknown, task: Parameters<typeof callback>[0]): void => callback(task)
1263 getIpc().on(channel, handler)
1264 return () => getIpc().removeListener(channel, handler)
1265 },
1266 parseStyleFile: (payload: { filePath: string; modelConfigId?: string }) =>
1267 getIpc().invoke('styles:parseFile', payload) as Promise<StyleParseResult>,
1268 parseStylePptx: (payload: { filePath: string; modelConfigId?: string }) =>
1269 getIpc().invoke('styles:parsePptx', payload) as Promise<StyleParseResult>,
1270 parseStyleImage: (payload: { imageBase64: string; mimeType: string; modelConfigId?: string }) =>
1271 getIpc().invoke('styles:parseImage', payload) as Promise<StyleParseResult>,
1272 importStylePackageZip: () =>
1273 getIpc().invoke('styles:importPackageZip') as Promise<{
1274 success: boolean
1275 cancelled?: boolean
1276 id: string
1277 source: 'custom' | 'override'
1278 }>,
1279 importStylePackageDirectory: () =>
1280 getIpc().invoke('styles:importPackageDirectory') as Promise<{
1281 success: boolean
1282 cancelled?: boolean
1283 id: string
1284 source: 'custom' | 'override'
1285 }>,
1286 exportStylePackageZip: (payload: { styleId: string }) =>
1287 getIpc().invoke('styles:exportPackageZip', payload) as Promise<{
1288 success: boolean
1289 canceled?: boolean
1290 filePath?: string
1291 }>,
1292 createStyle: (payload: {
1293 label: string
1294 description: string
1295 category?: string
1296 aliases?: string[]
1297 styleSkill: string
1298 styleCase?: string
1299 }) =>
1300 getIpc().invoke('styles:create', payload) as Promise<{
1301 success: boolean
1302 id: string
1303 source: 'custom' | 'override'
1304 }>,
1305 updateStyle: (payload: {
1306 id: string
1307 label: string
1308 description: string
1309 category?: string
1310 aliases?: string[]
1311 styleSkill: string
1312 styleCase?: string
1313 }) =>
1314 getIpc().invoke('styles:update', payload) as Promise<{
1315 success: boolean
1316 id: string
1317 source: 'custom' | 'override'
1318 }>,
1319 deleteStyle: (styleId: string) =>
1320 getIpc().invoke('styles:delete', styleId) as Promise<{
1321 success: boolean
1322 deleted: boolean
1323 }>,
1324 loadPreview: (htmlPath: string, sessionId?: string) =>
1325 getIpc().invoke('preview:load', { htmlPath, sessionId }) as Promise<string>,
1326 loadPagePreview: (htmlPath: string, pageId: string, sessionId?: string) =>
1327 getIpc().invoke('preview:loadPage', { htmlPath, pageId, sessionId }) as Promise<{
1328 pageNumber: number
1329 pageId: string
1330 title: string
1331 html: string
1332 }>,
1333 updateElementLayout: (payload: UpdateElementLayoutPayload) =>
1334 getIpc().invoke('drag-editor:update-element-layout', payload) as Promise<{
1335 success: boolean
1336 }>,
1337 ensureElementAnchor: (payload: EnsureElementAnchorPayload) =>
1338 getIpc().invoke('element-anchor:ensure', payload) as Promise<EnsureElementAnchorResult>,
1339 getElementAnimation: (payload: {
1340 sessionId: string
1341 htmlPath: string
1342 pageId: string
1343 selector: string
1344 }) =>
1345 getIpc().invoke('element-animation:get', payload) as Promise<{
1346 animation: ElementAnimationConfig | null
1347 }>,
1348 setElementAnimation: (payload: {
1349 sessionId: string
1350 htmlPath: string
1351 pageId: string
1352 selector: string
1353 patch: ElementAnimationPatch
1354 }) =>
1355 getIpc().invoke('element-animation:set', payload) as Promise<{
1356 success: boolean
1357 changed: boolean
1358 animation: ElementAnimationConfig | null
1359 }>,
1360 updateElementProperties: (payload: UpdateElementPropertiesPayload) =>
1361 getIpc().invoke('text-editor:update-element-properties', payload) as Promise<{
1362 success: boolean
1363 }>,
1364 deleteElement: (payload: {
1365 sessionId: string
1366 htmlPath: string
1367 pageId: string
1368 selector: string
1369 }) =>
1370 getIpc().invoke('element-editor:delete-element', payload) as Promise<{
1371 success: boolean
1372 }>,
1373 saveEditBatch: (payload: {
1374 sessionId: string
1375 htmlPath: string
1376 pageId: string
1377 dragEdits: unknown[]
1378 textEdits: unknown[]
1379 propertyEdits?: unknown[]
1380 deletes?: unknown[]
1381 addElements?: unknown[]
1382 prompt?: string
1383 }) =>
1384 getIpc().invoke('edit:save-batch', payload) as Promise<{
1385 success: boolean
1386 dragCount: number
1387 textCount: number
1388 propertyCount?: number
1389 deleteCount: number
1390 addCount: number
1391 warnings?: string[]
1392 }>,
1393 applySyncElementToAllPages: (payload: {
1394 sessionId: string
1395 htmlPath: string
1396 pageId: string
1397 sourceHtmlFragment: string
1398 syncElementId?: string
1399 sourceBlockId?: string
1400 }) =>
1401 getIpc().invoke('element-editor:apply-sync-to-all-pages', payload) as Promise<{
1402 success: boolean
1403 syncElementId: string
1404 changedCount: number
1405 insertedCount: number
1406 updatedCount: number
1407 }>,
1408 chooseAndParseChartData: () =>
1409 getIpc().invoke('chart-data:choose-and-parse') as Promise<ParsedChartDataResult>,
1410 openFile: (filePath: string, sessionId?: string) =>
1411 getIpc().invoke('file:open', { path: filePath, sessionId }) as Promise<string>,
1412 revealFile: (filePath: string, sessionId?: string) =>
1413 getIpc().invoke('file:reveal', { path: filePath, sessionId }) as Promise<{ success: boolean }>,
1414 openInBrowser: (filePath: string, hash?: string, sessionId?: string) =>
1415 getIpc().invoke('file:openInBrowser', { path: filePath, hash, sessionId }) as Promise<{
1416 success: boolean
1417 }>,
1418 saveFile: (payload: { path: string; content: string; sessionId?: string }) =>
1419 getIpc().invoke('file:save', payload) as Promise<{ success: boolean }>,
1420 onGenerateChunk: (callback: (chunk: GenerateChunkEvent) => void): (() => void) => {
1421 const channel = 'generate:chunk'
1422 const handler = (_event: unknown, chunk: unknown): void => callback(chunk as GenerateChunkEvent)
1423 getIpc().on(channel, handler)
1424 return () => getIpc().removeListener(channel, handler)
1425 },
1426 onPptxImportProgress: (callback: (payload: PptxImportProgressPayload) => void): (() => void) => {
1427 const channel = 'pptx:import:progress'
1428 const handler = (_event: unknown, payload: unknown): void =>
1429 callback(payload as PptxImportProgressPayload)
1430 getIpc().on(channel, handler)
1431 return () => getIpc().removeListener(channel, handler)
1432 },
1433 onTemplatePptxImportProgress: (
1434 callback: (payload: PptxImportProgressPayload) => void
1435 ): (() => void) => {
1436 const channel = 'templates:importPptx:progress'
1437 const handler = (_event: unknown, payload: unknown): void =>
1438 callback(payload as PptxImportProgressPayload)
1439 getIpc().on(channel, handler)
1440 return () => getIpc().removeListener(channel, handler)
1441 },
1442 onUpdateAvailable: (callback: (payload: UpdateAvailablePayload) => void): (() => void) => {
1443 const channel = 'app:update-available'
1444 const handler = (_event: unknown, payload: unknown): void =>
1445 callback(payload as UpdateAvailablePayload)
1446 getIpc().on(channel, handler)
1447 return () => getIpc().removeListener(channel, handler)
1448 },
1449 getAppVersion: () =>
1450 getIpc().invoke('app:getVersion') as Promise<{
1451 version: string
1452 }>,
1453 openPresentation: (payload: { sessionId: string; startIndex?: number }) =>
1454 getIpc().invoke('presentation:open', payload) as Promise<{ success: boolean }>,
1455 generateSpeechScript: (sessionId: string, config: SpeechConfig & { currentPageId?: string }) =>
1456 getIpc().invoke('speech:generateScript', { sessionId, ...config }) as Promise<{
1457 success: boolean
1458 }>,
1459 getSpeechScript: (sessionId: string) =>
1460 getIpc().invoke('speech:getScript', { sessionId }) as Promise<{
1461 success: boolean
1462 script: string | null
1463 }>,
1464 openSpeechScriptFile: (sessionId: string) =>
1465 getIpc().invoke('speech:openScriptFile', { sessionId }) as Promise<{
1466 success: boolean
1467 path: string
1468 }>,
1469 clearSpeechScript: (sessionId: string) =>
1470 getIpc().invoke('speech:clearScript', { sessionId }) as Promise<{ success: boolean }>,
1471 onSpeechProgress: (
1472 callback: (payload: { sessionId: string; current: number; total: number }) => void
1473 ): (() => void) => {
1474 const channel = 'speech:progress'
1475 const handler = (_event: unknown, payload: unknown): void =>
1476 callback(payload as { sessionId: string; current: number; total: number })
1477 getIpc().on(channel, handler)
1478 return () => getIpc().removeListener(channel, handler)
1479 },
1480
1481 thinkingCreateWorkspace: () =>
1482 getIpc().invoke('thinking:createWorkspace') as Promise<ThinkingWorkspace>,
1483 thinkingGetWorkspace: (thinkingId: string) =>
1484 getIpc().invoke('thinking:getWorkspace', thinkingId) as Promise<ThinkingWorkspace>,
1485 thinkingGetLatestWorkspace: () =>
1486 getIpc().invoke('thinking:getLatestWorkspace') as Promise<ThinkingWorkspace | null>,
1487 thinkingListWorkspaces: (payload?: { limit?: number }) =>
1488 getIpc().invoke('thinking:listWorkspaces', payload || {}) as Promise<
1489 ThinkingWorkspaceListItem[]
1490 >,
1491 thinkingDeleteWorkspace: (thinkingId: string) =>
1492 getIpc().invoke('thinking:deleteWorkspace', thinkingId) as Promise<{ success: boolean }>,
1493 thinkingRevealWorkspace: (thinkingId: string) =>
1494 getIpc().invoke('thinking:revealWorkspace', thinkingId) as Promise<{ success: boolean }>,
1495 thinkingUpdatePageOutline: (payload: {
1496 thinkingId: string
1497 page: import('@shared/thinking').ThinkingPageOutlineUpdate
1498 }) =>
1499 getIpc().invoke('thinking:updatePageOutline', payload) as Promise<{
1500 success: boolean
1501 thinkingMd: string
1502 }>,
1503 thinkingUploadSources: (payload: {
1504 thinkingId: string
1505 files: Array<{ path: string; name?: string }>
1506 }) =>
1507 getIpc().invoke('thinking:uploadSources', payload) as Promise<{
1508 sources: Array<{ id: string; name: string; kind: string }>
1509 }>,
1510 thinkingRemoveSource: (payload: { thinkingId: string; sourceId: string }) =>
1511 getIpc().invoke('thinking:removeSource', payload) as Promise<{
1512 success: boolean
1513 removed: boolean
1514 }>,
1515 thinkingChat: (payload: {
1516 thinkingId: string
1517 modelConfigId?: string
1518 userMessage: string
1519 recentMessages?: ThinkingChatMessage[]
1520 attachments?: ThinkingChatMessage['attachments']
1521 }) => getIpc().invoke('thinking:chat', payload) as Promise<ThinkingChatResult>,
1522 thinkingPrepareGeneration: (payload: { thinkingId: string }) =>
1523 getIpc().invoke(
1524 'thinking:prepareGeneration',
1525 payload
1526 ) as Promise<ThinkingPrepareGenerationResult>,
1527 onThinkingStreamThinking: (
1528 callback: (payload: {
1529 thinkingId: string
1530 type: string
1531 toolName: string
1532 summary: string
1533 }) => void
1534 ): (() => void) => {
1535 const channel = 'thinking:stream:thinking'
1536 const handler = (_event: unknown, payload: unknown): void =>
1537 callback(payload as { thinkingId: string; type: string; toolName: string; summary: string })
1538 getIpc().on(channel, handler)
1539 return () => getIpc().removeListener(channel, handler)
1540 },
1541 onThinkingStreamEnd: (
1542 callback: (payload: {
1543 thinkingId: string
1544 reply: string
1545 thinkingMd: string
1546 contextMd: string
1547 stage: ThinkingStage
1548 }) => void
1549 ): (() => void) => {
1550 const channel = 'thinking:stream:end'
1551 const handler = (_event: unknown, payload: unknown): void =>
1552 callback(
1553 payload as {
1554 thinkingId: string
1555 reply: string
1556 thinkingMd: string
1557 contextMd: string
1558 stage: ThinkingStage
1559 }
1560 )
1561 getIpc().on(channel, handler)
1562 return () => getIpc().removeListener(channel, handler)
1563 }
1564 }
1565
1565 lines TYPESCRIPT