返回 oh-my-ppt
StyleSwitchJobBar.tsx
根目录 / src / renderer / src / components / session-detail / style / StyleSwitchJobBar.tsx
1 import { Loader2, RotateCcw, X } from 'lucide-react'
2 import { ipc } from '@renderer/lib/ipc'
3 import { useModelAction } from '@renderer/hooks/useModelAction'
4 import { hydrateStyleSwitchJob, useGenerateStore, useToastStore } from '@renderer/store'
5 import { useCancelStyleSwitch } from '../hooks/useCancelStyleSwitch'
6 import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/Tooltip'
7
8 export function StyleSwitchJobBar({ sessionId }: { sessionId: string }): React.JSX.Element | null {
9 const job = useGenerateStore((state) => state.styleSwitchJobs[sessionId] || null)
10 const { selectedModelConfigId, ensureModelActive } = useModelAction()
11 const toastError = useToastStore((state) => state.error)
12 const cancelStyleSwitch = useCancelStyleSwitch(sessionId)
13 if (!job || job.status === 'completed') return null
14
15 const active =
16 job.status === 'starting' || job.status === 'running' || job.status === 'cancelling'
17 const failedPages = job.pages.filter((page) => page.status === 'failed')
18 const handleRetry = async (): Promise<void> => {
19 if (active || failedPages.length === 0) return
20 const modelConfigId = await ensureModelActive(selectedModelConfigId)
21 if (!modelConfigId) return
22 useGenerateStore.getState().startStyleSwitch(sessionId, {
23 styleId: job.styleId,
24 styleName: job.styleName,
25 totalPages: failedPages.length,
26 pages: failedPages.map((page) => ({ ...page, status: 'pending', error: null }))
27 })
28 try {
29 const result = await ipc.retryFailedStyleSwitchPages({
30 sessionId,
31 styleId: job.styleId,
32 modelConfigId,
33 failedRunId: job.runId
34 })
35 if (result.alreadyRunning) {
36 hydrateStyleSwitchJob(sessionId, await ipc.getStyleSwitchState(sessionId))
37 return
38 }
39 if (result.runId) {
40 const currentJob = useGenerateStore.getState().styleSwitchJobs[sessionId]
41 if (currentJob) {
42 useGenerateStore.getState().updateStyleSwitchJob(sessionId, {
43 runId: result.runId,
44 status: currentJob.status === 'cancelling' ? 'cancelling' : 'running'
45 })
46 }
47 }
48 } catch (error) {
49 const message = error instanceof Error ? error.message : '重试风格切换失败'
50 useGenerateStore.getState().finishStyleSwitch(sessionId, { status: 'failed', error: message })
51 toastError(message)
52 }
53 }
54
55 const statusText =
56 job.status === 'cancelling'
57 ? '正在取消'
58 : active
59 ? `正在切换风格${job.styleName ? `:${job.styleName}` : ''}`
60 : failedPages.length > 0
61 ? `${failedPages.length} 页切换失败`
62 : '风格切换已停止'
63
64 return (
65 <div className="mx-3 mt-1 flex h-9 shrink-0 items-center gap-2 border border-[#cbd8bd] bg-[#f4f8ee] px-3 text-xs text-[#43523a]">
66 {active ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : null}
67 <span className="min-w-0 flex-1 truncate">{statusText}</span>
68 <span className="tabular-nums text-[#68775f]">
69 {job.pages.filter((page) => page.status === 'completed').length}/{job.totalPages}
70 </span>
71 {active ? (
72 <Tooltip>
73 <TooltipTrigger asChild>
74 <button
75 type="button"
76 onClick={() => void cancelStyleSwitch()}
77 disabled={job.status === 'cancelling'}
78 className="inline-flex h-6 w-6 items-center justify-center text-[#73514b] hover:bg-[#f3dfd8] disabled:opacity-45"
79 aria-label="取消风格切换"
80 >
81 <X className="h-3.5 w-3.5" />
82 </button>
83 </TooltipTrigger>
84 <TooltipContent>取消</TooltipContent>
85 </Tooltip>
86 ) : failedPages.length > 0 ? (
87 <Tooltip>
88 <TooltipTrigger asChild>
89 <button
90 type="button"
91 onClick={() => void handleRetry()}
92 className="inline-flex h-6 w-6 items-center justify-center text-[#536943] hover:bg-[#dce9d0]"
93 aria-label="重试失败页面"
94 >
95 <RotateCcw className="h-3.5 w-3.5" />
96 </button>
97 </TooltipTrigger>
98 <TooltipContent>重试失败页面</TooltipContent>
99 </Tooltip>
100 ) : null}
101 </div>
102 )
103 }
104
104 lines Plain Text