feat: parallelize ad recreation intake
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user