返回 oh-my-ppt
useSessionToolbarController.ts
根目录 / src / renderer / src / components / session-detail / toolbar / useSessionToolbarController.ts
1 import { useMemo, useState } from 'react'
2 import { useNavigate } from 'react-router-dom'
3 import {
4 useGenerateStore,
5 useEditHistoryStore,
6 useSessionDetailUiStore,
7 useSessionStore,
8 useTemplateStore,
9 useToastStore
10 } from '@renderer/store'
11 import { ipc } from '@renderer/lib/ipc'
12 import { useT } from '@renderer/i18n'
13 import { normalizePagesForSelection } from '../shared/pageUtils'
14 import { useSessionExportActions } from '../hooks/useSessionExportActions'
15
16 export function useSessionToolbarController(sessionId: string) {
17 const t = useT()
18 const navigate = useNavigate()
19 const currentSession = useSessionStore((state) => state.currentSession)
20 const fetchSessions = useSessionStore((state) => state.fetchSessions)
21 const currentPages = useGenerateStore((state) => state.currentPages)
22 const isGenerating = useGenerateStore((state) => state.isGenerating)
23 const isDeckEditing = useGenerateStore((state) => Boolean(state.deckEditJobs[sessionId]))
24 const isPageBeautifying = useGenerateStore((state) => Boolean(state.pageBeautifyJobs[sessionId]))
25 const isStyleSwitching = useGenerateStore((state) => {
26 const job = state.styleSwitchJobs[sessionId]
27 return job?.status === 'starting' || job?.status === 'running' || job?.status === 'cancelling'
28 })
29 const selectedPageId = useSessionDetailUiStore((state) => state.selectedPageId)
30 const isAddingPage = useSessionDetailUiStore((state) => state.isAddingPage)
31 const isRetryingSinglePage = useSessionDetailUiStore((state) => state.isRetryingSinglePage)
32 const isManagingPages = useSessionDetailUiStore((state) => state.isManagingPages)
33 const setHistoryDialogOpen = useSessionDetailUiStore((state) => state.setHistoryDialogOpen)
34 const { createTemplateFromSession } = useTemplateStore()
35 const { success: toastSuccess, error: toastError } = useToastStore()
36 const exportActions = useSessionExportActions(sessionId)
37 const [saveTemplateOpen, setSaveTemplateOpen] = useState(false)
38 const [savingTemplate, setSavingTemplate] = useState(false)
39 const [saveAsNewSessionOpen, setSaveAsNewSessionOpen] = useState(false)
40 const [savingAsNewSession, setSavingAsNewSession] = useState(false)
41
42 const pages = useMemo(() => normalizePagesForSelection(currentPages), [currentPages])
43 const selectedPage = useMemo(
44 () => pages.find((page) => page.id === selectedPageId) ?? pages[0] ?? null,
45 [pages, selectedPageId]
46 )
47 const selectedPageHasPendingEdits = useEditHistoryStore((state) =>
48 state.hasPendingEdits(selectedPage?.pageId)
49 )
50 const sessionStatus =
51 currentSession && typeof (currentSession as { status?: unknown }).status === 'string'
52 ? String((currentSession as { status?: unknown }).status)
53 : ''
54 const historyDisabled =
55 isGenerating ||
56 isDeckEditing ||
57 isPageBeautifying ||
58 isStyleSwitching ||
59 isAddingPage ||
60 isRetryingSinglePage ||
61 isManagingPages ||
62 sessionStatus === 'active'
63 const saveAsNewSessionDisabled = historyDisabled || savingAsNewSession
64
65 const handleSaveTemplate = async (payload: {
66 name: string
67 description: string
68 tags: string[]
69 }): Promise<void> => {
70 if (!sessionId || savingTemplate) return
71 setSavingTemplate(true)
72 try {
73 await createTemplateFromSession({
74 sessionId,
75 ...payload
76 })
77 toastSuccess(t('sessionDetail.templateSaved'), {
78 action: {
79 label: t('sessionDetail.viewTemplates'),
80 onClick: () => navigate('/templates')
81 }
82 })
83 setSaveTemplateOpen(false)
84 } catch (err) {
85 toastError(t('sessionDetail.templateSaveFailed'), {
86 description: err instanceof Error ? err.message : t('common.retryLater')
87 })
88 } finally {
89 setSavingTemplate(false)
90 }
91 }
92
93 const handleSaveAsNewSession = async (payload: { title: string }): Promise<void> => {
94 const title = payload.title.trim()
95 if (!sessionId || savingAsNewSession || saveAsNewSessionDisabled) return
96 if (!title) {
97 toastError(t('sessionDetail.saveAsNewSessionTitleRequired'))
98 return
99 }
100 setSavingAsNewSession(true)
101 try {
102 await ipc.saveSessionAsNew({ sessionId, title })
103 await fetchSessions()
104 toastSuccess(t('sessionDetail.saveAsNewSessionSuccess'))
105 setSaveAsNewSessionOpen(false)
106 navigate('/sessions')
107 } catch (err) {
108 toastError(t('sessionDetail.saveAsNewSessionFailed'), {
109 description: err instanceof Error ? err.message : t('common.retryLater')
110 })
111 } finally {
112 setSavingAsNewSession(false)
113 }
114 }
115
116 return {
117 hasPages: pages.length > 0,
118 isGenerating: isGenerating || isDeckEditing || isPageBeautifying || isStyleSwitching,
119 historyDisabled,
120 selectedPageHasPendingEdits,
121 canPreview: Boolean(selectedPage?.htmlPath || pages[0]?.htmlPath),
122 canRevealFile: Boolean(selectedPage?.htmlPath),
123 sessionTitle: currentSession?.title || '',
124 saveTemplateOpen,
125 savingTemplate,
126 saveAsNewSessionOpen,
127 savingAsNewSession,
128 saveAsNewSessionDisabled,
129 defaultTemplateName: currentSession?.title || '未命名模板',
130 defaultSaveAsNewSessionName: t('sessionDetail.saveAsNewSessionDefaultName', {
131 title: currentSession?.title || t('sessionDetail.sessionFallback')
132 }),
133 setSaveTemplateOpen,
134 setSaveAsNewSessionOpen,
135 handleSaveTemplate,
136 handleSaveAsNewSession,
137 exportActions,
138 openHistory: () => {
139 if (!historyDisabled) setHistoryDialogOpen(true)
140 }
141 }
142 }
143
143 lines TYPESCRIPT