34ffe14aae
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 2s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 49s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 49s
CI/CD Pipeline / Build Staging API Image (push) Successful in 57s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 51s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m37s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 3m41s
CI/CD Pipeline / Integration Tests (push) Successful in 3m52s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m2s
CI/CD Pipeline / Validate - Style (push) Successful in 4m17s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m27s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m32s
CI/CD Pipeline / Validate - Security (push) Successful in 8m22s
CI/CD Pipeline / Unit Tests (push) Successful in 10m32s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / CI Gate (push) Has been skipped
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Canary Release to Production (push) Has been skipped
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 57s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m30s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m11s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 13s
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 / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 23s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 51s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 17s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m7s
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 / 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 / Validate - Style (pull_request) Successful in 7m14s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 6m24s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 7m30s
CI/CD Pipeline / Unit Tests (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
CI/CD Pipeline / Validate - Security (pull_request) Has been cancelled
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
265 lines
8.3 KiB
TypeScript
265 lines
8.3 KiB
TypeScript
/**
|
||
* Step 1 选择剪辑模式 + 生成设置(#1970 新流程第一步)
|
||
* - 剪辑模式:🎲随机混剪 / 📖叙事剪辑,二选一,选中紫底紫框
|
||
* - 生成设置:生成数量(-/+ 1-10 默认1)、视频比例(9:16/16:9 默认9:16)、智能降重开关(默认开)
|
||
*/
|
||
import React from "react"
|
||
import { MinusOutlined, PlusOutlined } from "@ant-design/icons"
|
||
|
||
export type EditMode = "random" | "narrative"
|
||
|
||
interface Step1EditModeProps {
|
||
editMode: EditMode
|
||
onEditModeChange: (mode: EditMode) => void
|
||
/** 生成数量(1-10,默认1) */
|
||
previewCount: number
|
||
onPreviewCountChange: (n: number) => void
|
||
/** 视频比例 */
|
||
videoRatio: "9:16" | "16:9"
|
||
onVideoRatioChange: (ratio: "9:16" | "16:9") => void
|
||
/** 智能降重开关(默认 true) */
|
||
dedupEnabled: boolean
|
||
onDedupEnabledChange: (v: boolean) => void
|
||
}
|
||
|
||
const PURPLE = "#7c3aed"
|
||
const PURPLE_BG = "linear-gradient(135deg, #ede9fe, #ddd6fe)"
|
||
const PURPLE_BORDER = "2px solid #7c3aed"
|
||
|
||
const MODE_CARDS: Array<{
|
||
key: EditMode
|
||
emoji: string
|
||
title: string
|
||
desc: string
|
||
features: string[]
|
||
}> = [
|
||
{
|
||
key: "random",
|
||
emoji: "🎲",
|
||
title: "随机混剪",
|
||
desc: "根据配音时长随机抽取素材片段,灵活组合",
|
||
features: ["随机抽帧组合", "每次画面不同", "适合批量生成"],
|
||
},
|
||
{
|
||
key: "narrative",
|
||
emoji: "📖",
|
||
title: "叙事剪辑",
|
||
desc: "按文案内容匹配相关画面,有逻辑组织镜头",
|
||
features: ["画面匹配文案", "叙事感更强", "需要素材标签"],
|
||
},
|
||
]
|
||
|
||
const Step1EditMode: React.FC<Step1EditModeProps> = ({
|
||
editMode,
|
||
onEditModeChange,
|
||
previewCount,
|
||
onPreviewCountChange,
|
||
videoRatio,
|
||
onVideoRatioChange,
|
||
dedupEnabled,
|
||
onDedupEnabledChange,
|
||
}) => {
|
||
return (
|
||
<div className="xx-form-section">
|
||
<h3>🎬 选择剪辑模式</h3>
|
||
<p style={{ color: "#666", fontSize: 14, marginBottom: 16 }}>
|
||
选择适合您的剪辑方式,后续流程会根据模式自动调整
|
||
</p>
|
||
|
||
<div
|
||
style={{
|
||
display: "grid",
|
||
gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
|
||
gap: 16,
|
||
marginBottom: 24,
|
||
}}
|
||
>
|
||
{MODE_CARDS.map((card) => {
|
||
const selected = editMode === card.key
|
||
return (
|
||
<div
|
||
key={card.key}
|
||
onClick={() => onEditModeChange(card.key)}
|
||
style={{
|
||
padding: 20,
|
||
borderRadius: 12,
|
||
border: selected ? PURPLE_BORDER : "1px solid #e5e7eb",
|
||
background: selected ? PURPLE_BG : "#fff",
|
||
cursor: "pointer",
|
||
transition: "all 0.2s",
|
||
}}
|
||
>
|
||
<div style={{ fontSize: 36, marginBottom: 8 }}>{card.emoji}</div>
|
||
<div
|
||
style={{
|
||
fontSize: 18,
|
||
fontWeight: 600,
|
||
color: selected ? PURPLE : "#111",
|
||
marginBottom: 6,
|
||
}}
|
||
>
|
||
{card.title}
|
||
</div>
|
||
<div style={{ fontSize: 13, color: "#666", marginBottom: 12 }}>{card.desc}</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||
{card.features.map((f) => (
|
||
<div key={f} style={{ fontSize: 12, color: selected ? "#6d28d9" : "#6b7280" }}>
|
||
✅ {f}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
|
||
<h3 style={{ marginTop: 8 }}>⚙️ 生成设置</h3>
|
||
|
||
<div className="xx-form-field" style={{ marginTop: 12 }}>
|
||
<label>生成数量</label>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||
<div
|
||
style={{
|
||
display: "inline-flex",
|
||
alignItems: "center",
|
||
border: "1px solid #e5e7eb",
|
||
borderRadius: 8,
|
||
overflow: "hidden",
|
||
background: "#fff",
|
||
}}
|
||
>
|
||
<button
|
||
type="button"
|
||
onClick={() => onPreviewCountChange(Math.max(1, previewCount - 1))}
|
||
disabled={previewCount <= 1}
|
||
style={{
|
||
width: 36,
|
||
height: 36,
|
||
border: "none",
|
||
background: "transparent",
|
||
cursor: previewCount <= 1 ? "not-allowed" : "pointer",
|
||
color: previewCount <= 1 ? "#d1d5db" : "#374151",
|
||
fontSize: 16,
|
||
}}
|
||
>
|
||
<MinusOutlined />
|
||
</button>
|
||
<span
|
||
style={{
|
||
minWidth: 40,
|
||
textAlign: "center",
|
||
fontSize: 16,
|
||
fontWeight: 600,
|
||
color: "#111",
|
||
}}
|
||
>
|
||
{previewCount}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => onPreviewCountChange(Math.min(10, previewCount + 1))}
|
||
disabled={previewCount >= 10}
|
||
style={{
|
||
width: 36,
|
||
height: 36,
|
||
border: "none",
|
||
background: "transparent",
|
||
cursor: previewCount >= 10 ? "not-allowed" : "pointer",
|
||
color: previewCount >= 10 ? "#d1d5db" : "#374151",
|
||
fontSize: 16,
|
||
}}
|
||
>
|
||
<PlusOutlined />
|
||
</button>
|
||
</div>
|
||
<span style={{ fontSize: 12, color: "#6b7280" }}>最多一次生成 10 个</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="xx-form-field" style={{ marginTop: 16 }}>
|
||
<label>视频比例</label>
|
||
<div style={{ display: "flex", gap: 12, marginTop: 4 }}>
|
||
{[
|
||
{ key: "9:16" as const, emoji: "📱", label: "竖屏 9:16" },
|
||
{ key: "16:9" as const, emoji: "🖥️", label: "横屏 16:9" },
|
||
].map((opt) => {
|
||
const selected = videoRatio === opt.key
|
||
return (
|
||
<button
|
||
key={opt.key}
|
||
type="button"
|
||
onClick={() => onVideoRatioChange(opt.key)}
|
||
style={{
|
||
padding: "10px 20px",
|
||
borderRadius: 8,
|
||
border: selected ? PURPLE_BORDER : "1px solid #e5e7eb",
|
||
background: selected ? PURPLE_BG : "#fff",
|
||
color: selected ? PURPLE : "#374151",
|
||
cursor: "pointer",
|
||
fontSize: 14,
|
||
fontWeight: selected ? 600 : 400,
|
||
transition: "all 0.2s",
|
||
}}
|
||
>
|
||
{opt.emoji} {opt.label}
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
<div
|
||
className="xx-form-field"
|
||
style={{
|
||
marginTop: 16,
|
||
padding: "12px 16px",
|
||
background: "#f9fafb",
|
||
borderRadius: 8,
|
||
}}
|
||
>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ fontSize: 14, fontWeight: 500, color: "#111" }}>
|
||
🎯 智能降重 {dedupEnabled ? "已开启" : "已关闭"}
|
||
</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => onDedupEnabledChange(!dedupEnabled)}
|
||
style={{
|
||
width: 44,
|
||
height: 24,
|
||
borderRadius: 12,
|
||
border: "none",
|
||
background: dedupEnabled ? PURPLE : "#d1d5db",
|
||
position: "relative",
|
||
cursor: "pointer",
|
||
transition: "background 0.2s",
|
||
padding: 0,
|
||
flexShrink: 0,
|
||
}}
|
||
aria-label="toggle dedup"
|
||
>
|
||
<span
|
||
style={{
|
||
position: "absolute",
|
||
top: 2,
|
||
left: dedupEnabled ? 22 : 2,
|
||
width: 20,
|
||
height: 20,
|
||
borderRadius: "50%",
|
||
background: "#fff",
|
||
transition: "left 0.2s",
|
||
boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
|
||
}}
|
||
/>
|
||
</button>
|
||
</div>
|
||
<div style={{ fontSize: 12, color: "#6b7280", marginTop: 4 }}>
|
||
自动对画面做微调,避免查重不过
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default Step1EditMode
|