fix(frontend): #1913 智能剪辑无模板时后端自动兜底,移除空template硬拦截
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Step 1 素材选择 Hook(#1899 原 Step2 前移)
|
||||
* Step 1 素材选择 Hook(#1899 原 Step2 前移,#1913 修复空模板兜底)
|
||||
* 使用 clipCount 替代 templateSegments 决定片段数量
|
||||
* #1913:selectedTemplate 为空时不再硬拦截,仍发 from-assets 请求(后端自动兜底创建默认模板)
|
||||
*/
|
||||
import { useCallback, useEffect, useRef } from "react"
|
||||
import { message } from "antd"
|
||||
@@ -19,10 +20,10 @@ interface UseStep2MaterialsProps {
|
||||
onSmartSelectedIdsChange: (ids: string[]) => void
|
||||
/** 片段数量(#1899) */
|
||||
clipCount: number
|
||||
/** 当前选中的模板/草稿 ID,用于自动保存 */
|
||||
/** 当前选中的模板/草稿 ID;空字符串时由后端自动兜底创建默认模板(#1913) */
|
||||
selectedTemplate?: string
|
||||
/** 服务端 clips 创建成功后的回调,用于通知预览播放器 */
|
||||
onServerClipsChange?: (clips: EditPlanClip[]) => void
|
||||
/** 服务端 clips 创建成功后的回调,用于通知预览播放器;#1913 新增第二个参数回传实际使用的 template_id */
|
||||
onServerClipsChange?: (clips: EditPlanClip[], templateId?: string) => void
|
||||
}
|
||||
|
||||
export function useStep2Materials({
|
||||
@@ -33,7 +34,7 @@ export function useStep2Materials({
|
||||
smartSelectedIds,
|
||||
onSmartSelectedIdsChange,
|
||||
clipCount,
|
||||
selectedTemplate,
|
||||
selectedTemplate = "",
|
||||
onServerClipsChange,
|
||||
}: UseStep2MaterialsProps) {
|
||||
const {
|
||||
@@ -76,16 +77,20 @@ export function useStep2Materials({
|
||||
handleSmartMatch,
|
||||
])
|
||||
|
||||
/* ── 选择素材后自动保存草稿 asset_ids(防抖 500ms,失败静默) ── */
|
||||
const { scheduleSave } = useDraftAutoSave(selectedTemplate)
|
||||
/* ── 自动保存草稿 asset_ids(防抖 500ms,失败静默) ──
|
||||
* #1913:selectedTemplate 为空时不发草稿保存请求(后端需要 template_id)
|
||||
*/
|
||||
const { scheduleSave } = useDraftAutoSave(selectedTemplate || undefined)
|
||||
useEffect(() => {
|
||||
if (!selectedTemplate) return
|
||||
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials
|
||||
scheduleSave({ asset_ids: ids }, 500)
|
||||
}, [selectedTemplate, materialMode, selectedMaterials, smartSelectedIds, scheduleSave])
|
||||
|
||||
/* ── 选择素材后同步写入 edit_plan_clips(防抖 800ms,失败静默)──
|
||||
* 调用后端 POST /clips/from-assets,传递 clipCount 作为片段数量
|
||||
/* ── 选择素材后同步写入 edit_plan_clips(防抖 800ms)──
|
||||
* 调用后端 POST /clips/from-assets,传递 clipCount 作为片段数量。
|
||||
* #1913:selectedTemplate 为空时仍发请求,后端自动兜底创建默认 voice_over 模板。
|
||||
* 后端返回后若实际创建了新模板,通过 onServerClipsChange 回传 template_id。
|
||||
*/
|
||||
const clipsTimerRef = useRef<ReturnType<typeof setTimeout>>()
|
||||
const clipsAbortRef = useRef<AbortController | null>(null)
|
||||
@@ -95,10 +100,11 @@ export function useStep2Materials({
|
||||
selectedTemplateRef.current = selectedTemplate
|
||||
const onServerClipsChangeRef = useRef(onServerClipsChange)
|
||||
onServerClipsChangeRef.current = onServerClipsChange
|
||||
/** 记录后端兜底返回的新 template_id(#1913) */
|
||||
const fallbackTidRef = useRef<string>("")
|
||||
|
||||
useEffect(() => {
|
||||
const tid = selectedTemplateRef.current
|
||||
if (!tid) return
|
||||
const tid = selectedTemplateRef.current || fallbackTidRef.current
|
||||
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials
|
||||
if (!ids.length) {
|
||||
onServerClipsChangeRef.current?.([])
|
||||
@@ -112,31 +118,59 @@ export function useStep2Materials({
|
||||
clipsAbortRef.current = controller
|
||||
|
||||
const requiredClipsCount = clipCountRef.current
|
||||
const hasTid = !!tid
|
||||
const requestTid = tid // 空串时由后端兜底
|
||||
|
||||
try {
|
||||
// 1. 清空旧片段
|
||||
await updateEditPlanClips(tid, [], controller.signal, true)
|
||||
// 2. 调用后端 from-assets 接口创建片段,传递 clipCount
|
||||
await createClipsFromAssets(tid, ids, "main", requiredClipsCount, {
|
||||
signal: controller.signal,
|
||||
silentErrorToast: true,
|
||||
})
|
||||
// 3. 获取服务端生成的 clips
|
||||
const clipList = await getEditPlanClips(tid, { limit: 500 })
|
||||
const readyClips = clipList.items
|
||||
.filter((c) => c.status === "ready")
|
||||
.sort((a, b) => a.order - b.order)
|
||||
onServerClipsChangeRef.current?.(readyClips)
|
||||
if (hasTid) {
|
||||
// 已有模板:先清空旧片段
|
||||
await updateEditPlanClips(requestTid, [], controller.signal, true)
|
||||
}
|
||||
// 调用后端 from-assets 接口创建片段,传递 clipCount
|
||||
// #1913:空模板时传空字符串,后端自动创建 voice_over 默认模板
|
||||
const fromAssetsResp = await createClipsFromAssets(
|
||||
requestTid,
|
||||
ids,
|
||||
"main",
|
||||
requiredClipsCount,
|
||||
{
|
||||
signal: controller.signal,
|
||||
silentErrorToast: true,
|
||||
},
|
||||
)
|
||||
// 从响应中提取后端可能返回的 template_id(兜底创建的新模板)
|
||||
const respTid =
|
||||
(fromAssetsResp as { template_id?: string })?.template_id ||
|
||||
(fromAssetsResp as { edit_plan_id?: string })?.edit_plan_id ||
|
||||
""
|
||||
const finalTid = respTid || requestTid
|
||||
if (respTid && !selectedTemplateRef.current) {
|
||||
fallbackTidRef.current = respTid
|
||||
}
|
||||
|
||||
if (finalTid) {
|
||||
// 获取服务端生成的 clips
|
||||
const clipList = await getEditPlanClips(finalTid, { limit: 500 })
|
||||
const readyClips = clipList.items
|
||||
.filter((c) => c.status === "ready")
|
||||
.sort((a, b) => a.order - b.order)
|
||||
onServerClipsChangeRef.current?.(readyClips, finalTid)
|
||||
} else {
|
||||
// 无有效 template_id(后端未返回),无法获取 clips
|
||||
console.warn("[useStep2Materials] 后端未返回 template_id,无法获取 clips")
|
||||
message.error("素材匹配失败,请刷新页面重试")
|
||||
}
|
||||
} catch (err) {
|
||||
const name = (err as { name?: string })?.name
|
||||
if (name === "CanceledError" || name === "AbortError") return
|
||||
const code = (err as { code?: string })?.code
|
||||
if (code === "ECONNABORTED" || /timeout/i.test((err as Error)?.message || "")) {
|
||||
console.warn("[useStep2Materials] 智能选片超时:", err)
|
||||
message.error("智能选片失败,请重试")
|
||||
message.error("智能选片超时,请检查网络后重试")
|
||||
return
|
||||
}
|
||||
console.warn("[useStep2Materials] 写入 clips 失败:", err)
|
||||
message.error("素材匹配失败,请稍后重试")
|
||||
}
|
||||
}, 800)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user