style: reformat useStep2Materials.ts with repo prettier config (#1899 #1900)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Has been cancelled
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Style (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled

This commit is contained in:
2026-09-14 21:37:48 +08:00
parent 0e2cefaa5f
commit 0697d9c67d
@@ -2,31 +2,27 @@
* Step 1 素材选择 Hook(#1899 原 Step2 前移)
* 使用 clipCount 替代 templateSegments 决定片段数量
*/
import { useCallback, useEffect, useRef } from "react";
import { message } from "antd";
import type { EditPlanClip } from "@/api/template-editor";
import {
updateEditPlanClips,
createClipsFromAssets,
getEditPlanClips,
} from "@/api/template-editor";
import { useMaterialLibrary } from "./step2-materials/useMaterialLibrary";
import { useSmartMatch } from "./step2-materials/useSmartMatch";
import { useDraftAutoSave } from "./useDraftAutoSave";
import { useCallback, useEffect, useRef } from "react"
import { message } from "antd"
import type { EditPlanClip } from "@/api/template-editor"
import { updateEditPlanClips, createClipsFromAssets, getEditPlanClips } from "@/api/template-editor"
import { useMaterialLibrary } from "./step2-materials/useMaterialLibrary"
import { useSmartMatch } from "./step2-materials/useSmartMatch"
import { useDraftAutoSave } from "./useDraftAutoSave"
interface UseStep2MaterialsProps {
materialMode: "manual" | "auto";
onMaterialModeChange: (mode: "manual" | "auto") => void;
selectedMaterials: string[];
onSelectedMaterialsChange: (ids: string[]) => void;
smartSelectedIds: string[];
onSmartSelectedIdsChange: (ids: string[]) => void;
materialMode: "manual" | "auto"
onMaterialModeChange: (mode: "manual" | "auto") => void
selectedMaterials: string[]
onSelectedMaterialsChange: (ids: string[]) => void
smartSelectedIds: string[]
onSmartSelectedIdsChange: (ids: string[]) => void
/** 片段数量(#1899) */
clipCount: number;
clipCount: number
/** 当前选中的模板/草稿 ID,用于自动保存 */
selectedTemplate?: string;
selectedTemplate?: string
/** 服务端 clips 创建成功后的回调,用于通知预览播放器 */
onServerClipsChange?: (clips: EditPlanClip[]) => void;
onServerClipsChange?: (clips: EditPlanClip[]) => void
}
export function useStep2Materials({
@@ -48,129 +44,114 @@ export function useStep2Materials({
selectableMaterials,
allMaterialsExhausted,
materialsLoading,
} = useMaterialLibrary();
} = useMaterialLibrary()
const smartMatch = useSmartMatch({
libraryId: selectedLibraryId,
materials: selectableMaterials,
onSmartSelectedIdsChange,
clipCount,
});
})
/* ── 自动触发智能匹配:选择视频库后自动调用 ── */
const autoTriggeredRef = useRef<string>("");
const { handleSmartMatch } = smartMatch;
const autoTriggeredRef = useRef<string>("")
const { handleSmartMatch } = smartMatch
useEffect(() => {
if (materialMode !== "auto") {
autoTriggeredRef.current = "";
return;
autoTriggeredRef.current = ""
return
}
if (!selectedLibraryId) return;
if (materialsLoading) return;
if (selectableMaterials.items.length === 0) return;
if (autoTriggeredRef.current === selectedLibraryId) return;
if (!selectedLibraryId) return
if (materialsLoading) return
if (selectableMaterials.items.length === 0) return
if (autoTriggeredRef.current === selectedLibraryId) return
autoTriggeredRef.current = selectedLibraryId;
handleSmartMatch();
autoTriggeredRef.current = selectedLibraryId
handleSmartMatch()
}, [
selectedLibraryId,
materialMode,
materialsLoading,
selectableMaterials.items,
handleSmartMatch,
]);
])
/* ── 选择素材后自动保存草稿 asset_ids(防抖 500ms,失败静默) ── */
const { scheduleSave } = useDraftAutoSave(selectedTemplate);
const { scheduleSave } = useDraftAutoSave(selectedTemplate)
useEffect(() => {
if (!selectedTemplate) return;
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials;
scheduleSave({ asset_ids: ids }, 500);
}, [
selectedTemplate,
materialMode,
selectedMaterials,
smartSelectedIds,
scheduleSave,
]);
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 作为片段数量
*/
const clipsTimerRef = useRef<ReturnType<typeof setTimeout>>();
const clipsAbortRef = useRef<AbortController | null>(null);
const clipCountRef = useRef(clipCount);
clipCountRef.current = clipCount;
const selectedTemplateRef = useRef(selectedTemplate);
selectedTemplateRef.current = selectedTemplate;
const onServerClipsChangeRef = useRef(onServerClipsChange);
onServerClipsChangeRef.current = onServerClipsChange;
const clipsTimerRef = useRef<ReturnType<typeof setTimeout>>()
const clipsAbortRef = useRef<AbortController | null>(null)
const clipCountRef = useRef(clipCount)
clipCountRef.current = clipCount
const selectedTemplateRef = useRef(selectedTemplate)
selectedTemplateRef.current = selectedTemplate
const onServerClipsChangeRef = useRef(onServerClipsChange)
onServerClipsChangeRef.current = onServerClipsChange
useEffect(() => {
const tid = selectedTemplateRef.current;
if (!tid) return;
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials;
const tid = selectedTemplateRef.current
if (!tid) return
const ids = materialMode === "auto" ? smartSelectedIds : selectedMaterials
if (!ids.length) {
onServerClipsChangeRef.current?.([]);
return;
onServerClipsChangeRef.current?.([])
return
}
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current);
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current)
clipsTimerRef.current = setTimeout(async () => {
if (clipsAbortRef.current) clipsAbortRef.current.abort();
const controller = new AbortController();
clipsAbortRef.current = controller;
if (clipsAbortRef.current) clipsAbortRef.current.abort()
const controller = new AbortController()
clipsAbortRef.current = controller
const requiredClipsCount = clipCountRef.current;
const requiredClipsCount = clipCountRef.current
try {
// 1. 清空旧片段
await updateEditPlanClips(tid, [], controller.signal, true);
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 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);
.sort((a, b) => a.order - b.order)
onServerClipsChangeRef.current?.(readyClips)
} 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("智能选片失败,请重试");
return;
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("智能选片失败,请重试")
return
}
console.warn("[useStep2Materials] 写入 clips 失败:", err);
console.warn("[useStep2Materials] 写入 clips 失败:", err)
}
}, 800);
}, 800)
return () => {
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current);
};
}, [
selectedTemplate,
materialMode,
selectedMaterials,
smartSelectedIds,
clipCount,
]);
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current)
}
}, [selectedTemplate, materialMode, selectedMaterials, smartSelectedIds, clipCount])
// 组件卸载时取消未完成请求
useEffect(() => {
return () => {
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current);
if (clipsAbortRef.current) clipsAbortRef.current.abort();
};
}, []);
if (clipsTimerRef.current) clearTimeout(clipsTimerRef.current)
if (clipsAbortRef.current) clipsAbortRef.current.abort()
}
}, [])
/* ── 手动选择素材 ── */
const handleToggleMaterial = useCallback(
@@ -179,10 +160,10 @@ export function useStep2Materials({
selectedMaterials.includes(materialId)
? selectedMaterials.filter((id) => id !== materialId)
: [...selectedMaterials, materialId],
);
)
},
[selectedMaterials, onSelectedMaterialsChange],
);
)
return {
libraries,
@@ -200,7 +181,7 @@ export function useStep2Materials({
hasMatched: smartMatch.hasMatched,
handleSmartMatch: smartMatch.handleSmartMatch,
handleRefreshMatch: smartMatch.handleRefreshMatch,
};
}
}
export default useStep2Materials;
export default useStep2Materials