style: format useStep2Materials.ts with prettier (#1899 #1900)
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
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 / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web 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 / CI Gate (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (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
CI/CD Pipeline / Check push changed paths (pull_request) Waiting to run
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 / Build Staging API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web 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 / CI Gate (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (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:
@@ -2,27 +2,31 @@
|
||||
* 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({
|
||||
@@ -44,114 +48,129 @@ 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(
|
||||
@@ -160,10 +179,10 @@ export function useStep2Materials({
|
||||
selectedMaterials.includes(materialId)
|
||||
? selectedMaterials.filter((id) => id !== materialId)
|
||||
: [...selectedMaterials, materialId],
|
||||
)
|
||||
);
|
||||
},
|
||||
[selectedMaterials, onSelectedMaterialsChange],
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
libraries,
|
||||
@@ -181,7 +200,7 @@ export function useStep2Materials({
|
||||
hasMatched: smartMatch.hasMatched,
|
||||
handleSmartMatch: smartMatch.handleSmartMatch,
|
||||
handleRefreshMatch: smartMatch.handleRefreshMatch,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default useStep2Materials
|
||||
export default useStep2Materials;
|
||||
|
||||
Reference in New Issue
Block a user