Bots

Animated characters that react to an AI agent. You bring the model and the stream; the bots are its face.

Install

npm i glyphflow

Bots ship in the same package, from 3.2.0: no second install, no extra dependency, and nothing loads until you import it. The peer dependencies are the same as the icons (Angular 20 to 22).

Four entry points, pay for what you import

Each one is a separate import, so you only pay for the pieces you name. Weights are KB gzip, measured by the bundle check in CI on every push.

Entry pointWhat it gives youWeight
glyphflow/botsThe engine, <gf-bot> and the shapes (catShape, mochiShape, ghostShape…)49.5 KB engine · 57.2 KB with one shape · 65.4 KB with the component
glyphflow/bots/gestures20 gestures, each one importable on its own+7.4 KB the first, +0.3 KB each after that
glyphflow/bots/extrasHats, toys and work routines, also one by one+26.7 KB if you take all of them
glyphflow/bots/aibindAgent and the Vercel AI SDK and Anthropic adapters0.6 KB

Icons and bots are independent in both directions: installing one never loads the other.

Your first bot

A bot needs a shape. Each shape is a plain object you import, so the ones you do not use never reach your bundle. Pick a look with skin and name your gestures in gestures: a gesture you do not import is a gesture you do not pay for.

import { Component } from '@angular/core';
import { GfBotComponent, catShape } from 'glyphflow/bots';
import { superBounce } from 'glyphflow/bots/gestures';

@Component({
  selector: 'app-mascot',
  imports: [GfBotComponent],
  template: '<gf-bot [shape]="shape" skin="g1" [gestures]="gestures" label="Cat" />',
})
export class Mascot {
  protected readonly shape = catShape;
  protected readonly gestures = { superBounce };
}

Gestures

There are 20, and each is an object you import from glyphflow/bots/gestures. Calling one returns a handle.

  • frontFlip
  • backflip
  • doubleFlip
  • superBounce
  • sideCartwheel
  • sideDodge
  • ghostSwoop
  • spinSquash
  • tornadoSpin
  • jellyWobble
  • waveThroughBody
  • inflateRelease
  • stretchSnap
  • scaredRecoil
  • puddleMorph
  • ballMorph
  • jellyDrop
  • squishTeleport
  • peekPop
  • diveEmerge
// bot = viewChild(GfBotComponent)
const run = bot()?.api?.gesture('superBounce', { intensity: 0.7, policy: 'queue' });

await run?.finished;   // 'done' | 'interrupted' | 'ignored' (never rejects)
run?.cancel();         // cut it if it is still running

policy decides what happens when one is already running: replace (the default) cuts it, queue waits its turn (up to three) and ignore drops the new one. intensity goes from 0 to 2, and 1 is the gesture as written.

Make it yours

Everything here is data you pass in. Nothing registers globally, so none of it costs anything until you use it.

Your own colors

palette takes three CSS colors, [light, mid, shadow], or { colors, rim } to also fix the outline color. A malformed one fails when the bot is created, with a clear message.

Your own hat or toy

defineHat takes a drawing in a local frame where (0, 0) is the crown of the head and up is negative y. Hats go in through extras. defineToy does the same for toys and lets you pick one of three choreographies: ball, star or treat.

import { createHatsExtra, defineHat } from 'glyphflow/bots/extras';

const partyCap = defineHat({
  label: 'Party cap',
  draw: (p) => `<rect class="${p}-cap" x="-12" y="-14" width="24" height="14" rx="4" fill="#E0457B"/>`,
});

// <gf-bot [shape]="shape" [palette]="['#FFD6E8', '#FF4F9A', '#7A1049']" hat="partyCap" [extras]="extras" />
const extras = { hats: createHatsExtra({ partyCap }) };   // expose it as a field of your component

Your own skin

A skin is an id with the x- prefix (skin="x-sunset") painted with CSS variables: --gf-skin-fill, --gf-skin-gloss, --gf-skin-edge and --gf-skin-edge-width. It changes color only: a skin with its own geometry is not supported.

/* <gf-bot class="sunset" [shape]="shape" skin="x-sunset" /> */
gf-bot.sunset {
  --gf-skin-fill: #ff8a5c;   /* body fill */
  --gf-skin-gloss: #fff3d6;  /* soft highlight on top (optional) */
  --gf-skin-edge: #b63a1e;   /* outline (optional) */
  --bot-base: #ff8a5c;       /* hats and toys inherit the --bot-* variables */
  --bot-primary: #ffb48f;
  --bot-shadow: #b63a1e;
}

Use it with an AI

glyphflow does not provide an AI API, a key or an endpoint. You bring your own model and its stream. The bots only need to know what the agent is doing, and that comes down to eight steps.

The eight steps

StepWhat it means
promptThe user just sent a message
thinkingThe model is reasoning
toolA tool is running
loadingWaiting: for the model, or for a person to approve
writingThe answer is streaming in
doneFinished
errorSomething failed
idleStopped: the bot goes back to rest

bindAgent reads your SDK stream and turns every event into one of those steps. It does not depend on any SDK, so it adds nothing to your package.json. Repeated steps collapse: a text-delta per token looks like a single writing.

Vercel AI SDK

import { bindAgent, vercelAi } from 'glyphflow/bots/ai';

const result = streamText({ model, prompt });   // your code, your key
const run = bindAgent(bot.api, result.fullStream, vercelAi);

await run.done;   // 'finished' | 'error' | 'stopped' (never rejects)
run.stop();       // cut the stream and release the bot

Anthropic Messages API

import { bindAgent, anthropic } from 'glyphflow/bots/ai';

const stream = client.messages.stream({ model, max_tokens: 1024, messages });   // your code, your key
const run = bindAgent(bot.api, stream, anthropic);

What the bot sees for each event

This table is computed by running the real adapters, not written by hand.

SDKEventStep the bot sees
Vercel AI SDKstart-steploading
Vercel AI SDKreasoning-deltathinking
Vercel AI SDKtool-calltool
Vercel AI SDKtool-result no change
Vercel AI SDKtool-approval-requestloading
Vercel AI SDKtool-error no change
Vercel AI SDKtext-deltawriting
Vercel AI SDKfinishdone
Vercel AI SDKerrorerror
Vercel AI SDKabortidle
Anthropicmessage_startthinking
Anthropiccontent_block_start (thinking)thinking
Anthropiccontent_block_start (tool_use)tool
Anthropiccontent_block_start (text)writing
Anthropicmessage_stop (stop_reason: end_turn)done
Anthropicmessage_stop (stop_reason: tool_use) no change
Anthropicerrorerror

Try every type of output

The chat on the Bots page simulates eight types of output: a plain answer, reasoning, one tool call, parallel tool calls, a tool that fails, waiting for a person to approve, an error mid-answer and a stop. Each runs with a stream shaped like the SDK you pick, through these same adapters.

Open the Bots page

Following the pointer

<gf-bot> follows the cursor by default; [followPointer]="false" turns it off. It never follows with reduced motion, while paused or asleep, while dragged, or on touch screens. When a gesture runs the head straightens, and once it ends the bot looks at the cursor again. With createBot it stays explicit: bot.followPointer(true).

<gf-bot [shape]="shape" [followPointer]="false" />

Server rendering and accessibility

On the server the host renders empty with its aspect ratio reserved, and the bot mounts after the first render, so there is no layout shift when it hydrates. With a label the bot is an image with a name; without one it is decorative. With prefers-reduced-motion the continuous motion (following the pointer, idle wandering) turns off and each gesture shrinks to a short hop.