-

Agent Status

PreviousNext

A compact status indicator for ZGI agents, voice sessions, and tool-running states.

Installation

pnpm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/agent-status.json
npm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/agent-status.json
yarn
yarn dlx shadcn@latest add https://ui.zgi.ai/r/agent-status.json

Component

components/ui/agent-status.tsx
"use client"

import * as React from "react"
import {
  AlertCircleIcon,
  CheckCircle2Icon,
  CircleIcon,
  Loader2Icon,
  MicIcon,
  PauseCircleIcon,
  RadioIcon,
  Volume2Icon,
  WrenchIcon,
} from "lucide-react"

import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"

export type AgentStatusState =
  | "idle"
  | "listening"
  | "thinking"
  | "speaking"
  | "tool"
  | "paused"
  | "complete"
  | "error"

export interface AgentStatusProps extends React.ComponentProps<"div"> {
  state: AgentStatusState
  label?: React.ReactNode
  description?: React.ReactNode
  latency?: React.ReactNode
  model?: React.ReactNode
  variant?: "badge" | "panel"
  showPulse?: boolean
}

const statusConfig: Record<
  AgentStatusState,
  {
    label: string
    icon: React.ComponentType<{ className?: string }>
    className: string
    active?: boolean
  }
> = {
  idle: {
    label: "Idle",
    icon: CircleIcon,
    className: "bg-muted text-muted-foreground border-border",
  },
  listening: {
    label: "Listening",
    icon: MicIcon,
    className:
      "border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-300",
    active: true,
  },
  thinking: {
    label: "Thinking",
    icon: Loader2Icon,
    className:
      "border-violet-500/20 bg-violet-500/10 text-violet-700 dark:text-violet-300",
    active: true,
  },
  speaking: {
    label: "Speaking",
    icon: Volume2Icon,
    className:
      "border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
    active: true,
  },
  tool: {
    label: "Running tool",
    icon: WrenchIcon,
    className:
      "border-amber-500/25 bg-amber-500/10 text-amber-700 dark:text-amber-300",
    active: true,
  },
  paused: {
    label: "Paused",
    icon: PauseCircleIcon,
    className:
      "border-slate-500/20 bg-slate-500/10 text-slate-700 dark:text-slate-300",
  },
  complete: {
    label: "Complete",
    icon: CheckCircle2Icon,
    className:
      "border-emerald-500/20 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
  },
  error: {
    label: "Error",
    icon: AlertCircleIcon,
    className: "border-destructive/20 bg-destructive/10 text-destructive",
  },
}

export function AgentStatus({
  state,
  label,
  description,
  latency,
  model,
  variant = "badge",
  showPulse = true,
  className,
  ...props
}: AgentStatusProps) {
  const config = statusConfig[state]
  const Icon = config.icon
  const content = label ?? config.label

  if (variant === "badge") {
    return (
      <Badge
        variant="outline"
        className={cn(
          "h-7 gap-1.5 rounded-md px-2.5 font-medium",
          config.className,
          className
        )}
        {...props}
      >
        <Icon
          className={cn(
            "size-3.5",
            state === "thinking" && "animate-spin",
            config.active &&
              showPulse &&
              state !== "thinking" &&
              "animate-pulse"
          )}
        />
        {content}
      </Badge>
    )
  }

  return (
    <div
      className={cn(
        "border-border bg-card text-card-foreground flex w-full items-center gap-3 rounded-lg border p-3 shadow-sm",
        className
      )}
      {...props}
    >
      <div
        className={cn(
          "flex size-9 shrink-0 items-center justify-center rounded-md border",
          config.className
        )}
      >
        <Icon
          className={cn(
            "size-4",
            state === "thinking" && "animate-spin",
            config.active &&
              showPulse &&
              state !== "thinking" &&
              "animate-pulse"
          )}
        />
      </div>
      <div className="min-w-0 flex-1">
        <div className="flex min-w-0 items-center gap-2">
          <p className="truncate text-sm font-medium">{content}</p>
          {model ? (
            <span className="text-muted-foreground truncate text-xs">
              {model}
            </span>
          ) : null}
        </div>
        {description ? (
          <p className="text-muted-foreground mt-0.5 line-clamp-2 text-xs leading-5">
            {description}
          </p>
        ) : null}
      </div>
      {latency ? (
        <div className="text-muted-foreground shrink-0 text-xs">{latency}</div>
      ) : null}
      <RadioIcon className="text-muted-foreground size-4 shrink-0" />
    </div>
  )
}

Usage

import { AgentStatus } from "@/components/ui/agent-status"
 
export function Example() {
  return (
    <AgentStatus
      state="thinking"
      variant="panel"
      label="Planning next step"
      description="Reviewing transcript context before calling a tool."
      model="ZGI Agent Pro"
      latency="824 ms"
    />
  )
}

Props

PropTypeDefault
stateAgentStatusStaterequired
variant"badge" | "panel""badge"
labelReactNodestate label
descriptionReactNode-
modelReactNode-
latencyReactNode-
showPulsebooleantrue