完善视频提炼和视频复刻完善提交测试
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import { api, ApiError, type QuickCreateJob } from "../api";
|
||||
import { ConfirmModal, OverlayPortal, useBodyScrollLock } from "../components/overlays";
|
||||
import { useFileDrop } from "../components/use-file-drop";
|
||||
import { forgetQuickCreateJob, readQuickCreateJobId, rememberQuickCreateJob, stripQuickCreateSuffix } from "../quick-create-lock";
|
||||
import type { ModelConfig } from "../types";
|
||||
import {
|
||||
@@ -135,6 +136,9 @@ export function QuickCreatePage({
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [cancelling, setCancelling] = useState(false);
|
||||
const [confirmCancel, setConfirmCancel] = useState(false);
|
||||
// 进页面先向后端确认有没有在跑的任务。localStorage 可能被清、也可能换了浏览器,
|
||||
// 只认它会让刷新后看到一张空表单,用户以为没任务又提交一次 —— 那会重复建商品、重复扣费。
|
||||
const [restoring, setRestoring] = useState(true);
|
||||
const [serviceUnavailable, setServiceUnavailable] = useState(false);
|
||||
const [unavailableMessage, setUnavailableMessage] = useState("");
|
||||
const [pollEpoch, setPollEpoch] = useState(0);
|
||||
@@ -183,8 +187,19 @@ export function QuickCreatePage({
|
||||
}))
|
||||
.catch(() => undefined);
|
||||
void api.quickCreateHistory()
|
||||
.then((payload) => setHistory((payload.results || []).filter(jobIsComplete)))
|
||||
.catch(() => undefined);
|
||||
.then((payload) => {
|
||||
setHistory((payload.results || []).filter(jobIsComplete));
|
||||
const running = payload.inflight || null;
|
||||
if (running) {
|
||||
setJob(running);
|
||||
setJobId(running.id);
|
||||
rememberQuickCreateJob(running.id);
|
||||
} else if (!savedJobId()) {
|
||||
forgetQuickCreateJob();
|
||||
}
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => setRestoring(false));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -325,7 +340,7 @@ export function QuickCreatePage({
|
||||
setPlaying({ url, title: title || "预览视频" });
|
||||
}
|
||||
|
||||
function selectImages(files: FileList | null) {
|
||||
function selectImages(files: FileList | File[] | null) {
|
||||
const selected = Array.from(files || []).filter((file) => file.type.startsWith("image/"));
|
||||
setImages((current) => {
|
||||
const room = Math.max(0, 9 - savedImages.length - current.length);
|
||||
@@ -386,6 +401,10 @@ export function QuickCreatePage({
|
||||
}
|
||||
|
||||
async function startGeneration() {
|
||||
if (restoring) {
|
||||
onNotify?.("info", "正在读取任务状态,请稍候");
|
||||
return;
|
||||
}
|
||||
if (!name.trim() || (!images.length && !savedImages.length)) {
|
||||
onNotify?.("info", "请先填写商品名称并上传商品图片");
|
||||
return;
|
||||
@@ -433,7 +452,16 @@ export function QuickCreatePage({
|
||||
onNotify?.("success", "一键成片任务已启动");
|
||||
onProjectCreated?.();
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 503) {
|
||||
if (error instanceof ApiError && error.status === 409) {
|
||||
// 后端单飞闸:已有任务在跑。直接接上它,别让用户对着报错干瞪眼。
|
||||
const running = (error.payload as { inflight?: QuickCreateJob } | undefined)?.inflight;
|
||||
if (running) {
|
||||
setJob(running);
|
||||
setJobId(running.id);
|
||||
rememberQuickCreateJob(running.id);
|
||||
}
|
||||
onNotify?.("info", error.message || "已有一个一键成片正在进行中");
|
||||
} else if (error instanceof ApiError && error.status === 503) {
|
||||
setServiceUnavailable(true);
|
||||
setUnavailableMessage(error.message || "一键成片服务暂不可用,请稍后再试");
|
||||
onNotify?.("info", error.message || "一键成片服务暂不可用,请稍后再试");
|
||||
@@ -520,6 +548,7 @@ export function QuickCreatePage({
|
||||
}
|
||||
|
||||
const isGenerating = submitting || job?.status === "queued" || job?.status === "running";
|
||||
const imageDrop = useFileDrop((files) => selectImages(files), { disabled: isGenerating });
|
||||
const isComplete = job?.status === "succeeded";
|
||||
const isCancelled = job?.status === "cancelled";
|
||||
const isFailed = !isGenerating && !isComplete && (job?.status === "failed" || isCancelled || serviceUnavailable);
|
||||
@@ -547,6 +576,7 @@ export function QuickCreatePage({
|
||||
const estimatedPoints = videoEstimate.points > 0 ? videoEstimate.points + sceneCount * 60 : sceneCount * 240;
|
||||
const shellClass = [
|
||||
"quick-create-shell",
|
||||
restoring ? "is-restoring" : "",
|
||||
isGenerating ? "is-generating" : "",
|
||||
isComplete ? "is-complete" : "",
|
||||
isFailed ? "is-failed" : "",
|
||||
@@ -562,7 +592,11 @@ export function QuickCreatePage({
|
||||
</header>
|
||||
|
||||
<div className={shellClass} id="quickCreateShell">
|
||||
<section className="quick-create-panel quick-form-panel">
|
||||
<section
|
||||
className={`quick-create-panel quick-form-panel${restoring ? " is-locked" : ""}`}
|
||||
aria-busy={restoring}
|
||||
inert={restoring}
|
||||
>
|
||||
<div className="quick-form-copy">
|
||||
<h2>告诉我们这是什么商品</h2>
|
||||
</div>
|
||||
@@ -614,7 +648,10 @@ export function QuickCreatePage({
|
||||
|
||||
<div className="quick-field">
|
||||
<span className="quick-field-label"><span>商品图片</span><small>必填 · 最多9张</small></span>
|
||||
<div className={`quick-upload${imageCount ? " has-images" : ""}`}>
|
||||
<div
|
||||
className={`quick-upload${imageCount ? " has-images" : ""}${imageDrop.dragging ? " is-dragover" : ""}`}
|
||||
{...imageDrop.dropProps}
|
||||
>
|
||||
<input ref={imageInputRef} type="file" accept="image/jpeg,image/png,image/webp" multiple onChange={(event) => { selectImages(event.target.files); event.currentTarget.value = ""; }} />
|
||||
{imageCount ? (
|
||||
<div className="quick-upload-filled">
|
||||
@@ -686,6 +723,12 @@ export function QuickCreatePage({
|
||||
</section>
|
||||
|
||||
<section className="quick-create-panel quick-status-panel" aria-live="polite">
|
||||
<div className="quick-state quick-state-restoring" role="status">
|
||||
<div className="quick-restoring-spinner" aria-hidden="true" />
|
||||
<h2>正在读取任务状态</h2>
|
||||
<p>确认有没有正在进行的一键成片,稍候</p>
|
||||
</div>
|
||||
|
||||
<div className="quick-state quick-state-ready">
|
||||
<div className="quick-ready-orbit"><span className="quick-ready-icon"><Sparkles /></span></div>
|
||||
<h2>核心参数可选,其余自动完成</h2>
|
||||
|
||||
Reference in New Issue
Block a user