- Removed unused hooks and optimized edge handling in the canvas component. - Adjusted positioning of handles in the compare node for better alignment. - Enhanced prompt node to utilize incoming edges for dynamic prompt generation and improved user feedback. - Updated text node to synchronize content changes with the React Flow state. - Improved logging in edge removal to handle idempotent operations gracefully.
86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
/** OpenRouter / Gemini image_config.aspect_ratio values */
|
|
export const DEFAULT_ASPECT_RATIO = "1:1" as const;
|
|
|
|
export type ImageFormatGroup = "square" | "landscape" | "portrait";
|
|
|
|
export type ImageFormatPreset = {
|
|
label: string;
|
|
aspectRatio: string;
|
|
group: ImageFormatGroup;
|
|
};
|
|
|
|
export const IMAGE_FORMAT_GROUP_LABELS: Record<ImageFormatGroup, string> = {
|
|
square: "Quadratisch",
|
|
landscape: "Querformat",
|
|
portrait: "Hochformat",
|
|
};
|
|
|
|
/** Presets for Prompt Node Select (labels DE, ratios API-compatible) */
|
|
export const IMAGE_FORMAT_PRESETS: ImageFormatPreset[] = [
|
|
{ label: "1:1 · Quadrat", aspectRatio: "1:1", group: "square" },
|
|
{ label: "16:9 · Breitbild", aspectRatio: "16:9", group: "landscape" },
|
|
{ label: "21:9 · Kino", aspectRatio: "21:9", group: "landscape" },
|
|
{ label: "4:3 · Klassisch", aspectRatio: "4:3", group: "landscape" },
|
|
{ label: "3:2 · Foto (quer)", aspectRatio: "3:2", group: "landscape" },
|
|
{ label: "5:4 · leicht quer", aspectRatio: "5:4", group: "landscape" },
|
|
{ label: "9:16 · Story", aspectRatio: "9:16", group: "portrait" },
|
|
{ label: "3:4 · Porträt", aspectRatio: "3:4", group: "portrait" },
|
|
{ label: "2:3 · Foto (hoch)", aspectRatio: "2:3", group: "portrait" },
|
|
{ label: "4:5 · Social hoch", aspectRatio: "4:5", group: "portrait" },
|
|
];
|
|
|
|
/** Header row + footer strip (prompt preview) inside AI Image node */
|
|
export const AI_IMAGE_NODE_HEADER_PX = 40;
|
|
export const AI_IMAGE_NODE_FOOTER_PX = 48;
|
|
|
|
export function parseAspectRatioString(aspectRatio: string): {
|
|
w: number;
|
|
h: number;
|
|
} {
|
|
const parts = aspectRatio.split(":").map((x) => Number.parseInt(x, 10));
|
|
if (
|
|
parts.length !== 2 ||
|
|
parts.some((n) => !Number.isFinite(n) || n <= 0)
|
|
) {
|
|
throw new Error(`Invalid aspect ratio: ${aspectRatio}`);
|
|
}
|
|
return { w: parts[0]!, h: parts[1]! };
|
|
}
|
|
|
|
/** Bildfläche: längere Kante = maxEdgePx */
|
|
export function getImageViewportSize(
|
|
aspectRatio: string,
|
|
options?: { maxEdge?: number }
|
|
): { width: number; height: number } {
|
|
const maxEdge = options?.maxEdge ?? 320;
|
|
const { w, h } = parseAspectRatioString(aspectRatio);
|
|
if (w >= h) {
|
|
return {
|
|
width: maxEdge,
|
|
height: Math.max(1, Math.round(maxEdge * (h / w))),
|
|
};
|
|
}
|
|
return {
|
|
width: Math.max(1, Math.round(maxEdge * (w / h))),
|
|
height: maxEdge,
|
|
};
|
|
}
|
|
|
|
/** Outer Convex / React Flow node size (includes chrome) */
|
|
export function getAiImageNodeOuterSize(viewport: {
|
|
width: number;
|
|
height: number;
|
|
}): { width: number; height: number } {
|
|
return {
|
|
width: viewport.width,
|
|
height: AI_IMAGE_NODE_HEADER_PX + viewport.height + AI_IMAGE_NODE_FOOTER_PX,
|
|
};
|
|
}
|
|
|
|
export function getPresetLabel(aspectRatio: string): string {
|
|
return (
|
|
IMAGE_FORMAT_PRESETS.find((p) => p.aspectRatio === aspectRatio)?.label ??
|
|
aspectRatio
|
|
);
|
|
}
|