Save current image generation workspace
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import type {
|
||||
CanvasConfigNodeMetadata,
|
||||
CanvasImageNodeMetadata,
|
||||
CanvasNode,
|
||||
CanvasNodeType,
|
||||
CanvasPosition,
|
||||
} from "@/lib/canvas/types";
|
||||
import type { HistoryItem } from "@/lib/image-workflow";
|
||||
|
||||
export const DEFAULT_NODE_WIDTH = 280;
|
||||
export const DEFAULT_NODE_HEIGHT = 170;
|
||||
export const DEFAULT_IMAGE_WIDTH = 320;
|
||||
export const DEFAULT_IMAGE_HEIGHT = 240;
|
||||
|
||||
export const defaultCanvasConfig: CanvasConfigNodeMetadata = {
|
||||
model: "gpt-image-2-2k",
|
||||
size: "1024x1024",
|
||||
quality: "high",
|
||||
outputFormat: "png",
|
||||
preserveIdentity: true,
|
||||
};
|
||||
|
||||
export function createCanvasNode(
|
||||
type: CanvasNodeType,
|
||||
position: CanvasPosition,
|
||||
): CanvasNode {
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
if (type === "prompt") {
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
title: "提示词",
|
||||
position,
|
||||
width: DEFAULT_NODE_WIDTH,
|
||||
height: DEFAULT_NODE_HEIGHT,
|
||||
metadata: {
|
||||
prompt: "描述你想生成的画面...",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "config") {
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
title: "生成配置",
|
||||
position,
|
||||
width: DEFAULT_NODE_WIDTH,
|
||||
height: DEFAULT_NODE_HEIGHT,
|
||||
metadata: { ...defaultCanvasConfig },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
title: "参考图片",
|
||||
position,
|
||||
width: DEFAULT_IMAGE_WIDTH,
|
||||
height: DEFAULT_IMAGE_HEIGHT,
|
||||
metadata: {
|
||||
imageUrl: "",
|
||||
mode: "reference",
|
||||
status: "idle",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createImageNodeFromHistory(
|
||||
item: HistoryItem,
|
||||
position: CanvasPosition,
|
||||
): CanvasNode {
|
||||
const size = getImageNodeSize(item);
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
type: "image",
|
||||
title: "历史图片",
|
||||
position,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
metadata: {
|
||||
historyId: item.id,
|
||||
imageUrl: item.imageUrl,
|
||||
mode: "reference",
|
||||
prompt: item.prompt,
|
||||
model: item.model,
|
||||
size: item.size,
|
||||
quality: item.quality,
|
||||
outputFormat: item.outputFormat,
|
||||
status: "success",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createImageNodeFromUpload(
|
||||
file: File,
|
||||
imageUrl: string,
|
||||
position: CanvasPosition,
|
||||
): CanvasNode {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
type: "image",
|
||||
title: file.name || "上传图片",
|
||||
position,
|
||||
width: DEFAULT_IMAGE_WIDTH,
|
||||
height: DEFAULT_IMAGE_HEIGHT,
|
||||
metadata: {
|
||||
imageUrl,
|
||||
mode: "reference",
|
||||
status: "success",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createImageResultNode(
|
||||
result: HistoryItem,
|
||||
position: CanvasPosition,
|
||||
): CanvasNode {
|
||||
const size = getImageNodeSize(result);
|
||||
const metadata: CanvasImageNodeMetadata = {
|
||||
imageUrl: result.imageUrl,
|
||||
mode: "result",
|
||||
historyId: result.id,
|
||||
prompt: result.prompt,
|
||||
model: result.model,
|
||||
size: result.size,
|
||||
quality: result.quality,
|
||||
outputFormat: result.outputFormat,
|
||||
status: "success",
|
||||
};
|
||||
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
type: "image",
|
||||
title: "生成结果",
|
||||
position,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
function getImageNodeSize(item: Pick<HistoryItem, "size">) {
|
||||
const match = /^(\d+)x(\d+)$/.exec(item.size || "");
|
||||
if (!match) {
|
||||
return { width: DEFAULT_IMAGE_WIDTH, height: DEFAULT_IMAGE_HEIGHT };
|
||||
}
|
||||
|
||||
const naturalWidth = Number(match[1]);
|
||||
const naturalHeight = Number(match[2]);
|
||||
if (!naturalWidth || !naturalHeight) {
|
||||
return { width: DEFAULT_IMAGE_WIDTH, height: DEFAULT_IMAGE_HEIGHT };
|
||||
}
|
||||
|
||||
const maxSide = 360;
|
||||
const minSide = 180;
|
||||
const scale = Math.min(maxSide / naturalWidth, maxSide / naturalHeight, 1);
|
||||
const imageWidth = Math.max(minSide, Math.round(naturalWidth * scale));
|
||||
const imageHeight = Math.max(minSide, Math.round(naturalHeight * scale));
|
||||
const padding = 16;
|
||||
const headerHeight = 34;
|
||||
const gap = 12;
|
||||
|
||||
return {
|
||||
width: imageWidth + padding * 2,
|
||||
height: imageHeight + padding * 2 + headerHeight + gap,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user