feat: parallelize ad recreation intake

This commit is contained in:
2026-05-18 10:31:18 +08:00
parent 75c5d113ee
commit 4c8cb066d6
5 changed files with 161 additions and 37 deletions

View File

@@ -58,6 +58,7 @@ const DEFAULT_PRODUCT_LIBRARY_IDS = [
"desktop-skg-product-angle-03",
"desktop-skg-product-angle-04",
]
const VIDEO_READY_STATUSES: Job["status"][] = ["downloaded", "frames_extracted", "transcribed", "failed"]
const PRODUCT_FUSION_WEARING_PROMPT = [
"Product placement must be physically correct:",
@@ -229,7 +230,7 @@ export default function Home() {
const created = await uploadJob(file)
addJob(created)
setProductionJobIds((prev) => new Set(prev).add(created.id))
toast.success(`已上传 ${created.id.slice(0, 8)}下载完成后自动解析音频`)
toast.success(`已上传 ${created.id.slice(0, 8)}视频就绪后自动跑音频和抽帧`)
} catch (e) {
toast.error("上传失败:" + (e instanceof Error ? e.message : String(e)))
} finally {
@@ -461,6 +462,44 @@ export default function Home() {
}
}, [activeJobId, jobs, updateJobInList])
const startProductionLanesForJob = useCallback(async (target: Job) => {
const videoReady = !!target.video_url && VIDEO_READY_STATUSES.includes(target.status)
if (!videoReady) return
const audioKey = `${target.id}:audio`
const hasAudioResult = !!target.audio_script?.source_text || target.transcript.length > 0
const audioRunning = target.status === "transcribing" || target.audio_script?.status === "rewriting"
if (!hasAudioResult && !audioRunning && !autoTriggeredRef.current.has(audioKey)) {
autoTriggeredRef.current.add(audioKey)
try {
const updated = await triggerTranscribe(target.id)
updateJobInList(updated)
toast.info("音频路已启动:字幕、讲话人、节奏和背景音同步解析")
} catch (e) {
autoTriggeredRef.current.delete(audioKey)
toast.error("音频解析启动失败:" + (e instanceof Error ? e.message : String(e)))
}
}
const visualKey = `${target.id}:visual`
const hasVisualResult = target.frames.length > 0
const visualRunning = target.status === "splitting"
if (!hasVisualResult && !visualRunning && !autoTriggeredRef.current.has(visualKey)) {
autoTriggeredRef.current.add(visualKey)
const frameTarget = frameTargets[target.id] ?? "motion"
const frameCount = frameCounts[target.id] ?? 12
const frameQuality = frameQualities[target.id] ?? "accurate"
try {
const updated = await analyzeJob(target.id, frameCount, frameTarget, "replace", frameQuality)
updateJobInList(updated)
toast.info(`视觉路已启动:${FRAME_QUALITY_LABELS[frameQuality]} · ${FRAME_TARGET_LABELS[frameTarget]} · ${frameCount} 张参考帧`)
} catch (e) {
autoTriggeredRef.current.delete(visualKey)
toast.error("视觉抽帧启动失败:" + (e instanceof Error ? e.message : String(e)))
}
}
}, [frameCounts, frameQualities, frameTargets, updateJobInList])
const ensureDefaultProductRefs = useCallback(async (jobId: string) => {
const cached = defaultProductRefsByJob[jobId]
if (cached?.length >= 4) return cached.slice(0, 4)
@@ -538,26 +577,19 @@ export default function Home() {
return
}
setProductionJobIds((prev) => new Set(prev).add(target.id))
toast.success("已进入第一步:下载完成后自动解析音频文案、讲话人和背景音")
if (target.video_url && ["downloaded", "frames_extracted", "transcribed", "failed"].includes(target.status)) {
void handleTranscribeAudio(target.id, { silent: true })
}
}, [handleSubmit, handleTranscribeAudio, job])
toast.success("已进入并行素材分析:下载完成后自动音频文案路和视觉抽帧路")
void startProductionLanesForJob(target)
}, [handleSubmit, job, startProductionLanesForJob])
useEffect(() => {
if (productionJobIds.size === 0) return
for (const item of jobs) {
if (!productionJobIds.has(item.id)) continue
const videoReady = !!item.video_url && ["downloaded", "frames_extracted", "transcribed", "failed"].includes(item.status)
const videoReady = !!item.video_url && VIDEO_READY_STATUSES.includes(item.status)
if (!videoReady) continue
const audioKey = `${item.id}:audio`
const hasAudioResult = !!item.audio_script?.source_text || item.transcript.length > 0
if (!autoTriggeredRef.current.has(audioKey) && item.audio_script?.status !== "rewriting" && !hasAudioResult) {
autoTriggeredRef.current.add(audioKey)
void handleTranscribeAudio(item.id, { silent: true })
}
void startProductionLanesForJob(item)
}
}, [handleTranscribeAudio, jobs, productionJobIds])
}, [jobs, productionJobIds, startProductionLanesForJob])
const handleQuickGenerateVideo = useCallback(async (frameIdx: number, scene: StoryboardScene, model: string) => {
if (!job) return