/** * GeneratePage 步骤内容渲染(#1970 流程重构) * 步骤顺序:选择模式(1) → 选择素材(2) → 选择标题(3) → 确认生成(4) → 选择封面(5) * 原步骤"选择配音"已从主流程移除,改为 Step1 下一步分支弹窗(VoiceSelectModal / ScriptSelectModal → TtsVoiceModal)。 */ import React from "react" import type { EditPlanClip } from "@/api/template-editor" import type { CoverConfig } from "../types/cover" import type { TitleSettings } from "../types" import type { ScriptItem } from "@/api/scripts" import Step1EditMode from "./Step1EditMode" import type { EditMode } from "./Step1EditMode" import Step2MaterialSelect from "../components/Step2MaterialSelect" import Step4TitleSettings from "../components/Step4TitleSettings" import Step6CoverSettings from "../components/Step6CoverSettings" import BatchGenerationGrid from "./BatchGenerationGrid" import type { BatchTaskState } from "../hooks/generate-video/useGenerationPolling" import type { GeneratedVideo } from "@/api/template-editor" export interface GenerateStepContentProps { currentStep: number /* Step1:剪辑模式 + 生成设置 */ editMode: EditMode onEditModeChange: (m: EditMode) => void dedupEnabled: boolean onDedupEnabledChange: (v: boolean) => void /* ── 片段数量(#1899) ── */ clipCount: number onClipCountChange: (n: number) => void /* ── 生成数量/比例(Step1 设置) ── */ previewCount: number onPreviewCountChange: (n: number) => void videoRatio: "9:16" | "16:9" onVideoRatioChange: (r: "9:16" | "16:9") => void /* ── 素材 ── */ materialMode: "manual" | "auto" onMaterialModeChange: (mode: "manual" | "auto") => void selectedMaterials: string[] onSelectedMaterialsChange: (ids: string[]) => void smartSelectedIds: string[] onSmartSelectedIdsChange: (ids: string[]) => void selectedTemplate?: string /* ── 标题 ── */ titleSettings: TitleSettings onTitleSettingsChange: (settings: TitleSettings) => void onUpdatePosition: (position: string) => void onUpdateFont: (font: string) => void onUpdateSize: (size: number) => void onToggleBold: () => void onToggleItalic: () => void onToggleStroke: () => void onToggleShadow: () => void onApplyPreset: (presetKey: string) => void activePreset: string | null titlePresets: { key: string; label: string; previewStyle: React.CSSProperties }[] /* ── 封面 ── */ coverSettings: CoverConfig onCoverSettingsChange: (settings: CoverConfig) => void /* ── 配音 ── */ selectedVoice: string onSelectedVoiceChange: (id: string) => void onServerClipsChange: (clips: EditPlanClip[]) => void /* ── 生成 ── */ generating: boolean generated: boolean generateError: string | null progress: number generatedVideos: GeneratedVideo[] onRetry: () => void onRetryBatchTask: (taskId: string) => void onDismissError: () => void batchTasks: BatchTaskState[] bgm: boolean bgmConfig?: { enabled: boolean; music_id?: string } /* ── 批量生成 ── */ previewTitles: string[] onPreviewTitlesChange: (titles: string[]) => void voiceModePerVideo: boolean onVoiceModePerVideoChange: (v: boolean) => void voiceLibraryIds: string[] onVoiceLibraryIdsChange: (ids: string[]) => void previewCovers: string[] onPreviewCoversChange: (urls: string[]) => void selectedVariantIds?: number[] /* ── 摘要信息(#1970 Step4 展示用) ── */ selectedScript: ScriptItem | null ttsVoiceId: string ttsVoiceSource: "preset" | "clone" } export const GenerateStepContent: React.FC = (props) => { // Only destructure props actually referenced in JSX below const { currentStep, editMode, onEditModeChange, dedupEnabled, onDedupEnabledChange, clipCount, onClipCountChange, previewCount, onPreviewCountChange, videoRatio, onVideoRatioChange, materialMode, onMaterialModeChange, selectedMaterials, onSelectedMaterialsChange, smartSelectedIds, onSmartSelectedIdsChange, selectedTemplate, titleSettings, onTitleSettingsChange, onUpdatePosition, onUpdateFont, onUpdateSize, onToggleBold, onToggleItalic, onToggleStroke, onToggleShadow, onApplyPreset, activePreset, titlePresets, coverSettings, onCoverSettingsChange, onServerClipsChange, generating, generateError, progress, onRetry, generatedVideos, batchTasks, onRetryBatchTask, previewTitles, onPreviewTitlesChange, previewCovers, onPreviewCoversChange, selectedVariantIds, selectedScript, ttsVoiceId, ttsVoiceSource, } = props const handleClipsChange = React.useCallback( (clips: EditPlanClip[], _templateId?: string) => { onServerClipsChange(clips) }, [onServerClipsChange], ) switch (currentStep) { case 1: return ( ) case 2: return ( ) case 3: return ( ) case 4: return (
{/* 配置摘要(#1970) */}
📋 生成配置
🎬 剪辑模式:{editMode === "random" ? "🎲 随机混剪" : "📖 叙事剪辑"}
{editMode === "random" ? (
🎙️ 配音来源:配音库音频
) : ( <>
📝 文案:{selectedScript?.title ?? "未选择"}
🎙️ 合成配音音色: {ttsVoiceId ? `${ttsVoiceSource === "clone" ? "克隆音色" : "系统音色"}(${ttsVoiceId.slice(0, 8)}...)` : "未选择"}
)}
📱 视频比例:{videoRatio}
🎯 智能降重:{dedupEnabled ? "已开启" : "已关闭"}
{previewCount > 1 &&
📦 生成数量:{previewCount} 个
}
{previewCount > 1 ? ( ) : generating ? (
⏳ 视频渲染中… {Math.round(progress)}%
生成过程中可以切换到其他页面,完成后可在任务历史查看
) : generateError ? (
生成失败
{generateError}
) : null}
) case 5: return ( ) default: return null } } export default GenerateStepContent