3687da034d
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 / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 41s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m2s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m2s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m35s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m0s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m49s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m59s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m20s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 1m43s
AI Code Review / AI Code Review (pull_request) Successful in 2m28s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 3m5s
CI/CD Pipeline / Integration Tests (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 / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 11s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 41s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 1m13s
278 lines
8.8 KiB
TypeScript
278 lines
8.8 KiB
TypeScript
/**
|
|
* 智能剪辑页面 — V21 原型 1:1 还原
|
|
* 7 步向导:选择模板 → 选择素材 → 生成预览 → 选择标题 → 选择配音 → 选择封面 → 确认生成
|
|
* 左右布局:左侧 generate-form + 右侧 generate-preview
|
|
* 主组件仅保留整体布局与事件编排
|
|
* 状态管理 → hooks/useGenerateFormState
|
|
* 步骤导航 → hooks/useStepNavigation
|
|
* 步骤内容 → components/GenerateStepContent
|
|
* 底部按钮 → components/GenerateStepActions
|
|
* 生成核心逻辑 → hooks/useGenerateVideo
|
|
*/
|
|
import React from "react"
|
|
import { Modal, message } from "antd"
|
|
import { useNavigate } from "react-router-dom"
|
|
import type { VoiceClone } from "@/api/voice-clone"
|
|
import { useCloneProgress } from "@/hooks/useCloneProgress"
|
|
import CloneModal from "@/components/voice/CloneModal"
|
|
import GenerateHeader from "./components/GenerateHeader"
|
|
import GenerateStepsBar from "./components/GenerateStepsBar"
|
|
import GenerateResultPanel from "./components/GenerateResultPanel"
|
|
import PreviewVideoPanel from "./components/PreviewVideoPanel"
|
|
import GenerateStepContent from "./components/GenerateStepContent"
|
|
import GenerateStepActions from "./components/GenerateStepActions"
|
|
import { useGenerateFormState } from "./hooks/useGenerateFormState"
|
|
import { useStepNavigation } from "./hooks/useStepNavigation"
|
|
import { useGenerateVideo } from "./hooks/useGenerateVideo"
|
|
import { useStep3Preview } from "./hooks/useStep3Preview"
|
|
import "./generate.css"
|
|
|
|
const GeneratePage: React.FC = () => {
|
|
const navigate = useNavigate()
|
|
|
|
/* ── 表单状态 ── */
|
|
const formState = useGenerateFormState()
|
|
const {
|
|
currentStep,
|
|
setCurrentStep,
|
|
selectedTemplate,
|
|
setSelectedTemplate,
|
|
userTemplates,
|
|
selectedMaterials,
|
|
setSelectedMaterials,
|
|
materialMode,
|
|
setMaterialMode,
|
|
smartSelectedIds,
|
|
setSmartSelectedIds,
|
|
titleSettings,
|
|
setTitleSettings,
|
|
coverSettings,
|
|
setCoverSettings,
|
|
selectedVoice,
|
|
setSelectedVoice,
|
|
voiceMode,
|
|
setVoiceMode,
|
|
selectedClonedVoice,
|
|
setSelectedClonedVoice,
|
|
presetVoices,
|
|
cloneModalOpen,
|
|
setCloneModalOpen,
|
|
generateCount,
|
|
setGenerateCount,
|
|
videoRatio,
|
|
duration,
|
|
style,
|
|
autoSubtitles,
|
|
bgm,
|
|
editPlanId,
|
|
previewVideo,
|
|
setPreviewVideo,
|
|
previewModalOpen,
|
|
setPreviewModalOpen,
|
|
} = formState
|
|
|
|
/* ── 克隆声音 ── */
|
|
const { clones: clonedVoices, addClone, hasProcessing } = useCloneProgress()
|
|
|
|
const handleCloneSuccess = (voice: VoiceClone) => {
|
|
addClone(voice)
|
|
setCloneModalOpen(false)
|
|
message.success("音色克隆成功!")
|
|
}
|
|
|
|
/* ── Step3 预览生成 ── */
|
|
const step3Preview = useStep3Preview({
|
|
templates: userTemplates,
|
|
selectedTemplate,
|
|
materialMode,
|
|
selectedMaterials,
|
|
smartSelectedIds,
|
|
duration,
|
|
videoRatio,
|
|
})
|
|
|
|
/* ── 步骤导航 ── */
|
|
const { goNext, goPrev } = useStepNavigation({
|
|
currentStep,
|
|
setCurrentStep,
|
|
selectedTemplate,
|
|
materialMode,
|
|
selectedMaterials,
|
|
smartSelectedIds,
|
|
titleSettings,
|
|
previewReady: step3Preview.canProceed,
|
|
})
|
|
|
|
/* ── 视频生成核心逻辑 ── */
|
|
const {
|
|
generating,
|
|
progress,
|
|
generated,
|
|
generateError,
|
|
generatedVideos,
|
|
generate: handleGenerate,
|
|
retry: handleRetryGenerate,
|
|
dismissError: handleDismissError,
|
|
download: handleDownload,
|
|
share: handleShare,
|
|
} = useGenerateVideo({
|
|
titleSettings,
|
|
selectedTemplate,
|
|
selectedMaterials,
|
|
materialMode,
|
|
smartSelectedIds,
|
|
voiceMode,
|
|
selectedVoice,
|
|
selectedClonedVoice,
|
|
coverSettings,
|
|
videoRatio,
|
|
style,
|
|
duration,
|
|
autoSubtitles,
|
|
bgm,
|
|
generateCount,
|
|
})
|
|
|
|
/* ================================================================
|
|
渲染 — 主页面
|
|
================================================================ */
|
|
|
|
return (
|
|
<div className="xx-generate-page">
|
|
{/* ── 页头 ── */}
|
|
<GenerateHeader fromEditPlan={!!editPlanId} />
|
|
|
|
{/* ── 步骤条 ── */}
|
|
<GenerateStepsBar currentStep={currentStep} onStepClick={setCurrentStep} />
|
|
|
|
{/* ── 主布局 ── */}
|
|
<div className="xx-generate-layout">
|
|
{/* ════ 左侧:表单区 ════ */}
|
|
<div className="xx-generate-form">
|
|
<GenerateStepContent
|
|
currentStep={currentStep}
|
|
userTemplates={userTemplates}
|
|
selectedTemplate={selectedTemplate}
|
|
onSelectTemplate={setSelectedTemplate}
|
|
materialMode={materialMode}
|
|
onMaterialModeChange={setMaterialMode}
|
|
selectedMaterials={selectedMaterials}
|
|
onSelectedMaterialsChange={setSelectedMaterials}
|
|
smartSelectedIds={smartSelectedIds}
|
|
onSmartSelectedIdsChange={setSmartSelectedIds}
|
|
titleSettings={titleSettings}
|
|
onTitleSettingsChange={setTitleSettings}
|
|
coverSettings={coverSettings}
|
|
onCoverSettingsChange={setCoverSettings}
|
|
duration={duration}
|
|
selectedVoice={selectedVoice}
|
|
onSelectedVoiceChange={setSelectedVoice}
|
|
voiceMode={voiceMode}
|
|
onVoiceModeChange={setVoiceMode}
|
|
selectedClonedVoice={selectedClonedVoice}
|
|
onSelectedClonedVoiceChange={setSelectedClonedVoice}
|
|
clonedVoices={clonedVoices}
|
|
addClone={addClone}
|
|
hasProcessing={hasProcessing}
|
|
cloneModalOpen={cloneModalOpen}
|
|
onCloneModalOpenChange={setCloneModalOpen}
|
|
generateCount={generateCount}
|
|
onGenerateCountChange={setGenerateCount}
|
|
generating={generating}
|
|
generated={generated}
|
|
generateError={generateError}
|
|
progress={progress}
|
|
generatedVideos={generatedVideos}
|
|
onRetry={handleRetryGenerate}
|
|
onDismissError={handleDismissError}
|
|
presetVoices={presetVoices}
|
|
videoRatio={videoRatio}
|
|
previewStatus={step3Preview.previewStatus}
|
|
previewResult={step3Preview.previewResult}
|
|
previewError={step3Preview.previewError}
|
|
previewTemplateName={step3Preview.templateName}
|
|
previewMaterialCount={step3Preview.materialCount}
|
|
previewProgress={step3Preview.progress}
|
|
onGeneratePreview={step3Preview.generatePreview}
|
|
onRegeneratePreview={step3Preview.regeneratePreview}
|
|
/>
|
|
|
|
<GenerateStepActions
|
|
currentStep={currentStep}
|
|
onPrev={goPrev}
|
|
onNext={goNext}
|
|
onGenerate={handleGenerate}
|
|
generating={generating}
|
|
generated={generated}
|
|
generateError={generateError}
|
|
/>
|
|
</div>
|
|
|
|
{/* ════ 右侧:预览 + 生成结果 ════ */}
|
|
<div className="xx-generate-right-col">
|
|
{/* 预览视频面板(Step3+ 常驻) */}
|
|
{currentStep >= 3 && (
|
|
<PreviewVideoPanel
|
|
previewStatus={step3Preview.previewStatus}
|
|
previewResult={step3Preview.previewResult}
|
|
previewError={step3Preview.previewError}
|
|
progress={step3Preview.progress}
|
|
videoRatio={videoRatio}
|
|
onRegenerate={step3Preview.regeneratePreview}
|
|
titleText={titleSettings.title}
|
|
titleSettings={titleSettings}
|
|
/>
|
|
)}
|
|
|
|
{/* 正式生成结果 */}
|
|
<GenerateResultPanel
|
|
generated={generated}
|
|
generating={generating}
|
|
progress={progress}
|
|
generateError={generateError}
|
|
generatedVideos={generatedVideos}
|
|
onVideoPreview={(video) => {
|
|
setPreviewVideo(video)
|
|
setPreviewModalOpen(true)
|
|
}}
|
|
onDownload={handleDownload}
|
|
onShare={handleShare}
|
|
onGoToLibrary={() => navigate("/app/products")}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── 视频预览弹窗 ── */}
|
|
<Modal
|
|
open={previewModalOpen}
|
|
onCancel={() => setPreviewModalOpen(false)}
|
|
footer={null}
|
|
width="80vw"
|
|
centered
|
|
destroyOnClose
|
|
>
|
|
{previewVideo && (
|
|
<div className="xx-preview-modal-content">
|
|
<video
|
|
src={previewVideo.download_url || previewVideo.file_url}
|
|
controls
|
|
autoPlay
|
|
style={{ width: "100%", maxHeight: "70vh", objectFit: "contain" }}
|
|
poster={previewVideo.thumbnail_url || undefined}
|
|
/>
|
|
</div>
|
|
)}
|
|
</Modal>
|
|
|
|
{/* ── 音色克隆弹窗 ── */}
|
|
<CloneModal
|
|
open={cloneModalOpen}
|
|
onClose={() => setCloneModalOpen(false)}
|
|
onSuccess={handleCloneSuccess}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default GeneratePage
|