fix: 修复多个运行时崩溃和功能bug,优化画布连接线和剧本评分
- 修复 EcommercePage generateEcommerceImage 调用不存在变量导致运行时崩溃 - 修复 DigitalHumanPage/ImageWorkbenchPage 变量名错误导致页面不可用 - 修复 ecommerceVideoService token 读取用错 key 导致请求 401 - 修复画布连接线在弹窗出现后仍跟随鼠标的问题 - 剧本评分 .docx 文件改为服务端 mammoth 解析(新增 /api/files/extract-text) - ErrorBoundary 加 key 支持切换页面时自动重置 - Vite proxy 改为指向公网域名 omniai.net.cn - 新增视频生成历史记录面板和删除确认弹窗 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ const ecommerceSlide4 = `${OSS_MUBAN}/slide-4.png`;
|
||||
const ecommerceSlide5 = `${OSS_MUBAN}/slide-5.png`;
|
||||
import ImageMentionMenu, { getImageMentionQuery, insertImageMentionValue, type MentionImageOption } from "./ImageMentionMenu";
|
||||
import EcommerceVideoWorkspace from "./EcommerceVideoWorkspace";
|
||||
import EcommerceVideoHistoryPanel from "./panels/EcommerceVideoHistoryPanel";
|
||||
import EcommerceDetailPanel from "./panels/EcommerceDetailPanel";
|
||||
import EcommerceSetPanel from "./panels/EcommerceSetPanel";
|
||||
import EcommerceTryOnPanel from "./panels/EcommerceTryOnPanel";
|
||||
@@ -787,6 +788,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
const [productImages, setProductImages] = useState<CloneImageItem[]>([]);
|
||||
const [isProductUploadDragging, setIsProductUploadDragging] = useState(false);
|
||||
const [cloneOutput, setCloneOutput] = useState<CloneOutputKey>("detail");
|
||||
const [videoHistoryVisible, setVideoHistoryVisible] = useState(false);
|
||||
const [videoPlanTrigger, setVideoPlanTrigger] = useState(0);
|
||||
const [openCloneBasicSelect, setOpenCloneBasicSelect] = useState<CloneBasicSelectKey | null>(null);
|
||||
const [openCloneModelSelect, setOpenCloneModelSelect] = useState<CloneModelSelectKey | null>(null);
|
||||
const [cloneModelSelectDropUp, setCloneModelSelectDropUp] = useState(false);
|
||||
@@ -1413,7 +1416,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
pRatio: string,
|
||||
pLanguage: string,
|
||||
pMarket: string,
|
||||
setStatusFn: (status: "generating" | "done" | "idle") => void,
|
||||
setStatusFn: (status: "generating" | "done" | "idle" | "failed") => void,
|
||||
setResultFn: (urls: string[]) => void,
|
||||
): Promise<void> => {
|
||||
setStatusFn("generating");
|
||||
@@ -1486,11 +1489,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
statusFn?: (status: "generating" | "done" | "idle" | "failed") => void,
|
||||
resultFn?: (results: CloneImageItem[]) => void,
|
||||
): Promise<void> => {
|
||||
setStatusFn("generating");
|
||||
statusFn?.("generating");
|
||||
try {
|
||||
const referenceUrls = await uploadCloneImages(images);
|
||||
if (!referenceUrls.length) {
|
||||
setStatusFn("idle");
|
||||
statusFn?.("idle");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1514,22 +1517,22 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
});
|
||||
|
||||
if (resultUrl) {
|
||||
setResultFn([{ id: `ecommerce-${stamp}`, src: resultUrl, label: selectedCloneOutput.label }]);
|
||||
setStatusFn("done");
|
||||
resultFn?.([{ id: `ecommerce-${stamp}`, src: resultUrl, label: selectedCloneOutput.label }]);
|
||||
statusFn?.("done");
|
||||
imageGen.updateTask(storeId, { status: "completed", progress: 100, resultUrl });
|
||||
} else {
|
||||
setStatusFn("idle");
|
||||
statusFn?.("idle");
|
||||
imageGen.updateTask(storeId, { status: "failed", error: "生成未返回结果" });
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof ServerRequestError && err.status === 402) {
|
||||
setResultFn([{ id: `ecommerce-error-402`, src: "", label: "余额不足,请充值后继续" }]);
|
||||
resultFn?.([{ id: `ecommerce-error-402`, src: "", label: "余额不足,请充值后继续" }]);
|
||||
toast.error("余额不足,请充值后继续");
|
||||
} else {
|
||||
const msg = err instanceof Error ? err.message : "生成失败";
|
||||
toast.error(msg);
|
||||
}
|
||||
setStatusFn("failed");
|
||||
statusFn?.("failed");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1563,10 +1566,10 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
});
|
||||
|
||||
const { waitForTask } = await import("../../api/taskSubscription");
|
||||
abortRef.current = { current: false };
|
||||
const resultUrl = await waitForTask(taskId, { abortRef: abortRef.current });
|
||||
imageAbortRef.current = { current: false };
|
||||
const resultUrl = await waitForTask(taskId, { abortRef: imageAbortRef.current });
|
||||
if (resultUrl) {
|
||||
setResults([{ id: crypto.randomUUID(), name: "换装视频", src: resultUrl, type: "video", size: 0 }]);
|
||||
setResults([{ id: crypto.randomUUID(), src: resultUrl, label: "换装视频" }]);
|
||||
}
|
||||
setStatus("done");
|
||||
} catch (err) {
|
||||
@@ -1602,7 +1605,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
void generateEcommerceImage(
|
||||
cloneOutput, productImages, requirement,
|
||||
platform, ratio, language, market,
|
||||
(s) => setStatus(s as ProductCloneStatus), setResults,
|
||||
undefined,
|
||||
(s: string) => setStatus(s as ProductCloneStatus), setResults,
|
||||
);
|
||||
lastFailedActionRef.current = () => handleGenerate();
|
||||
}
|
||||
@@ -1681,7 +1685,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
void generateEcommerceImage(
|
||||
"detail", detailProductImages, detailRequirement,
|
||||
detailPlatform, getPlatformDefaultRatio(detailPlatform), detailLanguage, detailMarket,
|
||||
(s) => setDetailStatus(s as DetailStatus),
|
||||
undefined,
|
||||
(s: string) => setDetailStatus(s as DetailStatus),
|
||||
(res) => setDetailResultUrl(res[0]?.src ?? null),
|
||||
);
|
||||
};
|
||||
@@ -1905,6 +1910,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
handleGenerate={handleGenerate}
|
||||
formatRatioDisplayValue={formatRatioDisplayValue}
|
||||
setVideoOutfitFiles={(video, ref) => { setVideoOutfitVideoFile(video); setVideoOutfitRefFile(ref); }}
|
||||
onStartVideoPlan={() => setVideoPlanTrigger((n) => n + 1)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2404,6 +2410,8 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
durationSeconds={cloneVideoDuration}
|
||||
resolution={cloneVideoQuality === "standard" ? "720P" : "1080P"}
|
||||
onRequestLogin={() => ((_props as Record<string, unknown>).isAuthenticated ? undefined : (window.location.hash = "#/login"))}
|
||||
onOpenHistory={() => setVideoHistoryVisible(true)}
|
||||
triggerPlan={videoPlanTrigger}
|
||||
/>
|
||||
</main>
|
||||
) : cloneOutput === "video-outfit" && results.length > 0 && results[0].type === "video" ? (
|
||||
@@ -2472,6 +2480,11 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
|
||||
</section>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<EcommerceVideoHistoryPanel
|
||||
visible={videoHistoryVisible}
|
||||
onClose={() => setVideoHistoryVisible(false)}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user