返回 oh-my-ppt
index.ts
根目录 / src / main / ipc / index.ts
1 import { BrowserWindow } from 'electron'
2 import fs from 'fs'
3 import path from 'path'
4 import type { PPTDatabase } from '../db/database'
5 import type { AgentManager } from '../agent-runtime/agent'
6 import { createIpcContext } from './context'
7 import { registerSessionHandlers } from '../session/handlers'
8 import { registerSessionImportHandlers } from '../session/import-handlers'
9 import { registerSessionSaveAsNewHandler } from '../session/save-as-new'
10 import { registerAssetHandlers, registerLocalAssetProtocol } from '../io/assets-handlers'
11 import { registerThumbnailHandlers } from '../io/thumbnails/handlers'
12 import { registerGenerationHandlers } from '../generation/handlers'
13 import { createGenerationContext } from '../generation/context'
14 import { registerExportHandlers } from '../io/export-handlers'
15 import { registerStyleHandlers } from '../styles/handlers'
16 import { registerStylePreviewHandlers } from '../styles/preview/handlers'
17 import { registerFontHandlers } from '../presentation/fonts/handlers'
18 import { registerSettingsHandlers } from '../config/settings-handlers'
19 import { registerImageModelHandlers } from '../config/image-model-handlers'
20 import { registerPreviewHandlers } from '../session/preview-handlers'
21 import { registerPageManagementHandlers } from '../session/page-management-handlers'
22 import { registerPageMergeHandlers } from '../session/page-merge-handlers'
23 import { registerFileHandlers } from '../io/file-handlers'
24 import { registerChartDataImportHandlers, registerEditorHandlers } from '../element-editor'
25 import { registerDocumentParseHandlers } from '../io/document-parse-handlers'
26 import { registerPptxImportHandlers } from '../io/pptx-import/handlers'
27 import { registerHistoryHandlers } from '../history/handlers'
28 import { registerPresentationHandlers } from '../session/presentation-handlers'
29 import { registerSpeechHandlers } from '../speech/handlers'
30 import { registerThinkingHandlers } from './thinking/thinking-handlers'
31 import { registerTemplateHandlers } from '../templates/template-handlers'
32 import { registerImageGenerationHandlers } from '../image-generation/handlers'
33 import { registerImageGenerationHistoryHandlers } from '../image-generation/handlers-history'
34 import { registerImageFulfillmentHandlers } from '../image-generation/fulfillment-handlers'
35 import { registerHtmlEditorHandlers } from '../html-editor/html-editor-handlers'
36 import { registerHtmlEditorAiHandlers } from '../html-editor/html-editor-ai-handlers'
37 import { JobCoordinator, TypedEventBus } from '../agent-runtime'
38 import { RuntimeEventBridge } from './runtime/event-bridge'
39 import { translateLegacyRuntimeEvent } from './runtime/event-contract'
40 import { DbModelUsageRecorder } from './runtime/model-usage-recorder'
41 import { registerDeckEditJobHandlers } from '../edit-jobs/deck-edit-job-service'
42 import { registerPageEditJobHandlers } from '../edit-jobs/page-edit-job-service'
43 import { registerStyleSwitchJobHandlers } from '../edit-jobs/style-switch-job-service'
44 import { registerMasterHandlers } from '../session/master-handlers'
45
46 export { registerLocalAssetProtocol }
47
48 export function setupIPC(
49 mainWindow: BrowserWindow,
50 db: PPTDatabase,
51 agentManager: AgentManager
52 ): void {
53 const runtimeEvents = new TypedEventBus({
54 onListenerError: (error, event) => {
55 console.warn('[runtime:event] listener failed', {
56 type: event.type,
57 jobId: event.jobId,
58 message: error instanceof Error ? error.message : String(error)
59 })
60 }
61 })
62 const runtimeEventBridge = new RuntimeEventBridge(runtimeEvents)
63 runtimeEventBridge.registerWindowBroadcast({
64 subscriberId: 'legacy-generate-chunk-broadcast',
65 windows: () => BrowserWindow.getAllWindows(),
66 translate: translateLegacyRuntimeEvent,
67 onSendError: ({ windowId, error }) => {
68 console.warn('[generate:chunk] send failed', {
69 windowId,
70 message: error instanceof Error ? error.message : String(error)
71 })
72 }
73 })
74 const context = createIpcContext(mainWindow, db, agentManager, runtimeEvents, {
75 recorder: new DbModelUsageRecorder(db)
76 })
77 const jobCoordinator = new JobCoordinator()
78 const generationContext = createGenerationContext({
79 ...context,
80 imageCoordinator: jobCoordinator
81 })
82 void (async () => {
83 const expiredJobs = await db.recoverExpiredImageFulfillmentJobs({ includePending: true })
84 await Promise.all(
85 expiredJobs.map(async (job) => {
86 const manifest = job.finalization_manifest_path || ''
87 if (!manifest.startsWith('images/.staging/')) return
88 const projectDir = await context.resolveSessionProjectDir(job.session_id)
89 const manifestPath = path.resolve(projectDir, manifest)
90 const stagingRoot = path.resolve(projectDir, 'images', '.staging')
91 if (!manifestPath.startsWith(`${stagingRoot}${path.sep}`)) return
92 const stagingDir = path.dirname(manifestPath)
93 try {
94 const parsed = JSON.parse(await fs.promises.readFile(manifestPath, 'utf-8')) as {
95 pageHtmlPath?: unknown
96 fallbackHtmlPath?: unknown
97 assets?: Array<{ finalPath?: unknown }>
98 }
99 const pageHtmlPath =
100 typeof parsed.pageHtmlPath === 'string' ? path.resolve(parsed.pageHtmlPath) : ''
101 const fallbackHtmlPath =
102 typeof parsed.fallbackHtmlPath === 'string' ? path.resolve(parsed.fallbackHtmlPath) : ''
103 const isProjectPath = (filePath: string): boolean =>
104 Boolean(filePath) && filePath.startsWith(`${path.resolve(projectDir)}${path.sep}`)
105 const isStagingPath = (filePath: string): boolean =>
106 Boolean(filePath) && filePath.startsWith(`${stagingRoot}${path.sep}`)
107 if (
108 isProjectPath(pageHtmlPath) &&
109 isStagingPath(fallbackHtmlPath) &&
110 fs.existsSync(fallbackHtmlPath)
111 ) {
112 const fallbackHtml = await fs.promises.readFile(fallbackHtmlPath, 'utf-8')
113 const imageRoot = path.resolve(projectDir, 'images')
114 const finalPaths = (parsed.assets || [])
115 .map((asset) =>
116 typeof asset.finalPath === 'string' ? path.resolve(asset.finalPath) : ''
117 )
118 .filter((assetPath) => assetPath.startsWith(`${imageRoot}${path.sep}`))
119 await Promise.all(
120 finalPaths.map((assetPath) => fs.promises.rm(assetPath, { force: true }))
121 )
122 const tempPagePath = `${pageHtmlPath}.${job.id}.recovery`
123 await fs.promises.writeFile(tempPagePath, fallbackHtml, 'utf-8')
124 await fs.promises.rename(tempPagePath, pageHtmlPath)
125 const intents = await db.listImageFulfillmentIntents(job.id)
126 await Promise.all(
127 intents.map((intent) =>
128 db.transitionImageFulfillmentIntent({
129 intentId: intent.id,
130 from: ['failed'],
131 status: 'fallback',
132 error: 'Image fulfillment recovered to the page fallback.'
133 })
134 )
135 )
136 await db.transitionImageFulfillmentJob({
137 jobId: job.id,
138 from: ['failed'],
139 status: 'degraded',
140 error: 'Image fulfillment recovered to the page fallback.'
141 })
142 }
143 } finally {
144 await fs.promises.rm(stagingDir, { recursive: true, force: true })
145 }
146 })
147 )
148 })().catch((error) => {
149 console.warn('[image:fulfillment] failed to recover expired jobs', {
150 message: error instanceof Error ? error.message : String(error)
151 })
152 })
153
154 registerSessionHandlers(context)
155 registerSessionSaveAsNewHandler(context)
156 registerSessionImportHandlers(context)
157 registerMasterHandlers(context)
158 registerPageManagementHandlers(context)
159 registerPageMergeHandlers(context)
160 registerAssetHandlers(context)
161 registerThumbnailHandlers(context)
162 const pageEditJobs = registerPageEditJobHandlers(context, jobCoordinator)
163 const deckEditJobs = registerDeckEditJobHandlers(context, jobCoordinator)
164 const styleSwitchJobs = registerStyleSwitchJobHandlers(context, jobCoordinator)
165 registerGenerationHandlers(
166 generationContext,
167 jobCoordinator,
168 styleSwitchJobs,
169 pageEditJobs,
170 deckEditJobs
171 )
172 registerExportHandlers(context)
173 registerStyleHandlers(context)
174 registerStylePreviewHandlers(context)
175 registerFontHandlers()
176 registerSettingsHandlers(context)
177 registerImageModelHandlers(context)
178 registerPreviewHandlers(context)
179 registerFileHandlers(context)
180 registerEditorHandlers(context)
181 registerChartDataImportHandlers(context)
182 registerDocumentParseHandlers(context)
183 registerPptxImportHandlers(context)
184 registerHistoryHandlers(context)
185 registerPresentationHandlers(context)
186 registerSpeechHandlers(context)
187 registerThinkingHandlers(context)
188 registerTemplateHandlers(context)
189 registerImageGenerationHandlers(context, jobCoordinator, runtimeEvents)
190 registerImageFulfillmentHandlers(context, jobCoordinator)
191 registerImageGenerationHistoryHandlers(context)
192 registerHtmlEditorHandlers(context)
193 registerHtmlEditorAiHandlers(context)
194 }
195
195 lines TYPESCRIPT