完善视频复刻

This commit is contained in:
Azmat@qq.com
2026-08-27 15:56:44 +08:00
parent 3fbc2f2dab
commit d2b786a3cf
10 changed files with 722 additions and 164 deletions
+25 -6
View File
@@ -12,6 +12,7 @@ import {
Package,
RefreshCw,
Replace,
ShieldCheck,
Upload,
UserRound,
X,
@@ -53,6 +54,8 @@ const REPLACE_MODE_COPY = {
temporaryFallback: "临时商品素材",
generatingTitle: "正在进行商品复刻",
generatingCopy: "正在匹配商品外观与原片镜头",
reviewingTitle: "正在审核参考素材",
reviewingCopy: "真人视频需先通过合规审核,通过后自动开始复刻",
resultTitle: "商品复刻已完成",
resultPreview: "商品复刻预览",
consistency: "商品一致性检查通过",
@@ -76,6 +79,8 @@ const REPLACE_MODE_COPY = {
temporaryFallback: "临时角色素材",
generatingTitle: "正在进行角色复刻",
generatingCopy: "正在匹配角色外观、表情与原片动作",
reviewingTitle: "正在审核参考素材",
reviewingCopy: "真人视频需先通过合规审核,通过后自动开始复刻",
resultTitle: "角色复刻已完成",
resultPreview: "角色复刻预览",
consistency: "角色一致性检查通过",
@@ -254,6 +259,7 @@ export function VideoReplacePage({
const videoInputRef = useRef<HTMLInputElement>(null);
const tempInputRef = useRef<HTMLInputElement>(null);
const completedNoticeRef = useRef("");
const wasReviewingRef = useRef(false);
const videoConfigs = useMemo(
() => modelConfigs.filter((config) => config.capability === "video" && config.status === "active"),
@@ -307,15 +313,17 @@ export function VideoReplacePage({
}, billingRates);
const points = estimated.points || 220;
const generating = Boolean(job && isInFlight(job.status)) || submitting || videoUploading;
const reviewing = submitting || Boolean(job && (job.review_stage === "reviewing" || job.status === "created"));
const hasResult = Boolean(job && job.status === "succeeded" && job.video_url);
const resultCopy = hasResult && job ? REPLACE_MODE_COPY[modeFromTask(job)] : copy;
const panelClass = [
"video-result-panel replace-result-panel",
generating ? "is-generating" : "",
reviewing ? "is-reviewing" : "",
hasResult ? "has-result" : "",
].filter(Boolean).join(" ");
const generateLabel = generating
? "正在复刻…"
? (reviewing ? "正在审核素材…" : "正在复刻…")
: hasResult
? `再次${copy.modeLabel} · 消耗 ${points} 积分`
: `开始${copy.modeLabel} · 消耗 ${points} 积分`;
@@ -364,8 +372,14 @@ export function VideoReplacePage({
const data = await api.pollVideoReplace(jobId);
if (cancelled) return;
setJob(data.task);
const stillReviewing = data.task.review_stage === "reviewing" || data.task.status === "created";
if (stillReviewing) wasReviewingRef.current = true;
else if (wasReviewingRef.current && isInFlight(data.task.status)) {
wasReviewingRef.current = false;
onNotify("success", "素材审核已通过,正在复刻");
}
if (isInFlight(data.task.status)) {
timer = window.setTimeout(poll, 2500);
timer = window.setTimeout(poll, stillReviewing ? 2000 : 2500);
return;
}
if (data.task.status === "succeeded") {
@@ -575,7 +589,12 @@ export function VideoReplacePage({
setJobId(data.task.id);
rememberJob(data.task.id);
completedNoticeRef.current = "";
onNotify("success", "视频复刻任务已开始");
wasReviewingRef.current = data.task.review_stage === "reviewing" || data.task.status === "created";
if (wasReviewingRef.current) {
onNotify("success", "已提交素材审核,通过后自动开始复刻");
} else {
onNotify("success", "视频复刻任务已开始");
}
if (!isInFlight(data.task.status) && data.task.status === "succeeded") {
onNotify("success", "视频复刻成片已生成");
void loadHistory();
@@ -879,11 +898,11 @@ export function VideoReplacePage({
<div className="replace-generating-state" role="status" aria-live="polite">
<div className="replace-generating-content">
<div className="replace-generating-visual">
<span className="replace-generating-frame"><Clapperboard /></span>
<span className="replace-generating-frame">{reviewing ? <ShieldCheck /> : <Clapperboard />}</span>
<span className="replace-generating-product">{replaceMode === "character" ? <UserRound /> : <Package />}</span>
</div>
<strong>{copy.generatingTitle}</strong>
<span>{copy.generatingCopy}</span>
<strong>{reviewing ? copy.reviewingTitle : copy.generatingTitle}</strong>
<span>{reviewing ? copy.reviewingCopy : copy.generatingCopy}</span>
<div className="replace-generating-bar" aria-hidden="true"><span /></div>
</div>
</div>