Browse Source

feat(46-03): remove header props from 9 node components (batch 1)

- EaseCurveNode.tsx
- Generate3DNode.tsx
- GenerateAudioNode.tsx
- GenerateImageNode.tsx
- GenerateVideoNode.tsx
- GLBViewerNode.tsx
- ImageCompareNode.tsx
- LLMGenerateNode.tsx
- OutputGalleryNode.tsx

Removed deprecated BaseNode props: title, customTitle, comment, onCustomTitleChange, onCommentChange, commentNavigation, headerAction, headerButtons, titlePrefix, onExpand, onRun

Also removed unused useCommentNavigation imports and variables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
8e98dc4b43
  1. 21
      src/components/nodes/EaseCurveNode.tsx
  2. 8
      src/components/nodes/GLBViewerNode.tsx
  3. 48
      src/components/nodes/Generate3DNode.tsx
  4. 26
      src/components/nodes/GenerateAudioNode.tsx
  5. 11
      src/components/nodes/GenerateImageNode.tsx
  6. 11
      src/components/nodes/GenerateVideoNode.tsx
  7. 12
      src/components/nodes/ImageCompareNode.tsx
  8. 9
      src/components/nodes/LLMGenerateNode.tsx
  9. 8
      src/components/nodes/OutputGalleryNode.tsx

21
src/components/nodes/EaseCurveNode.tsx

