Compare commits

...

2 Commits

Author SHA1 Message Date
xiaoxia 4cf2981a25 chore: retrigger CI
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1s
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 / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration 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 / 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 / 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 / PR Build Web Image (pull_request) Successful in 1m0s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m23s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m28s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m43s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 2m25s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 4m20s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m54s
AI Code Review / AI Code Review (pull_request) Successful in 7m0s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m38s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 12m43s
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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 1s
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) Successful in 43s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 55s
2026-09-25 23:19:02 +08:00
xiaoxia a2ea6cc51f fix(cover): 批量生成场景补封面模板选择;批量生成应用所选模板
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
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 / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 11s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (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 / PR Build Web Image (pull_request) Successful in 57s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m37s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m51s
AI Code Review / AI Code Review (pull_request) Successful in 6m36s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 10m40s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Failing after 14m40s
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m6s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 3m46s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 4m1s
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
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
问题:批量生成(count>1)时,Step6CoverSettings 走 isBatch 分支直接 return 了卡片列表,
没有渲染封面模板选择入口;useBatchCovers.generateOne 硬编码 generateCover('default', ...),
即使用户想选自定义模板也不生效。

修改:
1. Step6CoverSettings 批量分支:在『一键全部自动生成』按钮旁增加『⚙️ 封面模板』和『➕ 新建模板』按钮,
   并在批量分支底部渲染 CoverSettingsModal/CoverEditorModal,与单视频分支一致
2. 批量分支的提示条显示当前选中的模板名称,与单视频一致
3. 批量分支补 Modal loading 覆盖层(应用模板生成时的提示)
4. useBatchCovers:去掉 selectedTemplate 参数名的下划线前缀(之前故意不使用),
   generateOne 里 generateCover 第一个参数改用 selectedTemplate(默认 'default'),
   用户选了非默认模板时一键生成/单个生成都会应用该模板
5. generateOne 的依赖数组补 selectedTemplate
6. Step6CoverSettings 增加 onTemplateChange 回调 prop(用于父组件如需监听模板变化),
   同时 shared 的 canGenerate 在批量场景下设为 false,避免误触单视频生成逻辑
7. 批量上传 change 事件从直接拼接 blob 改为调用 batchCovers.uploadOne,走素材库上传流程
   (之前直接把 blob URL 塞 previewCovers,刷新后丢失且不入库)
2026-09-25 22:52:36 +08:00
2 changed files with 85 additions and 25 deletions
@@ -1,11 +1,11 @@
/**
* Step 5/6 选择封面(Issue #1677 批量生成改造 + #2033 封面bug修复)
* Step 5/6 选择封面(Issue #1677 批量生成改造 + #2033 封面bug修复 + #2044 批量模板选择)
* - 单视频:保留原封面流程(自动生成/封面设置模板/封面预览/自定义上传)
* - N 个视频:N 张封面卡片,每张带对应视频标题,可逐个自动生成或上传
* - N 个视频:N 张封面卡片,每张带对应视频标题,支持统一选择封面模板、逐个自动生成或上传
*
* 模板 CRUD + 编辑器弹窗 + 自动生成 + 上传 复用 components/cover/useSharedCover
*/
import React, { useEffect } from "react"
import React, { useEffect, useMemo } from "react"
import { Modal, Spin } from "antd"
import { LoadingOutlined } from "@ant-design/icons"
import type { CoverConfig } from "../types/cover"
@@ -29,6 +29,7 @@ interface Step6CoverSettingsProps {
previewCovers?: string[]
onPreviewCoversChange?: (urls: string[]) => void
selectedVariantIndexes?: number[]
onTemplateChange?: (templateId: string) => void
}
const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
@@ -46,12 +47,24 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
props.generatedVideos.find((v) => v.status === "completed" || v.status === "awaiting_cover") ||
props.generatedVideos[0]
const completedVideos = useMemo(
() =>
props.generatedVideos.filter(
(v) => v.status === "completed" || v.status === "awaiting_cover",
),
[props.generatedVideos],
)
/**
* 单视频自动生成(点击"自动生成封面"按钮):使用当前选中的模板
* 批量场景 canGenerate=false,避免 shared.generateAutoCover 被误触发
*/
const shared = useSharedCover({
canGenerate: !!finalVideo,
disabledHint: "请先生成视频再选择封面",
initialTemplateId: "default",
canGenerate: !!finalVideo && !isBatch,
disabledHint: isBatch ? "批量场景请在上方操作卡片" : "请先生成视频再选择封面",
initialTemplateId: props.selectedTemplate || "default",
generateFn: async (tplId) => {
if (!finalVideo) return null
if (!finalVideo || isBatch) return null
const response = await apiGenerateCover(tplId, {
generated_video_id: finalVideo.id,
video_url: finalVideo.file_url || finalVideo.download_url || "",
@@ -83,6 +96,14 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
},
})
// 选中模板变化时通知父组件(用于批量生成时透传 template_id)
const { onTemplateChange, selectedTemplate: parentSelectedTemplate } = props
useEffect(() => {
if (isBatch && onTemplateChange && shared.selectedTemplateId !== parentSelectedTemplate) {
onTemplateChange(shared.selectedTemplateId)
}
}, [isBatch, shared.selectedTemplateId, parentSelectedTemplate, onTemplateChange])
useEffect(() => {
shared.setOnUploadFile(() => null)
}, [shared])
@@ -99,21 +120,18 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
const cardPos = batchUploadCard
setBatchUploadCard(null)
if (!file || cardPos == null) return
const url = URL.createObjectURL(file)
const prevCardView = cardIndexes.map((vi) => (props.previewCovers || [])[vi] || "")
const nextCardView = [...prevCardView]
nextCardView[cardPos] = url
const next = [...(props.previewCovers || [])]
cardIndexes.forEach((vi, i) => {
next[vi] = nextCardView[i] || ""
})
props.onPreviewCoversChange?.(next)
void batchCovers.uploadOne(cardPos, file)
}
const batchTitles = cardIndexes.map((vi) => previewTitles[vi] || "")
const batchCoversList = cardIndexes.map((vi) => previewCovers[vi] || "")
/**
* 批量生成:selectedTemplateId 来自用户在 CoverSettingsModal 中选择的模板,
* 透传给 useBatchCovers,由其在 generateOne/generateAll 中发给后端。
*/
const batchCovers = useBatchCovers({
selectedTemplate: props.selectedTemplate || "",
selectedTemplate: shared.selectedTemplateId || "default",
generatedVideos: props.generatedVideos,
titles: batchTitles,
titleStyle: {
@@ -139,10 +157,6 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
const previewUrl = props.coverSettings.thumbnail_url || props.coverSettings.upload_url
const completedVideos = props.generatedVideos.filter(
(v) => v.status === "completed" || v.status === "awaiting_cover",
)
/* ── 批量封面 ── */
if (isBatch) {
return (
@@ -161,17 +175,32 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
}}
>
🎬 共 {completedVideos.length} 个成片,封面将从对应成片中智能选帧并叠加该视频的标题
{shared.selectedTemplateId && shared.selectedTemplateId !== "default" && (
<>
{" · "}当前模板:<strong>{shared.selectedTemplateName}</strong>
</>
)}
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 16 }}>
<div style={{ display: "flex", gap: 8, marginBottom: 16, flexWrap: "wrap" }}>
<Button
buttonType="primary"
onClick={() => void batchCovers.generateAll()}
disabled={completedVideos.length === 0 || batchCovers.busyIndexes.length > 0}
style={{ whiteSpace: "nowrap", flexShrink: 0 }}
loading={batchCovers.busyIndexes.length > 0}
>
✨ 一键全部自动生成
</Button>
<Button buttonType="ghost" onClick={() => shared.setShowCoverSettings(true)}>
⚙️ 封面模板
{shared.selectedTemplateId && shared.selectedTemplateId !== "default"
? `:${shared.selectedTemplateName}`
: ""}
</Button>
<Button buttonType="ghost" onClick={shared.handleCreateTemplate}>
➕ 新建模板
</Button>
</div>
<div className="xx-cover-grid">
@@ -242,6 +271,35 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
)
})}
</div>
{/* 隐藏的文件选择 input,批量上传复用 */}
<input
ref={batchUploadRef}
type="file"
accept="image/*"
style={{ display: "none" }}
onChange={handleBatchUploadChange}
/>
<CoverSettingsModal
open={shared.showCoverSettings}
onClose={() => shared.setShowCoverSettings(false)}
templates={shared.templates}
loading={shared.templatesLoading}
error={shared.templatesError}
selectedTemplateId={shared.selectedTemplateId}
onSelectTemplate={shared.handleSelectTemplate}
onEditTemplate={shared.handleEditTemplate}
onDeleteTemplate={shared.handleDeleteTemplate}
onCreateNew={shared.handleCreateTemplate}
/>
<CoverEditorModal
open={shared.showCoverEditor}
onClose={() => shared.setShowCoverEditor(false)}
template={shared.editingTemplate}
onSave={shared.handleSaveTemplate}
/>
</div>
)
}
@@ -360,4 +418,6 @@ const Step6CoverSettings: React.FC<Step6CoverSettingsProps> = (props) => {
)
}
Step6CoverSettings.displayName = "Step6CoverSettings"
export default Step6CoverSettings
@@ -53,7 +53,7 @@ interface UseBatchCoversOptions {
}
export function useBatchCovers({
selectedTemplate: _selectedTemplate,
selectedTemplate,
generatedVideos,
titles,
titleStyle,
@@ -107,7 +107,7 @@ export function useBatchCovers({
addBusy(index)
try {
const titleText = titles[index] || ""
const response = await generateCover("default", {
const response = await generateCover(selectedTemplate || "default", {
generated_video_id: target.id,
video_url: target.file_url || target.download_url || "",
cover_type: "ai_frame",
@@ -171,7 +171,7 @@ export function useBatchCovers({
removeBusy(index)
}
},
[generatedVideos, titles, titleStyle, patchCover, addBusy, removeBusy],
[generatedVideos, titles, titleStyle, selectedTemplate, patchCover, addBusy, removeBusy],
)
/** 为第 index 个视频上传自定义封面 */