diff --git a/frontend/src/components/graph/AnimatedEdge.tsx b/frontend/src/components/graph/AnimatedEdge.tsx index 742c25a..65ceaf5 100644 --- a/frontend/src/components/graph/AnimatedEdge.tsx +++ b/frontend/src/components/graph/AnimatedEdge.tsx @@ -7,6 +7,7 @@ import { import { useCanvasStore } from '@/app/canvas/canvasStore' import { selectConnectionStatusForEdge } from '@/app/canvas/canvasStore.selectors' import { CONNECTION_STATUS_CLASS } from '@/lib/graph/connectionStatus' +import { getConnectionLabelForTargetAndStatus } from '@/lib/graph/nodeRegistry' const EDGE_STROKE_WIDTH = 2 const DOT_MARKER_R = 1.5 @@ -31,7 +32,10 @@ function AnimatedEdgeInner({ ) const statusClass = CONNECTION_STATUS_CLASS[connectionStatus] - const label = labelProp ?? (data as { connectionLabel?: string } | undefined)?.connectionLabel + const targetType = (data as { targetType?: string } | undefined)?.targetType ?? '' + const dataLabel = (data as { connectionLabel?: string } | undefined)?.connectionLabel + const displayLabel = + getConnectionLabelForTargetAndStatus(targetType, connectionStatus) ?? labelProp ?? dataLabel const [edgePath, edgeLabelX, edgeLabelY] = getBezierPath({ sourceX, @@ -92,7 +96,7 @@ function AnimatedEdgeInner({ className={`animated-edge-path${statusClass ? ` ${statusClass}` : ''}`} interactionWidth={interactionWidth} /> - {label != null && ( + {displayLabel != null && displayLabel !== '' && ( - {label} + {displayLabel} )} @@ -130,8 +134,10 @@ function edgePropsAreEqual(prev: EdgeProps, next: EdgeProps): boolean { prev.targetPosition === next.targetPosition && prev.style === next.style && prev.label === next.label && - (prev.data as { connectionLabel?: string } | undefined)?.connectionLabel === - (next.data as { connectionLabel?: string } | undefined)?.connectionLabel + (prev.data as { connectionLabel?: string; targetType?: string } | undefined)?.connectionLabel === + (next.data as { connectionLabel?: string; targetType?: string } | undefined)?.connectionLabel && + (prev.data as { targetType?: string } | undefined)?.targetType === + (next.data as { targetType?: string } | undefined)?.targetType ) } diff --git a/frontend/src/components/nodes/agent/descriptor.tsx b/frontend/src/components/nodes/agent/descriptor.tsx index 26f9c5a..50065f4 100644 --- a/frontend/src/components/nodes/agent/descriptor.tsx +++ b/frontend/src/components/nodes/agent/descriptor.tsx @@ -17,7 +17,7 @@ export function getAgentNodeDescriptor(): NodeTypeDescriptor { .allowedTargetTypes(['render']) .help(NODE_HELP.agent) .menu('Agent', ) - .connectionLabel('prompt/context') + .connectionLabelByStatus({ default: 'prompt/context', updating: 'running', paused: 'pending', error: 'corrupted' }) .withFullscreen() .sourceRenderingLogic(agentRenderingLogic) .outputMenuContent(() => null) diff --git a/frontend/src/components/nodes/config/descriptor.tsx b/frontend/src/components/nodes/config/descriptor.tsx index 348e7f4..e745c5f 100644 --- a/frontend/src/components/nodes/config/descriptor.tsx +++ b/frontend/src/components/nodes/config/descriptor.tsx @@ -82,7 +82,7 @@ export function getConfigNodeDescriptor(): NodeTypeDescriptor { content: '@startuml\n\n@enduml\n', title: nodeId ?? '', })) - .connectionLabel('adding input') + .connectionLabelByStatus({ default: 'adding input', updating: 'pushing new form', paused: 'pending', error: 'corrupted' }) .withFullscreen() .sourceRenderingLogic({ defaultUpdateMode: 'auto', diff --git a/frontend/src/components/nodes/data/descriptor.tsx b/frontend/src/components/nodes/data/descriptor.tsx index 12e0d4e..a58be1e 100644 --- a/frontend/src/components/nodes/data/descriptor.tsx +++ b/frontend/src/components/nodes/data/descriptor.tsx @@ -20,7 +20,7 @@ export function getDataNodeDescriptor(): NodeTypeDescriptor { .allowedTargetTypes(['config', 'agent']) .help(NODE_HELP.data) .menu('Data', ) - .connectionLabel('data source') + .connectionLabelByStatus({ default: 'data source' }) .withFullscreen() .build() } diff --git a/frontend/src/components/nodes/function/descriptor.tsx b/frontend/src/components/nodes/function/descriptor.tsx index 6bac6cb..2504cff 100644 --- a/frontend/src/components/nodes/function/descriptor.tsx +++ b/frontend/src/components/nodes/function/descriptor.tsx @@ -21,7 +21,7 @@ export function getFunctionNodeDescriptor(): NodeTypeDescriptor { .help(NODE_HELP.function) .menu('Function', ) .getResetData(() => ({ body: '' })) - .connectionLabel('adding input') + .connectionLabelByStatus({ default: 'adding input', updating: 'running', paused: 'pending', error: 'corrupted' }) .withFullscreen() .build() } diff --git a/frontend/src/components/nodes/render/descriptor.tsx b/frontend/src/components/nodes/render/descriptor.tsx index e5d5202..e756823 100644 --- a/frontend/src/components/nodes/render/descriptor.tsx +++ b/frontend/src/components/nodes/render/descriptor.tsx @@ -20,7 +20,7 @@ export function getRenderNodeDescriptor(): NodeTypeDescriptor { .allowedSourceTypes(['config', 'agent']) .help(NODE_HELP.render) .menu('Renderer', ) - .connectionLabel('rendering') + .connectionLabelByStatus({ default: 'listening', updating: 'giving life', paused: 'pending', error: 'corrupted' }) .withFullscreen() .build() } diff --git a/frontend/src/lib/graph/nodeRegistry.ts b/frontend/src/lib/graph/nodeRegistry.ts index b292de2..b7b2b14 100644 --- a/frontend/src/lib/graph/nodeRegistry.ts +++ b/frontend/src/lib/graph/nodeRegistry.ts @@ -5,6 +5,7 @@ */ import type React from 'react' +import type { ConnectionStatus } from './connectionStatus' import { registerSourceRenderingLogic } from './sourceRenderingLogic' import type { SourceRenderingLogic } from './sourceRenderingLogic' @@ -44,8 +45,8 @@ export type NodeTypeDescriptor = { getDefaultData?: (newId?: string) => Record /** Optional: data for Reset action; if omitted, getDefaultData(nodeId) or defaultData is used. */ getResetData?: (nodeId?: string) => Record - /** Optional: label shown on edge when this type is the target (e.g. "render", "add input"). */ - connectionLabel?: string + /** Optional: label per connection status shown on edge when this type is the target. */ + connectionLabelByStatus?: Partial> /** When true, double-clicking the node header opens a fullscreen dialog for this node. */ supportsFullscreen?: boolean /** @@ -157,9 +158,21 @@ export function isConnectionAllowed( return true } -/** Edge label when target is of this type (for AnimatedEdge). */ +/** Edge label when target is of this type (default status only; for edge data fallback). */ export function getConnectionLabelForTarget(targetType: string): string | undefined { - return getNodeType(targetType)?.connectionLabel + return getNodeType(targetType)?.connectionLabelByStatus?.['default'] +} + +/** Edge label for target type and connection status; falls back to default when status not in map. */ +export function getConnectionLabelForTargetAndStatus( + targetType: string, + status: ConnectionStatus +): string | undefined { + const desc = getNodeType(targetType) + if (!desc?.connectionLabelByStatus) return undefined + const byStatus = desc.connectionLabelByStatus[status] + if (byStatus !== undefined) return byStatus + return desc.connectionLabelByStatus['default'] } /** Help entry for a node type. Use in NodeHelpPopover. */ diff --git a/frontend/src/lib/graph/nodeTypeBuilder.ts b/frontend/src/lib/graph/nodeTypeBuilder.ts index 0f6a447..b952c12 100644 --- a/frontend/src/lib/graph/nodeTypeBuilder.ts +++ b/frontend/src/lib/graph/nodeTypeBuilder.ts @@ -14,6 +14,7 @@ */ import type React from 'react' +import type { ConnectionStatus } from './connectionStatus' import type { NodeTypeDescriptor, NodeClassification, NodeHelpEntry } from './nodeRegistry' import type { SourceRenderingLogic } from './sourceRenderingLogic' @@ -97,8 +98,8 @@ export class NodeTypeBuilder { return this } - connectionLabel(label: string): this { - this.partial.connectionLabel = label + connectionLabelByStatus(partial: Partial>): this { + this.partial.connectionLabelByStatus = partial return this }