updates on the agent ui and all those things
This commit is contained in:
@@ -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)} />
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user