mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
fix graph refresh issue
This commit is contained in:
@@ -398,6 +398,7 @@ function App() {
|
|||||||
<DependencyGraph
|
<DependencyGraph
|
||||||
graphData={graphData}
|
graphData={graphData}
|
||||||
onNodeClick={handleGraphNodeClick}
|
onNodeClick={handleGraphNodeClick}
|
||||||
|
activeAgents={wsState.activeAgents}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="h-full flex items-center justify-center">
|
<div className="h-full flex items-center justify-center">
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ import {
|
|||||||
} from '@xyflow/react'
|
} from '@xyflow/react'
|
||||||
import dagre from 'dagre'
|
import dagre from 'dagre'
|
||||||
import { CheckCircle2, Circle, Loader2, AlertTriangle, RefreshCw } from 'lucide-react'
|
import { CheckCircle2, Circle, Loader2, AlertTriangle, RefreshCw } from 'lucide-react'
|
||||||
import type { DependencyGraph as DependencyGraphData, GraphNode } from '../lib/types'
|
import type { DependencyGraph as DependencyGraphData, GraphNode, ActiveAgent, AgentMascot, AgentState } from '../lib/types'
|
||||||
|
import { AgentAvatar } from './AgentAvatar'
|
||||||
import '@xyflow/react/dist/style.css'
|
import '@xyflow/react/dist/style.css'
|
||||||
|
|
||||||
// Node dimensions
|
// Node dimensions
|
||||||
@@ -26,6 +27,13 @@ const NODE_HEIGHT = 80
|
|||||||
interface DependencyGraphProps {
|
interface DependencyGraphProps {
|
||||||
graphData: DependencyGraphData
|
graphData: DependencyGraphData
|
||||||
onNodeClick?: (nodeId: number) => void
|
onNodeClick?: (nodeId: number) => void
|
||||||
|
activeAgents?: ActiveAgent[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Agent info to display on a node
|
||||||
|
interface NodeAgentInfo {
|
||||||
|
name: AgentMascot
|
||||||
|
state: AgentState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error boundary to catch and recover from ReactFlow rendering errors
|
// Error boundary to catch and recover from ReactFlow rendering errors
|
||||||
@@ -85,7 +93,7 @@ class GraphErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom node component
|
// Custom node component
|
||||||
function FeatureNode({ data }: { data: GraphNode & { onClick?: () => void } }) {
|
function FeatureNode({ data }: { data: GraphNode & { onClick?: () => void; agent?: NodeAgentInfo } }) {
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
pending: 'bg-neo-pending border-neo-border',
|
pending: 'bg-neo-pending border-neo-border',
|
||||||
in_progress: 'bg-neo-progress border-neo-border',
|
in_progress: 'bg-neo-progress border-neo-border',
|
||||||
@@ -112,17 +120,31 @@ function FeatureNode({ data }: { data: GraphNode & { onClick?: () => void } }) {
|
|||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
px-4 py-3 rounded-lg border-2 cursor-pointer
|
px-4 py-3 rounded-lg border-2 cursor-pointer
|
||||||
transition-all hover:shadow-neo-md
|
transition-all hover:shadow-neo-md relative
|
||||||
${statusColors[data.status]}
|
${statusColors[data.status]}
|
||||||
`}
|
`}
|
||||||
onClick={data.onClick}
|
onClick={data.onClick}
|
||||||
style={{ minWidth: NODE_WIDTH - 20, maxWidth: NODE_WIDTH }}
|
style={{ minWidth: NODE_WIDTH - 20, maxWidth: NODE_WIDTH }}
|
||||||
>
|
>
|
||||||
|
{/* Agent avatar badge - positioned at top right */}
|
||||||
|
{data.agent && (
|
||||||
|
<div className="absolute -top-3 -right-3 z-10">
|
||||||
|
<div className="rounded-full border-2 border-neo-border bg-white shadow-neo-sm">
|
||||||
|
<AgentAvatar name={data.agent.name} state={data.agent.state} size="sm" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<StatusIcon />
|
<StatusIcon />
|
||||||
<span className="text-xs font-mono text-neo-text-on-bright/70">
|
<span className="text-xs font-mono text-neo-text-on-bright/70">
|
||||||
#{data.priority}
|
#{data.priority}
|
||||||
</span>
|
</span>
|
||||||
|
{/* Show agent name inline if present */}
|
||||||
|
{data.agent && (
|
||||||
|
<span className="text-xs font-bold text-neo-text-on-bright ml-auto">
|
||||||
|
{data.agent.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="font-bold text-sm text-neo-text-on-bright truncate" title={data.name}>
|
<div className="font-bold text-sm text-neo-text-on-bright truncate" title={data.name}>
|
||||||
{data.name}
|
{data.name}
|
||||||
@@ -184,7 +206,7 @@ function getLayoutedElements(
|
|||||||
return { nodes: layoutedNodes, edges }
|
return { nodes: layoutedNodes, edges }
|
||||||
}
|
}
|
||||||
|
|
||||||
function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps) {
|
function DependencyGraphInner({ graphData, onNodeClick, activeAgents = [] }: DependencyGraphProps) {
|
||||||
const [direction, setDirection] = useState<'TB' | 'LR'>('LR')
|
const [direction, setDirection] = useState<'TB' | 'LR'>('LR')
|
||||||
|
|
||||||
// Use ref for callback to avoid triggering re-renders when callback identity changes
|
// Use ref for callback to avoid triggering re-renders when callback identity changes
|
||||||
@@ -198,6 +220,15 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
onNodeClickRef.current?.(nodeId)
|
onNodeClickRef.current?.(nodeId)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Create a map of featureId to agent info for quick lookup
|
||||||
|
const agentByFeatureId = useMemo(() => {
|
||||||
|
const map = new Map<number, NodeAgentInfo>()
|
||||||
|
for (const agent of activeAgents) {
|
||||||
|
map.set(agent.featureId, { name: agent.agentName, state: agent.state })
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}, [activeAgents])
|
||||||
|
|
||||||
// Convert graph data to React Flow format
|
// Convert graph data to React Flow format
|
||||||
// Only recalculate when graphData or direction changes (not when onNodeClick changes)
|
// Only recalculate when graphData or direction changes (not when onNodeClick changes)
|
||||||
const initialElements = useMemo(() => {
|
const initialElements = useMemo(() => {
|
||||||
@@ -208,6 +239,7 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
data: {
|
data: {
|
||||||
...node,
|
...node,
|
||||||
onClick: () => handleNodeClick(node.id),
|
onClick: () => handleNodeClick(node.id),
|
||||||
|
agent: agentByFeatureId.get(node.id),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -225,7 +257,7 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
return getLayoutedElements(nodes, edges, direction)
|
return getLayoutedElements(nodes, edges, direction)
|
||||||
}, [graphData, direction, handleNodeClick])
|
}, [graphData, direction, handleNodeClick, agentByFeatureId])
|
||||||
|
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialElements.nodes)
|
const [nodes, setNodes, onNodesChange] = useNodesState(initialElements.nodes)
|
||||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialElements.edges)
|
const [edges, setEdges, onEdgesChange] = useEdgesState(initialElements.edges)
|
||||||
@@ -237,9 +269,16 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Create a simple hash of the graph data to detect actual changes
|
// Create a simple hash of the graph data to detect actual changes
|
||||||
|
// Include agent assignments so nodes update when agents change
|
||||||
|
const agentInfo = Array.from(agentByFeatureId.entries()).map(([id, agent]) => ({
|
||||||
|
featureId: id,
|
||||||
|
agentName: agent.name,
|
||||||
|
agentState: agent.state,
|
||||||
|
}))
|
||||||
const graphHash = JSON.stringify({
|
const graphHash = JSON.stringify({
|
||||||
nodes: graphData.nodes.map(n => ({ id: n.id, status: n.status })),
|
nodes: graphData.nodes.map(n => ({ id: n.id, status: n.status })),
|
||||||
edges: graphData.edges,
|
edges: graphData.edges,
|
||||||
|
agents: agentInfo,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Only update if graph data or direction actually changed
|
// Only update if graph data or direction actually changed
|
||||||
@@ -255,7 +294,7 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
setNodes(layoutedNodes)
|
setNodes(layoutedNodes)
|
||||||
setEdges(layoutedEdges)
|
setEdges(layoutedEdges)
|
||||||
}
|
}
|
||||||
}, [graphData, direction, setNodes, setEdges, initialElements])
|
}, [graphData, direction, setNodes, setEdges, initialElements, agentByFeatureId])
|
||||||
|
|
||||||
const onLayout = useCallback(
|
const onLayout = useCallback(
|
||||||
(newDirection: 'TB' | 'LR') => {
|
(newDirection: 'TB' | 'LR') => {
|
||||||
@@ -374,7 +413,7 @@ function DependencyGraphInner({ graphData, onNodeClick }: DependencyGraphProps)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper component with error boundary for stability
|
// Wrapper component with error boundary for stability
|
||||||
export function DependencyGraph({ graphData, onNodeClick }: DependencyGraphProps) {
|
export function DependencyGraph({ graphData, onNodeClick, activeAgents }: DependencyGraphProps) {
|
||||||
// Use a key based on graph data length to force remount on structural changes
|
// Use a key based on graph data length to force remount on structural changes
|
||||||
// This helps recover from corrupted ReactFlow state
|
// This helps recover from corrupted ReactFlow state
|
||||||
const [resetKey, setResetKey] = useState(0)
|
const [resetKey, setResetKey] = useState(0)
|
||||||
@@ -385,7 +424,7 @@ export function DependencyGraph({ graphData, onNodeClick }: DependencyGraphProps
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<GraphErrorBoundary key={resetKey} onReset={handleReset}>
|
<GraphErrorBoundary key={resetKey} onReset={handleReset}>
|
||||||
<DependencyGraphInner graphData={graphData} onNodeClick={onNodeClick} />
|
<DependencyGraphInner graphData={graphData} onNodeClick={onNodeClick} activeAgents={activeAgents} />
|
||||||
</GraphErrorBoundary>
|
</GraphErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user