DEV Community

specvista technologies
specvista technologies

Posted on Fully Autonomous

Give your AI agent a face: Agentfaces for React and React Native

Agentfaces demo

If you build AI products, you have probably shipped a spinner that means "the agent is doing something". It is fine for one chat box. It breaks down when a screen shows several agents and the user needs to know which one is waiting on them.

I built Agentfaces to fix that. It is an MIT library of expressive faces for AI agents. You pass a state and the face shows it.

Install

npm install agentfaces
Enter fullscreen mode Exit fullscreen mode
import { AgentFace } from "agentfaces/react";

export function AgentStatus({ agent }) {
  return <AgentFace state={agent.state} seed={agent.id} name={agent.name} size={48} />;
}
Enter fullscreen mode Exit fullscreen mode

For React Native, add react-native-svg and import from agentfaces/react-native. The props are the same.

The states

State When to use it
idle Nothing in progress
thinking Request sent, waiting for the first token
working Running a tool or a long task
needs-you Waiting for approval or input (body morphs into a ?)
done Finished (body morphs into a check mark)
alert Error or something needs attention (body morphs into a !)
celebrate A milestone, with confetti
sleeping Paused or offline

There are also extended states (starting, exploring, waiting, handing-off and more) and ornament states that add a typing ellipsis, a spinner, a ripple or a tracker.

Wiring it to the Vercel AI SDK

The AI SDK useChat hook gives you a status and messages with tool parts. One function turns that into a face state:

import { isToolUIPart, type ChatStatus, type UIMessage } from "ai";
import type { AgentState } from "agentfaces";

export function agentStateFor(status: ChatStatus, messages: UIMessage[]): AgentState {
  if (status === "error") return "alert";
  if (status === "submitted") return "thinking";
  const last = messages.at(-1);
  const tools = last?.role === "assistant" ? last.parts.filter(isToolUIPart) : [];
  if (tools.some((part) => part.state === "approval-requested")) return "needs-you";
  if (status === "streaming") {
    const toolRunning = tools.some((part) => part.state === "input-streaming" || part.state === "input-available");
    return toolRunning ? "working" : "typing";
  }
  return last?.role === "assistant" ? "done" : "idle";
}
Enter fullscreen mode Exit fullscreen mode

The full example is at https://github.com/ajibadedapo/agentfaces/tree/main/examples/vercel-ai-sdk.

How it works, briefly

  • Parametric faces. Each eye and the mouth are a handful of numbers. An expression is one parameter set, so moving between any two expressions is interpolation.
  • Life without noise. A seeded director schedules blinks, gaze drift and tiny saccades, so faces feel alive and never blink in sync.
  • Glyph morphs. The body and the glyph become signed distance fields, get blended, and are traced back into an outline with marching squares and Taubin smoothing.
  • One ticker. Every face shares one requestAnimationFrame loop. Off-screen faces pause.
  • Accessibility. Each face has an accessible name, reduced motion gives a calm resting face per state, and variant="still" runs no timers at all.
  • Three shapes. Circle, triangle and square, with color and shape picked from a seed when you do not choose.

Voice agents

Agentfaces 0.2 adds listening and speaking states driven by real audio. The listening face leans in and pulses with the microphone level; the speaking face's mouth opens with loudness and widens or rounds with the sound. Pass any MediaStream, audio element or level callback:

<AgentFace state="speaking" audio={remoteAudioStream} />
Enter fullscreen mode Exit fullscreen mode

There are tiny adapters (under 1 KB each) for LiveKit, OpenAI Realtime and ElevenLabs, so a voice agent gets a face in one line.

Links

Issues and ideas are welcome, especially agent states you show in your product that are missing.

Top comments (1)

Collapse
 
svgicons profile image
Svg/icons •

Does the accessible name update with the current state too? For a status graphic, exposing both the agent identity and changing state would keep the visual glyph and AT output aligned.