fix(assets): 智能选素材传 limit 参数,避免「有几个选几个」 #1570

Merged
auto-approve-bot merged 2 commits from fix/smart-match-limit into develop 2026-08-31 14:21:00 +08:00
3 changed files with 37 additions and 9 deletions
+7 -4
View File
@@ -69,10 +69,13 @@ interface SmartMatchWrappedItem {
breakdown?: unknown
}
export const smartMatchAssets = async (libraryId: string): Promise<SmartMatchResult> => {
const response = await apiClient.post("/assets/smart-match", {
library_id: libraryId,
})
export const smartMatchAssets = async (
libraryId: string,
limit?: number,
): Promise<SmartMatchResult> => {
const payload: Record<string, unknown> = { 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) =>
@@ -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,14 @@ export function useSmartMatch({
setSmartMatching(true)
try {
// 根据目标视频时长计算合理的素材数量上限,避免"有几个选几个"
const limit = computeLimitFromSegments(templateSegments)
// 调用后端智能匹配 API(后端也会排除已用尽素材,这里前端兜底过滤)
// items 已在 API 层归一化(兼容后端 {asset, score} 包装结构);
// 这里再过滤一遍无 id/已用尽项,杜绝 undefined id 流入预览链路
const result = await smartMatchAssets(libraryId)
const result = await smartMatchAssets(libraryId, limit)
// 兜底过滤:id 为空或不可用的素材不参与匹配(smartMatchAssets 已做归一化,这里双保险)
const matched = (result.items ?? []).filter((a) => !!a?.id && isAssetUsable(a))
const matchedIds = matched.map((a) => a.id)
const matchedIds = matched.map((a: AssetItem) => a.id)
if (matchedIds.length > 0) {
onSmartSelectedIdsChange(matchedIds)
@@ -67,7 +91,7 @@ export function useSmartMatch({
} finally {
setSmartMatching(false)
}
}, [libraryId, materials.items, onSmartSelectedIdsChange])
}, [libraryId, materials.items, onSmartSelectedIdsChange, templateSegments])
/* ── 换一批 = 重新触发智能匹配 ── */
const handleRefreshMatch = useCallback(async () => {
@@ -51,6 +51,7 @@ export function useStep2Materials({
libraryId: selectedLibraryId,
materials: selectableMaterials,
onSmartSelectedIdsChange,
templateSegments,
})
/* ── 自动触发智能匹配:选择视频库后自动调用 ── */