From 856647a4e5a01a75272aa6269d3bf58a4c281875 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sun, 19 Jul 2026 22:40:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(#584):=20=E6=99=BA=E8=83=BD=E7=B4=A0?= =?UTF-8?q?=E6=9D=90=E5=8C=B9=E9=85=8D=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 智能剪辑新增智能素材匹配交互:用户输入描述后AI推荐素材 - 支持推荐素材列表展示(匹配度+匹配理由+缩略图+时长) - 支持勾选/取消勾选、全选/清空、换一批等交互 - auto模式下使用智能匹配选中的素材生成视频 - 生成预览和确认步骤显示智能匹配的素材数量 - 下一步验证:auto模式需选中素材才能继续 - 暂用模拟数据,后端LLM接入后替换 --- apps/web/src/pages/generate/GeneratePage.tsx | 313 ++++++++++++++++--- apps/web/src/pages/generate/generate.css | 245 +++++++++++++++ 2 files changed, 522 insertions(+), 36 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 625381bad..84331ae13 100755 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -266,6 +266,18 @@ const GeneratePage: React.FC = () => { const [selectedMaterials, setSelectedMaterials] = useState([]) /* 素材选择模式:手动选择 / 自动匹配 */ const [materialMode, setMaterialMode] = useState<"manual" | "auto">("manual") + /* 智能素材匹配:用户描述输入 */ + const [smartMatchInput, setSmartMatchInput] = useState("") + /* 智能素材匹配:是否正在匹配中 */ + const [smartMatching, setSmartMatching] = useState(false) + /* 智能素材匹配:推荐结果列表 */ + const [smartMatchedResults, setSmartMatchedResults] = useState< + Array<{ asset: AssetItem; matchScore: number; matchReason: string }> + >([]) + /* 智能素材匹配:已选择的素材ID(auto模式下使用) */ + const [smartSelectedIds, setSmartSelectedIds] = useState([]) + /* 智能素材匹配:是否已执行过匹配 */ + const [hasMatched, setHasMatched] = useState(false) /* ── 标题设置 ── */ const [titleSettings, setTitleSettings] = useState(DEFAULT_TITLE_SETTINGS) @@ -471,6 +483,102 @@ const GeneratePage: React.FC = () => { enabled: !!selectedLibraryId, }) + /* ── 智能素材匹配 ── */ + const SMART_MATCH_REASONS = [ + "画面清晰度高,构图专业", + "与描述场景高度契合", + "时长适中,适合剪辑节奏", + "色彩风格统一", + "包含关键动作镜头", + "镜头运动流畅自然", + "光影效果出色", + "人物表情生动", + ] + + const handleSmartMatch = useCallback(async () => { + if (!smartMatchInput.trim()) { + message.warning("请先输入视频内容描述") + return + } + if (materials.items.length === 0) { + message.warning("当前视频库暂无素材") + return + } + + setSmartMatching(true) + setHasMatched(true) + + // 模拟 AI 匹配延迟 + await new Promise((resolve) => setTimeout(resolve, 1500)) + + // 从素材库中随机选取 5-8 个作为推荐结果 + const shuffled = [...materials.items].sort(() => Math.random() - 0.5) + const count = Math.min(shuffled.length, 5 + Math.floor(Math.random() * 4)) + const picked = shuffled.slice(0, count) + + const results = picked.map((asset, idx) => ({ + asset, + matchScore: Math.round(85 + Math.random() * 14), // 85-99 分 + matchReason: + SMART_MATCH_REASONS[idx % SMART_MATCH_REASONS.length] + + (Math.random() > 0.5 ? ",画面质感优秀" : ""), + })) + + // 按匹配度从高到低排序 + results.sort((a, b) => b.matchScore - a.matchScore) + + setSmartMatchedResults(results) + // 默认选中匹配度 >= 90 的素材 + const defaultSelected = results.filter((r) => r.matchScore >= 90).map((r) => r.asset.id) + setSmartSelectedIds( + defaultSelected.length > 0 ? defaultSelected : results.slice(0, 3).map((r) => r.asset.id), + ) + setSmartMatching(false) + }, [smartMatchInput, materials.items]) + + const handleToggleSmartSelect = useCallback((assetId: string) => { + setSmartSelectedIds((prev) => + prev.includes(assetId) ? prev.filter((id) => id !== assetId) : [...prev, assetId], + ) + }, []) + + const handleRefreshMatch = useCallback(async () => { + if (materials.items.length <= 5) { + message.info("素材库素材较少,无法换一批") + return + } + setSmartMatching(true) + await new Promise((resolve) => setTimeout(resolve, 800)) + + const remaining = materials.items.filter( + (m) => !smartMatchedResults.some((r) => r.asset.id === m.id), + ) + const shuffled = [...remaining].sort(() => Math.random() - 0.5) + const count = Math.min(shuffled.length, 5 + Math.floor(Math.random() * 3)) + const picked = shuffled.slice(0, count) + + const results = picked.map((asset, idx) => ({ + asset, + matchScore: Math.round(80 + Math.random() * 19), + matchReason: + SMART_MATCH_REASONS[(idx + 2) % SMART_MATCH_REASONS.length] + + (Math.random() > 0.5 ? ",节奏明快" : ""), + })) + results.sort((a, b) => b.matchScore - a.matchScore) + + setSmartMatchedResults(results) + setSmartSelectedIds([]) + setSmartMatching(false) + }, [materials.items, smartMatchedResults]) + + const handleSelectAllMatched = useCallback(() => { + setSmartSelectedIds(smartMatchedResults.map((r) => r.asset.id)) + }, [smartMatchedResults]) + + const handleClearSmartSelect = useCallback(() => { + setSmartSelectedIds([]) + }, []) + const handleCloneSuccess = useCallback( (voice: VoiceClone) => { addClone(voice) @@ -700,7 +808,7 @@ const GeneratePage: React.FC = () => { template_id: selectedTemplate, name: titleSettings.title.trim(), config: { - asset_ids: selectedMaterials, + asset_ids: materialMode === "auto" ? smartSelectedIds : selectedMaterials, title_config: { ai_auto_select: titleSettings.aiAutoSelect, content: titleSettings.title, @@ -905,6 +1013,7 @@ const GeneratePage: React.FC = () => { generateCount, materialMode, coverSettings, + smartSelectedIds, ]) /* ── 下载视频 ── */ @@ -953,6 +1062,10 @@ const GeneratePage: React.FC = () => { message.warning("请至少选择一个素材") return } + if (currentStep === 2 && materialMode === "auto" && smartSelectedIds.length === 0) { + message.warning("请先进行智能匹配并选择素材") + return + } if (currentStep === 4 && !titleSettings.title.trim()) { message.warning("请选择或输入标题") return @@ -960,7 +1073,14 @@ const GeneratePage: React.FC = () => { if (currentStep < 7) { setCurrentStep((s) => s + 1) } - }, [currentStep, selectedTemplate, selectedMaterials.length, titleSettings, materialMode]) + }, [ + currentStep, + selectedTemplate, + selectedMaterials.length, + titleSettings, + materialMode, + smartSelectedIds.length, + ]) const goPrev = useCallback(() => { if (currentStep > 1) { @@ -1176,40 +1296,157 @@ const GeneratePage: React.FC = () => { {/* ── 自动匹配模式 ── */} {materialMode === "auto" && ( -
-
🤖
-
-

智能素材匹配

-

- 系统将根据所选模板和标题,从视频库中自动分析并匹配最合适的素材进行视频生成。 - 无需手动挑选,AI 会综合素材质量、时长、内容相关性等维度进行智能筛选。 -

-
- 📊 质量评分筛选 - 🎯 内容相关性匹配 - ⏱️ 时长智能分配 +
+ {/* 描述输入区 */} +
+ +