Browse Source

refactor: remove 3D code from GenerateImageNode and nanoBananaExecutor

- Remove text-to-3d/image-to-3d from IMAGE_CAPABILITIES
- Remove is3D memo and conditional handle/label styling
- Remove 3D output preview block
- Remove output3dUrl from model change handlers
- Remove is3DModel detection and mediaType from nanoBanana executor
- Remove 3D response handling from nanoBanana executor
- Remove output3dUrl from NanoBananaNodeData and its defaults
- Remove 3D fallback from nanoBanana in connectedInputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
71776b9464
  1. 27
      src/components/nodes/GenerateImageNode.tsx
  2. 40
      src/store/execution/nanoBananaExecutor.ts
  3. 3
      src/store/utils/connectedInputs.ts
  4. 1
      src/store/utils/nodeDefaults.ts
  5. 1
      src/types/nodes.ts

27
src/components/nodes/GenerateImageNode.tsx

@ -61,7 +61,7 @@ const GEMINI_IMAGE_MODELS: { value: ModelType; label: string }[] = [
]; ];
// Image generation capabilities // Image generation capabilities
const IMAGE_CAPABILITIES: ModelCapability[] = ["text-to-image", "image-to-image", "text-to-3d", "image-to-3d"]; const IMAGE_CAPABILITIES: ModelCapability[] = ["text-to-image", "image-to-image"];
type NanoBananaNodeType = Node<NanoBananaNodeData, "nanoBanana">; type NanoBananaNodeType = Node<NanoBananaNodeData, "nanoBanana">;
@ -212,8 +212,8 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
displayName: model.name, displayName: model.name,
capabilities: model.capabilities, capabilities: model.capabilities,
}; };
// Clear parameters and 3D output when changing models (different models have different schemas) // Clear parameters when changing models (different models have different schemas)
updateNodeData(id, { selectedModel: newSelectedModel, parameters: {}, output3dUrl: null }); updateNodeData(id, { selectedModel: newSelectedModel, parameters: {} });
} }
}, },
[id, currentProvider, externalModels, updateNodeData] [id, currentProvider, externalModels, updateNodeData]
@ -386,12 +386,11 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
displayName: model.name, displayName: model.name,
capabilities: model.capabilities, capabilities: model.capabilities,
}; };
updateNodeData(id, { selectedModel: newSelectedModel, parameters: {}, output3dUrl: null }); updateNodeData(id, { selectedModel: newSelectedModel, parameters: {} });
setIsBrowseDialogOpen(false); setIsBrowseDialogOpen(false);
}, [id, updateNodeData]); }, [id, updateNodeData]);
const isGeminiProvider = currentProvider === "gemini"; const isGeminiProvider = currentProvider === "gemini";
const is3D = useMemo(() => nodeData.selectedModel?.capabilities?.some((c: string) => c.includes("3d")) ?? false, [nodeData.selectedModel]);
// Dynamic title based on selected model - just the model name // Dynamic title based on selected model - just the model name
const displayTitle = useMemo(() => { const displayTitle = useMemo(() => {
@ -533,13 +532,13 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
> >
Prompt Prompt
</div> </div>
{/* Output handle — always "image" ID for stable connections; visual style changes for 3D */} {/* Output handle */}
<Handle <Handle
type="source" type="source"
position={Position.Right} position={Position.Right}
id="image" id="image"
style={{ top: "50%" }} style={{ top: "50%" }}
data-handletype={is3D ? "3d" : "image"} data-handletype="image"
/> />
{/* Output label */} {/* Output label */}
<div <div
@ -547,23 +546,15 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
style={{ style={{
left: `calc(100% + 8px)`, left: `calc(100% + 8px)`,
top: "calc(50% - 18px)", top: "calc(50% - 18px)",
color: is3D ? "var(--handle-color-3d)" : "var(--handle-color-image)", color: "var(--handle-color-image)",
}} }}
> >
{is3D ? "3D" : "Image"} Image
</div> </div>
<div className="flex-1 flex flex-col min-h-0 gap-2"> <div className="flex-1 flex flex-col min-h-0 gap-2">
{/* Preview area */} {/* Preview area */}
{nodeData.output3dUrl ? ( {nodeData.outputImage ? (
<div className="w-full flex-1 min-h-[80px] flex flex-col items-center justify-center gap-2 bg-neutral-800 rounded border border-neutral-700 p-3">
<svg className="w-8 h-8 text-orange-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" />
</svg>
<span className="text-[11px] text-orange-400 font-medium">3D Model Generated</span>
<span className="text-[10px] text-neutral-500 truncate max-w-full">Connect to 3D Viewer</span>
</div>
) : nodeData.outputImage ? (
<> <>
<div className="relative w-full flex-1 min-h-0"> <div className="relative w-full flex-1 min-h-0">
<img <img

40
src/store/execution/nanoBananaExecutor.ts

@ -80,9 +80,6 @@ export async function executeNanoBanana(
const provider = nodeData.selectedModel?.provider || "gemini"; const provider = nodeData.selectedModel?.provider || "gemini";
const headers = buildGenerateHeaders(provider, providerSettings); const headers = buildGenerateHeaders(provider, providerSettings);
// Determine mediaType from model capabilities
const is3DModel = nodeData.selectedModel?.capabilities?.some((c: string) => c.includes("3d"));
const requestPayload = { const requestPayload = {
images, images,
prompt: promptText, prompt: promptText,
@ -93,7 +90,6 @@ export async function executeNanoBanana(
selectedModel: nodeData.selectedModel, selectedModel: nodeData.selectedModel,
parameters: nodeData.parameters, parameters: nodeData.parameters,
dynamicInputs, dynamicInputs,
...(is3DModel ? { mediaType: "3d" as const } : {}),
}; };
try { try {
@ -123,41 +119,6 @@ export async function executeNanoBanana(
const result = await response.json(); const result = await response.json();
// Handle 3D model response
if (result.success && result.model3dUrl) {
updateNodeData(node.id, {
output3dUrl: result.model3dUrl,
outputImage: null,
status: "complete",
error: null,
});
// Track cost if applicable
if (nodeData.selectedModel?.provider === "fal" && nodeData.selectedModel?.pricing) {
addIncurredCost(nodeData.selectedModel.pricing.amount);
}
// Auto-save 3D model to generations folder if configured
if (generationsPath) {
const savePromise = fetch("/api/save-generation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
directoryPath: generationsPath,
model3d: result.model3dUrl,
prompt: promptText,
}),
})
.then((res) => res.json())
.catch((err) => {
console.error("Failed to save 3D model:", err);
});
trackSaveGeneration(`3d-${Date.now()}`, savePromise);
}
return;
}
if (result.success && result.image) { if (result.success && result.image) {
const timestamp = Date.now(); const timestamp = Date.now();
const imageId = `${timestamp}`; const imageId = `${timestamp}`;
@ -183,7 +144,6 @@ export async function executeNanoBanana(
updateNodeData(node.id, { updateNodeData(node.id, {
outputImage: result.image, outputImage: result.image,
output3dUrl: null,
status: "complete", status: "complete",
error: null, error: null,
imageHistory: updatedHistory, imageHistory: updatedHistory,

3
src/store/utils/connectedInputs.ts

@ -63,9 +63,6 @@ function getSourceOutput(sourceNode: WorkflowNode): { type: "image" | "text" | "
return { type: "image", value: (sourceNode.data as AnnotationNodeData).outputImage }; return { type: "image", value: (sourceNode.data as AnnotationNodeData).outputImage };
} else if (sourceNode.type === "nanoBanana") { } else if (sourceNode.type === "nanoBanana") {
const nbData = sourceNode.data as NanoBananaNodeData; const nbData = sourceNode.data as NanoBananaNodeData;
if (nbData.output3dUrl) {
return { type: "3d", value: nbData.output3dUrl };
}
return { type: "image", value: nbData.outputImage }; return { type: "image", value: nbData.outputImage };
} else if (sourceNode.type === "generate3d") { } else if (sourceNode.type === "generate3d") {
const g3dData = sourceNode.data as Generate3DNodeData; const g3dData = sourceNode.data as Generate3DNodeData;

1
src/store/utils/nodeDefaults.ts

@ -123,7 +123,6 @@ export const createDefaultNodeData = (type: NodeType): WorkflowNodeData => {
inputImages: [], inputImages: [],
inputPrompt: null, inputPrompt: null,
outputImage: null, outputImage: null,
output3dUrl: null,
aspectRatio, aspectRatio,
resolution, resolution,
model: legacyDefaults.model, // Keep legacy model field for backward compat model: legacyDefaults.model, // Keep legacy model field for backward compat

1
src/types/nodes.ts

@ -151,7 +151,6 @@ export interface NanoBananaNodeData extends BaseNodeData {
useGoogleSearch: boolean; // Only available for Nano Banana Pro useGoogleSearch: boolean; // Only available for Nano Banana Pro
parameters?: Record<string, unknown>; // Model-specific parameters for external providers parameters?: Record<string, unknown>; // Model-specific parameters for external providers
inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles
output3dUrl?: string | null; // URL to generated 3D GLB model
status: NodeStatus; status: NodeStatus;
error: string | null; error: string | null;
imageHistory: CarouselImageItem[]; // Carousel history (IDs only) imageHistory: CarouselImageItem[]; // Carousel history (IDs only)

Loading…
Cancel
Save