A drop-in pixel-art visualization for AI agent pipelines.
PixelFloor renders a cozy office where pixel characters represent your agents. They work, celebrate, chat, and pass data to each other in real-time. Use it standalone as a demo or connect it to a live backend via WebSocket to watch your pipeline come alive.
- Single HTML file -- zero dependencies, zero build step, just open it
- 9 hand-crafted pixel sprites -- detective, hacker, scientist, analyst, researcher, manager, trader, cowboy, engineer
- 60fps HTML5 Canvas with pre-rendered tiled backgrounds and smooth animation
- Data flow particles trace bezier curves between agents to visualize your pipeline
- Agent states -- idle, working, celebrating, error -- with speech bubbles, sparkle effects, and status dots
- Interactive -- click agents for a detail side panel; click office objects (water cooler, pizza, snack machine) for fun
- Day/night cycle with animated monitors showing charts, coffee steam particles, and a neon office sign
- Real-time integration via WebSocket or direct JavaScript API
- Python server included (FastAPI) for log file tailing and REST event endpoints
- MIT licensed -- use it however you want
PixelFloor ships with a 9-agent demo, but setting up your own pipeline is just a config swap.
Open index.html and find the DEMO_CONFIG object near the bottom. Replace it with your pipeline:
window.PIXELFLOOR_CONFIG = {
title: 'My RAG Pipeline',
agents: [
{ id: 'scraper', name: 'Scraper', role: 'Web Crawler', color: '#4A90D9', sprite: 'hacker', position: 0 },
{ id: 'embedder', name: 'Embed', role: 'Embeddings', color: '#E8B830', sprite: 'scientist', position: 2 },
{ id: 'retriever', name: 'Fetch', role: 'Vector Search', color: '#22D3EE', sprite: 'researcher', position: 4 },
{ id: 'llm', name: 'Brain', role: 'LLM Generator', color: '#8B5CF6', sprite: 'analyst', position: 7 },
],
flows: [
{ from: 'scraper', to: 'embedder' },
{ from: 'embedder', to: 'retriever' },
{ from: 'retriever', to: 'llm' },
],
};Each agent needs: an id (for API calls), a name (display), a sprite (pick from 9 characters), and a position (desk 0-8 in the office). Flows define the particle paths between agents.
From Python (using the included server):
import requests
URL = "http://localhost:8421"
# Agent starts working
requests.post(f"{URL}/api/event", json={
"agent": "scraper", "state": "working", "speech": "Crawling 12 pages..."
})
# Data flows to next agent
requests.post(f"{URL}/api/particle", json={
"from_agent": "scraper", "to_agent": "embedder"
})
# Agent celebrates when done
requests.post(f"{URL}/api/event", json={
"agent": "llm", "state": "celebrating", "speech": "Response ready!"
})From JavaScript (in the browser console or your frontend):
const floor = window._pixelFloorInstance;
floor.agentWorking('scraper', 'Crawling site...');
floor.sendParticle('scraper', 'embedder');
floor.agentCelebrating('llm', 'Response ready!');From any language via WebSocket:
{"type": "agent_state", "agent": "scraper", "state": "working", "speech": "Crawling..."}
{"type": "particle", "from": "scraper", "to": "embedder"}pip install -r requirements.txt
python server.py --port 8421
# Open http://localhost:8421Or skip the server entirely -- just open index.html in a browser. Without a WebSocket connection, demo mode kicks in automatically to show off the animations.
# That's it. Demo mode auto-runs with 9 sample agents.
open index.html
# or
xdg-open index.htmlpip install -r requirements.txt
python server.py
# Visit http://localhost:3000Add a <script> tag before the closing </body>, or load it after index.html:
<script>
const floor = new PixelFloor({
title: 'My Pipeline',
agents: [
{ id: 'scanner', name: 'Scout', role: 'Data Collector', color: '#B8860B', sprite: 'detective', position: 0 },
{ id: 'processor', name: 'Nova', role: 'Analyzer', color: '#8B5CF6', sprite: 'analyst', position: 1 },
{ id: 'output', name: 'Echo', role: 'Reporter', color: '#06B6D4', sprite: 'engineer', position: 2 },
],
flows: [
{ from: 'scanner', to: 'processor' },
{ from: 'processor', to: 'output' },
],
websocket: 'ws://localhost:3000/ws', // optional -- omit for JS-only control
});
</script>The PixelFloor constructor accepts a single config object:
const floor = new PixelFloor({
title: 'My Pipeline', // Displayed in the header bar
agents: [
{
id: 'scanner', // Unique identifier (used in API calls)
name: 'Scout', // Display name
role: 'Data Collector', // Subtitle shown in tooltips and side panel
color: '#B8860B', // Accent color for status dot and particles
sprite: 'detective', // One of the 9 built-in sprites
position: 0, // Desk position (0-8, left to right)
},
// ... more agents
],
flows: [
{ from: 'scanner', to: 'processor' }, // Defines particle paths
],
websocket: 'ws://localhost:3000/ws', // Optional WebSocket URL
});If websocket is omitted, PixelFloor runs in demo mode with simulated activity.
All methods are available on the PixelFloor instance:
Set an agent to the working state. They lean into their monitor and a colored status dot appears.
floor.agentWorking('scanner', 'Scanning feeds...', 400);Trigger a celebration -- the agent jumps and sparkles appear.
floor.agentCelebrating('processor', 'Found a match!', 300);Put an agent into error state with a red status indicator.
floor.agentError('output', 'Connection lost', 480);Return an agent to their default idle state.
floor.agentIdle('scanner');Send a glowing data particle along the flow path between two agents. Works even if no flow was pre-defined (an ad-hoc path is created automatically).
floor.sendParticle('scanner', 'processor');Flash the entire screen white -- useful for signaling major events. Duration is in frames (default 600).
floor.flash(400);Push a line to the log feed at the bottom of the screen. Types: "info", "success", "error", "highlight", "special".
floor.pushLog('Batch complete: 42 items processed', 'success');Set the metric counter displayed in the header bar. Pass a number -- positive values show green, negative show red.
floor.setMetric(1547);| Sprite | Description | Default Color |
|---|---|---|
detective |
Fedora, trench coat, press badge | #B8860B |
hacker |
Blue hoodie, headphones | #4A90D9 |
scientist |
Glasses, hair bun, lab coat | #E8B830 |
analyst |
Dark hair, purple hoodie | #8B5CF6 |
researcher |
Teal hair, white lab coat, glasses | #22D3EE |
manager |
Red blazer, glasses, clipboard | #DC2626 |
trader |
Green jacket, bob haircut | #16A34A |
cowboy |
Cowboy hat, orange vest | #EA580C |
engineer |
Cyan sweater, spiky blue hair | #06B6D4 |
All sprites are 16x16 pixel art with idle and walk frames, rendered at runtime on the canvas. You can assign any sprite to any agent regardless of its default color -- the color field in your config controls the accent color independently.
Connect to the WebSocket endpoint and send JSON messages to control the visualization in real-time.
{
"type": "agent_state",
"agent": "scanner",
"state": "working",
"speech": "Processing...",
"duration": 300
}Valid states: "working", "celebrating", "error", "idle".
{
"type": "particle",
"from": "scanner",
"to": "processor"
}{
"type": "log",
"text": "Event received from upstream",
"level": "info"
}{
"type": "flash",
"duration": 600
}server.py is a lightweight FastAPI server that serves index.html and relays WebSocket messages between clients. It can also tail a log file and broadcast new lines.
python server.py # Serve on port 3000
python server.py --port 3000 # Custom port
python server.py --log-file myapp.log # Tail a log file and broadcast linesFor systems that prefer HTTP over WebSocket, the server exposes REST endpoints that broadcast to all connected clients:
| Method | Endpoint | Body |
|---|---|---|
| POST | /api/event |
{ "agent": "id", "state": "working", "speech": "...", "duration": 300 } |
| POST | /api/log |
{ "text": "Log message", "level": "info" } |
| POST | /api/particle |
{ "from_agent": "id1", "to_agent": "id2" } |
| POST | /api/flash |
Query param: ?duration=600 |
# Example: trigger an agent state change via curl
curl -X POST http://localhost:3000/api/event \
-H "Content-Type: application/json" \
-d '{"agent": "scanner", "state": "celebrating", "speech": "Done!", "duration": 300}'import asyncio
import json
import websockets
async def notify_pixelfloor():
async with websockets.connect("ws://localhost:3000/ws") as ws:
# Set an agent to working
await ws.send(json.dumps({
"type": "agent_state",
"agent": "analyzer",
"state": "working",
"speech": "Processing batch..."
}))
# Send a data particle
await ws.send(json.dumps({
"type": "particle",
"from": "scanner",
"to": "analyzer"
}))
# Log a message
await ws.send(json.dumps({
"type": "log",
"text": "Batch 47 started",
"level": "info"
}))
asyncio.run(notify_pixelfloor())import requests
requests.post("http://localhost:3000/api/event", json={
"agent": "scanner",
"state": "celebrating",
"speech": "Found 3 results!",
"duration": 400,
})const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:3000/ws');
ws.on('open', () => {
ws.send(JSON.stringify({
type: 'agent_state',
agent: 'processor',
state: 'working',
speech: 'Crunching numbers...',
}));
});If you find PixelFloor useful, consider buying us a coffee:
- ETH:
0x450081c69630BfBcA8354a83Fd9948f4993751E1 - BTC:
bc1q70m03kp9fz43kt6mjgnymltg6f8z4qdq3jvedu
MIT -- see LICENSE.
Built with care and too much coffee.