| null;
@@ -1833,10 +1837,13 @@ function resolveVideoCanvasBillingDisplay(params: {
}
if (params.modelKind === "video") {
- const videoCost = getVideoCreditCostFromConfig(
+ const videoCost = getGenerationCreditCostFromImageGenConfig(
matchedModel.imageGenConfig,
- params.duration,
- params.resolution,
+ "video",
+ {
+ duration: params.duration,
+ resolution: params.resolution,
+ },
);
if (videoCost > 0) {
return {
@@ -1848,10 +1855,7 @@ function resolveVideoCanvasBillingDisplay(params: {
}
if (params.modelKind === "image") {
- const unitCost = Math.max(
- 0,
- Math.round(Number(matchedModel.imageGenConfig.creditCostPerImage ?? 0)),
- );
+ const unitCost = getImageCreditCostFromConfig(matchedModel.imageGenConfig);
if (unitCost > 0) {
const safeImageCount = Math.max(
1,
diff --git a/components/canvas/RealtimeCanvasDialog.tsx b/components/canvas/RealtimeCanvasDialog.tsx
index 808216b48..32b794aac 100644
--- a/components/canvas/RealtimeCanvasDialog.tsx
+++ b/components/canvas/RealtimeCanvasDialog.tsx
@@ -943,6 +943,12 @@ export function RealtimeCanvasDialog({
previewUrl,
selectedImageLayer?.src,
]);
+ const previewDisplayUrl = useMemo(() => {
+ if (!previewUrl) return "";
+ return /^https?:\/\//i.test(previewUrl)
+ ? buildImageProxyUrl(previewUrl, { delivery: "proxy" })
+ : previewUrl;
+ }, [previewUrl]);
const resolveRealtimeModeItem = useCallback(
(
key: "realtimeModeRealtime" | "realtimeModeEnhance",
@@ -3787,7 +3793,7 @@ export function RealtimeCanvasDialog({
) : (
diff --git a/components/canvas/nodes/ImageNode.tsx b/components/canvas/nodes/ImageNode.tsx
index a2eacaae6..916868a72 100644
--- a/components/canvas/nodes/ImageNode.tsx
+++ b/components/canvas/nodes/ImageNode.tsx
@@ -5344,6 +5344,26 @@ export function ImageNode({
result.data.previewImageUrl ||
params.previewImageUrl ||
params.sourceImageUrl;
+ const responseAssetPayload =
+ result.data.assetPayload &&
+ typeof result.data.assetPayload === "object" &&
+ !Array.isArray(result.data.assetPayload)
+ ? (result.data.assetPayload as Record)
+ : null;
+ const nextAssetPayload = {
+ ...(responseAssetPayload || {}),
+ providerId:
+ params.providerId ||
+ (typeof responseAssetPayload?.providerId === "string"
+ ? responseAssetPayload.providerId
+ : null),
+ providerName:
+ params.providerName ||
+ (typeof responseAssetPayload?.providerName === "string"
+ ? responseAssetPayload.providerName
+ : null),
+ sourceImageUrl: params.sourceImageUrl,
+ };
if (!modelUrl) {
const error = "3D 模型已完成,但没有返回模型文件";
updateNode(params.nodeId, {
@@ -5401,11 +5421,7 @@ export function ImageNode({
sourceImageUrl: params.sourceImageUrl,
progress: 100,
output: result.data.output || null,
- assetPayload: {
- providerId: params.providerId || null,
- providerName: params.providerName || null,
- sourceImageUrl: params.sourceImageUrl,
- },
+ assetPayload: nextAssetPayload,
},
},
});
@@ -5420,11 +5436,7 @@ export function ImageNode({
modelUrl,
previewImageUrl,
error: null,
- assetPayload: {
- providerId: params.providerId || null,
- providerName: params.providerName || null,
- sourceImageUrl: params.sourceImageUrl,
- },
+ assetPayload: nextAssetPayload,
createdAt: Date.now(),
});
persistCanvasStateSnapshot();
@@ -6621,7 +6633,7 @@ export function ImageNode({
/>
) : null}
- {/* 提示词显示 */}
- {data.prompt && isHovered && !isProcessing && (
-
- )}
-
{data.note && !isSelected && (
+ resolvePreferredModelAssetUrl({
+ primaryUrl: data.src,
+ sourceUrl: sourceModelUrl,
+ }),
+ [data.src, sourceModelUrl]
+ );
+ const modelFileName = useMemo(
+ () => inferModelFileName(resolvedModelUrl || data.src || null, node.id),
+ [data.src, node.id, resolvedModelUrl]
+ );
+ const modelFormatLabel = useMemo(
+ () => inferModelFormat(resolvedModelUrl || data.src || null, data.format),
+ [data.format, data.src, resolvedModelUrl]
+ );
+ const primaryOpenUrl = worldUrl || resolvedModelUrl || null;
const primaryOpenItem = worldUrl ? model3dOpenWorldItem : model3dOpen3dItem;
const primaryOpenLabel = primaryOpenItem.label;
const shouldRenderLiveViewer =
- isSelected && data.status === "completed" && Boolean(data.src);
+ isSelected && data.status === "completed" && Boolean(resolvedModelUrl);
const placeholderSizeScale = useMemo(
() => getNodeContentScale(node.size, { width: 360, height: 300 }, 0.76),
[node.size.height, node.size.width]
@@ -195,11 +208,11 @@ export function Model3DNode({
};
const handleOpenMesh = () => {
- if (!data.src) {
+ if (!resolvedModelUrl) {
toast.error("暂无可打开的 Mesh 地址");
return;
}
- window.open(data.src, "_blank", "noopener,noreferrer");
+ window.open(resolvedModelUrl, "_blank", "noopener,noreferrer");
};
const handleOpenImage = () => {
@@ -211,13 +224,13 @@ export function Model3DNode({
};
const handleDownload = () => {
- if (!data.src) {
+ if (!resolvedModelUrl) {
toast.error("暂无可下载的 3D 文件");
return;
}
const link = document.createElement("a");
- link.href = data.src;
- link.download = inferModelFileName(data, node.id);
+ link.href = resolvedModelUrl;
+ link.download = modelFileName;
link.click();
};
@@ -296,7 +309,7 @@ export function Model3DNode({
>
@@ -314,12 +327,12 @@ export function Model3DNode({
)}
- {inferModelFormat(data)}
+ {modelFormatLabel}
- {data.prompt || inferModelFileName(data, node.id)}
+ {data.prompt || modelFileName}
{data.modelVersion ? `模型: ${modelVersionLabel}` : providerLabel}
@@ -367,7 +380,7 @@ export function Model3DNode({
/>
{primaryOpenLabel}
- {worldUrl && data.src && data.src !== worldUrl && (
+ {worldUrl && resolvedModelUrl && resolvedModelUrl !== worldUrl && (
fetch(url).then((res) => res.json());
@@ -217,8 +221,15 @@ export function ModelSelector({