From 5810d20c2585d2e2f70c8fc963476a31feabaa9f Mon Sep 17 00:00:00 2001 From: Tata0703 Date: Tue, 15 Apr 2025 17:00:50 +0800 Subject: [PATCH 1/2] temp frontend --- .../components/ControlPanel.tsx | 2 +- .../components/ControlPanelLATS.tsx | 2 +- visual-tree-search-app/components/Layout.tsx | 2 +- .../components/MessageLogPanelLATS.tsx | 116 +++++++++++++++++- 4 files changed, 118 insertions(+), 4 deletions(-) diff --git a/visual-tree-search-app/components/ControlPanel.tsx b/visual-tree-search-app/components/ControlPanel.tsx index 6d20ac2..a239ffe 100644 --- a/visual-tree-search-app/components/ControlPanel.tsx +++ b/visual-tree-search-app/components/ControlPanel.tsx @@ -34,7 +34,7 @@ const ControlPanel: React.FC = ({
-

Visual Tree Search

+

Visual Tree Search: Simple Search (BFS/DFS)

); + case 'tree_update_node_children_evaluation': + case 'tree_update_node_backpropagation': + case 'tree_update_simulation': + case 'trajectory_update': + case 'removed_simulation': + return ( +
+ {getIcon(message)} +
+
+ {message.description || message.type.split('_').join(' ')} +
+ {message.node_id && ( +
+ Node ID: {message.node_id} +
+ )} +
+
+ ); + + case 'node_selected': + case 'node_selected_for_simulation': + case 'node_created': + case 'node_simulated': + case 'node_terminal': + return ( +
+ {getIcon(message)} +
+
+ {message.description || message.type.split('_').join(' ')} +
+ {message.node_id && ( +
+ Node ID: {message.node_id} + {message.value !== undefined && ` | Value: ${message.value.toFixed(2)}`} + {message.visits !== undefined && ` | Visits: ${message.visits}`} +
+ )} +
+
+ ); + default: return (
From 4b353a9a4d3b2debe071c6f3e86379725322a35c Mon Sep 17 00:00:00 2001 From: Tata0703 Date: Tue, 15 Apr 2025 18:53:55 +0800 Subject: [PATCH 2/2] v1 lats visualization done --- .../components/LATSVisual.tsx | 263 +++++++++++++++--- 1 file changed, 232 insertions(+), 31 deletions(-) diff --git a/visual-tree-search-app/components/LATSVisual.tsx b/visual-tree-search-app/components/LATSVisual.tsx index 2055c3f..9da32cb 100644 --- a/visual-tree-search-app/components/LATSVisual.tsx +++ b/visual-tree-search-app/components/LATSVisual.tsx @@ -13,6 +13,7 @@ interface TreeNode { visits?: number; feedback?: string; reward?: number; + isSimulated?: boolean; // Flag to track newly simulated nodes } interface Message { @@ -31,8 +32,10 @@ const LATSVisual: React.FC = ({ messages }) => { const tooltipRef = useRef(null); const { theme } = useTheme(); const [selectedNodeId, setSelectedNodeId] = useState(null); + const [simulationStartNodeId, setSimulationStartNodeId] = useState(null); // Track simulation starting node (existing node) const [treeNodes, setTreeNodes] = useState([]); const [containerWidth, setContainerWidth] = useState(0); + const [simulatedNodes, setSimulatedNodes] = useState([]); // Keep track of new simulated node IDs // Set up resize observer to make the visualization responsive useEffect(() => { @@ -71,27 +74,91 @@ const LATSVisual: React.FC = ({ messages }) => { let updatedTreeNodes: TreeNode[] = [...treeNodes]; let newSelectedNodeId = selectedNodeId; + let newSimulationStartNodeId = simulationStartNodeId; + let newSimulatedNodes = [...simulatedNodes]; let hasChanges = false; messages.forEach(msg => { try { const data = JSON.parse(msg.content); - // Handle node selection updates + // Handle regular node selection (during tree expansion/evaluation) if (data.type === 'node_selected' && data.node_id !== undefined) { newSelectedNodeId = data.node_id; hasChanges = true; } + // Handle simulation start node selection (existing node highlighted as simulation start) + if (data.type === 'node_selected_for_simulation' && data.node_id !== undefined) { + newSimulationStartNodeId = data.node_id; + hasChanges = true; + } + // Handle tree structure updates - if (data.type === 'tree_update_node_expansion' && Array.isArray(data.tree)) { - updatedTreeNodes = data.tree; + if ((data.type === 'tree_update_node_expansion' || data.type === 'tree_update_node_children_evaluation') + && Array.isArray(data.tree)) { + // Preserve simulation flags when updating from tree + if (updatedTreeNodes.some(node => node.isSimulated)) { + // Find all nodes with isSimulated flag + const simulatedNodeMap = new Map(); + updatedTreeNodes.forEach(node => { + if (node.isSimulated) { + simulatedNodeMap.set(node.id, true); + } + }); + + // Apply the flag to the updated tree + updatedTreeNodes = data.tree.map((node: TreeNode) => ({ + ...node, + isSimulated: simulatedNodeMap.has(node.id) ? true : false + })); + } else { + updatedTreeNodes = data.tree; + } hasChanges = true; } + + // Handle simulated node creation + if (data.type === 'node_simulated' && data.node_id !== undefined && data.parent_id !== undefined) { + // Check if the node already exists in the tree + const nodeExists = updatedTreeNodes.some(node => node.id === data.node_id); + + if (!nodeExists) { + // Add the new simulated node to the tree + updatedTreeNodes.push({ + id: data.node_id, + parent_id: data.parent_id, + action: data.action, + description: data.description, + isSimulated: true, // Mark as simulated + }); + + // Add to our list of simulated nodes + newSimulatedNodes.push(data.node_id); + hasChanges = true; + } else { + // If node already exists, update it to mark as simulated + updatedTreeNodes = updatedTreeNodes.map(node => + node.id === data.node_id ? { ...node, isSimulated: true } : node + ); + + if (!newSimulatedNodes.includes(data.node_id)) { + newSimulatedNodes.push(data.node_id); + hasChanges = true; + } + } + } - // Handle node evaluation updates - if (data.type === 'tree_update_node_evaluation' && Array.isArray(data.tree)) { - updatedTreeNodes = data.tree; + // Handle simulation removal + if (data.type === 'removed_simulation') { + // Remove simulation flags instead of removing nodes + updatedTreeNodes = updatedTreeNodes.map(node => ({ + ...node, + isSimulated: false // Remove simulation flag + })); + + newSimulatedNodes = []; // Clear simulated nodes list + newSimulationStartNodeId = null; // Clear simulation start node hasChanges = true; } } catch { @@ -102,8 +169,10 @@ const LATSVisual: React.FC = ({ messages }) => { if (hasChanges) { setTreeNodes(updatedTreeNodes); setSelectedNodeId(newSelectedNodeId); + setSimulationStartNodeId(newSimulationStartNodeId); + setSimulatedNodes(newSimulatedNodes); } - }, [messages, treeNodes, selectedNodeId]); + }, [messages]); // Render the tree visualization useEffect(() => { @@ -136,6 +205,7 @@ const LATSVisual: React.FC = ({ messages }) => { .style("z-index", "1000") .style("max-width", "400px") .style("box-shadow", "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)") + .style("line-height", "1.5") .node() as HTMLDivElement; }; @@ -187,9 +257,41 @@ const LATSVisual: React.FC = ({ messages }) => { return `M${sourceY},${sourceX}C${(sourceY + targetY) / 2},${sourceX} ${(sourceY + targetY) / 2},${targetX} ${targetY},${targetX}`; }) .attr("fill", "none") - .attr("stroke", theme === 'dark' ? "#9CA3AF" : "#6B7280") - .attr("stroke-width", 1.5) - .attr("stroke-opacity", 0.7); + .attr("stroke", d => { + // Link to a simulated node gets an orange color + if (d.target.data.isSimulated) { + return theme === 'dark' ? "#F97316" : "#FB923C"; // Orange for simulated paths + } + + // Link from simulation start node + if (d.source.data.id === simulationStartNodeId) { + return theme === 'dark' ? "#10B981" : "#34D399"; // Green for simulation start path + } + + // Default link color + return theme === 'dark' ? "#9CA3AF" : "#6B7280"; + }) + .attr("stroke-width", d => { + // Thicker link for simulation paths + if (d.target.data.isSimulated || d.source.data.id === simulationStartNodeId) { + return 2.5; + } + return 1.5; + }) + .attr("stroke-opacity", d => { + // More visible for simulation paths + if (d.target.data.isSimulated || d.source.data.id === simulationStartNodeId) { + return 0.9; + } + return 0.7; + }) + .attr("stroke-dasharray", d => { + // Dashed line for simulation paths + if (d.target.data.isSimulated) { + return "5,3"; + } + return null; + }); // Create node groups const nodes = g.selectAll(".node") @@ -203,6 +305,16 @@ const LATSVisual: React.FC = ({ messages }) => { nodes.append("circle") .attr("r", 12) .attr("fill", d => { + // Simulated node (orange) + if (d.data.isSimulated) { + return theme === 'dark' ? "#F97316" : "#FDBA74"; // Orange for simulated nodes + } + + // Simulation start node (green) + if (d.data.id === simulationStartNodeId) { + return theme === 'dark' ? "#10B981" : "#34D399"; // Green for simulation start node + } + // Selected node (blue) if (d.data.id === selectedNodeId) { return theme === 'dark' ? "#3B82F6" : "#60A5FA"; @@ -216,12 +328,29 @@ const LATSVisual: React.FC = ({ messages }) => { // Action node (default) return theme === 'dark' ? "#4B5563" : "#E5E7EB"; }) - .attr("stroke", d => d.data.id === selectedNodeId - ? theme === 'dark' ? "#93C5FD" : "#2563EB" - : theme === 'dark' ? "#374151" : "#D1D5DB") - .attr("stroke-width", d => d.data.id === selectedNodeId ? 3 : 2); + .attr("stroke", d => { + if (d.data.isSimulated) { + return theme === 'dark' ? "#EA580C" : "#F97316"; // Darker orange stroke for simulated nodes + } + + if (d.data.id === simulationStartNodeId) { + return theme === 'dark' ? "#059669" : "#10B981"; // Darker green stroke for simulation start + } + + if (d.data.id === selectedNodeId) { + return theme === 'dark' ? "#93C5FD" : "#2563EB"; + } + + return theme === 'dark' ? "#374151" : "#D1D5DB"; + }) + .attr("stroke-width", d => { + if (d.data.isSimulated || d.data.id === simulationStartNodeId || d.data.id === selectedNodeId) { + return 3; + } + return 2; + }); - // Add node labels directly on the node circles + // Add node labels with tooltips nodes.append("text") .attr("dy", ".35em") .attr("x", d => d.children ? -18 : 18) @@ -230,20 +359,28 @@ const LATSVisual: React.FC = ({ messages }) => { // For root node if (d.data.parent_id === null) return "ROOT"; - // Extract action name from action string + // Show full action string if (d.data.action) { - const actionMatch = d.data.action.match(/^([a-zA-Z_]+)\(/); - return actionMatch ? actionMatch[1] : "action"; + return d.data.action; } return d.data.id.toString().slice(-4); }) - .attr("font-size", "14px") + .attr("font-size", "15px") .attr("font-weight", "500") .attr("fill", d => { + if (d.data.isSimulated) { + return theme === 'dark' ? "#FDBA74" : "#C2410C"; // Orange for simulated node + } + + if (d.data.id === simulationStartNodeId) { + return theme === 'dark' ? "#A7F3D0" : "#047857"; // Green for simulation start + } + if (d.data.id === selectedNodeId) { - return theme === 'dark' ? "#93C5FD" : "#2563EB"; + return theme === 'dark' ? "#93C5FD" : "#1D4ED8"; // Blue for selected node } + return theme === 'dark' ? "#FFFFFF" : "#111827"; }); @@ -265,28 +402,66 @@ const LATSVisual: React.FC = ({ messages }) => { nodes .on("mouseover", function(event, d) { if (tooltipRef.current) { - let content = `
Node ID: ${d.data.id}
`; - if (d.data.action) content += `
Action: ${d.data.action}
`; - if (d.data.description) content += `
Description: ${d.data.description}
`; - if (typeof d.data.value === 'number') content += `
Value: ${d.data.value.toFixed(2)}
`; - if (typeof d.data.reward === 'number') content += `
Reward: ${d.data.reward.toFixed(2)}
`; - if (typeof d.data.visits === 'number') content += `
Visits: ${d.data.visits}
`; - if (d.data.feedback) content += `
Feedback: ${d.data.feedback}
`; + let tooltipContent = ''; + + // Add description if available + if (d.data.description) { + tooltipContent += `

${d.data.description}

`; + } + + // Add node status information + const nodeInfo = []; + + if (d.data.id === simulationStartNodeId) { + nodeInfo.push(`Simulation Starting Node`); + } + + if (d.data.isSimulated) { + nodeInfo.push(`Simulated Node`); + } + + if (d.data.id === selectedNodeId) { + nodeInfo.push(`Selected Node`); + } + + if (nodeInfo.length > 0) { + tooltipContent += `
${nodeInfo.join(' | ')}
`; + } + + // Add reward info if available + if (typeof d.data.reward === 'number') { + tooltipContent += `
Reward: ${d.data.reward.toFixed(2)}
`; + } + + // Add value info if available + if (typeof d.data.value === 'number') { + tooltipContent += `
Value: ${d.data.value.toFixed(2)}
`; + } + + // Add visits info if available + if (typeof d.data.visits === 'number') { + tooltipContent += `
Visits: ${d.data.visits}
`; + } + + // Add depth info if available + if (typeof d.data.depth === 'number') { + tooltipContent += `
Depth: ${d.data.depth}
`; + } const tooltip = d3.select(tooltipRef.current); tooltip.transition() .duration(200) .style("opacity", .9); - tooltip.html(content) + tooltip.html(tooltipContent) .style("left", (event.pageX + 15) + "px") - .style("top", (event.pageY - 30) + "px"); + .style("top", (event.pageY - 60) + "px"); } }) .on("mousemove", function(event) { if (tooltipRef.current) { d3.select(tooltipRef.current) .style("left", (event.pageX + 15) + "px") - .style("top", (event.pageY - 30) + "px"); + .style("top", (event.pageY - 28) + "px"); } }) .on("mouseout", function() { @@ -307,7 +482,7 @@ const LATSVisual: React.FC = ({ messages }) => { svg.call(zoom); - }, [treeNodes, selectedNodeId, theme, containerWidth]); + }, [treeNodes, selectedNodeId, simulationStartNodeId, simulatedNodes, theme, containerWidth]); return (
@@ -318,6 +493,32 @@ const LATSVisual: React.FC = ({ messages }) => { Tree Visualization + + {/* Simulation indicator */} + {simulationStartNodeId && ( +
+ + + Simulation Mode + +
+ )} + + {/* Legend */} +
+
+ + Selected +
+
+ + Sim Start +
+
+ + Simulated +
+