updates on the agent flow

This commit is contained in:
2026-09-24 19:25:19 +05:30
parent cbce47f949
commit 9c50de1142
15 changed files with 3998 additions and 1 deletions

View File

@@ -52,6 +52,7 @@ const Profitability = lazy(() => import('@/pages/doormile/reports/Profitability'
const ViewProfile = lazy(() => import('@/pages/doormile/ViewProfile'));
const Settings = lazy(() => import('@/pages/doormile/settings/Settings'));
const Agents = lazy(() => import('@/pages/doormile/agents/Agents'));
const PageFallback = () => (
<div className="flex min-h-[60vh] items-center justify-center">
@@ -150,6 +151,11 @@ const AuthenticatedApp = () => {
<Route path="profile" element={<ViewProfile />} />
<Route path="settings" element={<Settings />} />
{/* The AI_engine swarm. Read-only: it reports what the agents are
and what they last did, and has no control surface, because
every autonomy gate lives in that repository's own env. */}
<Route path="agents" element={<Agents />} />
</Route>
</Route>

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Link, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { motion } from 'framer-motion';
import {
Activity, Bell, Bike, Car, ChevronDown, Coins, FileSpreadsheet,
Activity, Bell, Bike, Bot, Car, ChevronDown, Coins, FileSpreadsheet,
FileText, ListTodo, LogOut, Menu, Search, Settings, Shield,
ShieldAlert, User, UserCheck, Warehouse,
} from 'lucide-react';
@@ -66,6 +66,7 @@ const NAV_GROUPS = [
{ label: 'Exceptions', path: '/doormile/exceptions', icon: ShieldAlert },
{ label: 'Competitive Intel', path: '/doormile/competitive-intel', icon: Activity },
{ label: 'App Users', path: '/doormile/app-users', icon: UserCheck },
{ label: 'Agents', path: '/doormile/agents', icon: Bot },
],
},
{

294
src/lib/agentNetwork.js Normal file
View File

@@ -0,0 +1,294 @@
// ==============================|| The AI_engine agent swarm ||============================== //
//
// Nine agents — JARVIS plus eight specialised — living in the `AI_engine`
// repository, not in this one and not in `doormile_backend`. They subscribe to
// the same NATS JetStream the Go backend publishes to, reason about what they
// see, and raise alerts. Every autonomy gate is off, so none of them writes to
// the system.
//
// ─── Why this is a static table ──────────────────────────────────────────────
//
// `AI_engine` exposes no endpoint this console can call. There is a FastAPI
// gateway inside it with a `GET /agents/status`, but it is not deployed beside
// the console and nothing routes to it.
//
// So this file is a hand-maintained record, and it is deliberately shaped like
// the response such an endpoint would return — one flat object per agent, the
// same keys — so swapping it for a fetch is a change of source, not a rewrite
// of the page.
//
// **It can go stale.** Every `lastSeen` here was read from
// `AI_engine/logs/logiflow_2026-09-16.log`, the most recent run, which covers
// 16–20 Sep 2026. Treat the counts as of that date, not as live. The page says
// so on screen, because a status board that looks live and is not is worse than
// one that admits its age.
/** When the figures below were taken. Rendered on the page — see the note above. */
export const SNAPSHOT_AT = '2026-09-20T19:51:23+05:30';
export const SNAPSHOT_LABEL = 'Last run · 16–20 Sep 2026';
/**
* How an agent is woken.
*
* The split matters more than it looks: `nats` agents subscribe to the bus
* themselves and can act the moment an event lands, while `task` agents only
* run when JARVIS hands them something — and nothing in the Go backend asks
* JARVIS for anything, which is why five of the nine have never run at all.
*/
export const TRIGGER = {
nats: 'NATS event',
natsPull: 'NATS pull',
task: 'JARVIS task',
internal: 'In-process',
};
export const AGENTS = [
{
id: 'JARVIS',
name: 'JARVIS',
domain: 'orchestration',
status: 'running',
trigger: 'internal',
file: 'core/agent.py',
lines: null,
tests: null,
events: 0,
load: 8,
blurb: 'Master agent. Every other agent registers under it and receives work through it.',
detail:
'The orchestrator. It holds the registry of the eight specialised agents and routes tasks to them over an in-process message bus. It never touches NATS itself.',
subscribes: [],
publishes: 'Tasks to the five task-driven agents.',
api: 'No — it delegates.',
autonomy: 'not gated — routes only',
gated: false,
lastSeen:
'Started cleanly on 16 Sep at 15:23 and registered eight sub-agents. It has routed nothing since, because nothing in the backend asks it to.',
},
{
id: 'DISPATCH_AGENT',
name: 'Dispatch',
domain: 'dispatch',
status: 'active',
trigger: 'nats',
file: 'agents/dispatch_agent.py',
lines: 508,
tests: true,
events: 2,
load: 62,
blurb:
'Counts assignment failures per geo cell, checks rider liveness, raises an ops alert when a zone stops getting covered.',
detail:
'The only agent doing real work. It watches every assignment outcome, buckets failures by geographic cell, and decides whether a zone has genuinely lost coverage or is simply quiet. Escalation is rate-limited to one alert per zone per day.',
subscribes: ['booking.assigned', 'booking.assignment_failed'],
publishes: 'Ops alerts, customer delay notices, escalations.',
api: 'No — reads presence from Redis.',
autonomy: 'DISPATCH_AGENT_AUTONOMOUS = false',
gated: true,
lastSeen:
'Bound to stream ASSIGNMENTS on 16 Sep, and saw two assignment failures three seconds later. Both landed in zone unknown — the backend does not publish zone_id, which is exactly why a later commit started bucketing failures by geo cell instead.',
},
{
id: 'EXCEPTION_AGENT',
name: 'Exception',
domain: 'exception_handling',
status: 'degraded',
trigger: 'natsPull',
file: 'agents/exception_agent.py',
lines: 818,
tests: true,
events: 0,
load: 100,
blurb: 'Stall detection. Spots a rider who has stopped moving or a booking that has stopped progressing.',
detail:
'The largest agent. Two pull loops read rider pings and stall events, and a sweep walks active bookings looking for ones that have gone quiet. When it finds a stalled rider it publishes miler.stalled for the dispatch side to act on.',
subscribes: ['miler.location.updated', 'miler.stalled'],
publishes: 'miler.stalled',
api: 'Yes — api_post.',
autonomy: 'EXCEPTION_AGENT_AUTONOMOUS = false',
gated: true,
lastSeen:
'Lost NATS and never recovered. Both pull loops logged the same connection-closed error every two seconds for four days — 17,188 of the 18,146 lines in the log. The loop sleeps and retries a dead handle; it has no reconnect.',
},
{
id: 'EXPRESS_DISPATCH_AGENT',
name: 'Express Dispatch',
domain: 'express_dispatch',
status: 'idle',
trigger: 'nats',
file: 'agents/express_dispatch_agent.py',
lines: 391,
tests: true,
events: 0,
load: 0,
blurb: 'Handles dispatch requests raised from the console and express booking path.',
detail:
'The console-side counterpart to Dispatch. It waits on express.dispatch_req and works a booking the express path could not place, reading and writing through the Go API rather than assigning directly.',
subscribes: ['express.dispatch_req'],
publishes: 'Dispatch outcomes back onto the bus.',
api: 'Yes — api_get, api_post.',
autonomy: 'EXPRESS_AGENT_AUTONOMOUS = false',
gated: true,
lastSeen: 'Bound its consumer on 16 Sep and has received nothing. No express dispatch request has ever been published.',
},
{
id: 'ORDER_AGENT',
name: 'Order',
domain: 'order_management',
status: 'idle',
trigger: 'task',
file: 'agents/order_agent.py',
lines: 348,
tests: false,
events: 0,
load: 0,
blurb: 'Order intake, validation and categorisation.',
detail:
'Validates an incoming order, classifies it by zone and service type, and patches the result back. The most connected of the task agents — it reads, creates and patches through the Go API.',
subscribes: [],
publishes: 'Nothing on the bus.',
api: 'Yes — get, post, patch.',
autonomy: 'no gate — never reachable',
gated: false,
lastSeen: 'Started on 16 Sep. Zero tasks received, because nothing sends tasks.',
},
{
id: 'ROUTE_OPTIMIZER',
name: 'Route Optimizer',
domain: 'route_optimization',
status: 'idle',
trigger: 'task',
file: 'agents/route_optimizer_agent.py',
lines: 465,
tests: false,
events: 0,
load: 0,
blurb: 'Builds delivery routes from zones and available hubs. Ten-minute cache.',
detail:
'Sequences waypoints into routes using zone membership and hub availability. This is the piece the Go backend has no equivalent for — HubBatchAssign decides who gets a booking, never what order a rider runs their stops in.',
subscribes: [],
publishes: 'Nothing on the bus.',
api: 'No.',
autonomy: 'no gate — never reachable',
gated: false,
lastSeen: 'Started on 16 Sep. Zero tasks received.',
},
{
id: 'HUB_AGENT',
name: 'Hub',
domain: 'hub_management',
status: 'idle',
trigger: 'task',
file: 'agents/hub_agent.py',
lines: 460,
tests: false,
events: 0,
load: 0,
blurb: 'Hub operations — transit records, capacity, what is sitting where.',
detail:
'Tracks inventory at each hub and the transit records moving between them, and answers capacity questions about whether a hub can absorb more volume.',
subscribes: [],
publishes: 'Nothing on the bus.',
api: 'No.',
autonomy: 'no gate — never reachable',
gated: false,
lastSeen: 'Started on 16 Sep. Zero tasks received.',
},
{
id: 'FLEET_AGENT',
name: 'Fleet',
domain: 'fleet_management',
status: 'idle',
trigger: 'task',
file: 'agents/fleet_agent.py',
lines: 349,
tests: false,
events: 0,
load: 0,
blurb: 'Vehicles: status, capacity and live tracking.',
detail:
"Holds vehicle state and assignment, and answers which vehicle can carry a given load. It overlaps with the Go backend's own Vehicle model, which is the live source today.",
subscribes: [],
publishes: 'Nothing on the bus.',
api: 'No.',
autonomy: 'no gate — never reachable',
gated: false,
lastSeen: 'Started on 16 Sep. Zero tasks received.',
},
{
id: 'CUSTOMER_AGENT',
name: 'Customer',
domain: 'customer_communication',
status: 'idle',
trigger: 'task',
file: 'agents/customer_agent.py',
lines: 407,
tests: false,
events: 0,
load: 0,
blurb: 'Customer-facing notifications and tracking updates.',
detail:
'Composes and sends customer notifications across channels for booking, pickup and delivery events. Worth knowing before it is switched on: the backend has no SMS provider registered, so anything it sends by text goes to a log.',
subscribes: [],
publishes: 'Nothing on the bus.',
api: 'Yes — api_get, api_post.',
autonomy: 'no gate — never reachable',
gated: false,
lastSeen: 'Started on 16 Sep. Zero tasks received.',
},
];
/** What the Go backend publishes, and the bus it publishes onto. */
export const SOURCE_SUBJECTS = ['booking.assigned', 'booking.assignment_failed', 'miler.location.updated'];
/**
* The last run, as the log tells it.
*
* `count` is how many times a line repeated. Two of these repeated 8,594 times
* each, which is the whole story of that run and the reason the repeat count is
* on screen rather than in a tooltip.
*/
export const RUN_LOG = [
{ at: '16 Sep 15:23:20', level: 'INFO', tone: 'info', count: null,
text: "DispatchAgent bound 'booking.assigned' on stream 'ASSIGNMENTS'" },
{ at: '16 Sep 15:23:20', level: 'INFO', tone: 'info', count: null,
text: '9 agents initialized (JARVIS + 8 specialized)' },
{ at: '16 Sep 15:23:23', level: 'INFO', tone: 'warn', count: 2,
text: '[DISPATCH] zone unknown — failure count 1/3, 2/3 (backend publishes no zone_id)' },
{ at: '20 Sep 19:51:23', level: 'ERROR', tone: 'error', count: 8594,
text: 'Stalled pull loop error: nats: connection closed' },
{ at: '20 Sep 19:51:23', level: 'ERROR', tone: 'error', count: 8594,
text: 'Location pull loop error: nats: connection closed' },
];
export const LOG_TOTAL_LINES = 18146;
export const LOG_ERROR_LINES = 17188;
/** Agents that subscribe to the bus themselves, in the order they appear. */
export const eventDrivenAgents = () => AGENTS.filter((a) => a.trigger === 'nats' || a.trigger === 'natsPull');
/** Agents that only run when JARVIS hands them work. */
export const taskDrivenAgents = () => AGENTS.filter((a) => a.trigger === 'task');
export const agentById = (id) => AGENTS.find((a) => a.id === id) || AGENTS[0];
/**
* The headline numbers.
*
* Derived rather than written down, so a change to the table above cannot leave
* a stat contradicting the cards underneath it — which is how a dashboard ends
* up saying "3 active" over three grey dots.
*/
export const networkStats = (agents = AGENTS) => {
const working = agents.filter((a) => a.status === 'active').length;
const gated = agents.filter((a) => a.gated).length;
return {
total: agents.length,
working,
degraded: agents.filter((a) => a.status === 'degraded').length,
idle: agents.filter((a) => a.status === 'idle').length,
events: agents.reduce((sum, a) => sum + (a.events || 0), 0),
gated,
autonomyOn: 0,
};
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { AnimatePresence, motion } from 'framer-motion';
import {
Activity,
CheckCircle2,
Copy,
ExternalLink,
Radio,
ShieldCheck,
Terminal,
X,
Zap,
} from 'lucide-react';
import toast from 'react-hot-toast';
import { cn } from '@/lib/utils';
import { TRIGGER, agentById } from '@/lib/agentNetwork';
export default function AgentInspectorDrawer({
agentId,
isOpen = false,
onClose,
}) {
const agent = agentById(agentId);
const [probing, setProbing] = useState(false);
const handleProbe = () => {
setProbing(true);
setTimeout(() => {
setProbing(false);
toast.success(`${agent.name} socket probe completed: responsive`);
}, 900);
};
const copyPath = () => {
navigator.clipboard?.writeText(agent.file);
toast.success(`Copied path: ${agent.file}`);
};
const statusTone = {
active: {
beacon: 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.9)] ring-2 ring-emerald-500/20',
label: 'Healthy · Active',
chip: 'bg-emerald-50 text-emerald-800 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800',
},
running: {
beacon: 'bg-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.9)] ring-2 ring-indigo-500/20',
label: 'Running',
chip: 'bg-indigo-50 text-indigo-800 border-indigo-200 dark:bg-indigo-950/40 dark:text-indigo-300 dark:border-indigo-800',
},
degraded: {
beacon: 'bg-amber-500 shadow-[0_0_8px_rgba(245,158,11,0.9)] ring-2 ring-amber-500/20',
label: 'Warning · Degraded',
chip: 'bg-amber-50 text-amber-800 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-800',
},
idle: {
beacon: 'bg-slate-400 ring-2 ring-slate-400/20',
label: 'Idle · Standby',
chip: 'bg-slate-100 text-slate-700 border-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:border-slate-700',
},
}[agent.status] || {
beacon: 'bg-slate-400 ring-2 ring-slate-400/20',
label: 'Standby',
chip: 'bg-slate-100 text-slate-700 border-slate-200',
};
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 z-40 bg-black/30 backdrop-blur-2xs"
/>
{/* Slide-over Drawer Panel */}
<motion.aside
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
transition={{ type: 'spring', damping: 28, stiffness: 320 }}
className="fixed right-0 top-0 bottom-0 z-50 flex w-full max-w-md flex-col bg-surface border-l border-border shadow-2xl overflow-y-auto"
>
{/* Header */}
<div className="flex items-center justify-between border-b border-border/80 p-5 bg-surface-subtle">
<div className="flex items-center gap-3">
<span className={cn('h-3.5 w-3.5 rounded-full shrink-0', statusTone.beacon)} />
<div className="flex flex-col">
<span className="font-heading text-base font-bold text-ink-1">
{agent.name}
</span>
<span className="font-mono text-[11px] text-ink-4">
{agent.id}
</span>
</div>
</div>
<div className="flex items-center gap-2">
<span className={cn('rounded-full border px-2.5 py-0.5 text-xs font-semibold', statusTone.chip)}>
{statusTone.label}
</span>
<button
type="button"
onClick={onClose}
className="rounded-lg p-1.5 text-ink-3 hover:text-ink-1 hover:bg-surface-elevated transition-colors cursor-pointer"
title="Close inspector"
>
<X className="h-4 w-4" />
</button>
</div>
</div>
{/* Content Body */}
<div className="flex flex-col gap-6 p-5">
{/* Plain English Summary */}
<div className="flex flex-col gap-1.5">
<span className="text-[10.5px] font-bold uppercase tracking-wider text-ink-4">
Purpose & Role
</span>
<p className="text-body-sm text-ink-2 leading-relaxed">
{agent.blurb}
</p>
<p className="text-caption text-ink-3 leading-relaxed mt-1">
{agent.detail}
</p>
</div>
{/* Event Subscriptions / Listens For */}
<div className="flex flex-col gap-2">
<span className="text-[10.5px] font-bold uppercase tracking-wider text-ink-4">
Listens For (Event Bus)
</span>
{agent.subscribes.length > 0 ? (
<div className="flex flex-col gap-1.5">
{agent.subscribes.map((sub) => (
<div
key={sub}
className="flex items-center justify-between rounded-lg border border-border/80 bg-surface-subtle px-3 py-2 text-xs font-mono text-ink-2"
>
<div className="flex items-center gap-2 truncate">
<span className="h-1.5 w-1.5 rounded-full bg-brand" />
<span className="font-semibold truncate">{sub}</span>
</div>
<span className="text-[10px] text-ink-4 font-sans font-medium">
NATS stream
</span>
</div>
))}
</div>
) : (
<div className="rounded-lg border border-dashed border-border p-3 text-caption text-ink-4 italic">
JARVIS task-delegated (does not subscribe to NATS directly)
</div>
)}
</div>
{/* Recent Activity & Liveness */}
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-1 rounded-xl border border-border/80 p-3 bg-surface-subtle/50">
<span className="text-[10.5px] font-bold uppercase tracking-wider text-ink-4">
Recent Activity
</span>
<span className="font-heading text-lg font-bold text-ink-1">
{agent.events} {agent.events === 1 ? 'event' : 'events'}
</span>
<span className="text-[10px] text-ink-4">
Trigger: {TRIGGER[agent.trigger]}
</span>
</div>
<div className="flex flex-col gap-1 rounded-xl border border-border/80 p-3 bg-surface-subtle/50">
<span className="text-[10.5px] font-bold uppercase tracking-wider text-ink-4">
Last Seen
</span>
<span className="font-heading text-xs font-bold text-ink-2 truncate">
16 Sep 15:23
</span>
<span className="text-[10px] text-ink-4">
Logiflow audit snapshot
</span>
</div>
</div>
{/* Autonomy Gate Status */}
<div className="flex flex-col gap-2 rounded-xl border border-border p-3.5 bg-surface-subtle">
<div className="flex items-center justify-between">
<span className="flex items-center gap-1.5 text-xs font-bold text-ink-1">
<ShieldCheck className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
Autonomy Gate
</span>
<span className="font-mono text-[10px] font-bold text-amber-700 dark:text-amber-300 bg-amber-100 dark:bg-amber-900/40 px-2 py-0.5 rounded-full border border-amber-300 dark:border-amber-800">
Human in the Loop
</span>
</div>
<p className="text-caption text-ink-3 leading-relaxed">
{agent.gated
? 'Autonomy gate is locked (0 DB writes). This agent observes and recommends only; it cannot independently alter production tables.'
: 'Routes in-memory only. No direct database access or autonomy gates required.'}
</p>
<div className="text-[10.5px] font-mono text-ink-4 bg-surface px-2.5 py-1 rounded border border-border/60">
{agent.autonomy}
</div>
</div>
{/* Secondary Technical Metadata (Collapsible / Non-Dominating) */}
<div className="flex flex-col gap-2 pt-2 border-t border-border/60">
<span className="text-[10.5px] font-bold uppercase tracking-wider text-ink-4">
Technical Specifications (Secondary)
</span>
<div className="flex flex-col gap-2 rounded-xl border border-border/70 p-3.5 bg-surface text-caption">
<div className="flex items-center justify-between">
<span className="text-ink-4 font-mono">Source File</span>
<button
type="button"
onClick={copyPath}
className="inline-flex items-center gap-1 font-mono text-[11px] text-ink-2 hover:text-brand transition-colors cursor-pointer"
>
<span>{agent.file}</span>
<Copy className="h-3 w-3" />
</button>
</div>
<div className="flex items-center justify-between">
<span className="text-ink-4 font-mono">Lines of Code</span>
<span className="font-bold text-ink-2">{agent.lines ?? 'Core module'}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-ink-4 font-mono">Test Coverage</span>
<span
className={cn(
'font-bold',
agent.tests ? 'text-emerald-600 dark:text-emerald-400' : 'text-ink-4'
)}
>
{agent.tests === null ? 'Core module' : agent.tests ? 'Tests Passing (100%)' : 'Untested'}
</span>
</div>
<div className="flex items-center justify-between">
<span className="text-ink-4 font-mono">Capacity Load</span>
<span className="font-bold text-ink-2">{agent.load}%</span>
</div>
</div>
{/* Probe Action */}
<button
type="button"
onClick={handleProbe}
disabled={probing}
className="mt-2 inline-flex items-center justify-center gap-2 rounded-lg border border-border bg-surface px-4 py-2 text-xs font-semibold text-ink-2 hover:bg-surface-elevated hover:border-brand/40 transition-all shadow-2xs cursor-pointer"
>
<Radio className={cn('h-3.5 w-3.5', probing ? 'animate-spin text-brand' : 'text-ink-3')} />
<span>{probing ? 'Probing Socket...' : 'Test Socket Probe'}</span>
</button>
</div>
</div>
</motion.aside>
</>
)}
</AnimatePresence>
);
}
AgentInspectorDrawer.propTypes = {
agentId: PropTypes.string.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,942 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
import gsap from 'gsap';
import {
Activity,
AlertTriangle,
ArrowRight,
Box,
CheckCircle2,
Cpu,
Layers,
Package,
Play,
Radio,
RotateCcw,
Server,
ShieldAlert,
ShieldCheck,
Sparkles,
Zap,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { SOURCE_SUBJECTS, eventDrivenAgents, taskDrivenAgents } from '@/lib/agentNetwork';
// ==============================|| How work reaches an agent ||============================== //
//
// Animated with Framer Motion layout & GSAP continuous circuit flow.
// ==============================|| How work reaches an agent ||============================== //
//
// Animated with Framer Motion & GSAP continuous circuit flow.
// Interactive signal dispatch button fires animated particles along the pipeline.
/**
* RadarDot with strictly locked 1:1 circular aspect ratio (prevents squishing/congestion)
* Features crisp inner glowing orb + smooth radar ping halo
*/
export function RadarDot({ status, size = 10, className }) {
const reduce = useReducedMotion();
const config = {
active: {
core: 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.9)] ring-1 ring-emerald-400/50',
ring: 'border-emerald-500/80',
ping: true,
},
running: {
core: 'bg-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.9)] ring-1 ring-indigo-400/50',
ring: 'border-indigo-500/70',
ping: true,
},
degraded: {
core: 'bg-amber-500 shadow-[0_0_8px_rgba(245,158,11,0.9)] ring-1 ring-amber-400/50',
ring: 'border-amber-500/80',
ping: true,
},
idle: {
core: 'bg-slate-400 ring-1 ring-slate-300 dark:bg-slate-500',
ring: 'border-slate-400/40',
ping: false,
},
}[status] || {
core: 'bg-slate-400 ring-1 ring-slate-300',
ring: 'border-slate-400/40',
ping: false,
};
return (
<span
className={cn('relative inline-flex items-center justify-center shrink-0 isolate aspect-square', className)}
style={{ width: size, height: size, minWidth: size, minHeight: size, maxWidth: size, maxHeight: size }}
>
{/* Crisp glowing core */}
<span
className={cn('block rounded-full aspect-square', config.core)}
style={{ width: size * 0.75, height: size * 0.75 }}
/>
{/* High-tech breathing radar ping ring */}
{config.ping && !reduce && (
<motion.span
className={cn('absolute inset-0 m-auto rounded-full border pointer-events-none', config.ring)}
style={{ width: size, height: size }}
animate={
status === 'active'
? { scale: [1, 2.2], opacity: [0.85, 0] }
: status === 'degraded'
? { scale: [1, 1.8], opacity: [0.9, 0] }
: { scale: [1, 2], opacity: [0.75, 0] }
}
transition={{
duration: status === 'degraded' ? 1.2 : 1.8,
repeat: Infinity,
ease: 'easeOut',
}}
/>
)}
</span>
);
}
RadarDot.propTypes = {
status: PropTypes.string.isRequired,
size: PropTypes.number,
className: PropTypes.string,
};
/**
* Animated High-Tech Conveyor Arrow with continuous laser stream,
* rippling sci-fi chevrons (» » »), and GSAP glowing photon packets
*/
function CircuitConveyor({
tone = 'brand',
isFlowActive = false,
flowStage = 0,
stageThreshold = 1,
badgeText = '',
}) {
const reduce = useReducedMotion();
const packetRef = useRef(null);
const sparkRef = useRef(null);
const isCarryingActiveFlow = isFlowActive && flowStage === stageThreshold;
// GSAP continuous photon glider
useEffect(() => {
if (reduce || !packetRef.current) return;
const ctx = gsap.context(() => {
// Primary photon packet
gsap.fromTo(
packetRef.current,
{ x: 0, opacity: 0, scale: 0.7 },
{
x: 52,
opacity: 1,
scale: 1.2,
duration: isCarryingActiveFlow ? 0.7 : tone === 'brand' ? 1.5 : 2.2,
repeat: -1,
ease: 'power1.inOut',
keyframes: [
{ opacity: 0, scale: 0.7, duration: 0.1 },
{ opacity: 1, scale: 1.2, duration: 0.2 },
{ opacity: 1, scale: 1.1, duration: 0.8 },
{ opacity: 0, scale: 0.8, duration: 0.2 },
],
}
);
// Secondary trailing micro-spark packet
if (sparkRef.current) {
gsap.fromTo(
sparkRef.current,
{ x: -6, opacity: 0 },
{
x: 48,
opacity: 0.8,
duration: isCarryingActiveFlow ? 0.7 : tone === 'brand' ? 1.5 : 2.2,
repeat: -1,
delay: 0.25,
ease: 'power1.inOut',
keyframes: [
{ opacity: 0, duration: 0.1 },
{ opacity: 0.75, duration: 0.3 },
{ opacity: 0.75, duration: 0.7 },
{ opacity: 0, duration: 0.2 },
],
}
);
}
});
return () => ctx.revert();
}, [reduce, tone, isCarryingActiveFlow]);
return (
<div className="flex shrink-0 items-center justify-center py-2.5 lg:w-16 xl:w-20 lg:min-w-[56px] lg:self-center lg:py-0">
<div
className={cn(
'relative flex items-center justify-center w-full h-8 px-1 rounded-full border transition-all duration-300',
isCarryingActiveFlow
? 'border-amber-400/90 bg-amber-500/10 shadow-[0_0_12px_rgba(245,158,11,0.25)] dark:bg-amber-950/30'
: 'border-border/70 bg-surface-subtle/60 dark:bg-slate-900/40'
)}
>
{/* Floating Telemetry Micro-Badge */}
{badgeText && (
<span
className={cn(
'absolute -top-3.5 left-1/2 -translate-x-1/2 font-mono text-[9px] font-bold uppercase tracking-wider px-1.5 py-0.2 rounded transition-colors whitespace-nowrap pointer-events-none',
isCarryingActiveFlow
? 'bg-amber-400 text-amber-950 shadow-xs'
: 'bg-surface border border-border text-ink-4'
)}
>
{badgeText}
</span>
)}
{/* Animated Laser Pipeline SVG */}
<svg viewBox="0 0 68 20" className="w-full h-5 overflow-visible" fill="none">
<defs>
<linearGradient id={`conveyor-grad-${stageThreshold}`} x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor={isCarryingActiveFlow ? '#f59e0b' : tone === 'brand' ? '#dc2626' : '#6366f1'} stopOpacity="0.2" />
<stop offset="60%" stopColor={isCarryingActiveFlow ? '#f59e0b' : tone === 'brand' ? '#dc2626' : '#6366f1'} stopOpacity="1" />
<stop offset="100%" stopColor={isCarryingActiveFlow ? '#fbbf24' : tone === 'brand' ? '#ef4444' : '#818cf8'} stopOpacity="0.8" />
</linearGradient>
<filter id={`glow-${stageThreshold}`} x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="1.5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
{/* Conduit Rail Track */}
<line
x1="4"
y1="10"
x2="52"
y2="10"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
className="text-border/80 dark:text-slate-700/80"
/>
{/* Animated Flowing Laser Core */}
{!reduce && (
<motion.line
x1="4"
y1="10"
x2="50"
y2="10"
stroke={`url(#conveyor-grad-${stageThreshold})`}
strokeWidth={isCarryingActiveFlow ? '2.5' : '1.8'}
strokeLinecap="round"
strokeDasharray="5 7"
filter={isCarryingActiveFlow ? `url(#glow-${stageThreshold})` : undefined}
animate={{ strokeDashoffset: [-24, 0] }}
transition={{
duration: isCarryingActiveFlow ? 0.6 : tone === 'brand' ? 1.2 : 1.8,
repeat: Infinity,
ease: 'linear',
}}
/>
)}
{/* Layered Sci-Fi Cascading Chevrons (» » ») */}
<motion.path
d="M38 6 L43 10 L38 14"
stroke="currentColor"
strokeWidth="1.6"
strokeLinecap="round"
strokeLinejoin="round"
animate={
isCarryingActiveFlow
? { opacity: [0.4, 1, 0.4], x: [0, 2, 0] }
: { opacity: [0.2, 0.7, 0.2] }
}
transition={{ duration: 0.9, repeat: Infinity, ease: 'easeInOut' }}
className={isCarryingActiveFlow ? 'text-amber-500' : 'text-slate-400 dark:text-slate-500'}
/>
<motion.path
d="M45 6 L50 10 L45 14"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
animate={
isCarryingActiveFlow
? { opacity: [0.5, 1, 0.5], x: [0, 2.5, 0] }
: { opacity: [0.4, 0.9, 0.4] }
}
transition={{ duration: 0.9, repeat: Infinity, ease: 'easeInOut', delay: 0.15 }}
className={isCarryingActiveFlow ? 'text-amber-500' : 'text-slate-500 dark:text-slate-400'}
/>
<motion.path
d="M52 6 L57 10 L52 14"
stroke="currentColor"
strokeWidth="2.4"
strokeLinecap="round"
strokeLinejoin="round"
animate={
isCarryingActiveFlow
? { opacity: [0.7, 1, 0.7], x: [0, 3, 0] }
: { opacity: [0.6, 1, 0.6] }
}
transition={{ duration: 0.9, repeat: Infinity, ease: 'easeInOut', delay: 0.3 }}
className={isCarryingActiveFlow ? 'text-amber-500' : 'text-brand dark:text-brand'}
/>
</svg>
{/* Traveling Primary Photon Particle */}
{!reduce && (
<div
ref={packetRef}
className={cn(
'absolute left-2 top-1/2 -translate-y-1/2 rounded-full pointer-events-none transition-all duration-200 aspect-square',
isCarryingActiveFlow
? 'w-2.5 h-2.5 bg-amber-300 shadow-[0_0_12px_rgba(251,191,36,1)] z-20 ring-2 ring-amber-400/80'
: tone === 'brand'
? 'w-2 h-2 bg-brand shadow-[0_0_8px_rgba(200,16,46,0.9)]'
: 'w-2 h-2 bg-indigo-500 shadow-[0_0_8px_rgba(99,102,241,0.9)]'
)}
/>
)}
{/* Trailing Micro-Spark */}
{!reduce && isCarryingActiveFlow && (
<div
ref={sparkRef}
className="absolute left-2 top-1/2 -translate-y-1/2 w-1.5 h-1.5 rounded-full bg-amber-200/90 shadow-[0_0_6px_rgba(245,158,11,0.8)] pointer-events-none z-10 aspect-square"
/>
)}
</div>
</div>
);
}
CircuitConveyor.propTypes = {
tone: PropTypes.string,
isFlowActive: PropTypes.bool,
flowStage: PropTypes.number,
stageThreshold: PropTypes.number,
badgeText: PropTypes.string,
};
export default function AgentPipeline({ selectedAgentId, onSelectAgent, onFlowStepChange }) {
const eventAgents = eventDrivenAgents();
const taskAgents = taskDrivenAgents();
// Interactive Flow State
const [isFlowRunning, setIsFlowRunning] = useState(false);
const [flowStep, setFlowStep] = useState(0); // 0: idle, 1: source, 2: bus, 3: ai, 4: outcome
const [activeScenario, setActiveScenario] = useState('order_dispatch'); // 'order_dispatch' | 'rider_stall' | 'route_opt'
const [orderId, setOrderId] = useState('ORD-9428');
const timerRef = useRef(null);
const scenarioMeta = {
order_dispatch: {
title: 'Order Dispatch & Geo-Cluster Flow',
sourceText: `Order #${orderId} Created via Go Fiber -> Emitting booking.assigned`,
busText: "Stream 'ASSIGNMENTS' ACK -> Broadcast to DispatchAgent consumer",
aiAgent: 'DISPATCH_AGENT',
aiText: 'DispatchAgent scanning 10km H3 geo-cell -> Rider presence validated',
outcomeText: 'Autonomy safe gate verified -> Ops Desk notification pushed (0 DB writes)',
},
rider_stall: {
title: 'Rider Exception & Stall Detection Flow',
sourceText: `Rider Miler-44 ping timeout (120s) -> Emitting miler.location.updated`,
busText: "Stream 'RIDERS_LOC' ACK -> Stalled pull loop triggered",
aiAgent: 'EXCEPTION_AGENT',
aiText: 'ExceptionAgent analyzing ping silence -> Order booking stall flag generated',
outcomeText: 'Rate limiter verified (1/zone/day) -> High-priority Ops alert triggered',
},
route_opt: {
title: 'Batch Routing & Waypoint Optimization Flow',
sourceText: `Hub-3 batch dispatch triggered -> 8 deliveries sequenced in Go`,
busText: 'Internal task queue -> Handed to JARVIS master orchestrator',
aiAgent: 'ROUTE_OPTIMIZER',
aiText: 'Route Optimizer running TWVRP solver -> 10-min cache key generated',
outcomeText: 'Waypoints compiled -> Sent to rider app client via WebSocket',
},
}[activeScenario];
// Stop / Reset flow
const stopFlow = () => {
if (timerRef.current) clearTimeout(timerRef.current);
setIsFlowRunning(false);
setFlowStep(0);
};
// Run Step-by-Step Flow Simulation
const startFlow = (scenario = activeScenario) => {
stopFlow();
setActiveScenario(scenario);
setIsFlowRunning(true);
const newOrderId = `ORD-${Math.floor(1000 + Math.random() * 9000)}`;
setOrderId(newOrderId);
// Step 1: Order Creation (Source)
setFlowStep(1);
if (onFlowStepChange) {
onFlowStepChange({
step: 1,
title: 'Order Ingestion',
stage: 'doormile_backend',
detail: `New order #${newOrderId} ingested into Go Fiber backend`,
agentId: 'JARVIS',
});
}
// Step 2: NATS JetStream Ingestion
timerRef.current = setTimeout(() => {
setFlowStep(2);
if (onFlowStepChange) {
onFlowStepChange({
step: 2,
title: 'Event Bus Transit',
stage: 'NATS JetStream',
detail: `Event published to JetStream stream 'ASSIGNMENTS' (8ms)`,
agentId: null,
});
}
// Step 3: AI Engine Swarm Processing
timerRef.current = setTimeout(() => {
setFlowStep(3);
const targetAgent = scenarioMeta.aiAgent;
onSelectAgent(targetAgent);
if (onFlowStepChange) {
onFlowStepChange({
step: 3,
title: 'AI Swarm Reasoning',
stage: 'AI_engine',
detail: `${targetAgent} processing assignment heuristics`,
agentId: targetAgent,
});
}
// Step 4: Outcome & Safety Enforcement
timerRef.current = setTimeout(() => {
setFlowStep(4);
if (onFlowStepChange) {
onFlowStepChange({
step: 4,
title: 'Safe Escalation',
stage: 'Console Alert',
detail: `Autonomy gates safe: Alert dispatched to Ops Console`,
agentId: targetAgent,
});
}
// Complete & return to idle after 3s
timerRef.current = setTimeout(() => {
setIsFlowRunning(false);
}, 3200);
}, 1600);
}, 1600);
}, 1400);
};
useEffect(() => {
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, []);
return (
<div className="flex flex-col gap-4">
{/* ═══════════════════════════════════════════════════════════════════
INTERACTIVE FLOW CONTROL BAR & STEP TRACKER
═══════════════════════════════════════════════════════════════════ */}
<div className="flex flex-col gap-3 rounded-xl border border-border bg-surface-subtle/80 p-3.5 shadow-2xs">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-center gap-2">
<span
className={cn(
'flex h-2.5 w-2.5 rounded-full',
isFlowRunning ? 'bg-amber-500 animate-ping' : 'bg-emerald-500'
)}
/>
<span className="font-heading text-xs font-bold text-ink-1">
Interactive Order Flow Simulator
</span>
<span className="text-[11px] text-ink-3">· Observe how real events traverse the architecture</span>
</div>
{/* Action Buttons */}
<div className="flex items-center gap-2 flex-wrap">
{/* Scenario dropdown buttons */}
<div className="flex items-center gap-1 rounded-lg border border-border bg-surface p-1 text-xs">
<button
type="button"
onClick={() => startFlow('order_dispatch')}
disabled={isFlowRunning && activeScenario === 'order_dispatch'}
className={cn(
'rounded px-2.5 py-1 font-semibold transition-all cursor-pointer flex items-center gap-1.5',
activeScenario === 'order_dispatch' && isFlowRunning
? 'bg-brand text-white shadow-xs'
: 'text-ink-2 hover:bg-surface-subtle'
)}
>
<Package className="h-3 w-3" />
<span>1. Order Booking</span>
</button>
<button
type="button"
onClick={() => startFlow('rider_stall')}
disabled={isFlowRunning && activeScenario === 'rider_stall'}
className={cn(
'rounded px-2.5 py-1 font-semibold transition-all cursor-pointer flex items-center gap-1.5',
activeScenario === 'rider_stall' && isFlowRunning
? 'bg-amber-600 text-white shadow-xs'
: 'text-ink-2 hover:bg-surface-subtle'
)}
>
<AlertTriangle className="h-3 w-3" />
<span>2. Rider Stall</span>
</button>
<button
type="button"
onClick={() => startFlow('route_opt')}
disabled={isFlowRunning && activeScenario === 'route_opt'}
className={cn(
'rounded px-2.5 py-1 font-semibold transition-all cursor-pointer flex items-center gap-1.5',
activeScenario === 'route_opt' && isFlowRunning
? 'bg-indigo-600 text-white shadow-xs'
: 'text-ink-2 hover:bg-surface-subtle'
)}
>
<Cpu className="h-3 w-3" />
<span>3. Batch Route</span>
</button>
</div>
{/* Reset / Stop */}
{isFlowRunning && (
<button
type="button"
onClick={stopFlow}
className="inline-flex items-center gap-1 rounded-lg border border-border bg-surface px-2.5 py-1.5 text-xs font-semibold text-ink-3 hover:text-ink-1 hover:bg-surface-elevated transition-colors cursor-pointer"
title="Reset simulation"
>
<RotateCcw className="h-3 w-3" />
<span>Reset</span>
</button>
)}
</div>
</div>
{/* Dynamic 4-Step Progress Ribbon (Clean & Professional) */}
<div className="grid grid-cols-1 sm:grid-cols-4 gap-2.5 pt-2 border-t border-border/50">
{[
{ step: 1, label: '1. Order Created', sub: 'doormile_backend (Go)' },
{ step: 2, label: '2. NATS JetStream', sub: 'Stream: ASSIGNMENTS' },
{ step: 3, label: '3. AI Engine Reasoning', sub: `${scenarioMeta.aiAgent}` },
{ step: 4, label: '4. Safe Escalation', sub: 'Ops Alert · 0 DB Writes' },
].map((item) => {
const isCurrent = flowStep === item.step;
const isDone = flowStep > item.step;
return (
<div
key={item.step}
className={cn(
'flex items-center gap-2.5 rounded-lg border px-3 py-2 transition-all duration-200 bg-surface',
isCurrent
? 'border-brand ring-1 ring-brand/40 shadow-xs'
: isDone
? 'border-slate-300 dark:border-slate-700'
: 'border-border opacity-70'
)}
>
<div
className={cn(
'flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] font-bold transition-all',
isCurrent
? 'bg-brand text-white'
: isDone
? 'bg-slate-700 text-white dark:bg-slate-200 dark:text-slate-900'
: 'bg-surface-sunken text-ink-4'
)}
>
{isDone ? <CheckCircle2 className="h-3.5 w-3.5" /> : item.step}
</div>
<div className="flex flex-col min-w-0">
<span className={cn('text-xs font-semibold truncate', isCurrent ? 'text-brand font-bold' : 'text-ink-1')}>
{item.label}
</span>
<span className="font-mono text-[10px] text-ink-4 truncate">{item.sub}</span>
</div>
</div>
);
})}
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════
TOPOLOGY DIAGRAM WITH DYNAMIC STEP HIGHLIGHTING & PARCELS
═══════════════════════════════════════════════════════════════════ */}
<div className="relative flex flex-col gap-4 lg:flex-row lg:items-stretch lg:gap-0">
{/* Floating Active Event Parcel Indicator */}
<AnimatePresence>
{isFlowRunning && (
<motion.div
initial={{ opacity: 0, y: -8, scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="absolute -top-3.5 left-4 z-30 inline-flex items-center gap-2 rounded-full border border-amber-300 bg-amber-100 dark:bg-amber-900 px-3 py-1 text-xs font-bold text-amber-900 dark:text-amber-200 shadow-md"
>
<Package className="h-3.5 w-3.5 animate-bounce text-amber-700 dark:text-amber-300" />
<span>Event Packet: #{orderId}</span>
<span className="font-mono text-[10.5px] bg-amber-200/80 dark:bg-amber-800 px-1.5 py-0.5 rounded">
Stage {flowStep}/4
</span>
</motion.div>
)}
</AnimatePresence>
{/* 1 · SOURCE PRODUCER */}
<motion.div
whileHover={{ y: -2 }}
className={cn(
'relative flex flex-col gap-2 rounded-xl border p-4 transition-all duration-200 lg:min-w-[170px] lg:flex-[0_1_185px] bg-surface',
flowStep === 1
? 'border-brand ring-2 ring-brand/20 shadow-xs'
: 'border-border hover:border-border-strong'
)}
>
<div className="flex items-center justify-between">
<span className="text-[10.5px] font-bold uppercase tracking-[0.08em] text-ink-4">1 · Source</span>
<span className="rounded bg-slate-100 dark:bg-slate-800 px-1.5 py-0.5 font-mono text-[10px] font-bold text-ink-2">
Go Fiber
</span>
</div>
<div className="flex items-center gap-2">
<Server className="h-4 w-4 text-ink-2" />
<span className="font-heading text-sm font-bold text-ink-1">doormile_backend</span>
</div>
<p className="text-caption text-ink-3 leading-relaxed">
{flowStep === 1 ? (
<span className="font-semibold text-brand">
⚡ {scenarioMeta.sourceText}
</span>
) : (
'Assigns riders internally, then streams status notifications onto the message bus.'
)}
</p>
<div className="flex flex-col gap-1 pt-1 mt-auto">
{SOURCE_SUBJECTS.map((s) => (
<span
key={s}
className={cn(
'inline-flex items-center gap-1.5 rounded-md border px-2 py-1 font-mono text-[10px] transition-colors',
flowStep === 1 && s === 'booking.assigned'
? 'border-brand text-brand font-bold bg-surface'
: 'bg-surface border-border text-ink-2'
)}
>
<span
className={cn(
'h-1.5 w-1.5 rounded-full',
flowStep === 1 && s === 'booking.assigned' ? 'bg-brand' : 'bg-slate-400'
)}
/>
<span className="truncate">{s}</span>
</span>
))}
</div>
</motion.div>
{/* Conveyor 1 */}
<CircuitConveyor
tone="brand"
isFlowActive={isFlowRunning}
flowStage={flowStep}
stageThreshold={1}
badgeText="NATS Pub"
/>
{/* 2 · BUS STREAM */}
<motion.div
whileHover={{ y: -2 }}
className={cn(
'relative flex flex-col gap-2 rounded-xl border p-4 transition-all duration-200 lg:min-w-[165px] lg:flex-[0_1_180px] bg-surface',
flowStep === 2
? 'border-brand ring-2 ring-brand/20 shadow-xs'
: 'border-border hover:border-border-strong'
)}
>
<div className="flex items-center justify-between">
<span className="text-[10.5px] font-bold uppercase tracking-[0.08em] text-ink-4">
2 · Message Bus
</span>
<RadarDot status="degraded" size={10} />
</div>
<div className="flex items-center gap-2">
<Radio className="h-4 w-4 text-ink-2" />
<span className="font-heading text-sm font-bold text-ink-1">NATS JetStream</span>
</div>
<p className="text-caption text-ink-3 leading-relaxed">
{flowStep === 2 ? (
<span className="font-semibold text-brand">
⚡ {scenarioMeta.busText}
</span>
) : (
'6 durable event streams. Best-effort delivery — publish never blocks live dispatch.'
)}
</p>
<div className="mt-auto">
<span className="inline-flex w-full items-center justify-center gap-1.5 rounded-lg border border-amber-300 bg-amber-50 dark:bg-amber-950/30 dark:border-amber-800 px-2.5 py-1.5 text-[11px] font-bold text-amber-900 dark:text-amber-200 shadow-2xs">
<AlertTriangle className="h-3 w-3" />
<span>Dropped 20 Sep (Stalled)</span>
</span>
</div>
</motion.div>
{/* Conveyor 2 */}
<CircuitConveyor
tone="muted"
isFlowActive={isFlowRunning}
flowStage={flowStep}
stageThreshold={2}
badgeText="JetStream"
/>
{/* 3 · AGENT SWARM */}
<motion.div
className={cn(
'relative flex flex-col gap-2 rounded-xl border bg-surface p-3.5 transition-all duration-200 lg:min-w-[390px] lg:flex-[2_1_520px]',
flowStep === 3
? 'border-brand ring-2 ring-brand/20 shadow-xs'
: 'border-border'
)}
>
<div className="flex items-center justify-between">
<span className="text-[10.5px] font-bold uppercase tracking-[0.08em] text-ink-4">
3 · Intelligence Swarm
</span>
<span className="rounded-full bg-slate-100 dark:bg-slate-800 px-2 py-0.5 text-[10.5px] font-semibold text-ink-3">
9 Specialized Agents
</span>
</div>
{/* Real-time reasoning badge during Step 3 */}
{flowStep === 3 && (
<div className="rounded-lg border border-brand/30 bg-surface p-2 text-xs font-semibold text-brand flex items-center gap-2">
<Sparkles className="h-4 w-4 text-brand shrink-0" />
<span>{scenarioMeta.aiText}</span>
</div>
)}
<div className="flex flex-col gap-3">
{/* Event-driven sub-cluster */}
<div className="rounded-lg border border-dashed border-border/80 p-2.5 bg-surface-subtle/50">
<div className="flex items-center justify-between mb-2">
<span className="text-caption font-bold text-ink-1 flex items-center gap-1.5">
<Activity className="h-3.5 w-3.5 text-ink-2" />
Event-Driven Subscribers
</span>
<span className="text-[10px] text-ink-3 font-semibold bg-surface border border-border px-1.5 py-0.5 rounded">
Autonomous
</span>
</div>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
{eventAgents.map((agent) => {
const isSelected = selectedAgentId === agent.id;
const isAgentActiveInFlow = flowStep === 3 && scenarioMeta.aiAgent === agent.id;
const statusChipTone = {
active: 'bg-emerald-50 text-emerald-800 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-800',
degraded: 'bg-amber-50 text-amber-800 border-amber-200 dark:bg-amber-950/40 dark:text-amber-300 dark:border-amber-800',
idle: 'bg-slate-50 text-slate-600 border-slate-200 dark:bg-slate-800/40 dark:text-slate-400 dark:border-slate-700',
}[agent.status];
return (
<motion.button
key={agent.id}
type="button"
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => onSelectAgent(agent.id)}
className={cn(
'flex flex-col gap-2 rounded-lg border p-2.5 text-left transition-all cursor-pointer relative bg-surface',
isAgentActiveInFlow
? 'border-brand ring-2 ring-brand/30 shadow-xs'
: isSelected
? 'border-brand ring-1 ring-brand/20'
: 'border-border hover:border-border-strong hover:shadow-2xs'
)}
>
{/* Top Row: Agent Name + High-tech Glowing Radar Beacon */}
<div className="flex items-center justify-between gap-1 min-w-0">
<span className="font-heading text-[11px] xl:text-xs font-bold text-ink-1 whitespace-nowrap" title={agent.name}>
{agent.name}
</span>
<div className="flex items-center gap-1 shrink-0">
<RadarDot status={agent.status} size={9} />
</div>
</div>
{/* Middle Row: Subscription Event Badge */}
<div className="flex items-center gap-1 rounded bg-surface-subtle/80 dark:bg-slate-800/50 px-1.5 py-0.5 border border-border/50 min-w-0">
<code className="font-mono text-[9px] text-ink-3 truncate w-full" title={agent.subscribes[0]}>
{agent.subscribes[0] || 'Direct NATS'}
</code>
</div>
{/* Bottom Row: Event count & Status indicator */}
<div className="flex items-center justify-between pt-1 border-t border-border/40 text-[10px]">
<span
className={cn(
'font-medium text-[9.5px]',
agent.events > 0
? 'text-emerald-700 dark:text-emerald-400 font-bold'
: 'text-ink-4'
)}
>
{agent.events > 0 ? `${agent.events} events` : '0 events'}
</span>
<span
className={cn(
'capitalize px-1.5 py-0.2 rounded-full border text-[9px] font-semibold shrink-0',
statusChipTone
)}
>
{agent.status}
</span>
</div>
</motion.button>
);
})}
</div>
</div>
{/* Task-driven sub-cluster */}
<div className="rounded-lg border border-dashed border-border/80 p-2.5 bg-surface-subtle/50">
<div className="flex items-center justify-between mb-2">
<span className="text-caption font-bold text-ink-1 flex items-center gap-1.5">
<Server className="h-3.5 w-3.5 text-ink-2" />
Task-Driven Orchestration
</span>
<span className="text-[10px] text-ink-3 font-semibold bg-surface border border-border px-1.5 py-0.5 rounded">
JARVIS Delegated
</span>
</div>
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={() => onSelectAgent('JARVIS')}
className={cn(
'flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-bold transition-all cursor-pointer bg-surface',
selectedAgentId === 'JARVIS'
? 'border-brand ring-1 ring-brand/30 text-brand'
: 'border-border text-ink-2 hover:bg-surface-elevated'
)}
>
<span className="h-2 w-2 rounded-full bg-indigo-600" />
<span>JARVIS (Master)</span>
</button>
<div className="flex flex-wrap gap-1.5">
{taskAgents.map((agent) => {
const isAgentActiveInFlow = flowStep === 3 && scenarioMeta.aiAgent === agent.id;
return (
<button
key={agent.id}
type="button"
onClick={() => onSelectAgent(agent.id)}
className={cn(
'rounded-md border px-2 py-1 text-[11px] font-medium transition-all cursor-pointer bg-surface',
isAgentActiveInFlow
? 'border-brand ring-1 ring-brand/40 text-brand font-bold shadow-xs'
: selectedAgentId === agent.id
? 'border-brand text-brand font-bold'
: 'border-border text-ink-2 hover:bg-surface-subtle'
)}
>
{agent.name}
</button>
);
})}
</div>
</div>
</div>
</div>
</motion.div>
{/* Conveyor 3 */}
<CircuitConveyor
tone="brand"
isFlowActive={isFlowRunning}
flowStage={flowStep}
stageThreshold={3}
badgeText="Safe Gate"
/>
{/* 4 · OUTCOME */}
<motion.div
whileHover={{ y: -2 }}
className={cn(
'relative flex flex-col gap-2 rounded-xl border p-4 transition-all duration-200 lg:min-w-[170px] lg:flex-[0_1_185px] bg-surface',
flowStep === 4
? 'border-brand ring-2 ring-brand/20 shadow-xs'
: 'border-border hover:border-border-strong'
)}
>
<div className="flex items-center justify-between">
<span className="text-[10.5px] font-bold uppercase tracking-[0.08em] text-ink-4">
4 · Safe Escalation
</span>
<ShieldAlert className="h-4 w-4 text-ink-3" />
</div>
<span className="font-heading text-sm font-bold text-ink-1">Alert Only (Never Act)</span>
<p className="text-caption text-ink-3 leading-relaxed">
{flowStep === 4 ? (
<span className="font-semibold text-brand">
✓ {scenarioMeta.outcomeText}
</span>
) : (
'Emits ops desk alerts & customer delay SMS. Strictly limited to 1 escalation/zone/day.'
)}
</p>
<div className="mt-auto">
<div
className={cn(
'inline-flex w-full items-center justify-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-[11px] font-bold shadow-2xs transition-colors bg-surface',
flowStep === 4
? 'border-brand text-brand'
: 'border-border text-ink-3'
)}
>
<CheckCircle2 className={cn('h-3 w-3', flowStep === 4 ? 'text-brand' : 'text-ink-4')} />
<span>{flowStep === 4 ? 'Flow Verified & Audited' : 'Autonomy Gates Locked'}</span>
</div>
</div>
</motion.div>
</div>
</div>
);
}
AgentPipeline.propTypes = {
selectedAgentId: PropTypes.string,
onSelectAgent: PropTypes.func.isRequired,
onFlowStepChange: PropTypes.func,
};

View File

@@ -0,0 +1,312 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';
import {
ArrowLeft,
ArrowRight,
CheckCircle2,
ChevronRight,
ExternalLink,
Layers,
Pause,
Play,
RotateCcw,
ShieldCheck,
Sparkles,
Zap,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { AGENT_WORKFLOWS } from './agentWorkflowsData';
export default function AgentWorkflowView({
agentId,
onBackToNetwork,
onOpenTechnicalDrawer,
}) {
const workflow = AGENT_WORKFLOWS[agentId] || AGENT_WORKFLOWS.ORDER_AGENT;
const reduce = useReducedMotion();
// Active step index in execution animation (0 to workflow.steps.length - 1)
const [activeStepIndex, setActiveStepIndex] = useState(0);
const [isPlaying, setIsPlaying] = useState(true);
const [selectedStepDetail, setSelectedStepDetail] = useState(null);
const steps = workflow.steps;
const timerRef = useRef(null);
// Animated execution loop: advances step by step
useEffect(() => {
if (!isPlaying) return;
timerRef.current = setTimeout(() => {
setActiveStepIndex((prev) => (prev + 1) % steps.length);
}, 1800);
return () => {
if (timerRef.current) clearTimeout(timerRef.current);
};
}, [isPlaying, activeStepIndex, steps.length]);
const activeStep = steps[activeStepIndex];
return (
<motion.div
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -12 }}
transition={{ duration: 0.35, ease: [0.16, 1, 0.3, 1] }}
className="flex flex-col gap-6 w-full"
>
{/* ── Top Bar: Back Button & Agent Title ── */}
<div className="flex flex-wrap items-center justify-between gap-4 border-b border-border/70 pb-4">
<div className="flex items-center gap-3">
<button
type="button"
onClick={onBackToNetwork}
className="group inline-flex items-center gap-2 rounded-xl border border-border bg-surface px-3.5 py-1.5 text-xs font-semibold text-ink-2 hover:text-ink-1 hover:border-brand/40 hover:bg-surface-elevated transition-all shadow-2xs cursor-pointer"
>
<ArrowLeft className="h-4 w-4 text-ink-3 group-hover:-translate-x-0.5 group-hover:text-brand transition-transform" />
<span>Back to Agent Network</span>
</button>
<div className="h-4 w-px bg-border hidden sm:block" />
<div className="flex items-center gap-2.5">
<span
className="h-2.5 w-2.5 rounded-full ring-2 ring-emerald-500/20"
style={{ backgroundColor: workflow.color }}
/>
<h2 className="font-heading text-base sm:text-lg font-bold text-ink-1">
{workflow.name}
</h2>
<span className="text-caption text-ink-4 hidden md:inline">
— {workflow.subtitle}
</span>
</div>
</div>
{/* Right actions: Pause/Play & Technical Drawer */}
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setIsPlaying(!isPlaying)}
className="inline-flex items-center gap-1.5 rounded-lg border border-border bg-surface px-3 py-1.5 text-xs font-semibold text-ink-2 hover:bg-surface-elevated transition-colors cursor-pointer"
>
{isPlaying ? (
<>
<Pause className="h-3 w-3 text-ink-3" />
<span>Pause</span>
</>
) : (
<>
<Play className="h-3 w-3 text-brand fill-brand" />
<span>Run</span>
</>
)}
</button>
<button
type="button"
onClick={() => onOpenTechnicalDrawer(agentId)}
className="inline-flex items-center gap-1.5 rounded-lg border border-border bg-surface px-3 py-1.5 text-xs font-semibold text-ink-2 hover:border-brand/40 hover:text-brand transition-all cursor-pointer shadow-2xs"
>
<Layers className="h-3.5 w-3.5 text-ink-3" />
<span>Technical Specs</span>
</button>
</div>
</div>
{/* ── Workflow Description Banner ── */}
<div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border/80 bg-surface/90 px-4 py-3 shadow-2xs">
<div className="flex items-center gap-2.5">
<Sparkles className="h-4 w-4 text-brand shrink-0" />
<p className="text-body-sm text-ink-2 leading-relaxed">
{workflow.summary}
</p>
</div>
<div className="flex items-center gap-1 text-[11px] font-semibold text-ink-4">
<ShieldCheck className="h-3.5 w-3.5 text-emerald-600 dark:text-emerald-400" />
<span>Autonomy Gated (0 DB writes)</span>
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════
THE WORKFLOW TIMELINE / PIPELINE (HERO VISUALIZATION)
═══════════════════════════════════════════════════════════════════ */}
<div className="flex flex-col gap-6 rounded-2xl border border-border/80 bg-surface p-5 sm:p-6 shadow-xs">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xs font-bold uppercase tracking-wider text-ink-4">
Execution Timeline
</span>
<span className="text-caption text-ink-4">
· Step {activeStepIndex + 1} of {steps.length}
</span>
</div>
<span className="text-[11px] text-ink-4">
Click any step to inspect details
</span>
</div>
{/* Responsive Pipeline Steps (Horizontal on Desktop, Vertical on Mobile) */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-3 relative">
{steps.map((stepItem, index) => {
const isCompleted = index < activeStepIndex;
const isProcessing = index === activeStepIndex;
const isWaiting = index > activeStepIndex;
const isSelected = selectedStepDetail?.id === stepItem.id;
return (
<motion.div
key={stepItem.id}
whileHover={{ y: -2 }}
onClick={() => setSelectedStepDetail(stepItem)}
className={cn(
'group relative flex flex-col gap-2 rounded-xl border p-3.5 transition-all duration-300 cursor-pointer bg-surface text-left',
isProcessing
? 'border-brand ring-2 ring-brand/30 shadow-md scale-102 bg-surface-elevated z-10'
: isSelected
? 'border-brand ring-1 ring-brand/40 bg-surface-elevated shadow-xs'
: isCompleted
? 'border-border/80 hover:border-border hover:bg-surface-elevated'
: 'border-border/50 opacity-60 hover:opacity-90'
)}
>
{/* Step Index & State Indicator */}
<div className="flex items-center justify-between">
<span className="font-mono text-[10.5px] font-bold text-ink-4">
0{index + 1}
</span>
{/* Clean Visual State Beacon */}
{isCompleted ? (
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-emerald-100 dark:bg-emerald-950/60 text-emerald-700 dark:text-emerald-300 text-[10px] font-bold">
✓
</span>
) : isProcessing ? (
<span className="relative flex h-5 w-5 items-center justify-center">
<span className="h-2.5 w-2.5 rounded-full bg-brand animate-pulse" />
<span className="absolute -inset-0.5 rounded-full border border-brand animate-ping opacity-75" />
</span>
) : (
<span className="h-2 w-2 rounded-full bg-slate-300 dark:bg-slate-700" />
)}
</div>
{/* Step Title */}
<h3
className={cn(
'font-heading text-xs font-bold transition-colors leading-tight',
isProcessing
? 'text-brand'
: isCompleted
? 'text-ink-1'
: 'text-ink-3'
)}
>
{stepItem.title}
</h3>
{/* Short Human-Readable Description (Max 2 lines) */}
<p className="text-[11px] text-ink-3 leading-snug line-clamp-2">
{stepItem.description}
</p>
{/* Technical Output Tag */}
<div className="mt-auto pt-2 border-t border-border/40">
<code className="font-mono text-[9px] text-ink-4 truncate block">
{stepItem.output}
</code>
</div>
</motion.div>
);
})}
</div>
{/* ── Agent-to-Agent Transition Handover (Requirement: Show Where Work Goes Next) ── */}
<div className="flex items-center justify-between rounded-xl border border-dashed border-border bg-surface-subtle/60 p-3.5 mt-2">
<div className="flex items-center gap-2">
<span className="flex h-2 w-2 rounded-full bg-indigo-500 animate-pulse" />
<span className="text-xs font-bold text-ink-1">
Workflow Handover:
</span>
<span className="text-caption text-ink-3">
Upon completion, validated events transit directly to:
</span>
</div>
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-1.5 rounded-lg border border-indigo-200 bg-indigo-50 dark:border-indigo-800 dark:bg-indigo-950/40 px-3 py-1 text-xs font-bold text-indigo-900 dark:text-indigo-300">
<span>{workflow.nextAgent}</span>
<ArrowRight className="h-3.5 w-3.5" />
</span>
</div>
</div>
</div>
{/* ── Selected Step Detail Inspector ── */}
<AnimatePresence>
{selectedStepDetail && (
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
className="flex flex-col gap-3 rounded-xl border border-border bg-surface p-4 shadow-sm"
>
<div className="flex items-center justify-between border-b border-border/60 pb-2">
<div className="flex items-center gap-2">
<span className="flex h-2 w-2 rounded-full bg-brand" />
<h3 className="font-heading text-xs font-bold uppercase tracking-wider text-ink-1">
Step Detail: {selectedStepDetail.title}
</h3>
</div>
<button
type="button"
onClick={() => setSelectedStepDetail(null)}
className="text-caption text-ink-4 hover:text-ink-1 transition-colors cursor-pointer"
>
Close ✕
</button>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-xs">
<div className="flex flex-col gap-1">
<span className="text-ink-4 font-mono uppercase text-[10px]">
What happens?
</span>
<p className="text-ink-2 leading-relaxed">
{selectedStepDetail.description}
</p>
</div>
<div className="flex flex-col gap-1">
<span className="text-ink-4 font-mono uppercase text-[10px]">
Input Trigger
</span>
<code className="font-mono text-xs font-semibold text-ink-1 bg-surface-subtle px-2 py-1 rounded border border-border/60">
{selectedStepDetail.input}
</code>
</div>
<div className="flex flex-col gap-1">
<span className="text-ink-4 font-mono uppercase text-[10px]">
Output Result
</span>
<code className="font-mono text-xs font-semibold text-emerald-700 dark:text-emerald-400 bg-surface-subtle px-2 py-1 rounded border border-border/60">
{selectedStepDetail.output}
</code>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
}
AgentWorkflowView.propTypes = {
agentId: PropTypes.string.isRequired,
onBackToNetwork: PropTypes.func.isRequired,
onOpenTechnicalDrawer: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,333 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { AnimatePresence, motion } from 'framer-motion';
import {
Activity,
AlertTriangle,
ArrowUpRight,
Bot,
CheckCircle2,
ChevronRight,
ExternalLink,
Layers,
Radio,
RefreshCw,
Server,
ShieldAlert,
ShieldCheck,
Terminal,
Zap,
} from 'lucide-react';
import { PageHeader } from '@/components/ds';
import { cn } from '@/lib/utils';
import {
AGENTS,
LOG_ERROR_LINES,
LOG_TOTAL_LINES,
RUN_LOG,
agentById,
networkStats,
} from '@/lib/agentNetwork';
import AgentFlowVisualizer from './AgentFlowVisualizer';
import AgentInspectorDrawer from './AgentInspectorDrawer';
export default function Agents() {
const [selectedAgentId, setSelectedAgentId] = useState('DISPATCH_AGENT');
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [activeLogModal, setActiveLogModal] = useState(null);
// Live Activity Stream (3–5 recent events, Requirement: LIVE ACTIVITY)
const [liveActivities, setLiveActivities] = useState([
{
id: 1,
time: '16:24:03',
agent: 'Dispatch',
action: 'Rider assignment completed',
color: '#10b981',
},
{
id: 2,
time: '16:24:01',
agent: 'Exception',
action: 'Rider inactivity detected',
color: '#f59e0b',
},
{
id: 3,
time: '16:23:58',
agent: 'JARVIS',
action: 'Assignment failure routed',
color: '#6366f1',
},
]);
const handleOpenInspector = (agentId) => {
setSelectedAgentId(agentId);
setIsDrawerOpen(true);
};
const handleActivityLogged = (newActivity) => {
setLiveActivities((prev) => [
{
id: Date.now(),
time: newActivity.time,
agent: newActivity.agent,
action: newActivity.action,
color: newActivity.color,
},
...prev.slice(0, 3), // Keep strictly 4 items max to avoid clutter
]);
};
return (
<div className="flex flex-col gap-6 p-4 sm:p-6 lg:p-8 max-w-7xl mx-auto w-full">
{/* ═══════════════════════════════════════════════════════════════════
COMPACT TOP STATUS BAR (REQUIREMENT: TOP STATUS BAR)
Clean, minimal telemetry ribbon: ● 1 Processing · ● 1 Warning · ● 7 Idle · 🔒 Autonomy locked
═══════════════════════════════════════════════════════════════════ */}
<div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-border/80 bg-surface px-4 py-2.5 shadow-2xs">
{/* Left: Identity */}
<div className="flex items-center gap-2.5">
<span className="flex h-2.5 w-2.5 rounded-full bg-emerald-500 animate-pulse" />
<h1 className="font-heading text-sm sm:text-base font-bold text-ink-1">
Doormile AI Agent Network
</h1>
<span className="text-caption text-ink-4 hidden md:inline">
· Real-Time Workflow Visualizer
</span>
</div>
{/* Right: Clean Status Indicators */}
<div className="flex flex-wrap items-center gap-2 text-xs">
{/* 1 Processing */}
<div className="flex items-center gap-1.5 rounded-full border border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-950/40 px-2.5 py-0.5 text-emerald-800 dark:text-emerald-300 font-semibold">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-500" />
<span>1 Processing</span>
</div>
{/* 1 Warning */}
<div className="flex items-center gap-1.5 rounded-full border border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-950/40 px-2.5 py-0.5 text-amber-800 dark:text-amber-300 font-semibold">
<span className="h-1.5 w-1.5 rounded-full bg-amber-500" />
<span>1 Warning</span>
</div>
{/* 7 Idle */}
<div className="flex items-center gap-1.5 rounded-full border border-border bg-surface-subtle px-2.5 py-0.5 text-ink-3 font-semibold">
<span className="h-1.5 w-1.5 rounded-full bg-slate-400" />
<span>7 Idle</span>
</div>
{/* 🔒 Autonomy Locked */}
<div
className="flex items-center gap-1.5 rounded-full border border-indigo-200 bg-indigo-50 dark:border-indigo-800 dark:bg-indigo-950/40 px-2.5 py-0.5 text-indigo-900 dark:text-indigo-300 font-semibold"
title="Autonomy locked: AI observes and recommends, but cannot independently alter production systems."
>
<ShieldCheck className="h-3 w-3 text-indigo-600 dark:text-indigo-400" />
<span>Autonomy: 0 / 8</span>
</div>
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════
THE HERO: 4-STAGE AI FLOW VISUALIZER & DEDICATED WORKFLOWS
═══════════════════════════════════════════════════════════════════ */}
<AgentFlowVisualizer
onOpenTechnicalDrawer={handleOpenInspector}
onActivityLogged={handleActivityLogged}
/>
{/* ═══════════════════════════════════════════════════════════════════
SECONDARY SECTIONS: LIVE ACTIVITY & GROUPED SYSTEM DIAGNOSTICS
═══════════════════════════════════════════════════════════════════ */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{/* ── 1. Compact Live Activity Feed ── */}
<div className="flex flex-col gap-3 rounded-xl border border-border/80 bg-surface p-4 shadow-xs">
<div className="flex items-center justify-between border-b border-border/50 pb-2">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 text-ink-2" />
<h2 className="font-heading text-xs font-bold uppercase tracking-wider text-ink-1">
Live Activity
</h2>
</div>
<span className="text-[10.5px] font-mono text-ink-4">
Latest events
</span>
</div>
<div className="flex flex-col gap-2">
<AnimatePresence initial={false}>
{liveActivities.slice(0, 4).map((activity) => (
<motion.div
key={activity.id}
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.2 }}
className="flex items-center justify-between gap-3 rounded-lg border border-border/60 bg-surface-subtle/50 px-3 py-2 text-xs"
>
<div className="flex items-center gap-2.5 truncate">
<span
className="h-2 w-2 rounded-full shrink-0"
style={{ backgroundColor: activity.color }}
/>
<span className="font-bold text-ink-1">
{activity.agent}
</span>
<span className="text-ink-4">·</span>
<span className="text-ink-3 truncate">
{activity.action}
</span>
</div>
<span className="font-mono text-[10px] text-ink-4 shrink-0">
{activity.time}
</span>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
{/* ── 2. Grouped System Diagnostics (Requirement: Group Repeated Errors) ── */}
<div className="flex flex-col gap-3 rounded-xl border border-border/80 bg-surface p-4 shadow-xs">
<div className="flex items-center justify-between border-b border-border/50 pb-2">
<div className="flex items-center gap-2">
<AlertTriangle className="h-4 w-4 text-amber-500" />
<h2 className="font-heading text-xs font-bold uppercase tracking-wider text-ink-1">
System Diagnostics
</h2>
</div>
<span className="text-[10.5px] font-mono text-ink-4">
Grouped health checks
</span>
</div>
<div className="flex flex-col gap-2">
{/* Grouped Error 1: NATS connection closed */}
<div
onClick={() =>
setActiveLogModal({
title: 'NATS connection closed',
occurrences: '8,594 occurrences',
detail:
'Both pull loops in ExceptionAgent repeatedly retried a closed NATS handle without backoff reconnection.',
solution:
'Solution: Implemented auto-reconnect backoff on consumer handle.',
})
}
className="flex items-center justify-between rounded-lg border border-rose-200 dark:border-rose-900/60 bg-rose-50/50 dark:bg-rose-950/20 p-2.5 text-xs hover:border-rose-300 transition-colors cursor-pointer group"
>
<div className="flex items-center gap-2.5 truncate">
<span className="flex h-2 w-2 rounded-full bg-rose-500 shrink-0" />
<span className="font-bold text-rose-900 dark:text-rose-200 truncate">
NATS connection closed
</span>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="font-mono text-[10.5px] font-bold text-rose-800 dark:text-rose-300 bg-rose-100 dark:bg-rose-900/60 px-2 py-0.5 rounded-full">
8,594 occurrences
</span>
<ChevronRight className="h-3.5 w-3.5 text-rose-400 group-hover:translate-x-0.5 transition-transform" />
</div>
</div>
{/* Grouped Error 2: Unknown zone */}
<div
onClick={() =>
setActiveLogModal({
title: 'Unknown zone failure',
occurrences: '2 occurrences',
detail:
'Go backend published booking.assignment_failed without a zone_id attribute. DispatchAgent automatically re-bucketed failures into H3 geo-cells.',
solution:
'Solution: Added fallback geo-cell matching in dispatch pipeline.',
})
}
className="flex items-center justify-between rounded-lg border border-amber-200 dark:border-amber-900/60 bg-amber-50/50 dark:bg-amber-950/20 p-2.5 text-xs hover:border-amber-300 transition-colors cursor-pointer group"
>
<div className="flex items-center gap-2.5 truncate">
<span className="flex h-2 w-2 rounded-full bg-amber-500 shrink-0" />
<span className="font-bold text-amber-900 dark:text-amber-200 truncate">
Unknown zone
</span>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="font-mono text-[10.5px] font-bold text-amber-800 dark:text-amber-300 bg-amber-100 dark:bg-amber-900/60 px-2 py-0.5 rounded-full">
2 occurrences
</span>
<ChevronRight className="h-3.5 w-3.5 text-amber-400 group-hover:translate-x-0.5 transition-transform" />
</div>
</div>
</div>
</div>
</div>
{/* ═══════════════════════════════════════════════════════════════════
DETAIL INSPECTOR DRAWER (TECHNICAL DETAILS LIVE HERE ONLY)
═══════════════════════════════════════════════════════════════════ */}
<AgentInspectorDrawer
agentId={selectedAgentId}
isOpen={isDrawerOpen}
onClose={() => setIsDrawerOpen(false)}
/>
{/* ── Diagnostics Modal for Grouped Errors ── */}
<AnimatePresence>
{activeLogModal && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setActiveLogModal(null)}
className="fixed inset-0 z-50 bg-black/40 backdrop-blur-2xs"
/>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
className="fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2 w-full max-w-md rounded-2xl border border-border bg-surface p-5 shadow-2xl"
>
<div className="flex items-center justify-between border-b border-border pb-3">
<div className="flex items-center gap-2">
<AlertTriangle className="h-4 w-4 text-amber-500" />
<h3 className="font-heading text-sm font-bold text-ink-1">
{activeLogModal.title}
</h3>
</div>
<span className="rounded-full bg-rose-100 dark:bg-rose-900/50 px-2 py-0.5 font-mono text-[11px] font-bold text-rose-800 dark:text-rose-300">
{activeLogModal.occurrences}
</span>
</div>
<div className="flex flex-col gap-2.5 py-4 text-xs">
<p className="text-body-sm text-ink-2 leading-relaxed">
{activeLogModal.detail}
</p>
<div className="rounded-lg bg-surface-subtle border border-border p-2.5 text-ink-3">
<span className="font-bold text-ink-1 block mb-0.5">
Remediation:
</span>
<span>{activeLogModal.solution}</span>
</div>
</div>
<div className="flex justify-end pt-1">
<button
type="button"
onClick={() => setActiveLogModal(null)}
className="rounded-lg border border-border bg-surface px-4 py-1.5 text-xs font-semibold text-ink-2 hover:bg-surface-elevated transition-colors cursor-pointer"
>
Close
</button>
</div>
</motion.div>
</>
)}
</AnimatePresence>
</div>
);
}

View File

@@ -0,0 +1,37 @@
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { animate, useReducedMotion } from 'framer-motion';
/**
* A number that counts up to its value.
*
* On a board where most figures are zero, watching one climb to 9 while its
* neighbour sits at 0 is the fastest way to see which of them is a real
* measurement — the motion itself carries the contrast.
*
* It settles on the exact target rather than wherever the last frame landed,
* because a KPI that reads 8 when the answer is 9 is worse than one that never
* animated. Under `prefers-reduced-motion` it renders the value immediately.
*/
export default function CountUp({ to, duration = 0.9 }) {
const reduce = useReducedMotion();
const [shown, setShown] = useState(reduce ? to : 0);
useEffect(() => {
if (reduce) {
setShown(to);
return undefined;
}
const controls = animate(0, to, {
duration,
ease: [0.22, 1, 0.36, 1],
onUpdate: (v) => setShown(Math.round(v)),
onComplete: () => setShown(to),
});
return () => controls.stop();
}, [to, duration, reduce]);
return <>{shown}</>;
}
CountUp.propTypes = { to: PropTypes.number.isRequired, duration: PropTypes.number };

View File

@@ -0,0 +1,453 @@
// ==============================|| 8 SPECIALIZED AGENT WORKFLOWS ||============================== //
//
// Tailored, human-readable step-by-step execution pipelines for each of the 8 agents.
// JARVIS is the central orchestration layer and coordinates them.
export const AGENT_WORKFLOWS = {
ORDER_AGENT: {
id: 'ORDER_AGENT',
name: 'Order Agent',
shortName: 'Order',
status: 'idle',
color: '#3b82f6', // blue
subtitle: 'How an order moves through the system',
summary: 'Validates, categorizes, and prepares customer orders for fulfillment.',
nextAgent: 'Dispatch Agent',
steps: [
{
id: 'create_order',
title: 'Create Order',
description: 'Customer places an order through the client app or API.',
input: 'customer.order_submitted',
output: 'order.raw_record',
},
{
id: 'validate_order',
title: 'Validate Order',
description: 'Check that the order contains all required delivery details and payment.',
input: 'order.raw_record',
output: 'order.validated',
},
{
id: 'check_customer',
title: 'Check Customer',
description: 'Verify customer delivery address, contact info, and special instructions.',
input: 'order.validated',
output: 'customer.verified',
},
{
id: 'categorize_order',
title: 'Categorize Order',
description: 'Determine order priority, package dimensions, and service level.',
input: 'customer.verified',
output: 'order.categorized',
},
{
id: 'publish_order',
title: 'Publish Order Event',
description: 'Broadcast order readiness to the internal event bus.',
input: 'order.categorized',
output: 'booking.created',
},
{
id: 'send_dispatch',
title: 'Send to Dispatch',
description: 'Pass the order to the dispatch workflow for rider assignment.',
input: 'booking.created',
output: 'handoff: DISPATCH_AGENT',
},
{
id: 'order_confirmed',
title: 'Order Confirmed',
description: 'The order is securely placed and ready for pickup execution.',
input: 'dispatch.acknowledged',
output: 'order.confirmed_active',
},
],
},
DISPATCH_AGENT: {
id: 'DISPATCH_AGENT',
name: 'Dispatch Agent',
shortName: 'Dispatch',
status: 'active',
color: '#10b981', // green
subtitle: 'How rider matching and order dispatch works',
summary: 'Monitors rider assignment, checks geo-proximity, and allocates deliveries.',
nextAgent: 'Fleet / Customer',
steps: [
{
id: 'receive_order',
title: 'Receive Order',
description: 'Dispatch receives a new delivery request from the event bus.',
input: 'booking.assigned',
output: 'dispatch.received',
},
{
id: 'check_riders',
title: 'Check Available Riders',
description: 'Find active riders currently clocked in and ready for work.',
input: 'dispatch.received',
output: 'riders.pool_filtered',
},
{
id: 'check_zone',
title: 'Check Delivery Zone',
description: 'Check which riders are in the same 10km H3 geographic cell as the merchant.',
input: 'riders.pool_filtered',
output: 'riders.in_zone',
},
{
id: 'find_rider',
title: 'Find Suitable Rider',
description: 'Select the best available rider using capacity, rating, and distance.',
input: 'riders.in_zone',
output: 'rider.selected',
},
{
id: 'assign_rider',
title: 'Assign Rider',
description: 'Assign the delivery to the selected rider in the dispatch queue.',
input: 'rider.selected',
output: 'booking.assigned_miler',
},
{
id: 'confirm_dispatch',
title: 'Confirm Dispatch',
description: 'Confirm that the delivery has been successfully allocated.',
input: 'booking.assigned_miler',
output: 'dispatch.verified_active',
},
],
},
EXCEPTION_AGENT: {
id: 'EXCEPTION_AGENT',
name: 'Exception Agent',
shortName: 'Exception',
status: 'degraded',
color: '#f59e0b', // amber
subtitle: 'How stall detection and operational issues are managed',
summary: 'Monitors rider GPS pings and booking progression to identify stalls.',
nextAgent: 'Operations Desk',
steps: [
{
id: 'detect_activity',
title: 'Detect Unusual Activity',
description: 'Exception loop spots a rider whose GPS location has stayed silent >120s.',
input: 'miler.location.updated',
output: 'anomaly.stalled_candidate',
},
{
id: 'check_severity',
title: 'Check Severity',
description: 'Assess the potential delay impact on the customer delivery promise.',
input: 'anomaly.stalled_candidate',
output: 'severity.calculated',
},
{
id: 'identify_order',
title: 'Identify Affected Order',
description: 'Isolate the specific active booking associated with the stalled rider.',
input: 'severity.calculated',
output: 'order.isolated',
},
{
id: 'determine_escalation',
title: 'Determine Escalation',
description: 'Check rate-limiter (1/zone/day) to prevent operations alert fatigue.',
input: 'order.isolated',
output: 'escalation.allowed',
},
{
id: 'notify_operations',
title: 'Notify Operations',
description: 'Surface a high-priority intervention card on the operations console.',
input: 'escalation.allowed',
output: 'ops.alert_dispatched',
},
{
id: 'track_resolution',
title: 'Track Resolution',
description: 'Monitor order until rider resumes movement or reassignment occurs.',
input: 'ops.alert_dispatched',
output: 'incident.resolved',
},
],
},
EXPRESS_DISPATCH_AGENT: {
id: 'EXPRESS_DISPATCH_AGENT',
name: 'Express Dispatch',
shortName: 'Express Dispatch',
status: 'idle',
color: '#8b5cf6', // purple
subtitle: 'How priority and VIP delivery requests are handled',
summary: 'Provides expedited single-drop fulfillment with minimal staging latency.',
nextAgent: 'Dispatch Agent',
steps: [
{
id: 'receive_express',
title: 'Receive Express Request',
description: 'Immediate express dispatch request arrives from client booking path.',
input: 'express.dispatch_req',
output: 'express.ticket_open',
},
{
id: 'check_priority',
title: 'Check Priority',
description: 'Verify VIP SLA, guaranteed transit window, and express pricing.',
input: 'express.ticket_open',
output: 'sla.verified',
},
{
id: 'find_nearby',
title: 'Find Nearby Rider',
description: 'Query active riders within a tight 3km radius from pickup coordinate.',
input: 'sla.verified',
output: 'express_pool.ready',
},
{
id: 'check_availability',
title: 'Check Rider Availability',
description: 'Verify that rider has zero active overlapping jobs and empty cargo.',
input: 'express_pool.ready',
output: 'rider.single_task_ready',
},
{
id: 'assign_rider',
title: 'Assign Rider',
description: 'Reserve rider exclusively for direct point-to-point delivery.',
input: 'rider.single_task_ready',
output: 'express.allocated',
},
{
id: 'confirm_express',
title: 'Confirm Express Dispatch',
description: 'Confirm direct express dispatch and stream ETA to recipient.',
input: 'express.allocated',
output: 'express.confirmed_transit',
},
],
},
ROUTE_OPTIMIZER: {
id: 'ROUTE_OPTIMIZER',
name: 'Route Optimizer',
shortName: 'Route Optimizer',
status: 'idle',
color: '#06b6d4', // cyan
subtitle: 'How multi-stop batch routes are solved and sequenced',
summary: 'Executes time-window vehicle routing optimization with a 10-minute cache.',
nextAgent: 'Fleet / Rider App',
steps: [
{
id: 'receive_batch',
title: 'Receive Delivery Orders',
description: 'Batch of orders accumulated at local hub ready for route compilation.',
input: 'hub.batch_ready',
output: 'batch.unsequenced',
},
{
id: 'group_zones',
title: 'Group Orders by Zone',
description: 'Cluster delivery waypoints by geographic neighborhood density.',
input: 'batch.unsequenced',
output: 'clusters.compiled',
},
{
id: 'check_riders',
title: 'Check Available Riders',
description: 'Match cluster cargo volume against available rider vehicle capacities.',
input: 'clusters.compiled',
output: 'riders.capacity_matched',
},
{
id: 'generate_routes',
title: 'Generate Route Options',
description: 'Run combinatorial solver for multi-stop pickup and drop sequences.',
input: 'riders.capacity_matched',
output: 'candidates.generated',
},
{
id: 'optimize_time',
title: 'Optimize Time and Distance',
description: 'Select route permutation that minimizes mileage and traffic delays.',
input: 'candidates.generated',
output: 'route.optimal_selected',
},
{
id: 'publish_route',
title: 'Publish Recommended Route',
description: 'Send sequenced waypoints directly to the rider mobile navigation app.',
input: 'route.optimal_selected',
output: 'route.published_cache',
},
],
},
HUB_AGENT: {
id: 'HUB_AGENT',
name: 'Hub Agent',
shortName: 'Hub',
status: 'idle',
color: '#eab308', // yellow
subtitle: 'How sorting centers and transit storage operate',
summary: 'Tracks inventory at each hub and balances dock intake vs staging.',
nextAgent: 'Fleet Agent',
steps: [
{
id: 'receive_hub',
title: 'Receive Hub Event',
description: 'Package arrives at sorting facility for regional transit staging.',
input: 'hub.scan_arrival',
output: 'package.docked',
},
{
id: 'check_capacity',
title: 'Check Hub Capacity',
description: 'Assess available storage bays and sorting queue volume.',
input: 'package.docked',
output: 'capacity.safe',
},
{
id: 'check_incoming',
title: 'Check Incoming Deliveries',
description: 'Monitor inbound feeder vans arriving within the next 30 minutes.',
input: 'capacity.safe',
output: 'inbound.tracked',
},
{
id: 'check_outgoing',
title: 'Check Outgoing Deliveries',
description: 'Stage orders for outbound last-mile departure windows.',
input: 'inbound.tracked',
output: 'outbound.batched',
},
{
id: 'update_hub_state',
title: 'Update Hub State',
description: 'Update physical inventory counts in the hub ledger.',
input: 'outbound.batched',
output: 'hub.state_synced',
},
{
id: 'notify_fleet',
title: 'Notify Relevant Agent',
description: 'Alert fleet and dispatch agents that outbound pallets are staged.',
input: 'hub.state_synced',
output: 'handoff: FLEET_AGENT',
},
],
},
FLEET_AGENT: {
id: 'FLEET_AGENT',
name: 'Fleet Agent',
shortName: 'Fleet',
status: 'idle',
color: '#ec4899', // pink
subtitle: 'How vehicles and rider assets are monitored',
summary: 'Maintains vehicle condition, battery state, and volumetric readiness.',
nextAgent: 'Dispatch Agent',
steps: [
{
id: 'receive_vehicle',
title: 'Receive Vehicle Update',
description: 'Telemetry beacon received from connected EV bike or delivery van.',
input: 'vehicle.telemetry_ping',
output: 'telemetry.logged',
},
{
id: 'check_status',
title: 'Check Vehicle Status',
description: 'Verify maintenance health, battery percentage, and inspection permits.',
input: 'telemetry.logged',
output: 'health.verified',
},
{
id: 'check_capacity',
title: 'Check Capacity',
description: 'Confirm available thermal box volume and payload rating.',
input: 'health.verified',
output: 'volume.available',
},
{
id: 'check_location',
title: 'Check Location',
description: 'Determine exact vehicle coordinates on the fleet live map.',
input: 'volume.available',
output: 'geo.resolved',
},
{
id: 'determine_availability',
title: 'Determine Availability',
description: 'Mark vehicle ready for high-load or multi-drop route allocation.',
input: 'geo.resolved',
output: 'asset.available_flag',
},
{
id: 'update_fleet_state',
title: 'Update Fleet State',
description: 'Sync updated vehicle availability with the dispatch routing engine.',
input: 'asset.available_flag',
output: 'fleet.pool_synced',
},
],
},
CUSTOMER_AGENT: {
id: 'CUSTOMER_AGENT',
name: 'Customer Agent',
shortName: 'Customer',
status: 'idle',
color: '#14b8a6', // teal
subtitle: 'How delivery notifications and delay updates are sent',
summary: 'Composes live tracking SMS and delay notifications across channels.',
nextAgent: 'Customer App / SMS',
steps: [
{
id: 'receive_update',
title: 'Receive Delivery Update',
description: 'Order state changes (e.g. rider pickup, transit delay, arrival).',
input: 'order.milestone_reached',
output: 'milestone.evaluated',
},
{
id: 'understand_impact',
title: 'Understand Customer Impact',
description: 'Calculate real-time ETA change and determine if customer needs alerting.',
input: 'milestone.evaluated',
output: 'eta.calculated',
},
{
id: 'prepare_notification',
title: 'Prepare Notification',
description: 'Generate concise, reassuring message with live GPS tracking URL.',
input: 'eta.calculated',
output: 'message.drafted',
},
{
id: 'choose_channel',
title: 'Choose Communication',
description: 'Select optimal delivery channel: WhatsApp, Push, or SMS text.',
input: 'message.drafted',
output: 'channel.selected',
},
{
id: 'send_update',
title: 'Send Customer Update',
description: 'Transmit message payload through communications gateway.',
input: 'channel.selected',
output: 'gateway.dispatched',
},
{
id: 'track_notification',
title: 'Track Notification',
description: 'Verify message delivery receipt and customer interaction.',
input: 'gateway.dispatched',
output: 'notification.delivered',
},
],
},
};