0a004db1bd
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m16s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m52s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 3m20s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 46s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m29s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m31s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 51s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 5m16s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 6m33s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 6m40s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 7m43s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 14m30s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Successful in 17m40s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Failing after 3s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Waiting to run
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 32s
- P0: 批量生成由一次性提交count=N改为串行count=1逐任务提交; 遇到 429 USER_QUEUE_FULL / 503 SYSTEM_QUEUE_FULL 自动读取 estimated_wait_seconds 后重试,不再弹错误阻止提交; 新增 useGenerationPolling.pollBatchTaskQueued 支持增量排队轮询(不触发整体 onComplete); 卡片新增 queued 状态(时钟图标+排队文案); 单个任务提交失败不阻断其余任务,失败卡片独立展示错误+重试按钮; generating 状态改为所有任务到达终态(completed/awaiting_cover/failed)且无queued/running才关闭。 - P1: Step4 批量标题改为 CSS Grid 两列布局(gridTemplateColumns: repeat(2,minmax(0,1fr))), 单字段 maxWidth 由 640 改为 100%,纵向空间节省约一半。 - P1: Step3(标题页)集成 Step3VoiceWithMode,批量模式下支持共用/独立配音切换; 独立配音时每个视频可单独选择音色,默认继承全局配音; GenerateStepContent props 已从 GeneratePage 透传,之前组件只定义未渲染,本次补齐 JSX 引用。 - BatchTaskState.status 新增 queued 枚举; - useGenerateVideo 重构 generate 函数,拆分 buildBasePayload/submitOne 逻辑, 新增 cancelledRef/queueTimersRef 做 unmount 清理。
299 lines
10 KiB
TypeScript
299 lines
10 KiB
TypeScript
/**
|
|
* GeneratePage 步骤内容渲染(#1970 流程重构)
|
|
* 步骤顺序:选择模式(1) → 选择素材(2) → 选择标题(3) → 确认生成(4) → 选择封面(5)
|
|
* 原步骤"选择配音"已从主流程移除,改为 Step1 下一步分支弹窗(VoiceSelectModal / ScriptSelectModal → TtsVoiceModal)。
|
|
*/
|
|
import React from "react"
|
|
import type { CoverConfig } from "../types/cover"
|
|
import type { TitleSettings } from "../types"
|
|
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 Step3VoiceWithMode from "./Step3VoiceWithMode"
|
|
import type { BatchTaskState } from "../hooks/generate-video/useGenerationPolling"
|
|
import type { GeneratedVideo } from "@/api/template-editor"
|
|
import type { TitleTemplate } from "@/components/title/template-types"
|
|
|
|
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
|
|
onUpdateStyle?: (patch: Partial<TitleSettings>) => void
|
|
activePreset: string | null
|
|
titlePresets: Array<{
|
|
key: string
|
|
label: string
|
|
emoji?: string
|
|
style: Record<string, unknown>
|
|
}>
|
|
enableTemplates?: boolean
|
|
selectedTemplateId?: string | null
|
|
onApplyTemplate?: (settings: import("../types").TitleSettings, template: TitleTemplate) => void
|
|
/* ── 封面 ── */
|
|
coverSettings: CoverConfig
|
|
onCoverSettingsChange: (settings: CoverConfig) => void
|
|
/* ── 配音 ── */
|
|
selectedVoice: string
|
|
onSelectedVoiceChange: (id: string) => 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[]
|
|
selectedCoverTemplate?: string
|
|
onSelectedCoverTemplateChange?: (templateId: string) => void
|
|
/** 单视频任务 ID(兜底,awaiting_cover 状态下 results 接口未入库时用) */
|
|
currentTaskId?: string
|
|
/** Step3 右上角确认生成按钮 */
|
|
onConfirmGenerate?: () => void | Promise<void>
|
|
}
|
|
|
|
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,
|
|
onUpdateStyle,
|
|
enableTemplates,
|
|
selectedTemplateId,
|
|
onApplyTemplate,
|
|
activePreset,
|
|
titlePresets,
|
|
coverSettings,
|
|
onCoverSettingsChange,
|
|
generating,
|
|
generateError,
|
|
progress,
|
|
onRetry,
|
|
generatedVideos,
|
|
batchTasks,
|
|
onRetryBatchTask,
|
|
previewTitles,
|
|
onPreviewTitlesChange,
|
|
previewCovers,
|
|
onPreviewCoversChange,
|
|
selectedVariantIds,
|
|
selectedCoverTemplate,
|
|
onSelectedCoverTemplateChange,
|
|
onConfirmGenerate,
|
|
selectedVoice,
|
|
onSelectedVoiceChange,
|
|
voiceModePerVideo,
|
|
onVoiceModePerVideoChange,
|
|
voiceLibraryIds,
|
|
onVoiceLibraryIdsChange,
|
|
} = props
|
|
|
|
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}
|
|
/>
|
|
)
|
|
case 3:
|
|
return (
|
|
<>
|
|
<Step4TitleSettings
|
|
titleSettings={titleSettings}
|
|
onTitleSettingsChange={onTitleSettingsChange}
|
|
onUpdatePosition={onUpdatePosition}
|
|
onUpdateFont={onUpdateFont}
|
|
onUpdateSize={onUpdateSize}
|
|
onToggleBold={onToggleBold}
|
|
onToggleItalic={onToggleItalic}
|
|
onToggleStroke={onToggleStroke}
|
|
onToggleShadow={onToggleShadow}
|
|
onApplyPreset={onApplyPreset}
|
|
onUpdateStyle={onUpdateStyle}
|
|
activePreset={activePreset}
|
|
titlePresets={titlePresets}
|
|
enableTemplates={enableTemplates}
|
|
selectedTemplateId={selectedTemplateId}
|
|
onApplyTemplate={onApplyTemplate}
|
|
previewCount={previewCount}
|
|
previewTitles={previewTitles}
|
|
onPreviewTitlesChange={onPreviewTitlesChange}
|
|
onConfirmGenerate={onConfirmGenerate}
|
|
generating={props.generating}
|
|
selectedCount={
|
|
props.previewCount && props.previewCount > 1
|
|
? props.selectedVariantIds?.length || 1
|
|
: 1
|
|
}
|
|
/>
|
|
{/* 批量配音选择:共用/独立切换(#2096) */}
|
|
<Step3VoiceWithMode
|
|
previewCount={previewCount}
|
|
selectedVoice={selectedVoice}
|
|
onSelectedVoiceChange={onSelectedVoiceChange}
|
|
voiceModePerVideo={voiceModePerVideo}
|
|
onVoiceModePerVideoChange={onVoiceModePerVideoChange}
|
|
voiceLibraryIds={voiceLibraryIds}
|
|
onVoiceLibraryIdsChange={onVoiceLibraryIdsChange}
|
|
/>
|
|
</>
|
|
)
|
|
case 4:
|
|
return (
|
|
<div className="xx-form-section">
|
|
{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}
|
|
selectedTemplate={selectedCoverTemplate}
|
|
onTemplateChange={onSelectedCoverTemplateChange}
|
|
currentTaskId={props.currentTaskId}
|
|
/>
|
|
)
|
|
default:
|
|
return null
|
|
}
|
|
}
|
|
|
|
export default GenerateStepContent
|