Files
xiaoxia-saas/apps/web/src/pages/generate/components/GenerateStepContent.tsx
T
xiaoxia f9daa08b2e
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging Web Image (push) Successful in 31s
CI/CD Pipeline / Build Staging API Image (push) Successful in 40s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 1m46s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 2m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m1s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 36s
CI/CD Pipeline / Integration Tests (push) Successful in 4m53s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 2m36s
CI/CD Pipeline / Validate - Style (push) Failing after 5m37s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 6m10s
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
feat(generate): #1970 智能剪辑流程重构 - 选择模式→素材→标题→确认→封面 (#1973)
2026-09-18 00:23:19 +08:00

296 lines
9.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<GenerateStepContentProps> = (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 (
<Step1EditMode
editMode={editMode}
onEditModeChange={onEditModeChange}
previewCount={previewCount}
onPreviewCountChange={onPreviewCountChange}
videoRatio={videoRatio}
onVideoRatioChange={onVideoRatioChange}
dedupEnabled={dedupEnabled}
onDedupEnabledChange={onDedupEnabledChange}
/>
)
case 2:
return (
<Step2MaterialSelect
editMode={editMode}
materialMode={materialMode}
onMaterialModeChange={onMaterialModeChange}
selectedMaterials={selectedMaterials}
onSelectedMaterialsChange={onSelectedMaterialsChange}
smartSelectedIds={smartSelectedIds}
onSmartSelectedIdsChange={onSmartSelectedIdsChange}
clipCount={clipCount}
onClipCountChange={onClipCountChange}
selectedTemplate={selectedTemplate}
onServerClipsChange={handleClipsChange}
/>
)
case 3:
return (
<Step4TitleSettings
titleSettings={titleSettings}
onTitleSettingsChange={onTitleSettingsChange}
onUpdatePosition={onUpdatePosition}
onUpdateFont={onUpdateFont}
onUpdateSize={onUpdateSize}
onToggleBold={onToggleBold}
onToggleItalic={onToggleItalic}
onToggleStroke={onToggleStroke}
onToggleShadow={onToggleShadow}
onApplyPreset={onApplyPreset}
activePreset={activePreset}
titlePresets={titlePresets}
previewCount={previewCount}
previewTitles={previewTitles}
onPreviewTitlesChange={onPreviewTitlesChange}
/>
)
case 4:
return (
<div className="xx-form-section">
{/* 配置摘要(#1970) */}
<div
style={{
padding: 14,
background: "#f9fafb",
borderRadius: 8,
marginBottom: 16,
fontSize: 13,
lineHeight: 1.8,
color: "#374151",
}}
>
<div style={{ fontWeight: 600, fontSize: 14, marginBottom: 6, color: "#111" }}>
📋 生成配置
</div>
<div>🎬 剪辑模式:{editMode === "random" ? "🎲 随机混剪" : "📖 叙事剪辑"}</div>
{editMode === "random" ? (
<div>🎙️ 配音来源:配音库音频</div>
) : (
<>
<div>📝 文案:{selectedScript?.title ?? "未选择"}</div>
<div>
🎙️ 合成配音音色:
{ttsVoiceId
? `${ttsVoiceSource === "clone" ? "克隆音色" : "系统音色"}(${ttsVoiceId.slice(0, 8)}...)`
: "未选择"}
</div>
</>
)}
<div>📱 视频比例:{videoRatio}</div>
<div>🎯 智能降重:{dedupEnabled ? "已开启" : "已关闭"}</div>
{previewCount > 1 && <div>📦 生成数量:{previewCount} 个</div>}
</div>
{previewCount > 1 ? (
<BatchGenerationGrid
tasks={batchTasks}
titles={previewTitles}
onRetryTask={onRetryBatchTask}
/>
) : generating ? (
<div className="xx-gen-progress-card">
<div className="xx-gen-progress-header">
<div className="xx-gen-progress-info">
<div className="xx-gen-progress-phase">
⏳ 视频渲染中… {Math.round(progress)}%
</div>
<div className="xx-gen-progress-sub">
生成过程中可以切换到其他页面,完成后可在任务历史查看
</div>
</div>
</div>
<div className="xx-gen-progress-bar">
<div
className="xx-gen-progress-bar-fill"
style={{ width: `${Math.min(Math.round(progress), 100)}%` }}
/>
</div>
</div>
) : generateError ? (
<div className="xx-gen-error-card">
<div className="xx-gen-error-info">
<div className="xx-gen-error-title">生成失败</div>
<div className="xx-gen-error-msg">{generateError}</div>
</div>
<button type="button" className="xx-btn xx-btn-primary xx-btn-sm" onClick={onRetry}>
🔄 重试
</button>
</div>
) : null}
</div>
)
case 5:
return (
<Step6CoverSettings
coverSettings={coverSettings}
onCoverSettingsChange={onCoverSettingsChange}
titleSettings={titleSettings}
generatedVideos={generatedVideos}
previewCount={previewCount}
previewTitles={previewTitles}
previewCovers={previewCovers}
onPreviewCoversChange={onPreviewCoversChange}
selectedVariantIndexes={selectedVariantIds}
/>
)
default:
return null
}
}
export default GenerateStepContent