updates on the agent ui and all those things

This commit is contained in:
2026-09-25 11:19:13 +05:30
parent 9c50de1142
commit beff5a1cbe
2 changed files with 101 additions and 12 deletions

View File

@@ -271,6 +271,8 @@ export default function AdminLayout() {
const displayName = user?.displayname || user?.authname || user?.name || 'Operator'; const displayName = user?.displayname || user?.authname || user?.name || 'Operator';
const isHome = location.pathname === '/doormile/home' || location.pathname === '/doormile' || location.pathname === '/'; const isHome = location.pathname === '/doormile/home' || location.pathname === '/doormile' || location.pathname === '/';
const isAgentsPage = location.pathname === '/doormile/agents' || location.pathname.startsWith('/doormile/agents');
const hideAssistant = isHome || isAgentsPage;
return ( return (
/* Transparent so the ambient canvas painted behind the app reads through. /* Transparent so the ambient canvas painted behind the app reads through.
@@ -519,7 +521,7 @@ export default function AdminLayout() {
page runs the assistant as the page itself, so neither the docked page runs the assistant as the page itself, so neither the docked
panel nor its launcher belongs there. Splitting these two rules apart panel nor its launcher belongs there. Splitting these two rules apart
is how you end up with a button that opens nothing. */} is how you end up with a button that opens nothing. */}
{!isHome && ( {!hideAssistant && (
<> <>
<AILauncher isOpen={assistantOpen} onToggle={() => toggleAssistant((open) => !open)} /> <AILauncher isOpen={assistantOpen} onToggle={() => toggleAssistant((open) => !open)} />
<AIPanel isOpen={assistantOpen} onClose={() => toggleAssistant(false)} /> <AIPanel isOpen={assistantOpen} onClose={() => toggleAssistant(false)} />

View File

@@ -89,6 +89,31 @@ const AGENT_NETWORK_STRUCTURE = {
], ],
}; };
// ==============================|| COLOR BLENDING HELPER ||============================== //
/**
* Smoothly blends between two hex colors based on progress (0 to 1).
* Used for dynamic photon color transitions as events route through JARVIS.
*/
function interpolateColor(color1, color2, factor) {
try {
const c1 = color1.replace('#', '');
const c2 = color2.replace('#', '');
const r1 = parseInt(c1.substring(0, 2), 16);
const g1 = parseInt(c1.substring(2, 4), 16);
const b1 = parseInt(c1.substring(4, 6), 16);
const r2 = parseInt(c2.substring(0, 2), 16);
const g2 = parseInt(c2.substring(2, 4), 16);
const b2 = parseInt(c2.substring(4, 6), 16);
const r = Math.round(r1 + factor * (r2 - r1));
const g = Math.round(g1 + factor * (g2 - g1));
const b = Math.round(b1 + factor * (b2 - b1));
return `rgb(${r}, ${g}, ${b})`;
} catch {
return color1;
}
}
// ==============================|| MINIMAL AGENT NODE ||============================== // // ==============================|| MINIMAL AGENT NODE ||============================== //
/** /**
@@ -235,10 +260,23 @@ export default function AgentFlowVisualizer({
const [particle, setParticle] = useState({ const [particle, setParticle] = useState({
x: 0, x: 0,
y: 0, y: 0,
color: '#059669',
visible: false, visible: false,
trail: [], trail: [],
}); });
// Dynamic Impact Ripples (Tactile arrival shockwaves)
const [impactRipples, setImpactRipples] = useState([]);
const triggerImpact = (x, y, color = currentScenario.color) => {
if (!x || !y) return;
const id = `${Date.now()}-${Math.random()}`;
setImpactRipples((prev) => [...prev.slice(-4), { id, x, y, color }]);
setTimeout(() => {
setImpactRipples((prev) => prev.filter((r) => r.id !== id));
}, 850);
};
// Update SVG node coordinates dynamically on resize and scenario change // Update SVG node coordinates dynamically on resize and scenario change
const updateCoordinates = () => { const updateCoordinates = () => {
if (!canvasRef.current) return; if (!canvasRef.current) return;
@@ -344,6 +382,15 @@ export default function AgentFlowVisualizer({
const currentDist = progress * totalLen; const currentDist = progress * totalLen;
const pt = pathElement.getPointAtLength(currentDist); const pt = pathElement.getPointAtLength(currentDist);
// Dynamic Photon Color: Shifts towards JARVIS royal indigo (#6366f1) on Path 2,
// and transitions from JARVIS into target agent's color on Path 3
let particleColor = currentScenario.color;
if (segmentName === 'path2') {
particleColor = interpolateColor(currentScenario.color, '#6366f1', progress);
} else if (segmentName === 'path3') {
particleColor = interpolateColor('#6366f1', currentScenario.color, progress);
}
// Calculate trailing comet micro-beads // Calculate trailing comet micro-beads
const t1 = pathElement.getPointAtLength(Math.max(0, currentDist - 12)); const t1 = pathElement.getPointAtLength(Math.max(0, currentDist - 12));
const t2 = pathElement.getPointAtLength(Math.max(0, currentDist - 22)); const t2 = pathElement.getPointAtLength(Math.max(0, currentDist - 22));
@@ -352,6 +399,7 @@ export default function AgentFlowVisualizer({
setParticle({ setParticle({
x: pt.x, x: pt.x,
y: pt.y, y: pt.y,
color: particleColor,
visible: true, visible: true,
trail: [t1, t2, t3], trail: [t1, t2, t3],
}); });
@@ -373,7 +421,8 @@ export default function AgentFlowVisualizer({
setIsProcessing(false); setIsProcessing(false);
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
runParticleAlongPath(path1Ref.current, 'path1', 850, () => { runParticleAlongPath(path1Ref.current, 'path1', 850, () => {
// Arrives at Stage 2 // Arrives at Stage 2 Event Bus -> tactile arrival ripple
triggerImpact(p2.x, p2.y, currentScenario.color);
setCurrentEventFeed({ setCurrentEventFeed({
text: currentScenario.step1Human, text: currentScenario.step1Human,
tech: currentScenario.step1Tech, tech: currentScenario.step1Tech,
@@ -387,18 +436,24 @@ export default function AgentFlowVisualizer({
// Step 2: Information delivered, particle travels from Stage 2 to JARVIS // Step 2: Information delivered, particle travels from Stage 2 to JARVIS
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
runParticleAlongPath(path2Ref.current, 'path2', 750, () => { runParticleAlongPath(path2Ref.current, 'path2', 750, () => {
// Arrives at JARVIS Orchestrator Apex -> royal indigo routing shockwave!
triggerImpact(pJarvis.x, pJarvis.y, '#6366f1');
setStep(3); setStep(3);
}); });
}, 400); }, 400);
} else if (step === 3) { } else if (step === 3) {
// Step 3: JARVIS routes to Target Agent, Agent pulses & processes, then routes to Result // Step 3: JARVIS routes to Target Agent, Agent pulses & processes, then routes to Result
runParticleAlongPath(path3Ref.current, 'path3', 600, () => { runParticleAlongPath(path3Ref.current, 'path3', 600, () => {
// Arrives at Target Agent -> agent activation ripple!
triggerImpact(pAgent.x, pAgent.y, currentScenario.color);
setIsProcessing(true); setIsProcessing(true);
// Agent processes briefly for 800ms // Agent processes briefly for 800ms
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
setIsProcessing(false); setIsProcessing(false);
// Particle travels from Agent to Result // Particle travels from Agent to Result
runParticleAlongPath(path4Ref.current, 'path4', 850, () => { runParticleAlongPath(path4Ref.current, 'path4', 850, () => {
// Arrives at Stage 4 Result -> successful resolution ripple!
triggerImpact(p4.x, p4.y, currentScenario.color);
if (onActivityLogged) { if (onActivityLogged) {
onActivityLogged({ onActivityLogged({
agent: currentScenario.agentName, agent: currentScenario.agentName,
@@ -471,7 +526,10 @@ export default function AgentFlowVisualizer({
</div> </div>
{/* Clean Scenario Pill Buttons */} {/* Clean Scenario Pill Buttons */}
<div className="flex items-center gap-1.5"> {/* flex-wrap, because four scenario pills plus Pause measure 387px and a
phone viewport is 375 — without it the row pushed the whole page 56px
wide and every screen on the site scrolled sideways, not just this one. */}
<div className="flex flex-wrap items-center gap-1.5">
{SCENARIOS.map((scen, idx) => { {SCENARIOS.map((scen, idx) => {
const isSelected = scenarioIndex === idx; const isSelected = scenarioIndex === idx;
return ( return (
@@ -610,6 +668,32 @@ export default function AgentFlowVisualizer({
/> />
)} )}
{/* Dynamic Tactile Arrival Ripples (Shockwaves on node contact) */}
{impactRipples.map((ripple) => (
<g key={ripple.id} className="pointer-events-none">
{/* Inner High-Energy Ring */}
<motion.circle
cx={ripple.x}
cy={ripple.y}
initial={{ r: 3, opacity: 0.85, strokeWidth: 2.5 }}
animate={{ r: 24, opacity: 0, strokeWidth: 0.5 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
fill="none"
stroke={ripple.color}
/>
{/* Outer Shockwave Propagation Wave */}
<motion.circle
cx={ripple.x}
cy={ripple.y}
initial={{ r: 1, opacity: 0.5, strokeWidth: 1.5 }}
animate={{ r: 42, opacity: 0, strokeWidth: 0.5 }}
transition={{ duration: 0.8, ease: 'easeOut' }}
fill="none"
stroke={ripple.color}
/>
</g>
))}
{/* The Moving Event Particle with Fading Comet Trail */} {/* The Moving Event Particle with Fading Comet Trail */}
{particle.visible && !reduce && ( {particle.visible && !reduce && (
<g className="transition-opacity duration-150"> <g className="transition-opacity duration-150">
@@ -619,7 +703,7 @@ export default function AgentFlowVisualizer({
cx={particle.trail[2].x} cx={particle.trail[2].x}
cy={particle.trail[2].y} cy={particle.trail[2].y}
r="1.8" r="1.8"
fill={currentScenario.color} fill={particle.color || currentScenario.color}
opacity="0.3" opacity="0.3"
/> />
)} )}
@@ -629,7 +713,7 @@ export default function AgentFlowVisualizer({
cx={particle.trail[1].x} cx={particle.trail[1].x}
cy={particle.trail[1].y} cy={particle.trail[1].y}
r="2.5" r="2.5"
fill={currentScenario.color} fill={particle.color || currentScenario.color}
opacity="0.55" opacity="0.55"
/> />
)} )}
@@ -639,7 +723,7 @@ export default function AgentFlowVisualizer({
cx={particle.trail[0].x} cx={particle.trail[0].x}
cy={particle.trail[0].y} cy={particle.trail[0].y}
r="3.2" r="3.2"
fill={currentScenario.color} fill={particle.color || currentScenario.color}
opacity="0.8" opacity="0.8"
/> />
)} )}
@@ -649,7 +733,7 @@ export default function AgentFlowVisualizer({
cy={particle.y} cy={particle.y}
r="4.5" r="4.5"
fill="#ffffff" fill="#ffffff"
stroke={currentScenario.color} stroke={particle.color || currentScenario.color}
strokeWidth="2.5" strokeWidth="2.5"
filter="url(#light-particle-glow)" filter="url(#light-particle-glow)"
/> />
@@ -664,7 +748,7 @@ export default function AgentFlowVisualizer({
Mobile: 1-column vertical story Mobile: 1-column vertical story
(Step 4 has guaranteed reserved space and can never be pushed out!) (Step 4 has guaranteed reserved space and can never be pushed out!)
═══════════════════════════════════════════════════════════════ */} ═══════════════════════════════════════════════════════════════ */}
<div className="relative z-10 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1.35fr)_minmax(0,0.95fr)] gap-5 xl:gap-6 items-stretch"> <div className="relative z-10 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1.2fr)_minmax(0,1.1fr)] gap-5 xl:gap-6 items-stretch">
{/* ── STAGE 01: SOMETHING HAPPENS ── */} {/* ── STAGE 01: SOMETHING HAPPENS ── */}
<div className="flex flex-col justify-between gap-3 min-w-0 box-border"> <div className="flex flex-col justify-between gap-3 min-w-0 box-border">
@@ -801,8 +885,11 @@ export default function AgentFlowVisualizer({
<div className="flex justify-center w-full"> <div className="flex justify-center w-full">
<MinimalAgentNode <MinimalAgentNode
agent={AGENT_NETWORK_STRUCTURE.orchestrator} agent={AGENT_NETWORK_STRUCTURE.orchestrator}
isActive={step === 3 && currentScenario.targetAgentId === 'JARVIS'} isActive={
isDimmed={step === 3 && currentScenario.targetAgentId !== 'JARVIS'} (step === 2 && activeSegment === 'path2') ||
(step === 3 && (activeSegment === 'path3' || currentScenario.targetAgentId === 'JARVIS'))
}
isDimmed={step === 3 && currentScenario.targetAgentId !== 'JARVIS' && activeSegment !== 'path3'}
isSelected={activeWorkflowAgentId === 'JARVIS'} isSelected={activeWorkflowAgentId === 'JARVIS'}
onClick={handleAgentClick} onClick={handleAgentClick}
nodeRef={jarvisRef} nodeRef={jarvisRef}
@@ -914,10 +1001,10 @@ export default function AgentFlowVisualizer({
<CheckCircle2 className="h-3.5 w-3.5" /> <CheckCircle2 className="h-3.5 w-3.5" />
</span> </span>
<div className="flex flex-col gap-0.5 min-w-0"> <div className="flex flex-col gap-0.5 min-w-0">
<span className="text-xs font-bold text-slate-900 leading-tight uppercase tracking-wide truncate"> <span className="text-xs font-bold text-slate-900 leading-snug break-words">
{currentScenario.step4Human} {currentScenario.step4Human}
</span> </span>
<span className="text-[10px] text-slate-500 leading-tight"> <span className="text-[10.5px] text-slate-500 leading-normal break-words">
{currentScenario.step4Sub} {currentScenario.step4Sub}
</span> </span>
</div> </div>