@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom";
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { EaseCurveNodeData } from "@/types";
import { checkEncoderSupport } from "@/hooks/useStitchVideos";
@ -40,7 +39,6 @@ const PRESET_NAMES = new Set(EASING_PRESETS);
export function EaseCurveNode({ id, data, selected }: NodeProps<EaseCurveNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const regenerateNode = useWorkflowStore((state) => state.regenerateNode);
const isRunning = useWorkflowStore((state) => state.isRunning);
@ -300,13 +298,7 @@ export function EaseCurveNode({ id, data, selected }: NodeProps<EaseCurveNodeTyp
return (
<BaseNode
id={id}
title="Ease Curve"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
selected={selected}
commentNavigation={commentNavigation ?? undefined}
minWidth={340}
minHeight={480}
>
@ -336,13 +328,7 @@ export function EaseCurveNode({ id, data, selected }: NodeProps<EaseCurveNodeTyp
return (
<BaseNode
id={id}
title="Ease Curve"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
selected={selected}
commentNavigation={commentNavigation ?? undefined}
minWidth={340}
minHeight={480}
>
@ -363,16 +349,9 @@ export function EaseCurveNode({ id, data, selected }: NodeProps<EaseCurveNodeTyp
return (
<BaseNode
id={id}
title="Ease Curve"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
onRun={handleRun}
selected={selected}
isExecuting={isRunning}
hasError={nodeData.status === "error"}
commentNavigation={commentNavigation ?? undefined}
minWidth={340}
minHeight={VIDEO_HEIGHT}
>

8
src/components/nodes/GLBViewerNode.tsx

@ -5,7 +5,6 @@ import { Handle, Position, NodeProps, Node } from "@xyflow/react";
import { Canvas, useThree, useFrame } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { useToast } from "@/components/Toast";
import { GLBViewerNodeData } from "@/types";
@ -202,7 +201,6 @@ function LoadingIndicator() {
export function GLBViewerNode({ id, data, selected }: NodeProps<GLBViewerNodeType>) {
const nodeData = data as GLBViewerNodeData;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const fileInputRef = useRef<HTMLInputElement>(null);
const captureRef = useRef<(() => string | null) | null>(null);
@ -360,13 +358,7 @@ export function GLBViewerNode({ id, data, selected }: NodeProps<GLBViewerNodeTyp
return (
<BaseNode
id={id}
title="3D"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
selected={selected}
commentNavigation={commentNavigation ?? undefined}
contentClassName={nodeData.glbUrl ? "flex-1 min-h-0 overflow-hidden flex flex-col" : undefined}
>
<input

48
src/components/nodes/Generate3DNode.tsx

@ -3,7 +3,6 @@
import React, { useCallback, useState, useEffect, useMemo, useRef } from "react";
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore, useProviderApiKeys } from "@/store/workflowStore";
import { Generate3DNodeData, ProviderType, SelectedModel, ModelInputDef } from "@/types";
@ -20,7 +19,6 @@ type Generate3DNodeType = Node<Generate3DNodeData, "generate3d">;
export function Generate3DNode({ id, data, selected }: NodeProps<Generate3DNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const { replicateApiKey, falApiKey, kieApiKey } = useProviderApiKeys();
const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false);
@ -88,43 +86,6 @@ export function Generate3DNode({ id, data, selected }: NodeProps<Generate3DNodeT
return "Select 3D model...";
}, [nodeData.selectedModel?.displayName, nodeData.selectedModel?.modelId]);
// Provider badge as title prefix
const titlePrefix = useMemo(() => (
<ProviderBadge provider={currentProvider} />
), [currentProvider]);
// Compute model page URL for external link
const modelPageUrl = useMemo(() => {
if (!nodeData.selectedModel?.modelId) return null;
return getModelPageUrl(currentProvider, nodeData.selectedModel.modelId);
}, [currentProvider, nodeData.selectedModel?.modelId]);
// Header action element - external link + browse button
const headerAction = useMemo(() => (
<>
{modelPageUrl && nodeData.selectedModel?.modelId && (
<a
href={modelPageUrl}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="nodrag nopan text-neutral-500 hover:text-neutral-300 transition-colors"
title={`View on ${getProviderDisplayName(currentProvider)}`}
>
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
)}
<button
onClick={() => setIsBrowseDialogOpen(true)}
className="nodrag nopan text-[10px] py-0.5 px-1.5 bg-neutral-700 hover:bg-neutral-600 border border-neutral-600 rounded text-neutral-300 transition-colors"
>
Browse
</button>
</>
), [modelPageUrl, nodeData.selectedModel?.modelId, currentProvider]);
// Track previous status to detect error transitions
const prevStatusRef = useRef(nodeData.status);
@ -144,18 +105,9 @@ export function Generate3DNode({ id, data, selected }: NodeProps<Generate3DNodeT
<>
<BaseNode
id={id}
title={displayTitle}
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
onRun={handleRegenerate}
selected={selected}
isExecuting={isRunning}
hasError={nodeData.status === "error"}
headerAction={headerAction}
titlePrefix={titlePrefix}
commentNavigation={commentNavigation ?? undefined}
>
{/* Dynamic input handles based on model schema */}
{nodeData.inputSchema && nodeData.inputSchema.length > 0 ? (

26
src/components/nodes/GenerateAudioNode.tsx

@ -4,7 +4,6 @@ import React, { useCallback, useState, useEffect, useMemo } from "react";
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { ProviderBadge } from "./ProviderBadge";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore } from "@/store/workflowStore";
import { GenerateAudioNodeData, ProviderType, SelectedModel, ModelInputDef } from "@/types";
@ -17,7 +16,6 @@ type GenerateAudioNodeType = Node<GenerateAudioNodeData, "generateAudio">;
export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudioNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const generationsPath = useWorkflowStore((state) => state.generationsPath);
const [isBrowseDialogOpen, setIsBrowseDialogOpen] = useState(false);
@ -191,21 +189,6 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudi
return "Generate Audio";
}, [nodeData.selectedModel?.displayName, nodeData.selectedModel?.modelId]);
// Provider badge as title prefix
const titlePrefix = useMemo(() => (
<ProviderBadge provider={currentProvider} />
), [currentProvider]);
// Header action element - browse button
const headerAction = useMemo(() => (
<button
onClick={() => setIsBrowseDialogOpen(true)}
className="nodrag nopan text-[10px] py-0.5 px-1.5 bg-neutral-700 hover:bg-neutral-600 border border-neutral-600 rounded text-neutral-300 transition-colors"
>
Browse
</button>
), []);
// Dynamic handles based on inputSchema
const dynamicHandles = useMemo(() => {
if (!nodeData.inputSchema || nodeData.inputSchema.length === 0) return null;
@ -233,18 +216,9 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudi
<>
<BaseNode
id={id}
title={displayTitle}
titlePrefix={titlePrefix}
headerAction={headerAction}
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
onRun={handleRegenerate}
selected={selected}
isExecuting={isRunning}
hasError={nodeData.status === "error"}
commentNavigation={commentNavigation ?? undefined}
minWidth={300}
minHeight={250}
>

11
src/components/nodes/GenerateImageNode.tsx

@ -3,7 +3,6 @@
import React, { useCallback, useState, useEffect, useMemo, useRef } from "react";
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore, saveNanoBananaDefaults, useProviderApiKeys } from "@/store/workflowStore";
import { deduplicatedFetch } from "@/utils/deduplicatedFetch";
@ -39,7 +38,6 @@ type NanoBananaNodeType = Node<NanoBananaNodeData, "nanoBanana">;
export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const generationsPath = useWorkflowStore((state) => state.generationsPath);
// Use stable selector for API keys to prevent unnecessary re-fetches
@ -485,18 +483,9 @@ export function GenerateImageNode({ id, data, selected }: NodeProps<NanoBananaNo
<>
<BaseNode
id={id}
title={displayTitle}
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
onRun={handleRegenerate}
selected={selected}
isExecuting={isRunning}
hasError={nodeData.status === "error"}
headerAction={headerAction}
titlePrefix={titlePrefix}
commentNavigation={commentNavigation ?? undefined}
>
{/* Input handles - ALWAYS use same IDs and positions for connection stability */}
{/* Image input at 35%, Text input at 65% - never changes regardless of model */}

11
src/components/nodes/GenerateVideoNode.tsx

@ -3,7 +3,6 @@
import React, { useCallback, useState, useEffect, useMemo, useRef } from "react";
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { ModelParameters } from "./ModelParameters";
import { useWorkflowStore, useProviderApiKeys } from "@/store/workflowStore";
import { deduplicatedFetch } from "@/utils/deduplicatedFetch";
@ -48,7 +47,6 @@ type GenerateVideoNodeType = Node<GenerateVideoNodeData, "generateVideo">;
export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVideoNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
// Use stable selector for API keys to prevent unnecessary re-fetches
const { geminiApiKey, replicateApiKey, falApiKey, kieApiKey, replicateEnabled, kieEnabled } = useProviderApiKeys();
@ -414,18 +412,9 @@ export function GenerateVideoNode({ id, data, selected }: NodeProps<GenerateVide
<>
<BaseNode
id={id}
title={displayTitle}
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
onRun={handleRegenerate}
selected={selected}
isExecuting={isRunning}
hasError={nodeData.status === "error"}
headerAction={headerAction}
titlePrefix={titlePrefix}
commentNavigation={commentNavigation ?? undefined}
>
{/* Dynamic input handles based on model schema */}
{nodeData.inputSchema && nodeData.inputSchema.length > 0 ? (

12
src/components/nodes/ImageCompareNode.tsx

@ -7,7 +7,6 @@ import {
ReactCompareSliderImage,
} from "react-compare-slider";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { ImageCompareNodeData } from "@/types";
@ -19,7 +18,6 @@ export function ImageCompareNode({
selected,
}: NodeProps<ImageCompareNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const edges = useWorkflowStore((state) => state.edges);
const nodes = useWorkflowStore((state) => state.nodes);
@ -66,18 +64,8 @@ export function ImageCompareNode({
return (
<BaseNode
id={id}
title="Image Compare"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) =>
updateNodeData(id, { customTitle: title || undefined })
}
onCommentChange={(comment) =>
updateNodeData(id, { comment: comment || undefined })
}
selected={selected}
className="min-w-[200px]"
commentNavigation={commentNavigation ?? undefined}
>
{/* Two labeled image input handles */}
<Handle

9
src/components/nodes/LLMGenerateNode.tsx

@ -3,7 +3,6 @@
import { useCallback, useState } from "react";
import { Handle, Position, NodeProps, Node } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { LLMGenerateNodeData, LLMProvider, LLMModelType } from "@/types";
@ -35,7 +34,6 @@ type LLMGenerateNodeType = Node<LLMGenerateNodeData, "llmGenerate">;
export function LLMGenerateNode({ id, data, selected }: NodeProps<LLMGenerateNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const handleProviderChange = useCallback(
@ -112,15 +110,8 @@ export function LLMGenerateNode({ id, data, selected }: NodeProps<LLMGenerateNod
return (
<BaseNode
id={id}
title="LLM Generate"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
selected={selected}
hasError={nodeData.status === "error"}
commentNavigation={commentNavigation ?? undefined}
onRun={handleRegenerate}
isExecuting={isRunning}
>
{/* Image input - optional */}

8
src/components/nodes/OutputGalleryNode.tsx

@ -4,7 +4,6 @@ import { useState, useCallback, useMemo, useEffect } from "react";
import { createPortal } from "react-dom";
import { Handle, Position, NodeProps, Node } from "@xyflow/react";
import { BaseNode } from "./BaseNode";
import { useCommentNavigation } from "@/hooks/useCommentNavigation";
import { useWorkflowStore } from "@/store/workflowStore";
import { OutputGalleryNodeData } from "@/types";
@ -12,7 +11,6 @@ type OutputGalleryNodeType = Node<OutputGalleryNodeData, "outputGallery">;
export function OutputGalleryNode({ id, data, selected }: NodeProps<OutputGalleryNodeType>) {
const nodeData = data;
const commentNavigation = useCommentNavigation(id);
const updateNodeData = useWorkflowStore((state) => state.updateNodeData);
const edges = useWorkflowStore((state) => state.edges);
const nodes = useWorkflowStore((state) => state.nodes);
@ -113,14 +111,8 @@ export function OutputGalleryNode({ id, data, selected }: NodeProps<OutputGaller
<>
<BaseNode
id={id}
title="Output Gallery"
customTitle={nodeData.customTitle}
comment={nodeData.comment}
onCustomTitleChange={(title) => updateNodeData(id, { customTitle: title || undefined })}
onCommentChange={(comment) => updateNodeData(id, { comment: comment || undefined })}
selected={selected}
className="min-w-[200px]"
commentNavigation={commentNavigation ?? undefined}
>
<Handle
type="target"

Loading…
Cancel
Save