fix(1895): pages/generate/GeneratePage.tsx - fix TS errors (props, arg count, RechargeOutlined, stale GeneratePage, no-unused)
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 3s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m15s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m21s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 1m55s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 2m27s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 29h38m12s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 29h38m16s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 29h37m45s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 29h37m42s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 29h37m47s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 29h37m49s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 29h37m51s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 29h37m48s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 29h37m35s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 29h37m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 29h37m39s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 29h37m44s
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 3s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 1m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m15s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m21s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 1m55s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 2m27s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m51s
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 29h38m12s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 29h38m16s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 29h37m45s
CI/CD Pipeline / PR Build API Image (pull_request) Failing after 29h37m42s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 29h37m47s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 29h37m49s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 29h37m51s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 29h37m48s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 29h37m35s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 29h37m37s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 29h37m39s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / PR Build Worker Image (pull_request) Failing after 29h37m40s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 29h37m44s
This commit is contained in:
@@ -1,12 +1,8 @@
|
||||
/**
|
||||
* 智能剪辑页面(Issue #1677 多视频批量生成,修正版)
|
||||
* 固定 6 步向导:模板(弹数量) → 素材 → 配音 → 标题 → 确认生成 → 封面,单视频与批量完全一致
|
||||
*
|
||||
* 架构:
|
||||
* - 预览全部为纯前端 Canvas 实时播放(FrontendPreviewPlayer),不调任何后端渲染接口:
|
||||
* N=1 单播放器;N>1 CanvasPreviewGrid(variantSeed 让素材排布/起始点不同,画面有差异)
|
||||
* - 步骤5确认生成:正式生成接口(count + titles[]/voice_library_ids[]/cover_urls[]),
|
||||
* 批量时逐任务独立进度/失败重试(BatchGenerationGrid)
|
||||
* 智能剪辑页面(#1899 简化为 5 步)
|
||||
* 5 步向导:素材(1) → 配音(2) → 标题(3) → 确认生成(4) → 封面(5)
|
||||
* 模板选择已删除,模板在后台自动加载/选择(后端 API 兼容)。
|
||||
* 新增片段数量输入(默认3),系统按片段数智能匹配素材。
|
||||
*/
|
||||
import React, { useMemo, useState, useEffect, useRef, useCallback } from "react"
|
||||
import { message } from "antd"
|
||||
@@ -32,30 +28,27 @@ import { useVariantVoicePreview } from "./hooks/useVariantVoicePreview"
|
||||
import { useTitleStyleUpdaters } from "./hooks/useStep4Title/useTitleStyleUpdaters"
|
||||
import { getAssetsByKind } from "@/api/assets"
|
||||
import { previewTts } from "@/api/tts"
|
||||
// v2: 积分校验
|
||||
import { usePointsStore } from "@/store/pointsStore"
|
||||
import { hasEnoughPoints } from "./hooks/pointsCost"
|
||||
import { estimatePointsCost } from "./hooks/pointsCost"
|
||||
import "./generate.css"
|
||||
import "./generate-points.css"
|
||||
|
||||
const GeneratePage: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
/* ── v2: 积分余额 ── */
|
||||
const { balance: pointsBalance, init: initPoints } = usePointsStore()
|
||||
/* ── 表单状态 ── */
|
||||
const formState = useGenerateFormState()
|
||||
/* ── 积分状态 ── */
|
||||
const { balance, init: initPoints } = usePointsStore()
|
||||
useEffect(() => {
|
||||
initPoints()
|
||||
}, [initPoints])
|
||||
|
||||
/* ── 表单状态 ── */
|
||||
const formState = useGenerateFormState()
|
||||
const {
|
||||
currentStep,
|
||||
setCurrentStep,
|
||||
clipCount,
|
||||
setClipCount,
|
||||
selectedTemplate,
|
||||
setSelectedTemplate,
|
||||
userTemplates,
|
||||
handleInvalidTemplate,
|
||||
selectedMaterials,
|
||||
setSelectedMaterials,
|
||||
materialMode,
|
||||
@@ -102,9 +95,7 @@ const GeneratePage: React.FC = () => {
|
||||
|
||||
const isBatch = previewCount > 1
|
||||
|
||||
/* ── 配音选择同步:共用配音 ↔ 变体数组 ── */
|
||||
// 触发场景:①共用配音变化 ②批量模式进入/退出 ③独立→共用切换(需把所有变体刷成共用配音)
|
||||
// 独立模式下:仅同步变体[0](其选择器绑定共用配音),用户单独选择的其他变体不覆盖
|
||||
/* ── 配音选择同步 ── */
|
||||
const prevVoiceSyncRef = useRef({
|
||||
voice: selectedVoice,
|
||||
batch: isBatch,
|
||||
@@ -114,14 +105,16 @@ const GeneratePage: React.FC = () => {
|
||||
const prev = prevVoiceSyncRef.current
|
||||
const voiceChanged = prev.voice !== selectedVoice
|
||||
const modeChanged = prev.batch !== isBatch || prev.perVideo !== voiceModePerVideo
|
||||
prevVoiceSyncRef.current = { voice: selectedVoice, batch: isBatch, perVideo: voiceModePerVideo }
|
||||
prevVoiceSyncRef.current = {
|
||||
voice: selectedVoice,
|
||||
batch: isBatch,
|
||||
perVideo: voiceModePerVideo,
|
||||
}
|
||||
if (!voiceChanged && !modeChanged) return
|
||||
if (!isBatch) return
|
||||
if (!voiceModePerVideo) {
|
||||
// 共用模式(含刚从独立切回):所有变体跟随共用配音,未选择的补默认值
|
||||
setVoiceLibraryIds((prevIds) => (prevIds || []).map((id) => id || selectedVoice))
|
||||
} else if (voiceChanged) {
|
||||
// 独立模式下共用配音变化:仅同步变体[0](与共用选择器绑定),其余不覆盖
|
||||
setVoiceLibraryIds((prevIds) =>
|
||||
(prevIds || []).map((id, i) => (i === 0 ? selectedVoice : id)),
|
||||
)
|
||||
@@ -145,12 +138,9 @@ const GeneratePage: React.FC = () => {
|
||||
|
||||
const [previewVoiceAudioUrl, setPreviewVoiceAudioUrl] = useState<string | null>(null)
|
||||
const ttsAbortRef = useRef<AbortController | null>(null)
|
||||
// TTS 试听文案:批量跟随变体0标题(仅取首项,避免编辑其他变体标题触发多余 TTS 请求)
|
||||
const variant0Title = isBatch ? previewTitles?.[0] || "" : ""
|
||||
|
||||
useEffect(() => {
|
||||
// #1750:批量模式每变体独立/共用配音由 useVariantVoicePreview 接管(含独立模式每卡片挂各自 URL),
|
||||
// 本 effect 只服务单视频,批量模式直接跳过(避免共用 TTS 结果错误下发给所有变体)
|
||||
if (isBatch) return
|
||||
const voiceAsset = voiceMaterials.find((m) => m.id === selectedVoice)
|
||||
if (voiceAsset?.file_url) {
|
||||
@@ -158,7 +148,6 @@ const GeneratePage: React.FC = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 单视频 TTS 文案跟随主标题
|
||||
const ttsTitle = titleSettings.title
|
||||
const voiceId = selectedClonedVoice || selectedVoice
|
||||
if (!voiceId || !ttsTitle) {
|
||||
@@ -212,22 +201,16 @@ const GeneratePage: React.FC = () => {
|
||||
[materialMode, smartSelectedIds, selectedMaterials],
|
||||
)
|
||||
|
||||
/* ── 当前模板对象 ── */
|
||||
const currentTemplate = useMemo(
|
||||
() => userTemplates.find((t) => t.id === selectedTemplate) || null,
|
||||
[userTemplates, selectedTemplate],
|
||||
)
|
||||
|
||||
/* ── BGM 配置 ── */
|
||||
const bgmConfig = useMemo(
|
||||
() => ({
|
||||
enabled: bgm,
|
||||
music_id: currentTemplate?.bgm_config?.music_id || "",
|
||||
music_id: "",
|
||||
}),
|
||||
[bgm, currentTemplate],
|
||||
[bgm],
|
||||
)
|
||||
|
||||
/* ── 加载素材详情(供前端预览播放器使用) ── */
|
||||
/* ── 加载素材详情 ── */
|
||||
const previewAssetsEnabled = previewAssetIds.length > 0
|
||||
const {
|
||||
assets: previewAssets,
|
||||
@@ -235,13 +218,11 @@ const GeneratePage: React.FC = () => {
|
||||
ensureAssets,
|
||||
} = usePreviewAssets(previewAssetIds, previewAssetsEnabled)
|
||||
|
||||
/* ── 预览就绪:纯前端 Canvas 预览,素材详情加载完即可秒开(单视频/批量一致) ── */
|
||||
const previewReady = previewAssetsReady && !!currentTemplate
|
||||
/* ── 预览就绪 ── */
|
||||
// #1899: 不再依赖 currentTemplate,素材加载完即可预览
|
||||
const previewReady = previewAssetsReady
|
||||
|
||||
/* ── 批量变体真实片段(#1744):后端独立选片,预览即成片;失败静默降级本地模拟 ──
|
||||
仅批量(N>1)且在第 4 步预览时申请,避免选素材阶段频繁请求;
|
||||
变体 0 沿用草稿 plan(与单视频一致),变体 1..N-1 后端 reselect 独立选片 */
|
||||
// P0 fix:批量变体计划请求需携带配音参数,避免后端按"无配音"选片导致 clips 时长与配音错位
|
||||
/* ── 批量变体真实片段(#1744) ── */
|
||||
const batchVoiceLibraryId =
|
||||
voiceMode === "clone" ? selectedClonedVoice || selectedVoice || "" : selectedVoice || ""
|
||||
const {
|
||||
@@ -252,7 +233,7 @@ const GeneratePage: React.FC = () => {
|
||||
error: variantClipsError,
|
||||
retry: retryVariantClips,
|
||||
} = useBatchVariantPlans({
|
||||
enabled: isBatch && currentStep === 4 && previewAssetsReady,
|
||||
enabled: isBatch && currentStep === 3 && previewAssetsReady,
|
||||
count: previewCount,
|
||||
templateId: selectedTemplate || "",
|
||||
assetIds: previewAssetIds,
|
||||
@@ -262,8 +243,7 @@ const GeneratePage: React.FC = () => {
|
||||
voiceModePerVideo,
|
||||
})
|
||||
|
||||
/* ── 批量变体配音预览 URL(#1750):独立模式每变体挂各自配音,共用模式全挂同一条;
|
||||
hook 内部自取配音素材库并做 TTS 缓存/串行/防竞态 */
|
||||
/* ── 批量变体配音预览 URL ── */
|
||||
const variantVoiceAudioUrls = useVariantVoicePreview({
|
||||
enabled: isBatch,
|
||||
count: previewCount,
|
||||
@@ -274,8 +254,7 @@ const GeneratePage: React.FC = () => {
|
||||
titles: previewTitles || [],
|
||||
})
|
||||
|
||||
/* ── 变体计划 clips 引用的素材可能不在已选列表中(#1750):差集补拉素材详情,
|
||||
保证播放器拿得到 file_url,而不是静默丢片段/用假数据冒充 */
|
||||
/* ── 变体 clips 引用素材补拉 ── */
|
||||
const clipAssetIds = useMemo(() => {
|
||||
if (!isBatch || !variantClips?.length) return []
|
||||
const ids = new Set<string>()
|
||||
@@ -327,7 +306,6 @@ const GeneratePage: React.FC = () => {
|
||||
autoSubtitles,
|
||||
bgm,
|
||||
sourceEditPlanId: storedSourceEditPlanId || sourceEditPlanId,
|
||||
// #1744:批量预览阶段后端为每个变体生成的独立 plan id,正式生成回传 → 预览即成片
|
||||
variantPlanIds,
|
||||
previewTaskId,
|
||||
bgmConfig,
|
||||
@@ -343,12 +321,11 @@ const GeneratePage: React.FC = () => {
|
||||
},
|
||||
})
|
||||
|
||||
/* ── 数量弹窗确认:设置数量 + 同步批量数组长度 + 进入步骤2 ── */
|
||||
/* ── 数量弹窗确认 ── */
|
||||
const handleCountConfirm = useCallback(
|
||||
(count: number) => {
|
||||
setPreviewCount(count)
|
||||
setCountModalOpen(false)
|
||||
// 同步批量数组长度
|
||||
setPreviewTitles((prev) => {
|
||||
const list = prev || []
|
||||
const base = list[0] || titleSettings.title || ""
|
||||
@@ -377,18 +354,22 @@ const GeneratePage: React.FC = () => {
|
||||
],
|
||||
)
|
||||
|
||||
/* ── 步骤4「确认生成视频」:校验通过 → 创建正式生成任务 → 跳步骤5看实时进展 ── */
|
||||
/* ── 步骤3「确认生成视频」:校验通过 → 创建正式生成任务 → 跳步骤4看实时进展 ── */
|
||||
const handleConfirmGenerate = useCallback(async () => {
|
||||
// 标题校验:批量只校验已勾选的变体;单视频校验主标题
|
||||
const videoCount = isBatch ? selectedVariantIds.length : 1
|
||||
|
||||
// v2: 积分校验
|
||||
const pointsCheck = hasEnoughPoints(pointsBalance, videoCount)
|
||||
if (!pointsCheck.sufficient) {
|
||||
message.warning(pointsCheck.reason ?? "积分不足,请充值后再试")
|
||||
// 积分预检查
|
||||
const units = isBatch ? Math.max(selectedVariantIds.length, 1) : 1
|
||||
const est = estimatePointsCost({
|
||||
scene: "generate_video",
|
||||
units,
|
||||
isMember: !!balance?.is_member,
|
||||
freeClipsUsedToday: balance?.free_clips_used_today ?? 0,
|
||||
dailyFreeQuota: balance?.daily_free_quota ?? 2,
|
||||
})
|
||||
const currentBalance = balance?.balance ?? 0
|
||||
if (est.pointsCost > 0 && currentBalance < est.pointsCost) {
|
||||
message.error(`积分不足,还需 ${est.pointsCost - currentBalance} 积分,请充值`)
|
||||
return
|
||||
}
|
||||
|
||||
if (isBatch) {
|
||||
if (selectedVariantIds.length === 0) {
|
||||
message.warning("请至少勾选一个视频")
|
||||
@@ -400,8 +381,6 @@ const GeneratePage: React.FC = () => {
|
||||
return
|
||||
}
|
||||
} else if (!titleSettings.title?.trim()) {
|
||||
// 与 buildPayload.validateGenerateInputs 一致:AI 自动选标题模式(aiAutoSelect)
|
||||
// 允许空标题由后端生成;手动模式必须填写,避免提交空标题
|
||||
if (!titleSettings.aiAutoSelect) {
|
||||
message.warning("请先选择或输入标题")
|
||||
return
|
||||
@@ -413,8 +392,7 @@ const GeneratePage: React.FC = () => {
|
||||
}
|
||||
const ok = await handleGenerate()
|
||||
if (ok) {
|
||||
// 单视频与批量一致:任务创建成功后进入步骤5「确认生成」看实时渲染进展
|
||||
setCurrentStep(5)
|
||||
setCurrentStep(4)
|
||||
}
|
||||
}, [
|
||||
isBatch,
|
||||
@@ -425,26 +403,13 @@ const GeneratePage: React.FC = () => {
|
||||
previewReady,
|
||||
handleGenerate,
|
||||
setCurrentStep,
|
||||
pointsBalance,
|
||||
balance,
|
||||
])
|
||||
|
||||
/* v2: 积分消耗估算(步骤4按钮展示用) */
|
||||
const pointsInfo = useMemo(() => {
|
||||
const videoCount = isBatch ? selectedVariantIds.length : 1
|
||||
const check = hasEnoughPoints(pointsBalance, videoCount)
|
||||
return {
|
||||
cost: check.cost,
|
||||
insufficient: !check.sufficient,
|
||||
reason: check.reason,
|
||||
freeUsed: check.freeClipsUsed,
|
||||
}
|
||||
}, [isBatch, selectedVariantIds.length, pointsBalance])
|
||||
|
||||
/* ── 步骤导航 ── */
|
||||
const { goNext, goPrev } = useStepNavigation({
|
||||
currentStep,
|
||||
setCurrentStep,
|
||||
selectedTemplate,
|
||||
materialMode,
|
||||
selectedMaterials,
|
||||
smartSelectedIds,
|
||||
@@ -453,15 +418,31 @@ const GeneratePage: React.FC = () => {
|
||||
onOpenCountModal: () => setCountModalOpen(true),
|
||||
})
|
||||
|
||||
/* ── 最终成片(单视频右侧播放) ── */
|
||||
/* ── 最终成片 ── */
|
||||
const finalVideo = generatedVideos[0]
|
||||
|
||||
/* ── 布局 class:步骤4标题页=预览+标题侧栏两栏;其余步骤(含步骤5确认生成、步骤6封面)=整行宽 ── */
|
||||
/* ── 布局 class ── */
|
||||
const layoutClassName = useMemo(() => {
|
||||
if (currentStep === 4) return "xx-generate-layout step4-layout"
|
||||
if (currentStep === 3) return "xx-generate-layout step4-layout"
|
||||
return "xx-generate-layout full-width"
|
||||
}, [currentStep])
|
||||
|
||||
/* ── 积分消耗估算(步骤3确认生成展示用) ── */
|
||||
const unitsForCost = isBatch ? Math.max(selectedVariantIds.length, 1) : 1
|
||||
const pointsEstimate = useMemo(
|
||||
() =>
|
||||
estimatePointsCost({
|
||||
scene: "generate_video",
|
||||
units: unitsForCost,
|
||||
isMember: !!balance?.is_member,
|
||||
freeClipsUsedToday: balance?.free_clips_used_today ?? 0,
|
||||
dailyFreeQuota: balance?.daily_free_quota ?? 2,
|
||||
}),
|
||||
[unitsForCost, balance],
|
||||
)
|
||||
const insufficientPoints =
|
||||
pointsEstimate.pointsCost > 0 && (balance?.balance ?? 0) < pointsEstimate.pointsCost
|
||||
|
||||
/* ================================================================
|
||||
渲染
|
||||
================================================================ */
|
||||
@@ -473,11 +454,10 @@ const GeneratePage: React.FC = () => {
|
||||
<GenerateStepsBar currentStep={currentStep} onStepClick={setCurrentStep} />
|
||||
|
||||
<div className={layoutClassName}>
|
||||
{/* ════ 步骤4:左侧预览大区域(纯前端 Canvas 实时预览) ════ */}
|
||||
{currentStep === 4 && !!currentTemplate && (
|
||||
{/* ════ 步骤3:左侧预览大区域 ════ */}
|
||||
{currentStep === 3 && (
|
||||
<div className="xx-generate-preview-col">
|
||||
{!isBatch ? (
|
||||
/* 单视频:前端 Canvas 实时预览(与旧版一致,零回归) */
|
||||
<FrontendPreviewPlayer
|
||||
assets={previewAssets}
|
||||
videoRatio={videoRatio}
|
||||
@@ -500,11 +480,15 @@ const GeneratePage: React.FC = () => {
|
||||
onTitlePositionChange={styleUpdaters.updateTitlePosition}
|
||||
/>
|
||||
) : (
|
||||
/* 批量:N 个前端 Canvas 预览网格(不调任何后端渲染接口,秒开) */
|
||||
<div className="xx-form-section">
|
||||
<div className="xx-preview-header">
|
||||
<h3>🎬 {previewCount} 个视频预览</h3>
|
||||
<span style={{ fontSize: 13, color: "var(--text-secondary, #666)" }}>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: "var(--text-secondary, #666)",
|
||||
}}
|
||||
>
|
||||
实时预览,勾选要生成的视频
|
||||
</span>
|
||||
</div>
|
||||
@@ -529,19 +513,19 @@ const GeneratePage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ════ 右侧:步骤1~3 表单 / 步骤4 标题边栏 / 步骤5 确认生成进度 / 步骤6 封面 ════ */}
|
||||
{/* ════ 右侧:步骤1~2 表单 / 步骤3 标题边栏 / 步骤4 确认生成进度 / 步骤5 封面 ════ */}
|
||||
<div className="xx-generate-form">
|
||||
<GenerateStepContent
|
||||
currentStep={currentStep}
|
||||
userTemplates={userTemplates}
|
||||
selectedTemplate={selectedTemplate}
|
||||
onSelectTemplate={setSelectedTemplate}
|
||||
clipCount={clipCount}
|
||||
onClipCountChange={setClipCount}
|
||||
materialMode={materialMode}
|
||||
onMaterialModeChange={setMaterialMode}
|
||||
selectedMaterials={selectedMaterials}
|
||||
onSelectedMaterialsChange={setSelectedMaterials}
|
||||
smartSelectedIds={smartSelectedIds}
|
||||
onSmartSelectedIdsChange={setSmartSelectedIds}
|
||||
selectedTemplate={selectedTemplate}
|
||||
titleSettings={titleSettings}
|
||||
onTitleSettingsChange={setTitleSettings}
|
||||
onUpdatePosition={styleUpdaters.updatePosition}
|
||||
@@ -561,7 +545,6 @@ const GeneratePage: React.FC = () => {
|
||||
selectedVoice={selectedVoice}
|
||||
onSelectedVoiceChange={setSelectedVoice}
|
||||
onServerClipsChange={setServerClips}
|
||||
onTemplateInvalid={handleInvalidTemplate}
|
||||
generating={generating}
|
||||
generated={generated}
|
||||
generateError={generateError}
|
||||
@@ -583,8 +566,8 @@ const GeneratePage: React.FC = () => {
|
||||
selectedVariantIds={selectedVariantIds}
|
||||
/>
|
||||
|
||||
{/* ════ 步骤5(单视频):成片播放器置于按钮上方、居中展示 ════ */}
|
||||
{currentStep === 5 && !isBatch && generated && finalVideo && (
|
||||
{/* ════ 步骤4(单视频):成片播放器 ════ */}
|
||||
{currentStep === 4 && !isBatch && generated && finalVideo && (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -629,7 +612,14 @@ const GeneratePage: React.FC = () => {
|
||||
}}
|
||||
poster={finalVideo.thumbnail_url || undefined}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: 8, marginTop: 12, justifyContent: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
marginTop: 12,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<button className="xx-btn xx-btn-ghost xx-btn-sm" onClick={handleDownload}>
|
||||
⬇️ 下载
|
||||
</button>
|
||||
@@ -656,10 +646,9 @@ const GeneratePage: React.FC = () => {
|
||||
generated={generated}
|
||||
generateError={generateError}
|
||||
selectedCount={isBatch ? selectedVariantIds.length : 1}
|
||||
estimatedCost={pointsInfo.cost}
|
||||
pointsInsufficient={pointsInfo.insufficient}
|
||||
insufficientReason={pointsInfo.reason}
|
||||
freeClipsUsedThisTime={pointsInfo.freeUsed}
|
||||
estimatedCost={pointsEstimate.pointsCost}
|
||||
freeClipsUsedThisTime={pointsEstimate.usingFreeClips}
|
||||
pointsInsufficient={insufficientPoints}
|
||||
onRecharge={() => navigate("/app/points/recharge")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user