From 80247c969a58edd67e23fa3e679b0aafc1add73e Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 13:58:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(assets):=20=E6=99=BA=E8=83=BD=E9=80=89?= =?UTF-8?q?=E7=B4=A0=E6=9D=90=E4=BC=A0=20limit=20=E5=8F=82=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E3=80=8C=E6=9C=89=E5=87=A0=E4=B8=AA?= =?UTF-8?q?=E9=80=89=E5=87=A0=E4=B8=AA=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - smartMatchAssets 增加可选 limit 参数,传给后端 /assets/smart-match - useSmartMatch 根据模板 segments 总时长按 15s/素材估算 limit,钳制 [1,200] - 拿不到时长信息时默认 limit=10(后端上限 200) - useStep2Materials 透传 templateSegments 到 useSmartMatch --- apps/web/src/api/assets/assets.ts | 11 +++--- .../hooks/step2-materials/useSmartMatch.ts | 35 +++++++++++++++---- .../pages/generate/hooks/useStep2Materials.ts | 1 + 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/apps/web/src/api/assets/assets.ts b/apps/web/src/api/assets/assets.ts index 96a5b0840..502d603c5 100644 --- a/apps/web/src/api/assets/assets.ts +++ b/apps/web/src/api/assets/assets.ts @@ -69,10 +69,13 @@ interface SmartMatchWrappedItem { breakdown?: unknown } -export const smartMatchAssets = async (libraryId: string): Promise => { - const response = await apiClient.post("/assets/smart-match", { - library_id: libraryId, - }) +export const smartMatchAssets = async ( + libraryId: string, + limit?: number, +): Promise => { + const payload: Record = { library_id: libraryId } + if (limit && limit > 0) payload.limit = limit + const response = await apiClient.post("/assets/smart-match", payload) const rawItems: SmartMatchWrappedItem[] = response.data?.items ?? [] const items = rawItems .map((it) => diff --git a/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts b/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts index bf1733d33..736181ed1 100755 --- a/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts +++ b/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts @@ -1,12 +1,33 @@ import { useState, useCallback } from "react" import { message } from "antd" import type { AssetItem } from "@/api/assets" +import type { TemplateSegment } from "@/api/templates/types" import { smartMatchAssets, isAssetUsable } from "@/api/assets" interface UseSmartMatchOptions { libraryId: string materials: { items: AssetItem[]; total: number } onSmartSelectedIdsChange: (ids: string[]) => void + /** 当前模板的 segments,用于根据总时长计算 limit */ + templateSegments?: TemplateSegment[] +} + +/** 默认 limit(拿不到目标时长时的兜底上限) */ +const DEFAULT_LIMIT = 10 +/** 每个素材切片按 15 秒估算所需素材数 */ +const SECONDS_PER_ASSET = 15 + +/** + * 根据模板 segments 计算所需素材数量上限。 + * 取每个 segment 的 duration_min 之和作为目标视频总时长, + * 再按 15 秒/素材估算需要多少个素材;结果钳制到 [1, 200] 区间(后端 limit 上限 200)。 + */ +function computeLimitFromSegments(segments?: TemplateSegment[]): number { + if (!segments || segments.length === 0) return DEFAULT_LIMIT + const totalSeconds = segments.reduce((sum, seg) => sum + (seg.duration_min || 0), 0) + if (totalSeconds <= 0) return DEFAULT_LIMIT + const limit = Math.ceil(totalSeconds / SECONDS_PER_ASSET) + return Math.max(1, Math.min(limit, 200)) } /** @@ -18,6 +39,7 @@ export function useSmartMatch({ libraryId, materials, onSmartSelectedIdsChange, + templateSegments, }: UseSmartMatchOptions) { const [smartMatching, setSmartMatching] = useState(false) const [hasMatched, setHasMatched] = useState(false) @@ -42,12 +64,13 @@ export function useSmartMatch({ setSmartMatching(true) try { + // 根据目标视频时长计算合理的素材数量上限,避免"有几个选几个" + const limit = computeLimitFromSegments(templateSegments) + // 调用后端智能匹配 API(后端也会排除已用尽素材,这里前端兜底过滤) - // items 已在 API 层归一化(兼容后端 {asset, score} 包装结构); - // 这里再过滤一遍无 id/已用尽项,杜绝 undefined id 流入预览链路 - const result = await smartMatchAssets(libraryId) - const matched = (result.items ?? []).filter((a) => !!a?.id && isAssetUsable(a)) - const matchedIds = matched.map((a) => a.id) + const result = await smartMatchAssets(libraryId, limit) + const matched = (result.items ?? []).filter(isAssetUsable) + const matchedIds = matched.map((a: AssetItem) => a.id) if (matchedIds.length > 0) { onSmartSelectedIdsChange(matchedIds) @@ -67,7 +90,7 @@ export function useSmartMatch({ } finally { setSmartMatching(false) } - }, [libraryId, materials.items, onSmartSelectedIdsChange]) + }, [libraryId, materials.items, onSmartSelectedIdsChange, templateSegments]) /* ── 换一批 = 重新触发智能匹配 ── */ const handleRefreshMatch = useCallback(async () => { diff --git a/apps/web/src/pages/generate/hooks/useStep2Materials.ts b/apps/web/src/pages/generate/hooks/useStep2Materials.ts index 11ab59be5..7f9a0388f 100644 --- a/apps/web/src/pages/generate/hooks/useStep2Materials.ts +++ b/apps/web/src/pages/generate/hooks/useStep2Materials.ts @@ -51,6 +51,7 @@ export function useStep2Materials({ libraryId: selectedLibraryId, materials: selectableMaterials, onSmartSelectedIdsChange, + templateSegments, }) /* ── 自动触发智能匹配:选择视频库后自动调用 ── */ -- 2.54.0 From 140d3c7e671bea084ed1ca618cda58be4823dcdd Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 31 Aug 2026 14:09:30 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(assets):=20useSmartMatch=20=E5=85=9C?= =?UTF-8?q?=E5=BA=95=E8=BF=87=E6=BB=A4=E7=A9=BA=20id=20=E7=B4=A0=E6=9D=90?= =?UTF-8?q?=EF=BC=88AI=20Review=20=E5=BB=BA=E8=AE=AE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/generate/hooks/step2-materials/useSmartMatch.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts b/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts index 736181ed1..e297b4626 100755 --- a/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts +++ b/apps/web/src/pages/generate/hooks/step2-materials/useSmartMatch.ts @@ -69,7 +69,8 @@ export function useSmartMatch({ // 调用后端智能匹配 API(后端也会排除已用尽素材,这里前端兜底过滤) const result = await smartMatchAssets(libraryId, limit) - const matched = (result.items ?? []).filter(isAssetUsable) + // 兜底过滤:id 为空或不可用的素材不参与匹配(smartMatchAssets 已做归一化,这里双保险) + const matched = (result.items ?? []).filter((a) => !!a?.id && isAssetUsable(a)) const matchedIds = matched.map((a: AssetItem) => a.id) if (matchedIds.length > 0) { -- 2.54.0