A modular, voice-driven state machine application built with XState v5 + React + TypeScript.
This project demonstrates a production-ready architecture for voice-controlled applications, using the state machine as the application "spine" with clean separation of concerns.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β UI Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β VoiceConsoleβ β StatePanel β β LogsPanel β ... β
β ββββββββ¬βββββββ ββββββββ¬βββββββ ββββββββ¬βββββββ β
β β β β β
β βββββββββββββββββΌββββββββββββββββ β
β βΌ β
β βββββββββββ β
β β AppShellβ (thin client, no business logic)β
ββββββββββββββββββββββ΄βββββ¬βββββ΄βββββββββββββββββββββββββββββββββββ
β
βΌ events
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Voice Layer β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β βintentRouter β βresponseBank β β aliases β β
β ββββββββ¬βββββββ βββββββββββββββ βββββββββββββββ β
β β β
β βΌ machine events β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β State Machine β
β βββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β animalMachine.ts β β
β β (pure state machine - the application spine) β β
β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββ β
β β β
β ββββββββββββ βββββββββββ΄ββββββββββ ββββββββββββ β
β β types.ts β β guards.ts β βactions.tsβ β
β ββββββββββββ βββββββββββββββββββββ ββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ invokes
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Actors β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β βplaybackActorβ βgrooveSearch β βnarrationActorβ β
β β (timers) β β (search) β β (LLM/phi3) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
src/
βββ machine/ # Core state machine (pure, deterministic)
β βββ animalMachine.ts # The spine - machine definition
β βββ types.ts # Context, events, and type definitions
β βββ guards.ts # Pure guard functions
β βββ actions.ts # Action implementations
β
βββ actors/ # Side effects, async, cancellable
β βββ playbackActor.ts # Timer-based playback simulation
β βββ grooveSearchActor.ts # Deterministic groove search
β βββ narrationActor.ts # LLM-powered narration (phi3:mini)
β
βββ voice/ # Voice/utterance handling
β βββ intentRouter.ts # Utterance β intent β events
β βββ responseBank.ts # Fallback phrase banks
β βββ aliases.ts # Selection phrase aliases (A/B/C)
β
βββ runtime/ # Machine runtime utilities
β βββ interpret.ts # Actor creation and wiring
β βββ logger.ts # Structured logging
β
βββ ui/ # React UI (thin client)
β βββ AppShell.tsx # Main application shell
β βββ panels/ # UI panels
β βββ StatePanel.tsx
β βββ ContextPanel.tsx
β βββ LogsPanel.tsx
β βββ PlaybackPanel.tsx
β βββ VoiceConsole.tsx
β βββ AnimalSaysPanel.tsx
β
βββ util/ # Shared utilities
β βββ time.ts # Time formatting, delays
β βββ random.ts # Random selection helpers
β
βββ App.tsx # Entry point
βββ App.css # Styles
idleArmed (powered on, not listening)
β WAKE
idleListening (awaiting command)
β NEW_SESSION
sessionActive (parent state)
βββ askingVibe (prompts user)
βββ waitingForRequest (expects groove description)
βββ lookingUpGrooves (async search)
βββ announceAudition (brief pause)
βββ auditioning (AβBβC, 4 bars each)
βββ unsureSelection (retry/fallback)
βββ jamming (loop selected groove)
error (recoverable)
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build- Buttons simulate spoken phrases
VoiceConsolesendsVOICE_UTTERANCE({ text })intentRoutermaps utterances to machine events- No real speech recognition required
- Add
src/adapters/speechRecognitionAdapter.ts - Use Web Speech API
- Same intent routing, just different input source
- Add
src/adapters/ttsAdapter.ts - Use SpeechSynthesis or macOS
say - Speak
context.animalLineon change
The only LLM usage is in narrationActor.ts for generating Animal's spoken lines.
# Install Ollama (macOS)
brew install ollama
# Start Ollama server
ollama serve
# Pull the model
ollama pull phi3:miniCreate a .env file:
VITE_OLLAMA_BASE_URL=http://localhost:11434
VITE_NARRATION_ENABLED=true- Strict timeout: 600ms, then fallback to response bank
- JSON output: Forces
{ "line": "..." }format - Validation: Invalid responses trigger fallback
- Caching: Repeated prompts return cached responses
- Graceful degradation: Works without Ollama
- Wake greeting
- Ask vibe line
- Audition intro
- Unsure prompt
- Jamming confirmation
- Optional candidate callouts
Follow this exact click sequence to test the full workflow:
-
Click "animal" (wake word)
- State:
idleArmedβidleListening - Animal greets you
- State:
-
Click "start a new session"
- State:
idleListeningβsessionActive.waitingForRequest - Animal asks for vibe
- State:
-
Click "play a chill rock beat at 112 BPM..."
- State:
waitingForRequestβlookingUpGroovesβauditioning - Watch grooves A, B, C play
- State:
- While auditioning, click "first" (or "A")
- State:
auditioningβjamming - Selected groove loops
- State:
- Click BARGE_IN
- State:
jammingβwaitingForRequest - Loop stops, session preserved
- State:
- Start a new audition
- Click "gibberish / unclear" twice
- Fallback buttons appear
- Click a fallback button to select
const MY_COMMAND_PHRASES = [
'do something',
'make it happen',
];export type IntentType =
| ...existing...
| 'my_command';const INTENT_VALID_STATES = {
...existing...,
my_command: ['sessionActive.waitingForRequest'],
};case 'my_command':
send({ type: 'MY_COMMAND' });
break;Add the event type and transitions in animalMachine.ts.
| Scenario | Behavior |
|---|---|
| "new session" before wake | Implicitly wakes and starts session |
| Groove request in wrong state | Logged as blocked |
| "A"/"first" during audition | Interrupts and starts jamming |
| 2 unclear utterances | Enables fallback buttons |
| STOP from any state | Resets to idleArmed |
| BARGE_IN during jamming | Pauses without losing session |
| 200 bars in jamming | Auto-stops (safety limit) |
- Vite - Build tool
- React 18 - UI
- TypeScript - Type safety
- XState v5 - State machine library
- @xstate/react - React bindings
- Ollama + phi3:mini - LLM for narration (optional)
- Machine as Spine: The state machine is the source of truth
- Pure Core: Machine logic is deterministic and testable
- Actors for Effects: All side effects live in actors
- Thin UI: No business logic in components
- One LLM Actor: Narration only; everything else deterministic
- Graceful Fallbacks: Works without external dependencies
MIT