2b120f5c65
CI/CD Pipeline / Validate - Code Quality (push) Failing after 0s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Failing after 0s
CI/CD Pipeline / Validate - Migration (alembic) (push) Failing after 0s
CI/CD Pipeline / Frontend Lint (push) Failing after 0s
CI/CD Pipeline / Integration Tests (push) Failing after 1s
CI/CD Pipeline / Frontend Unit Tests (push) Failing after 0s
CI/CD Pipeline / Unit Tests (push) Failing after 1s
CI/CD Pipeline / PR Build API Image (push) Has been cancelled
CI/CD Pipeline / PR Build Web Image (push) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (push) Has been cancelled
CI/CD Pipeline / Build Staging API Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Web Image (push) Has been cancelled
CI/CD Pipeline / Build Staging Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
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 / ACR Image Cleanup (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 / Check if frontend-only change (push) Failing after 1218h38m8s
239 lines
7.3 KiB
TypeScript
239 lines
7.3 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 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 "./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("音色克隆成功!")
|
|
}
|
|
|
|
/* ── 步骤导航 ── */
|
|
const { goNext, goPrev } = useStepNavigation({
|
|
currentStep,
|
|
setCurrentStep,
|
|
selectedTemplate,
|
|
materialMode,
|
|
selectedMaterials,
|
|
smartSelectedIds,
|
|
titleSettings,
|
|
})
|
|
|
|
/* ── 视频生成核心逻辑 ── */
|
|
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}
|
|
/>
|
|
|
|
<GenerateStepActions
|
|
currentStep={currentStep}
|
|
onPrev={goPrev}
|
|
onNext={goNext}
|
|
onGenerate={handleGenerate}
|
|
generating={generating}
|
|
generated={generated}
|
|
generateError={generateError}
|
|
/>
|
|
</div>
|
|
|
|
{/* ════ 右侧:生成结果 ════ */}
|
|
<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>
|
|
|
|
{/* ── 视频预览弹窗 ── */}
|
|
<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
|