auto-save 2026-05-14 10:51 (~7)

This commit is contained in:
2026-05-14 10:53:54 +08:00
parent d0abed6740
commit 8bd52f676a
7 changed files with 104 additions and 28 deletions

View File

@@ -19,7 +19,7 @@ import { ThemeToggle } from "@/components/theme-toggle"
import { AudioStrip } from "@/components/audio-strip"
import {
addManualFrame, analyzeJob, createJob, getJob, listJobs, uploadJob, deleteJob, deleteFrame, deleteGeneratedImage,
deleteGeneratedVideo, deleteCutout, generateStoryboardVideo, createProductFusionGuide,
deleteGeneratedVideo, deleteCutout, generateStoryboardVideo, createProductFusionGuide, triggerTranscribe,
type Job, type ImageRef, type ProductFusionShot, type StoryboardScene, type FrameExtractMode, type FrameExtractQuality, type FrameExtractTarget,
} from "@/lib/api"
import { TRANSPARENT_HUMAN_NEGATIVE_PROMPT, TRANSPARENT_HUMAN_VIDEO_PROMPT } from "@/lib/workflow-target"
@@ -385,6 +385,36 @@ export default function Home() {
toast.success(`已复制:${ref.label || (ref.kind === "keyframe" ? "关键帧" : "元素")} · 到分镜头编排工作台粘贴`)
}, [])
const handleTranscribeAudio = useCallback(async (jobId?: string, options?: { silent?: boolean }) => {
const targetId = jobId ?? activeJobId
if (!targetId) return
const target = jobs.find((item) => item.id === targetId)
if (!target) return
if (!["frames_extracted", "transcribed", "failed"].includes(target.status)) {
if (!options?.silent) toast.info("先完成抽帧,音频轨会自动开始处理")
return
}
try {
const updated = await triggerTranscribe(targetId)
updateJobInList(updated)
if (!options?.silent) toast.success("音频处理已开始")
} catch (e) {
if (!options?.silent) toast.error("音频处理启动失败:" + (e instanceof Error ? e.message : String(e)))
}
}, [activeJobId, jobs, updateJobInList])
const autoAudioStartedRef = useRef<Set<string>>(new Set())
useEffect(() => {
for (const item of jobs) {
const audioStatus = item.audio_script?.status ?? "idle"
const hasAudioOutput = item.transcript.length > 0 || !!item.audio_script?.rewritten_text
const ready = item.status === "frames_extracted" && !hasAudioOutput && audioStatus !== "rewriting"
if (!ready || autoAudioStartedRef.current.has(item.id)) continue
autoAudioStartedRef.current.add(item.id)
void handleTranscribeAudio(item.id, { silent: true })
}
}, [jobs, handleTranscribeAudio])
const handleQuickGenerateVideo = useCallback(async (frameIdx: number, scene: StoryboardScene, model: string) => {
if (!job) return
const frame = job.frames.find((f) => f.index === frameIdx)
@@ -696,9 +726,10 @@ export default function Home() {
clipboard,
onCopyImage: handleCopyImage,
onGenerateProductFusionVideo: handleGenerateProductFusionVideo,
onTranscribeAudio: handleTranscribeAudio,
pinnedNodes,
onToggleNodePin: handleToggleNodePin,
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleCloseExpandedFrame, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, updateJobInList, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, handleOpenStoryboard, handleOpenWorkbench, clipboard, handleCopyImage, handleGenerateProductFusionVideo, pinnedNodes, handleToggleNodePin])
}), [job, jobs, activeJobId, submitting, analyzing, frameTargets, frameCounts, frameQualities, selectedFrames, expandedFrame, framePanelScale, framePanelPinned, framePanelDock, videoPanelJobId, videoPanelScale, videoPanelDock, handleSubmit, handleUpload, handleAnalyze, handleAnalyzeJob, handleFrameTargetChange, handleFrameCountChange, handleFrameQualityChange, handleToggleFrame, handleOpenFramePanel, handleFramePanelScaleChange, handleCloseExpandedFrame, handleAddManualFrame, handleAddManualFrameForJob, handleOpenVideoPanel, handleVideoPanelScaleChange, handleSwitchJob, updateJobInList, handleDeleteJob, handleDeleteFrame, handleDeleteFrameForJob, handleDeleteGenerated, handleDeleteVideo, handleDeleteCutout, handleOpenStoryboard, handleOpenWorkbench, clipboard, handleCopyImage, handleGenerateProductFusionVideo, handleTranscribeAudio, pinnedNodes, handleToggleNodePin])
// 用 useNodesState 让 ReactFlow 自己管位置(避免轮询时重置 drag
const savedSizes = useMemo(() => loadNodeSizes(), [])