diff --git a/apps/web/src/pages/editing-planner/EditingPlanner.tsx b/apps/web/src/pages/editing-planner/EditingPlanner.tsx index b4c85dc47..485f53ebf 100755 --- a/apps/web/src/pages/editing-planner/EditingPlanner.tsx +++ b/apps/web/src/pages/editing-planner/EditingPlanner.tsx @@ -38,6 +38,11 @@ import { getGenerationStatus, getGenerationTaskResults, cancelGenerationTask, + getEditPlanClips, + createEditPlanClip, + batchDeleteEditPlanClips, + type EditPlanClip, + type CreateEditPlanClipRequest, } from "@/api/editPlans"; import { useUndoRedo } from "./hooks/useUndoRedo"; import type { @@ -931,10 +936,62 @@ const EditingPlanner: React.FC = () => { } }; + /** + * 将本地编辑的片段同步到后端 clips 表 + * 策略:先删除后端所有片段,再批量创建(简单可靠,生成前使用) + */ + const syncClipsToBackend = async (planId: string): Promise => { + if (clips.length === 0) return; + + // 1. 获取后端现有片段 ID + try { + const existing = await getEditPlanClips(planId, { limit: 500 }); + if (existing.items.length > 0) { + await batchDeleteEditPlanClips( + planId, + existing.items.map((c) => c.id), + ); + } + } catch (err) { + console.warn("[获取后端片段失败,跳过删除]", err); + } + + // 2. 批量创建新片段(并发 3 个) + const clipDataList: CreateEditPlanClipRequest[] = clips.map((c, i) => ({ + clip_type: c.type === "voice" ? "voiceover" : "main", + order: i, + asset_id: c.media_asset_id || "", + text_content: c.script_text || "", + start_time: 0, + duration: c.duration, + transition_effect: c.transition?.type || "cut", + transition_duration: c.transition?.duration || 0, + playback_speed: c.speed?.rate || 1.0, + config: { + tts_config: c.tts_config || null, + trim_config: c.trim_config || null, + template_segment_id: c.template_segment_id || null, + }, + })); + + // 并发控制:最多同时 3 个请求 + const results: EditPlanClip[] = []; + const concurrency = 3; + for (let i = 0; i < clipDataList.length; i += concurrency) { + const batch = clipDataList.slice(i, i + concurrency); + const batchResults = await Promise.all( + batch.map((data) => createEditPlanClip(planId, data)), + ); + results.push(...batchResults); + } + + console.log(`[片段同步] 创建了 ${results.length} 个片段`); + }; + /** * 剪辑计划生成 - * 1. 有 planId → 更新计划配置 + 触发生成 - * 2. 无 planId(从模板库直接进入)→ 先创建计划 + 触发生成 + * 1. 有 planId → 更新计划配置 + 同步片段 + 触发生成 + * 2. 无 planId(从模板库直接进入)→ 先创建计划 + 同步片段 + 触发生成 * 3. 触发生成后轮询状态,完成后获取视频结果 */ const handleGoToGenerate = async () => { @@ -991,6 +1048,14 @@ const EditingPlanner: React.FC = () => { window.history.replaceState(null, "", `?${params.toString()}`); } + // 同步片段到后端 clips 表(生成前必须同步,后端生成从 clips 表读) + try { + await syncClipsToBackend(planId); + } catch (syncErr) { + console.warn("[片段同步失败]", syncErr); + // 同步失败不阻塞生成,后端有模板兜底 + } + // 触发生成 const genRes = await generateEditPlan(planId); setGenTotalClips(genRes.clip_count);