返回 oh-my-ppt
sessionDetailStore.ts
根目录 / src / renderer / src / store / sessionDetailStore.ts
1 import { create } from 'zustand'
2 import type { SelectedElementRuntimeContext, UploadedAsset } from '@shared/generation.js'
3 import type { SpeechConfig } from '@shared/speech'
4 import type {
5 ImageGenerationMessage,
6 InteractionMode,
7 SessionDetailAiPanelMode,
8 SessionDetailChatType,
9 SessionWorkspaceTab
10 } from '@renderer/types/session-detail'
11
12 export type {
13 ImageGenerationMessage,
14 InteractionMode,
15 SessionDetailAiPanelMode,
16 SessionDetailChatType,
17 SessionWorkspaceTab
18 } from '@renderer/types/session-detail'
19
20 export interface EditorGuideState {
21 vertical: number[]
22 horizontal: number[]
23 }
24
25 interface SessionDetailUiStore {
26 input: string
27 aiPanelMode: SessionDetailAiPanelMode
28 chatType: SessionDetailChatType
29 imagePrompt: string
30 imageMessages: ImageGenerationMessage[]
31 imageMessageCache: Record<string, ImageGenerationMessage[]>
32 loadedImageMessageKeys: Record<string, boolean>
33 selectedImageModelConfigId: string
34 imageSize: string
35 imageCount: number
36 isGeneratingImage: boolean
37 imageProgress: { label?: string; progress: number } | null
38 selectedPageId: string | null
39 consoleOpen: boolean
40 previewKey: number
41 isExportingPdf: boolean
42 isExportingPng: boolean
43 isExportingLongImage: boolean
44 isExportingPptx: boolean
45 isExportingVideo: boolean
46 isExportingSlidePack: boolean
47 isExportingSessionZip: boolean
48 interactionMode: InteractionMode
49 workspaceTab: SessionWorkspaceTab
50 editorSnapEnabled: boolean
51 editorGridVisible: boolean
52 editorGridSize: number
53 editorGuidesByPage: Record<string, EditorGuideState>
54 thumbnailVersions: Record<string, number>
55 selectedSelector: string | null
56 editSelectedSelector: string | null
57 selectorLabel: string
58 elementTag: string
59 elementText: string
60 selectedElementContext: SelectedElementRuntimeContext | null
61 pendingAssets: UploadedAsset[]
62 assetDragActive: boolean
63 isUploadingAssets: boolean
64 addPageDialogOpen: boolean
65 blankPageDialogOpen: boolean
66 mergeSessionPagesDialogOpen: boolean
67 mergeTemplatePagesDialogOpen: boolean
68 blankPageSourceId: string
69 historyDialogOpen: boolean
70 pageTitleEditPageId: string | null
71 pageTitleEditDraft: string
72 deleteConfirmPageId: string | null
73 isAddingPage: boolean
74 addingPageId: string | null
75 isRetryingSinglePage: boolean
76 retryingSinglePageId: string | null
77 isManagingPages: boolean
78 sidebarCollapsed: boolean
79 assetPickerOpen: boolean
80 assetPickerType: 'image' | 'video'
81 isGeneratingSpeechScript: boolean
82 speechProgress: { current: number; total: number } | null
83 speechScriptDialogOpen: boolean
84 speechConfig: SpeechConfig
85
86 setInput: (input: string) => void
87 setAiPanelMode: (mode: SessionDetailAiPanelMode) => void
88 setChatType: (chatType: SessionDetailChatType) => void
89 setImagePrompt: (input: string) => void
90 setImageMessages: (messages: ImageGenerationMessage[]) => void
91 cacheImageMessages: (key: string, messages: ImageGenerationMessage[]) => void
92 setLoadedImageMessages: (key: string, messages: ImageGenerationMessage[]) => void
93 addImageMessage: (message: ImageGenerationMessage) => void
94 addCachedImageMessage: (key: string, message: ImageGenerationMessage) => void
95 setSelectedImageModelConfigId: (id: string) => void
96 setImageSize: (size: string) => void
97 setImageCount: (count: number) => void
98 setIsGeneratingImage: (generating: boolean) => void
99 setImageProgress: (progress: { label?: string; progress: number } | null) => void
100 setSelectedPageId: (pageId: string | null) => void
101 setConsoleOpen: (open: boolean | ((open: boolean) => boolean)) => void
102 bumpPreviewKey: () => void
103 setIsExportingPdf: (isExporting: boolean) => void
104 setIsExportingPng: (isExporting: boolean) => void
105 setIsExportingLongImage: (isExporting: boolean) => void
106 setIsExportingPptx: (isExporting: boolean) => void
107 setIsExportingVideo: (isExporting: boolean) => void
108 setIsExportingSlidePack: (isExporting: boolean) => void
109 setIsExportingSessionZip: (isExporting: boolean) => void
110 setInteractionMode: (mode: InteractionMode) => void
111 setWorkspaceTab: (tab: SessionWorkspaceTab) => void
112 setEditorSnapEnabled: (enabled: boolean) => void
113 setEditorGridVisible: (visible: boolean) => void
114 setEditorGridSize: (size: number) => void
115 addEditorGuide: (pageId: string, axis: keyof EditorGuideState, position: number) => void
116 moveEditorGuide: (
117 pageId: string,
118 axis: keyof EditorGuideState,
119 index: number,
120 position: number
121 ) => void
122 removeEditorGuide: (pageId: string, axis: keyof EditorGuideState, index: number) => void
123 setSelectedElement: (
124 selector: string,
125 label: string,
126 elementTag?: string,
127 elementText?: string,
128 selectedElementContext?: SelectedElementRuntimeContext | null
129 ) => void
130 setEditSelectedElement: (selector: string | null) => void
131 clearEditSelectedElement: () => void
132 clearSelectedElement: () => void
133 addPendingAssets: (assets: UploadedAsset[]) => void
134 removePendingAsset: (assetId: string) => void
135 clearPendingAssets: () => void
136 setAssetDragActive: (active: boolean) => void
137 setIsUploadingAssets: (isUploading: boolean) => void
138 bumpThumbnailVersion: (pageId: string) => void
139 setAddPageDialogOpen: (open: boolean) => void
140 setBlankPageDialogOpen: (open: boolean) => void
141 setMergeSessionPagesDialogOpen: (open: boolean) => void
142 setMergeTemplatePagesDialogOpen: (open: boolean) => void
143 setBlankPageSourceId: (pageId: string) => void
144 openBlankPageDialog: (sourcePageId: string) => void
145 setHistoryDialogOpen: (open: boolean) => void
146 openPageTitleEdit: (pageId: string, title: string) => void
147 setPageTitleEditDraft: (title: string) => void
148 closePageTitleEdit: () => void
149 setDeleteConfirmPageId: (pageId: string | null) => void
150 setIsAddingPage: (adding: boolean) => void
151 setAddingPageId: (pageId: string | null) => void
152 setIsRetryingSinglePage: (retrying: boolean) => void
153 setRetryingSinglePageId: (pageId: string | null) => void
154 setIsManagingPages: (managing: boolean) => void
155 toggleSidebarCollapsed: () => void
156 setAssetPickerOpen: (open: boolean, type?: 'image' | 'video') => void
157 setIsGeneratingSpeechScript: (v: boolean) => void
158 setSpeechProgress: (progress: { current: number; total: number } | null) => void
159 setSpeechScriptDialogOpen: (v: boolean) => void
160 setSpeechConfig: (config: SpeechConfig) => void
161 finishAddPage: (selectedPageId?: string | null) => void
162 resetForPageChange: () => void
163 resetEditingPageState: () => void
164 resetForSessionChange: () => void
165 }
166
167 export const useSessionDetailUiStore = create<SessionDetailUiStore>((set) => ({
168 input: '',
169 aiPanelMode: 'chat',
170 chatType: 'page',
171 imagePrompt: '',
172 imageMessages: [],
173 imageMessageCache: {},
174 loadedImageMessageKeys: {},
175 selectedImageModelConfigId: '',
176 imageSize: '16:9',
177 imageCount: 1,
178 isGeneratingImage: false,
179 imageProgress: null,
180 selectedPageId: null,
181 consoleOpen: true,
182 previewKey: 0,
183 isExportingPdf: false,
184 isExportingPng: false,
185 isExportingLongImage: false,
186 isExportingPptx: false,
187 isExportingVideo: false,
188 isExportingSlidePack: false,
189 isExportingSessionZip: false,
190 interactionMode: 'preview' as InteractionMode,
191 workspaceTab: 'preview' as SessionWorkspaceTab,
192 editorSnapEnabled: true,
193 editorGridVisible: false,
194 editorGridSize: 20,
195 editorGuidesByPage: {},
196 thumbnailVersions: {},
197 selectedSelector: null,
198 editSelectedSelector: null,
199 selectorLabel: '',
200 elementTag: '',
201 elementText: '',
202 selectedElementContext: null,
203 pendingAssets: [],
204 assetDragActive: false,
205 isUploadingAssets: false,
206 addPageDialogOpen: false,
207 blankPageDialogOpen: false,
208 mergeSessionPagesDialogOpen: false,
209 mergeTemplatePagesDialogOpen: false,
210 blankPageSourceId: '',
211 historyDialogOpen: false,
212 pageTitleEditPageId: null,
213 pageTitleEditDraft: '',
214 deleteConfirmPageId: null,
215 isAddingPage: false,
216 addingPageId: null,
217 isRetryingSinglePage: false,
218 retryingSinglePageId: null,
219 isManagingPages: false,
220 sidebarCollapsed: false,
221 assetPickerOpen: false,
222 assetPickerType: 'image' as const,
223 isGeneratingSpeechScript: false,
224 speechProgress: null,
225 speechScriptDialogOpen: false,
226 speechConfig: {
227 scope: 'all' as const,
228 length: 'medium' as const,
229 style: 'conversational' as const
230 },
231
232 setInput: (input) => set({ input }),
233 setAiPanelMode: (aiPanelMode) => set({ aiPanelMode }),
234 setChatType: (chatType) => set({ chatType }),
235 setImagePrompt: (imagePrompt) => set({ imagePrompt }),
236 setImageMessages: (imageMessages) => set({ imageMessages }),
237 cacheImageMessages: (key, messages) =>
238 set((state) => ({
239 imageMessageCache: {
240 ...state.imageMessageCache,
241 [key]: messages
242 }
243 })),
244 setLoadedImageMessages: (key, messages) =>
245 set((state) => ({
246 imageMessageCache: {
247 ...state.imageMessageCache,
248 [key]: messages
249 },
250 loadedImageMessageKeys: {
251 ...state.loadedImageMessageKeys,
252 [key]: true
253 }
254 })),
255 addImageMessage: (message) =>
256 set((state) => ({
257 imageMessages: [...state.imageMessages, message].slice(-48)
258 })),
259 addCachedImageMessage: (key, message) =>
260 set((state) => {
261 const cached = state.imageMessageCache[key] || []
262 return {
263 imageMessageCache: {
264 ...state.imageMessageCache,
265 [key]: [...cached, message].slice(-48)
266 }
267 }
268 }),
269 setSelectedImageModelConfigId: (selectedImageModelConfigId) =>
270 set({ selectedImageModelConfigId }),
271 setImageSize: (imageSize) => set({ imageSize }),
272 setImageCount: (imageCount) => set({ imageCount: Math.max(1, Math.min(4, imageCount)) }),
273 setIsGeneratingImage: (isGeneratingImage) => set({ isGeneratingImage }),
274 setImageProgress: (imageProgress) => set({ imageProgress }),
275 setSelectedPageId: (selectedPageId) => set({ selectedPageId }),
276 setConsoleOpen: (open) =>
277 set((state) => ({
278 consoleOpen: typeof open === 'function' ? open(state.consoleOpen) : open
279 })),
280 bumpPreviewKey: () => set((state) => ({ previewKey: state.previewKey + 1 })),
281 setIsExportingPdf: (isExportingPdf) => set({ isExportingPdf }),
282 setIsExportingPng: (isExportingPng) => set({ isExportingPng }),
283 setIsExportingLongImage: (isExportingLongImage) => set({ isExportingLongImage }),
284 setIsExportingPptx: (isExportingPptx) => set({ isExportingPptx }),
285 setIsExportingVideo: (isExportingVideo) => set({ isExportingVideo }),
286 setIsExportingSlidePack: (isExportingSlidePack) => set({ isExportingSlidePack }),
287 setIsExportingSessionZip: (isExportingSessionZip) => set({ isExportingSessionZip }),
288 setInteractionMode: (interactionMode) => set({ interactionMode }),
289 setWorkspaceTab: (workspaceTab) => set({ workspaceTab }),
290 setEditorSnapEnabled: (editorSnapEnabled) => set({ editorSnapEnabled }),
291 setEditorGridVisible: (editorGridVisible) => set({ editorGridVisible }),
292 setEditorGridSize: (editorGridSize) =>
293 set({ editorGridSize: Math.max(4, Math.min(200, Math.round(editorGridSize))) }),
294 addEditorGuide: (pageId, axis, position) =>
295 set((state) => {
296 const current = state.editorGuidesByPage[pageId] || { vertical: [], horizontal: [] }
297 return {
298 editorGuidesByPage: {
299 ...state.editorGuidesByPage,
300 [pageId]: {
301 ...current,
302 [axis]: [...current[axis], Number(position.toFixed(1))]
303 }
304 }
305 }
306 }),
307 moveEditorGuide: (pageId, axis, index, position) =>
308 set((state) => {
309 const current = state.editorGuidesByPage[pageId]
310 if (!current || index < 0 || index >= current[axis].length) return state
311 const nextAxis = [...current[axis]]
312 nextAxis[index] = Number(position.toFixed(1))
313 return {
314 editorGuidesByPage: {
315 ...state.editorGuidesByPage,
316 [pageId]: { ...current, [axis]: nextAxis }
317 }
318 }
319 }),
320 removeEditorGuide: (pageId, axis, index) =>
321 set((state) => {
322 const current = state.editorGuidesByPage[pageId]
323 if (!current || index < 0 || index >= current[axis].length) return state
324 return {
325 editorGuidesByPage: {
326 ...state.editorGuidesByPage,
327 [pageId]: {
328 ...current,
329 [axis]: current[axis].filter((_, guideIndex) => guideIndex !== index)
330 }
331 }
332 }
333 }),
334 // Fix: only reset to preview when currently in preview mode.
335 // In edit/ai-inspect mode, selecting an element should NOT change the mode.
336 setSelectedElement: (
337 selectedSelector,
338 selectorLabel,
339 elementTag = '',
340 elementText = '',
341 selectedElementContext = null
342 ) =>
343 set((state) => ({
344 selectedSelector,
345 selectorLabel,
346 elementTag,
347 elementText,
348 selectedElementContext,
349 interactionMode:
350 state.interactionMode === 'preview' ? ('preview' as InteractionMode) : state.interactionMode
351 })),
352 setEditSelectedElement: (editSelectedSelector) => set({ editSelectedSelector }),
353 clearEditSelectedElement: () => set({ editSelectedSelector: null }),
354 clearSelectedElement: () =>
355 set({
356 selectedSelector: null,
357 selectorLabel: '',
358 elementTag: '',
359 elementText: '',
360 selectedElementContext: null
361 }),
362 addPendingAssets: (assets) =>
363 set((state) => ({
364 pendingAssets: [...state.pendingAssets, ...assets]
365 })),
366 removePendingAsset: (assetId) =>
367 set((state) => ({
368 pendingAssets: state.pendingAssets.filter((asset) => asset.id !== assetId)
369 })),
370 clearPendingAssets: () => set({ pendingAssets: [] }),
371 setAssetDragActive: (assetDragActive) => set({ assetDragActive }),
372 setIsUploadingAssets: (isUploadingAssets) => set({ isUploadingAssets }),
373 bumpThumbnailVersion: (pageId) =>
374 set((state) => ({
375 thumbnailVersions: {
376 ...state.thumbnailVersions,
377 [pageId]: (state.thumbnailVersions[pageId] || 0) + 1
378 }
379 })),
380 setAddPageDialogOpen: (addPageDialogOpen) => set({ addPageDialogOpen }),
381 setBlankPageDialogOpen: (blankPageDialogOpen) => set({ blankPageDialogOpen }),
382 setMergeSessionPagesDialogOpen: (mergeSessionPagesDialogOpen) =>
383 set({ mergeSessionPagesDialogOpen }),
384 setMergeTemplatePagesDialogOpen: (mergeTemplatePagesDialogOpen) =>
385 set({ mergeTemplatePagesDialogOpen }),
386 setBlankPageSourceId: (blankPageSourceId) => set({ blankPageSourceId }),
387 openBlankPageDialog: (blankPageSourceId) =>
388 set({
389 blankPageSourceId,
390 blankPageDialogOpen: true
391 }),
392 setHistoryDialogOpen: (historyDialogOpen) => set({ historyDialogOpen }),
393 openPageTitleEdit: (pageTitleEditPageId, pageTitleEditDraft) =>
394 set({
395 pageTitleEditPageId,
396 pageTitleEditDraft
397 }),
398 setPageTitleEditDraft: (pageTitleEditDraft) => set({ pageTitleEditDraft }),
399 closePageTitleEdit: () => set({ pageTitleEditPageId: null, pageTitleEditDraft: '' }),
400 setDeleteConfirmPageId: (deleteConfirmPageId) => set({ deleteConfirmPageId }),
401 setIsAddingPage: (isAddingPage) =>
402 set({ isAddingPage, ...(isAddingPage ? {} : { addingPageId: null }) }),
403 setAddingPageId: (addingPageId) => set({ addingPageId }),
404 setIsRetryingSinglePage: (isRetryingSinglePage) =>
405 set({ isRetryingSinglePage, ...(isRetryingSinglePage ? {} : { retryingSinglePageId: null }) }),
406 setRetryingSinglePageId: (retryingSinglePageId) => set({ retryingSinglePageId }),
407 setIsManagingPages: (isManagingPages) => set({ isManagingPages }),
408 toggleSidebarCollapsed: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
409 setAssetPickerOpen: (open, type) =>
410 set((state) => ({
411 assetPickerOpen: open,
412 ...(type ? { assetPickerType: type } : { assetPickerType: state.assetPickerType })
413 })),
414 setIsGeneratingSpeechScript: (isGeneratingSpeechScript) => set({ isGeneratingSpeechScript }),
415 setSpeechProgress: (speechProgress) => set({ speechProgress }),
416 setSpeechScriptDialogOpen: (speechScriptDialogOpen) => set({ speechScriptDialogOpen }),
417 setSpeechConfig: (speechConfig) => set({ speechConfig }),
418 finishAddPage: (selectedPageId) =>
419 set((state) => ({
420 isAddingPage: false,
421 addingPageId: null,
422 selectedPageId: typeof selectedPageId === 'undefined' ? state.selectedPageId : selectedPageId
423 })),
424 resetForPageChange: () =>
425 set({
426 interactionMode: 'preview' as InteractionMode,
427 selectedSelector: null,
428 editSelectedSelector: null,
429 selectorLabel: '',
430 elementTag: '',
431 elementText: '',
432 selectedElementContext: null
433 }),
434 resetEditingPageState: () =>
435 set({
436 interactionMode: 'preview' as InteractionMode,
437 workspaceTab: 'preview' as SessionWorkspaceTab,
438 editorSnapEnabled: true,
439 editorGridVisible: false,
440 editorGridSize: 20,
441 editorGuidesByPage: {},
442 selectedSelector: null,
443 editSelectedSelector: null,
444 selectorLabel: '',
445 elementTag: '',
446 elementText: '',
447 selectedElementContext: null,
448 pendingAssets: [],
449 assetDragActive: false,
450 assetPickerOpen: false,
451 isGeneratingSpeechScript: false,
452 speechProgress: null,
453 speechScriptDialogOpen: false
454 }),
455 resetForSessionChange: () =>
456 set({
457 input: '',
458 aiPanelMode: 'chat',
459 chatType: 'page',
460 imagePrompt: '',
461 imageMessages: [],
462 imageMessageCache: {},
463 loadedImageMessageKeys: {},
464 selectedImageModelConfigId: '',
465 imageSize: '16:9',
466 imageCount: 1,
467 isGeneratingImage: false,
468 imageProgress: null,
469 selectedPageId: null,
470 interactionMode: 'preview' as InteractionMode,
471 workspaceTab: 'preview' as SessionWorkspaceTab,
472 editorSnapEnabled: true,
473 editorGridVisible: false,
474 editorGridSize: 20,
475 editorGuidesByPage: {},
476 selectedSelector: null,
477 editSelectedSelector: null,
478 selectorLabel: '',
479 elementTag: '',
480 elementText: '',
481 selectedElementContext: null,
482 pendingAssets: [],
483 assetDragActive: false,
484 isUploadingAssets: false,
485 thumbnailVersions: {},
486 addPageDialogOpen: false,
487 blankPageDialogOpen: false,
488 mergeSessionPagesDialogOpen: false,
489 mergeTemplatePagesDialogOpen: false,
490 blankPageSourceId: '',
491 historyDialogOpen: false,
492 pageTitleEditPageId: null,
493 pageTitleEditDraft: '',
494 deleteConfirmPageId: null,
495 isAddingPage: false,
496 addingPageId: null,
497 isRetryingSinglePage: false,
498 retryingSinglePageId: null,
499 isManagingPages: false,
500 sidebarCollapsed: false,
501 assetPickerOpen: false,
502 isGeneratingSpeechScript: false,
503 speechProgress: null,
504 speechScriptDialogOpen: false,
505 speechConfig: {
506 scope: 'all' as const,
507 length: 'medium' as const,
508 style: 'conversational' as const
509 }
510 })
511 }))
512
512 lines TYPESCRIPT