af25045123
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 4s
CI/CD Pipeline / Check push changed paths (push) Successful in 5s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 25s
CI/CD Pipeline / Build Staging API Image (push) Successful in 25s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 30s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 1m50s
CI/CD Pipeline / Integration Tests (push) Successful in 1m51s
CI/CD Pipeline / Validate - Style (push) Successful in 2m29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 53s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m20s
CI/CD Pipeline / Validate - Security (push) Successful in 4m27s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m49s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m45s
AI Code Review / AI Code Review (pull_request) Successful in 6m21s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m51s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m49s
CI/CD Pipeline / Unit Tests (push) Successful in 9m32s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 14m36s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 4s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 25s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 22s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 21s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 269h53m9s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 269h53m8s
CI/CD Pipeline / Canary Release to Production (push) Failing after 270h11m3s
CI/CD Pipeline / Deploy Production (push) Failing after 270h11m3s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 270h11m6s
CI/CD Pipeline / Build Production Web Image (push) Failing after 270h11m6s
CI/CD Pipeline / Build Production API Image (push) Failing after 270h11m6s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Failing after 270h16m17s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 269h59m15s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 269h59m22s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 270h19m3s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 270h19m4s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 269h53m10s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 269h59m16s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 269h59m27s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 270h0m7s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 270h19m5s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 270h20m39s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 269h53m10s
CI/CD Pipeline / PR Build Web Image (push) Failing after 270h20m40s
CI/CD Pipeline / Frontend Lint (push) Failing after 270h20m40s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 270h0m8s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 269h59m27s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 270h20m48s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 270h0m0s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 270h0m1s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 270h0m1s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 270h0m2s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Failing after 270h0m2s
CI/CD Pipeline / Validate - Security (pull_request) Failing after 270h0m3s
CI/CD Pipeline / Validate - Style (pull_request) Failing after 270h0m3s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 270h27m37s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 270h33m45s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 270h33m56s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 270h34m37s
CI/CD Pipeline / CI Gate (push) Failing after 270h45m36s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 270h34m37s
CI/CD Pipeline / PR Build API Image (push) Failing after 270h55m10s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
128 lines
3.6 KiB
TypeScript
128 lines
3.6 KiB
TypeScript
/**
|
||
* 生成数量选择弹窗(Issue #1677)
|
||
* Step1 选完模板点「下一步」时弹出:要生成几个视频?(1~10)
|
||
* 默认 1,回车 = 1(零额外操作)
|
||
*/
|
||
import React, { useState, useEffect, useRef } from "react"
|
||
import { MAX_PREVIEW_COUNT } from "../constants"
|
||
|
||
interface PreviewCountModalProps {
|
||
open: boolean
|
||
/** 默认值(上次选择,默认1) */
|
||
defaultCount?: number
|
||
onConfirm: (count: number) => void
|
||
onCancel: () => void
|
||
}
|
||
|
||
const PreviewCountModal: React.FC<PreviewCountModalProps> = ({
|
||
open,
|
||
defaultCount = 1,
|
||
onConfirm,
|
||
onCancel,
|
||
}) => {
|
||
const [count, setCount] = useState(defaultCount)
|
||
const inputRef = useRef<HTMLInputElement>(null)
|
||
|
||
useEffect(() => {
|
||
if (open) {
|
||
setCount(defaultCount)
|
||
// 弹窗打开后聚焦并选中,方便直接回车=默认1
|
||
setTimeout(() => inputRef.current?.focus(), 50)
|
||
}
|
||
}, [open, defaultCount])
|
||
|
||
const clamp = (n: number) => Math.max(1, Math.min(MAX_PREVIEW_COUNT, n || 1))
|
||
|
||
const handleConfirm = () => {
|
||
onConfirm(clamp(count))
|
||
}
|
||
|
||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||
if (e.key === "Enter") {
|
||
e.preventDefault()
|
||
handleConfirm()
|
||
}
|
||
if (e.key === "Escape") {
|
||
onCancel()
|
||
}
|
||
}
|
||
|
||
if (!open) return null
|
||
|
||
return (
|
||
<div className="xx-modal-mask" onClick={onCancel}>
|
||
<div className="xx-modal-box xx-count-modal" onClick={(e) => e.stopPropagation()}>
|
||
<h3 style={{ margin: "0 0 8px", fontSize: 18 }}>要生成几个视频?</h3>
|
||
<p style={{ margin: "0 0 20px", fontSize: 13, color: "var(--text-secondary, #666)" }}>
|
||
素材共用,AI 随机剪辑出不同版本,每个视频可独立设置标题、配音和封面
|
||
</p>
|
||
|
||
<div className="xx-count-selector">
|
||
<button
|
||
type="button"
|
||
className="xx-count-btn"
|
||
onClick={() => setCount((c) => clamp(c - 1))}
|
||
disabled={count <= 1}
|
||
aria-label="减少"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
ref={inputRef}
|
||
type="number"
|
||
min={1}
|
||
max={MAX_PREVIEW_COUNT}
|
||
value={count}
|
||
onChange={(e) => setCount(clamp(parseInt(e.target.value, 10) || 1))}
|
||
onKeyDown={handleKeyDown}
|
||
className="xx-count-input"
|
||
/>
|
||
<button
|
||
type="button"
|
||
className="xx-count-btn"
|
||
onClick={() => setCount((c) => clamp(c + 1))}
|
||
disabled={count >= MAX_PREVIEW_COUNT}
|
||
aria-label="增加"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
|
||
<div className="xx-count-quick">
|
||
{[1, 3, 5, 10].map((n) => (
|
||
<button
|
||
key={n}
|
||
type="button"
|
||
className={`xx-count-chip ${count === n ? "active" : ""}`}
|
||
onClick={() => setCount(n)}
|
||
>
|
||
{n} 个
|
||
</button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="xx-count-actions">
|
||
<button type="button" className="xx-btn xx-btn-ghost" onClick={onCancel}>
|
||
取消
|
||
</button>
|
||
<button type="button" className="xx-btn xx-btn-primary" onClick={handleConfirm}>
|
||
{count === 1 ? "生成 1 个视频" : `生成 ${count} 个视频`}
|
||
</button>
|
||
</div>
|
||
<p
|
||
style={{
|
||
margin: "12px 0 0",
|
||
fontSize: 12,
|
||
color: "var(--text-tertiary, #999)",
|
||
textAlign: "center",
|
||
}}
|
||
>
|
||
直接按回车 = 生成 1 个
|
||
</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default PreviewCountModal
|