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 (