fix: address project review bugs
This commit is contained in:
@@ -11,7 +11,14 @@ import {
|
||||
SendOutlined,
|
||||
StopOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { runVideoPlan, renderSceneImage, renderScene, buildSceneTasks, saveVideoHistory } from "./ecommerceVideoService";
|
||||
import {
|
||||
runVideoPlan,
|
||||
renderSceneImage,
|
||||
renderScene,
|
||||
buildSceneTasks,
|
||||
saveVideoHistory,
|
||||
buildComplianceFailureMessage,
|
||||
} from "./ecommerceVideoService";
|
||||
import {
|
||||
PLAN_STEP_LABELS,
|
||||
PLAN_STEPS_DISPLAY,
|
||||
@@ -70,9 +77,11 @@ function buildInputFingerprint(input: {
|
||||
durationSeconds: number;
|
||||
resolution: string;
|
||||
}): string {
|
||||
const imageCount = input.productImageDataUrls.length;
|
||||
const imageSignature = input.productImageDataUrls
|
||||
.map((source) => `${source.length}:${hashString(source)}`)
|
||||
.join("|");
|
||||
return hashString([
|
||||
String(imageCount),
|
||||
imageSignature,
|
||||
input.requirement.trim(),
|
||||
input.platform,
|
||||
input.aspectRatio,
|
||||
@@ -81,6 +90,10 @@ function buildInputFingerprint(input: {
|
||||
].join("::"));
|
||||
}
|
||||
|
||||
function planAllowsVideoGeneration(plan: EcommerceVideoPlanResult | null): boolean {
|
||||
return plan?.compliance.allow_video_generation !== false;
|
||||
}
|
||||
|
||||
function mapResolutionToQuality(res: string): "720P" | "1080P" {
|
||||
return res.includes("720") ? "720P" : "1080P";
|
||||
}
|
||||
@@ -163,6 +176,10 @@ export default function EcommerceVideoWorkspace({
|
||||
useEffect(() => {
|
||||
const delay = 600;
|
||||
if (stage === "planned" && planResult && scenes.length > 0) {
|
||||
if (!planAllowsVideoGeneration(planResult)) {
|
||||
setError(buildComplianceFailureMessage(planResult.compliance));
|
||||
return;
|
||||
}
|
||||
const timer = setTimeout(() => { void handleGenerateImages(); }, delay);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
@@ -468,6 +485,7 @@ export default function EcommerceVideoWorkspace({
|
||||
let liveCompletedSteps: PlanStep[] = resume
|
||||
? ALL_STEPS.filter((s) => stepCompletedFromProgress(s, resume))
|
||||
: [];
|
||||
let liveCurrentStep: PlanStep | null = null;
|
||||
const persist = (stageNow: EcommerceVideoStage) => {
|
||||
saveEcommerceVideoState({
|
||||
inputFingerprint,
|
||||
@@ -484,7 +502,10 @@ export default function EcommerceVideoWorkspace({
|
||||
const result = await runVideoPlan(
|
||||
productImageSources, requirement, buildConfig(),
|
||||
{
|
||||
onStepStart: (step) => setCurrentStep(step),
|
||||
onStepStart: (step) => {
|
||||
liveCurrentStep = step;
|
||||
setCurrentStep(step);
|
||||
},
|
||||
onStepDone: (step) => {
|
||||
liveCompletedSteps = [...liveCompletedSteps, step];
|
||||
setCompletedSteps((prev) => [...prev, step]);
|
||||
@@ -517,7 +538,7 @@ export default function EcommerceVideoWorkspace({
|
||||
const message = err instanceof Error ? err.message : "策划失败";
|
||||
setError(message);
|
||||
// Mark the step that was in-progress as failed so user can resume
|
||||
setFailedStep((prev) => prev || currentStep);
|
||||
setFailedStep((prev) => prev || liveCurrentStep);
|
||||
setStage("idle");
|
||||
// Persist partial progress so the user can resume after a page switch
|
||||
persist("idle");
|
||||
@@ -526,8 +547,8 @@ export default function EcommerceVideoWorkspace({
|
||||
|
||||
const handlePlan = async () => {
|
||||
if (!isAuthenticated) { onRequestLogin?.(); return; }
|
||||
if (!productImageDataUrls.length && !requirement.trim()) {
|
||||
setError("请先上传产品图片或填写商品说明"); return;
|
||||
if (!productImageDataUrls.length) {
|
||||
setError("请先上传商品图片"); return;
|
||||
}
|
||||
await runPlanFlow(null);
|
||||
};
|
||||
@@ -542,6 +563,10 @@ export default function EcommerceVideoWorkspace({
|
||||
|
||||
const handleGenerateImages = async () => {
|
||||
if (!planResult || !scenes.length) return;
|
||||
if (!planAllowsVideoGeneration(planResult)) {
|
||||
setError(buildComplianceFailureMessage(planResult.compliance));
|
||||
return;
|
||||
}
|
||||
setStage("imaging"); setError(null);
|
||||
renderAbortRef.current = { current: false };
|
||||
const ratio = aspectRatio.includes("9:16") || aspectRatio.includes("9:16") ? "9:16"
|
||||
@@ -555,7 +580,11 @@ export default function EcommerceVideoWorkspace({
|
||||
};
|
||||
// Only redo scenes missing imageUrl — preserves successfully generated images on partial retry
|
||||
const scenesToProcess = currentScenes.filter((s) => !s.imageUrl);
|
||||
if (!scenesToProcess.length) { setStage("imaged"); return; }
|
||||
if (!scenesToProcess.length) {
|
||||
setStage("imaged");
|
||||
saveEcommerceVideoState({ inputFingerprint, stage: "imaged", completedSteps, planResult, scenes: currentScenes, sourceImageUrls });
|
||||
return;
|
||||
}
|
||||
for (const scene of scenesToProcess) {
|
||||
if (renderAbortRef.current.current) break;
|
||||
persistScenes(currentScenes.map((s) => s.sceneId === scene.sceneId ? { ...s, status: "pending", error: undefined } : s));
|
||||
@@ -597,6 +626,10 @@ export default function EcommerceVideoWorkspace({
|
||||
|
||||
const handleRenderVideos = async () => {
|
||||
if (!scenes.length) return;
|
||||
if (!planAllowsVideoGeneration(planResult)) {
|
||||
setError(planResult ? buildComplianceFailureMessage(planResult.compliance) : "合规检查未通过,已停止生成。");
|
||||
return;
|
||||
}
|
||||
if (!scenes.some((s) => s.imageUrl)) { setError("请先生成分镜图片"); return; }
|
||||
setStage("rendering"); setError(null);
|
||||
renderAbortRef.current = { current: false };
|
||||
@@ -609,7 +642,12 @@ export default function EcommerceVideoWorkspace({
|
||||
};
|
||||
// Only render scenes that haven't completed yet — preserves successful videos on partial retry
|
||||
const scenesToProcess = currentScenes.filter((s) => s.imageUrl && s.status !== "completed");
|
||||
if (!scenesToProcess.length) { setStage(currentScenes.every((s) => s.status === "completed") ? "completed" : "partial_failed"); return; }
|
||||
if (!scenesToProcess.length) {
|
||||
const finalStage = currentScenes.every((s) => s.status === "completed") ? "completed" as const : "partial_failed" as const;
|
||||
setStage(finalStage);
|
||||
saveEcommerceVideoState({ inputFingerprint, stage: finalStage, completedSteps, planResult, scenes: currentScenes, sourceImageUrls });
|
||||
return;
|
||||
}
|
||||
for (const scene of scenesToProcess) {
|
||||
if (renderAbortRef.current.current) break;
|
||||
if (!scene.imageUrl) continue;
|
||||
|
||||
Reference in New Issue
Block a user