import React, { useRef, useState } from 'react'; import { useFrame } from '@react-three/fiber'; import { Text, Html } from '@react-three/drei'; import * as THREE from 'three'; // ── Isometric grid helper: convert grid coords → world position ── // Isometric: x = (col - row) * tileW/2, z = (col + row) * tileH/2 const TILE_W = 1.0; const TILE_H = 0.5; export function gridToWorld(row, col) { return [ (col - row) * (TILE_W / 2), 0, (col + row) * (TILE_H / 2), ]; } // ── World pos to grid (inverse) ────────────────────────────────── export function worldToGrid(x, z) { const col = (x / (TILE_W / 2) + z / (TILE_H / 2)) / 2; const row = (z / (TILE_H / 2) - x / (TILE_W / 2)) / 2; return { row: Math.round(row), col: Math.round(col) }; } // ── Activity → glow color ──────────────────────────────────────── const ACTIVITY_COLORS = { idle: '#44cc44', thinking: '#ffcc00', working: '#4488ff', blocked: '#ff4444', offline: '#666666', }; const LERP_SPEED = 0.08; export default function AgentSprite({ agent, hovered, onHover }) { const meshRef = useRef(); const glowRef = useRef(); const [targetPos, setTargetPos] = useState(null); // Initialize position if (!targetPos && agent.position) { setTargetPos(gridToWorld(agent.position.row, agent.position.col)); } else if (agent.targetPosition) { setTargetPos(gridToWorld(agent.targetPosition.row, agent.targetPosition.col)); } useFrame((state, delta) => { if (!meshRef.current || !agent) return; // Calculate target world position const tgt = agent.targetPosition || agent.position; if (!tgt) return; const [tx, , tz] = gridToWorld(tgt.row, tgt.col); // Smooth lerp toward target meshRef.current.position.x += (tx - meshRef.current.position.x) * LERP_SPEED; meshRef.current.position.z += (tz - meshRef.current.position.z) * LERP_SPEED; // If close enough, snap to target if ( Math.abs(tx - meshRef.current.position.x) < 0.01 && Math.abs(tz - meshRef.current.position.z) < 0.01 ) { meshRef.current.position.x = tx; meshRef.current.position.z = tz; } // Floating bob const bob = Math.sin(state.clock.elapsedTime * 2 + agent.position?.row || 0) * 0.05; meshRef.current.position.y = 0.15 + bob; // Glow pulse based on activity if (glowRef.current) { const pulse = Math.sin(state.clock.elapsedTime * 3) * 0.5 + 0.5; glowRef.current.material.opacity = agent.activity === 'working' ? 0.3 + pulse * 0.4 : agent.activity === 'thinking' ? 0.2 + pulse * 0.3 : agent.activity === 'offline' ? 0 : 0.15; } }); if (!agent) return null; const color = agent.activity === 'offline' ? '#666666' : agent.color; const glowColor = ACTIVITY_COLORS[agent.activity] || '#44cc44'; return ( {/* Agent body — isometric cube */} { e.stopPropagation(); onHover(agent.id, true); }} onPointerLeave={(e) => { e.stopPropagation(); onHover(agent.id, false); }} castShadow > {/* Glow ring below agent */} {/* Agent name label */} {agent.name} {/* Hover tooltip */} {hovered && (
{agent.name}
Status: {agent.activity}
{agent.currentTask && (
{agent.currentTask}
)}
)}
); }