完成视频复刻和优化

This commit is contained in:
Azmat@qq.com
2026-08-27 11:54:47 +08:00
parent c25060c6c6
commit 51620bf25b
46 changed files with 2575 additions and 663 deletions
+23 -20
View File
@@ -20,8 +20,8 @@ import {
ArrowUpRight,
} from "lucide-react";
import { api, ApiError, type QuickCreateJob } from "../api";
import { ConfirmModal } from "../components/overlays";
import { forgetQuickCreateJob, readQuickCreateJobId, rememberQuickCreateJob } from "../quick-create-lock";
import { ConfirmModal, OverlayPortal, useBodyScrollLock } from "../components/overlays";
import { forgetQuickCreateJob, readQuickCreateJobId, rememberQuickCreateJob, stripQuickCreateSuffix } from "../quick-create-lock";
import type { ModelConfig } from "../types";
import {
DEFAULT_BILLING_RATES,
@@ -75,7 +75,7 @@ function formatClock(seconds: number) {
}
function historyTitle(item: QuickCreateJob) {
return (item.title || item.product_name || "极速成片").replace(/ · 极速成片$/, "");
return stripQuickCreateSuffix(item.title || item.product_name || "一键成片");
}
function jobIsComplete(item: QuickCreateJob) {
@@ -140,6 +140,7 @@ export function QuickCreatePage({
const [pollEpoch, setPollEpoch] = useState(0);
const [history, setHistory] = useState<QuickCreateJob[]>([]);
const [playing, setPlaying] = useState<{ url: string; title: string } | null>(null);
useBodyScrollLock(Boolean(playing));
const videoConfigs = useMemo(
() => modelConfigs.filter((config) => config.capability === "video" && config.status === "active"),
[modelConfigs],
@@ -261,7 +262,7 @@ export function QuickCreatePage({
if (next.status === "succeeded") {
if (watchedGeneratingRef.current && completedNoticeRef.current !== next.id) {
completedNoticeRef.current = next.id;
notifyRef.current?.("success", "极速成片已生成");
notifyRef.current?.("success", "一键成片已生成");
projectCreatedRef.current?.();
}
if (next.project_id) quickCreateStatusRef.current?.(next.project_id, next.status);
@@ -274,8 +275,8 @@ export function QuickCreatePage({
if (terminalNoticeRef.current !== next.id) {
terminalNoticeRef.current = next.id;
const message = next.status === "cancelled"
? "极速成片已取消"
: next.error_message || "极速成片未完成,已完成的步骤会保留,可重试继续";
? "一键成片已取消"
: next.error_message || "一键成片未完成,已完成的步骤会保留,可重试继续";
notifyRef.current?.(next.status === "cancelled" ? "info" : "error", message);
}
if (next.project_id) quickCreateStatusRef.current?.(next.project_id, next.status);
@@ -293,10 +294,10 @@ export function QuickCreatePage({
setJobId("");
clearDraft();
forgetQuickCreateJob();
notifyRef.current?.("info", "已清除其他账号的极速成片记录");
notifyRef.current?.("info", "已清除其他账号的一键成片记录");
return;
}
notifyRef.current?.("error", error instanceof Error ? error.message : "读取极速成片进度失败");
notifyRef.current?.("error", error instanceof Error ? error.message : "读取一键成片进度失败");
timer = window.setTimeout(poll, 8000);
}
};
@@ -369,7 +370,7 @@ export function QuickCreatePage({
if (next.status === "queued" || next.status === "running") {
onNotify?.("success", "已从上次进度继续生成");
} else if (next.status === "succeeded") {
onNotify?.("success", "极速成片已生成");
onNotify?.("success", "一键成片已生成");
} else {
onNotify?.("error", next.error_message || "继续生成失败,已完成的步骤会保留");
}
@@ -429,15 +430,15 @@ export function QuickCreatePage({
setSavedImages(created.product_images || []);
setSourceProductId(created.product_id || "");
rememberQuickCreateJob(created.id);
onNotify?.("success", "极速成片任务已启动");
onNotify?.("success", "一键成片任务已启动");
onProjectCreated?.();
} catch (error) {
if (error instanceof ApiError && error.status === 503) {
setServiceUnavailable(true);
setUnavailableMessage(error.message || "极速成片服务暂不可用,请稍后再试");
onNotify?.("info", error.message || "极速成片服务暂不可用,请稍后再试");
setUnavailableMessage(error.message || "一键成片服务暂不可用,请稍后再试");
onNotify?.("info", error.message || "一键成片服务暂不可用,请稍后再试");
} else {
onNotify?.("error", error instanceof Error ? error.message : "极速成片启动失败");
onNotify?.("error", error instanceof Error ? error.message : "一键成片启动失败");
}
} finally {
setSubmitting(false);
@@ -556,7 +557,7 @@ export function QuickCreatePage({
<header className="project-builder-header quick-create-header">
<div className="project-builder-title">
<button type="button" className="image-back-button" onClick={onBack} aria-label={backLabel}><ArrowLeft /></button>
<div><h1>极速成片</h1></div>
<div><h1>一键成片</h1></div>
</div>
</header>
@@ -760,7 +761,7 @@ export function QuickCreatePage({
<span className="quick-failed-icon"><AlertCircle /></span>
<h2>
{serviceUnavailable
? "极速成片暂不可用"
? "一键成片暂不可用"
: isCancelled
? "已取消本次生成"
: reviewBlocked
@@ -798,9 +799,9 @@ export function QuickCreatePage({
</section>
</div>
<section className="quick-history" aria-label="过往极速成片项目">
<section className="quick-history" aria-label="过往一键成片项目">
<div className="quick-history-head">
<h2>过往极速成片项目</h2>
<h2>过往一键成片项目</h2>
<span>{history.length}个项目</span>
</div>
{history.length ? (
@@ -828,7 +829,7 @@ export function QuickCreatePage({
<div className="quick-history-copy">
<span className={`quick-history-badge${badge === "已完成" ? "" : " is-wait"}`}>{badge}</span>
<h3>{historyTitle(item)}</h3>
<p>极速成片 · {scenes}场 · {item.result?.aspect_ratio || item.settings?.aspect_ratio || "9:16"} · {(item.result?.resolution || item.settings?.resolution || "720p").toUpperCase()} · {item.result?.video_model || item.settings?.video_model_label || "智能模型"}</p>
<p>一键成片 · {scenes}场 · {item.result?.aspect_ratio || item.settings?.aspect_ratio || "9:16"} · {(item.result?.resolution || item.settings?.resolution || "720p").toUpperCase()} · {item.result?.video_model || item.settings?.video_model_label || "智能模型"}</p>
</div>
<button type="button" className="quick-history-open" onClick={() => openProfessional(item.project_id, item.status)}>
<ArrowUpRight />查看项目
@@ -838,7 +839,7 @@ export function QuickCreatePage({
})}
</div>
) : (
<p className="quick-history-empty">还没有完成的极速成片,生成成功后会出现在这里。</p>
<p className="quick-history-empty">还没有完成的一键成片,生成成功后会出现在这里。</p>
)}
</section>
@@ -846,7 +847,7 @@ export function QuickCreatePage({
open={confirmCancel}
title="确认取消生成?"
subtitle="当前任务将停止"
detail="取消后,本次极速成片不会继续生成;已完成的商品和素材会保留在专业创作项目中。"
detail="取消后,本次一键成片不会继续生成;已完成的商品和素材会保留在专业创作项目中。"
confirmText="确认取消"
icon={<AlertCircle size={16} />}
dismissable={!cancelling}
@@ -855,6 +856,7 @@ export function QuickCreatePage({
/>
{playing ? (
<OverlayPortal>
<div className="quick-player-bg" onClick={() => setPlaying(null)} role="presentation">
<div className="quick-player" onClick={(event) => event.stopPropagation()} role="dialog" aria-modal="true" aria-label={playing.title}>
<div className="quick-player-bar">
@@ -864,6 +866,7 @@ export function QuickCreatePage({
<video src={playing.url} controls autoPlay playsInline controlsList="nodownload" />
</div>
</div>
</OverlayPortal>
) : null}
</div>
);