返回 oh-my-ppt
useImageGenerationActions.ts
根目录 / src / renderer / src / components / session-detail / hooks / useImageGenerationActions.ts
1 import { useMemo } from 'react'
2 import { nanoid } from 'nanoid'
3 import { useT } from '@renderer/i18n'
4 import { ipc } from '@renderer/lib/ipc'
5 import {
6 useGenerateStore,
7 useSessionDetailRuntimeStore,
8 useSessionDetailUiStore,
9 useToastStore,
10 type ImageGenerationMessage
11 } from '@renderer/store'
12 import type { GeneratedImageAsset } from '@shared/image-generation.js'
13 import {
14 buildImageMessageCacheKey,
15 imageHistoryToMessages,
16 mergeImageMessages
17 } from '../shared/imageMessageUtils'
18 import { normalizePagesForSelection } from '../shared/pageUtils'
19
20 export function useImageGenerationActions(sessionId: string) {
21 const t = useT()
22 const currentPages = useGenerateStore((state) => state.currentPages)
23 const selectedPageId = useSessionDetailUiStore((state) => state.selectedPageId)
24 const addElement = useSessionDetailRuntimeStore((state) => state.addElement)
25 const toastSuccess = useToastStore((state) => state.success)
26 const toastError = useToastStore((state) => state.error)
27 const toastWarning = useToastStore((state) => state.warning)
28
29 const pages = useMemo(() => normalizePagesForSelection(currentPages), [currentPages])
30 const selectedPage = useMemo(
31 () => pages.find((page) => page.id === selectedPageId) ?? pages[0] ?? null,
32 [pages, selectedPageId]
33 )
34
35 const generate = async (): Promise<void> => {
36 if (!sessionId || !selectedPage?.id) {
37 toastError(t('sessionDetail.selectPageFirst'))
38 return
39 }
40 const detailState = useSessionDetailUiStore.getState()
41 const prompt = detailState.imagePrompt.trim()
42 if (!prompt) {
43 toastWarning(t('sessionDetail.imagePromptRequired'))
44 return
45 }
46 if (detailState.isGeneratingImage) return
47
48 const pageId = selectedPage.id
49 const selectedPageKey = selectedPage.id
50 const cacheKey = buildImageMessageCacheKey(sessionId, pageId)
51 const pendingUserMessage: ImageGenerationMessage = {
52 id: `pending-image:${nanoid(8)}`,
53 role: 'user',
54 content: prompt,
55 createdAt: Math.floor(Date.now() / 1000)
56 }
57 detailState.setIsGeneratingImage(true)
58 detailState.setImageProgress({ progress: 8, label: t('sessionDetail.imageGenerating') })
59 detailState.setImagePrompt('')
60 detailState.addImageMessage(pendingUserMessage)
61 detailState.addCachedImageMessage(cacheKey, pendingUserMessage)
62 try {
63 const result = await ipc.generateImage({
64 sessionId,
65 pageId,
66 prompt,
67 imageModelConfigId: detailState.selectedImageModelConfigId || undefined,
68 size: detailState.imageSize,
69 count: 1
70 })
71 const latestState = useSessionDetailUiStore.getState()
72 const persistedMessages = imageHistoryToMessages([result.history])
73 const visibleMessages =
74 latestState.selectedPageId === selectedPageKey ? latestState.imageMessages : []
75 const cachedWithoutPending = mergeImageMessages(
76 latestState.imageMessageCache[cacheKey] || [],
77 visibleMessages
78 ).filter((message) => message.id !== pendingUserMessage.id)
79 const nextMessages = mergeImageMessages(cachedWithoutPending, persistedMessages)
80 latestState.cacheImageMessages(cacheKey, nextMessages)
81 if (useSessionDetailUiStore.getState().selectedPageId === selectedPageKey) {
82 latestState.setImageMessages(nextMessages)
83 }
84 detailState.setImageProgress({ progress: 100, label: t('sessionDetail.imageGenerated') })
85 toastSuccess(t('sessionDetail.imageGenerated'), {
86 description: t('sessionDetail.imageGeneratedDescription', {
87 count: result.history.assets.length
88 })
89 })
90 } catch (generationError) {
91 const message =
92 generationError instanceof Error
93 ? generationError.message
94 : t('sessionDetail.imageGenerateFailed')
95 toastError(t('sessionDetail.imageGenerateFailed'), { description: message })
96 } finally {
97 useSessionDetailUiStore.getState().setIsGeneratingImage(false)
98 }
99 }
100
101 const cancel = async (): Promise<void> => {
102 if (!sessionId) return
103 try {
104 await ipc.cancelImageGeneration(sessionId)
105 } finally {
106 useSessionDetailUiStore.getState().setIsGeneratingImage(false)
107 useSessionDetailUiStore.getState().setImageProgress(null)
108 }
109 }
110
111 const revealFile = async (filePath: string): Promise<void> => {
112 if (!sessionId || !filePath) return
113 try {
114 await ipc.revealFile(filePath, sessionId)
115 } catch (revealError) {
116 toastError(revealError instanceof Error ? revealError.message : t('common.retryLater'))
117 }
118 }
119
120 const addToCanvas = async (asset: GeneratedImageAsset): Promise<void> => {
121 if (!selectedPage?.pageId) {
122 toastError(t('sessionDetail.selectPageFirst'))
123 return
124 }
125 useSessionDetailUiStore.getState().setInteractionMode('edit')
126 try {
127 const added = await addElement(asset.relativePath, asset.fileName, {
128 persistImmediately: true,
129 prompt: '从生图结果添加图片到画布'
130 })
131 if (added) {
132 useSessionDetailUiStore.getState().setWorkspaceTab('edit')
133 toastSuccess(t('sessionDetail.imageAddedToCanvas'))
134 }
135 } catch (addError) {
136 toastError(addError instanceof Error ? addError.message : t('sessionDetail.layoutSaveFailed'))
137 }
138 }
139
140 const setAsBackground = async (asset: GeneratedImageAsset): Promise<void> => {
141 if (!selectedPage?.pageId) {
142 toastError(t('sessionDetail.selectPageFirst'))
143 return
144 }
145 useSessionDetailUiStore.getState().setInteractionMode('edit')
146 useSessionDetailUiStore.getState().setWorkspaceTab('ai')
147 try {
148 const added = await addElement(asset.relativePath, asset.fileName, {
149 persistImmediately: true,
150 asBackground: true,
151 prompt: '从生图结果设置页面背景'
152 })
153 if (added) toastSuccess(t('sessionDetail.imageSetAsBackground'))
154 } catch (addError) {
155 toastError(addError instanceof Error ? addError.message : t('sessionDetail.layoutSaveFailed'))
156 }
157 }
158
159 return {
160 selectedPage,
161 generate,
162 cancel,
163 revealFile,
164 addToCanvas,
165 setAsBackground
166 }
167 }
168
168 lines TYPESCRIPT