You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
216 lines
6.1 KiB
216 lines
6.1 KiB
import {
|
|
NodeType,
|
|
ImageInputNodeData,
|
|
AudioInputNodeData,
|
|
AnnotationNodeData,
|
|
PromptNodeData,
|
|
PromptConstructorNodeData,
|
|
NanoBananaNodeData,
|
|
GenerateVideoNodeData,
|
|
LLMGenerateNodeData,
|
|
SplitGridNodeData,
|
|
OutputNodeData,
|
|
OutputGalleryNodeData,
|
|
ImageCompareNodeData,
|
|
EaseCurveNodeData,
|
|
WorkflowNodeData,
|
|
GroupColor,
|
|
SelectedModel,
|
|
} from "@/types";
|
|
import { loadGenerateImageDefaults, loadNodeDefaults } from "./localStorage";
|
|
|
|
/**
|
|
* Default dimensions for each node type.
|
|
* Used in addNode and createGroup for consistent sizing.
|
|
*/
|
|
export const defaultNodeDimensions: Record<NodeType, { width: number; height: number }> = {
|
|
imageInput: { width: 300, height: 280 },
|
|
audioInput: { width: 300, height: 200 },
|
|
annotation: { width: 300, height: 280 },
|
|
prompt: { width: 320, height: 220 },
|
|
promptConstructor: { width: 340, height: 280 },
|
|
nanoBanana: { width: 300, height: 300 },
|
|
generateVideo: { width: 300, height: 300 },
|
|
llmGenerate: { width: 320, height: 360 },
|
|
splitGrid: { width: 300, height: 320 },
|
|
output: { width: 320, height: 320 },
|
|
outputGallery: { width: 320, height: 360 },
|
|
imageCompare: { width: 400, height: 360 },
|
|
videoStitch: { width: 400, height: 280 },
|
|
easeCurve: { width: 340, height: 480 },
|
|
};
|
|
|
|
/**
|
|
* Group color palette (dark mode tints).
|
|
*/
|
|
export const GROUP_COLORS: Record<GroupColor, string> = {
|
|
neutral: "#262626",
|
|
blue: "#1e3a5f",
|
|
green: "#1a3d2e",
|
|
purple: "#2d2458",
|
|
orange: "#3d2a1a",
|
|
red: "#3d1a1a",
|
|
};
|
|
|
|
/**
|
|
* Order in which group colors are assigned.
|
|
*/
|
|
export const GROUP_COLOR_ORDER: GroupColor[] = [
|
|
"neutral", "blue", "green", "purple", "orange", "red"
|
|
];
|
|
|
|
/**
|
|
* Creates default data for a node based on its type.
|
|
*/
|
|
export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
|
|
switch (type) {
|
|
case "imageInput":
|
|
return {
|
|
image: null,
|
|
filename: null,
|
|
dimensions: null,
|
|
} as ImageInputNodeData;
|
|
case "audioInput":
|
|
return {
|
|
audioFile: null,
|
|
filename: null,
|
|
duration: null,
|
|
format: null,
|
|
} as AudioInputNodeData;
|
|
case "annotation":
|
|
return {
|
|
sourceImage: null,
|
|
annotations: [],
|
|
outputImage: null,
|
|
} as AnnotationNodeData;
|
|
case "prompt":
|
|
return {
|
|
prompt: "",
|
|
} as PromptNodeData;
|
|
case "promptConstructor":
|
|
return {
|
|
template: "",
|
|
outputText: null,
|
|
unresolvedVars: [],
|
|
} as PromptConstructorNodeData;
|
|
case "nanoBanana": {
|
|
const nodeDefaults = loadNodeDefaults();
|
|
const legacyDefaults = loadGenerateImageDefaults();
|
|
|
|
// Determine selectedModel: prefer new nodeDefaults, fallback to legacy
|
|
let selectedModel: SelectedModel;
|
|
if (nodeDefaults.generateImage?.selectedModel) {
|
|
selectedModel = nodeDefaults.generateImage.selectedModel;
|
|
} else {
|
|
const modelDisplayName = legacyDefaults.model === "nano-banana" ? "Nano Banana" : "Nano Banana Pro";
|
|
selectedModel = {
|
|
provider: "gemini",
|
|
modelId: legacyDefaults.model,
|
|
displayName: modelDisplayName,
|
|
};
|
|
}
|
|
|
|
// Merge settings: new nodeDefaults override legacy defaults
|
|
const aspectRatio = nodeDefaults.generateImage?.aspectRatio ?? legacyDefaults.aspectRatio;
|
|
const resolution = nodeDefaults.generateImage?.resolution ?? legacyDefaults.resolution;
|
|
const useGoogleSearch = nodeDefaults.generateImage?.useGoogleSearch ?? legacyDefaults.useGoogleSearch;
|
|
|
|
return {
|
|
inputImages: [],
|
|
inputPrompt: null,
|
|
outputImage: null,
|
|
aspectRatio,
|
|
resolution,
|
|
model: legacyDefaults.model, // Keep legacy model field for backward compat
|
|
selectedModel,
|
|
useGoogleSearch,
|
|
status: "idle",
|
|
error: null,
|
|
imageHistory: [],
|
|
selectedHistoryIndex: 0,
|
|
} as NanoBananaNodeData;
|
|
}
|
|
case "generateVideo": {
|
|
const nodeDefaults = loadNodeDefaults();
|
|
return {
|
|
inputImages: [],
|
|
inputPrompt: null,
|
|
outputVideo: null,
|
|
selectedModel: nodeDefaults.generateVideo?.selectedModel,
|
|
status: "idle",
|
|
error: null,
|
|
videoHistory: [],
|
|
selectedVideoHistoryIndex: 0,
|
|
} as GenerateVideoNodeData;
|
|
}
|
|
case "llmGenerate": {
|
|
const nodeDefaults = loadNodeDefaults();
|
|
const llmDefaults = nodeDefaults.llm;
|
|
return {
|
|
inputPrompt: null,
|
|
inputImages: [],
|
|
outputText: null,
|
|
provider: llmDefaults?.provider ?? "google",
|
|
model: llmDefaults?.model ?? "gemini-3-flash-preview",
|
|
temperature: llmDefaults?.temperature ?? 0.7,
|
|
maxTokens: llmDefaults?.maxTokens ?? 8192,
|
|
status: "idle",
|
|
error: null,
|
|
} as LLMGenerateNodeData;
|
|
}
|
|
case "splitGrid":
|
|
return {
|
|
sourceImage: null,
|
|
targetCount: 6,
|
|
defaultPrompt: "",
|
|
generateSettings: {
|
|
aspectRatio: "1:1",
|
|
resolution: "1K",
|
|
model: "nano-banana-pro",
|
|
useGoogleSearch: false,
|
|
},
|
|
childNodeIds: [],
|
|
gridRows: 2,
|
|
gridCols: 3,
|
|
isConfigured: false,
|
|
status: "idle",
|
|
error: null,
|
|
} as SplitGridNodeData;
|
|
case "output":
|
|
return {
|
|
image: null,
|
|
outputFilename: "",
|
|
} as OutputNodeData;
|
|
case "outputGallery":
|
|
return {
|
|
images: [],
|
|
} as OutputGalleryNodeData;
|
|
case "imageCompare":
|
|
return {
|
|
imageA: null,
|
|
imageB: null,
|
|
} as ImageCompareNodeData;
|
|
case "videoStitch":
|
|
return {
|
|
clips: [],
|
|
clipOrder: [],
|
|
outputVideo: null,
|
|
status: "idle",
|
|
error: null,
|
|
progress: 0,
|
|
encoderSupported: null,
|
|
};
|
|
case "easeCurve":
|
|
return {
|
|
bezierHandles: [0.445, 0.05, 0.55, 0.95], // easeInOutSine preset
|
|
easingPreset: "easeInOutSine",
|
|
inheritedFrom: null,
|
|
outputDuration: 1.5,
|
|
outputVideo: null,
|
|
status: "idle",
|
|
error: null,
|
|
progress: 0,
|
|
encoderSupported: null,
|
|
} as EaseCurveNodeData;
|
|
}
|
|
};
|
|
|