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