From 15c405bfc5e4f12e1c4e1d381d93bc4e0d23f629 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Sat, 5 Sep 2026 12:36:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(#1677):=20AI=20Review=20=E5=8F=8D=E9=A6=88?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=94=E2=80=94=E6=A0=87=E9=A2=98=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C/TTS=E4=BE=9D=E8=B5=96=E6=94=B6=E7=AA=84/=E6=8E=92?= =?UTF-8?q?=E5=BA=8F=E5=81=A5=E5=A3=AE=E6=80=A7/=E9=A2=84=E8=A7=88?= =?UTF-8?q?=E6=95=B0=E7=A1=AC=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 单视频标题校验与 buildPayload.validateGenerateInputs 对齐:aiAutoSelect 自动模式允许空标题(后端生成,既有设计),手动模式必填,逻辑显式化 - TTS 试听 effect 依赖收窄到 previewTitles[0](variant0Title),编辑其他 变体标题不再触发多余 TTS 请求 - BatchGenerationGrid 排序加 (variantIndex || 0) 兜底防 NaN - CanvasPreviewGrid 渲染数加 MAX_PREVIEW_COUNT(10) 硬上限,防媒体元素过多卡顿 --- apps/web/src/pages/generate/GeneratePage.tsx | 16 +++++++++++----- .../generate/components/BatchGenerationGrid.tsx | 2 +- .../generate/components/CanvasPreviewGrid.tsx | 5 ++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index ef48442d7..32fa3d68a 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -132,6 +132,8 @@ const GeneratePage: React.FC = () => { const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState(null) const ttsAbortRef = useRef(null) + // TTS 试听文案:批量跟随变体0标题(仅取首项,避免编辑其他变体标题触发多余 TTS 请求) + const variant0Title = isBatch ? previewTitles[0] || "" : "" useEffect(() => { const voiceAsset = voiceMaterials.find((m) => m.id === selectedVoice) @@ -141,7 +143,7 @@ const GeneratePage: React.FC = () => { } // 批量模式下 TTS 文案跟随变体0标题;单视频跟随主标题 - const ttsTitle = isBatch ? previewTitles[0] || "" : titleSettings.title + const ttsTitle = isBatch ? variant0Title || "" : titleSettings.title const voiceId = selectedClonedVoice || selectedVoice if (!voiceId || !ttsTitle) { setPreviewVoiceAudioUrl(null) @@ -174,7 +176,7 @@ const GeneratePage: React.FC = () => { selectedVoice, selectedClonedVoice, titleSettings.title, - previewTitles, + variant0Title, isBatch, voiceMaterials, ]) @@ -321,9 +323,13 @@ const GeneratePage: React.FC = () => { message.warning("请为每个勾选的视频输入标题") return } - } else if (!titleSettings.aiAutoSelect && !titleSettings.title?.trim()) { - message.warning("请先选择或输入标题") - return + } else if (!titleSettings.title?.trim()) { + // 与 buildPayload.validateGenerateInputs 一致:AI 自动选标题模式(aiAutoSelect) + // 允许空标题由后端生成;手动模式必须填写,避免提交空标题 + if (!titleSettings.aiAutoSelect) { + message.warning("请先选择或输入标题") + return + } } if (!previewReady) { message.warning("预览素材正在加载,请稍候") diff --git a/apps/web/src/pages/generate/components/BatchGenerationGrid.tsx b/apps/web/src/pages/generate/components/BatchGenerationGrid.tsx index 3761a4a47..0c52e355d 100644 --- a/apps/web/src/pages/generate/components/BatchGenerationGrid.tsx +++ b/apps/web/src/pages/generate/components/BatchGenerationGrid.tsx @@ -22,7 +22,7 @@ const BatchGenerationGrid: React.FC = ({ titles, onRetryTask, }) => { - const sorted = [...tasks].sort((a, b) => a.variantIndex - b.variantIndex) + const sorted = [...tasks].sort((a, b) => (a.variantIndex || 0) - (b.variantIndex || 0)) return (
diff --git a/apps/web/src/pages/generate/components/CanvasPreviewGrid.tsx b/apps/web/src/pages/generate/components/CanvasPreviewGrid.tsx index 1d0b17674..a034e29b3 100644 --- a/apps/web/src/pages/generate/components/CanvasPreviewGrid.tsx +++ b/apps/web/src/pages/generate/components/CanvasPreviewGrid.tsx @@ -12,6 +12,7 @@ import type { AssetItem } from "@/api/assets" import type { EditingTemplate } from "@/api/editing-planner" import type { TitleSettings } from "../types" import FrontendPreviewPlayer from "./FrontendPreviewPlayer" +import { MAX_PREVIEW_COUNT } from "../constants" interface CanvasPreviewGridProps { count: number @@ -41,9 +42,11 @@ const CanvasPreviewGrid: React.FC = ({ onToggleSelect, selectable = true, }) => { + // 硬上限保护:同时播放的媒体元素数量不超过 MAX_PREVIEW_COUNT(10),避免浏览器卡顿 + const safeCount = Math.max(1, Math.min(count, MAX_PREVIEW_COUNT)) return (
- {Array.from({ length: count }, (_, i) => { + {Array.from({ length: safeCount }, (_, i) => { const checked = selectedIds.includes(i) return (