hey-townie is a remix of the talk-to-code Voice Edit Studio: a
voice-controlled workspace for building and editing Val Town
vals just by talking, powered by the OpenAI Realtime API — now with on-device
face detection that starts a call hands-free. A small camera preview sits in
the bottom-right corner; when your face is detected close and confident enough,
a voice session starts automatically and the AI greets you first by name.
The call stays open until you end it (the talk button) or say "goodbye" (the AI
hangs up itself).
The whole sidebar is now a hamburger-summoned drawer at every screen width (no permanent sidebar) — the face-tracking sliders live in it.
- A persistent camera+mic stream feeds MediaPipe FaceDetector
(
blaze_face_short_range). The bottom-right PiP draws the detection box and a live confidence / face-size readout. - Two sliders (in the drawer) gate when a call fires: min confidence and min closeness (face size as % of frame — bigger = must be nearer). A call auto-starts on the rising edge of crossing both thresholds, only when logged in and idle, and re-arms after your face leaves the frame.
- The AI can retune the gate by voice via
set_face_tuning("you're too sensitive", "wait until I'm closer"), and hang up viaend_callwhen you say goodbye. - The mic is shared from the camera stream (cloned for the call), so there's a single permission prompt on load. Greeting audio may be blocked by the browser until your first interaction — clicking anywhere once unlocks it.
Each logged-in user gets a private, append-only "second brain" (SQLite, scoped by Val Town handle) so calls don't start from scratch. It's one unified ledger:
- Subjects (projects, objects, topics — and
mefor the user themselves) each hold append-only facts; edges relate subjects. - A fact is pinned (auto-loads into the system prompt every call — the "never start from scratch" layer) or unpinned (recalled on demand). The pin flag is the single determiner of "always-loaded vs on-demand".
- Identity is not stored — the user's name, handle, and email are pulled
from their Val Town login (
/v2/me) and injected into the prompt each call.
Tools (write only when asked): remember_about_me (pinned fact under me),
record_fact(subject_id, assertion, pin?), recall_subject, recall_recent,
link_subjects, retract_fact. The agent is instructed to be conservative —
write only on request, pin only for "always remember…", never dump memory
unprompted.
The 🧠 Memory page (/memory) is the dashboard: a "📌 Always loaded"
panel, per-fact pin/unpin, subject timelines, manual entry, and the mindmap
(force-graph of subjects + relations). Backend + API live in routes/memory.ts.
- Talk to edit — describe a change ("add a dark mode toggle", "make the header bigger") and the AI edits the val's files, then reloads the preview.
- Open any val — say "open my X", type a val name in the box (a bare
name resolves to your handle;
handle/nameand pasted val.town URLs work too), click one from Recent vals, or seed it with?val=handle/name. - Create from scratch — the ✨ Create a new val button (or just asking the AI) spins up a minimal HTTP val with a starter page and loads it, so you have an instant canvas to iterate on.
- Newcomer-friendly — the AI is briefed to assume the person may be brand new to this and to Val Town, orient them in plain language, and offer a concrete first thing to build.
- Scenes & choreography — vals can ship a
CHOREOGRAPHY.md; the AI triggers pre-built overlays via theset_scenetool and can drive slideshows.
- The browser logs in with Val Town OAuth and opens a WebRTC mic connection.
POST /rtcnegotiates an OpenAI Realtime session, seeded with the official Townie system prompt plus a Studio-specific addendum, and attaches the signed-in user's Val Town MCP token so the AI can read/write their vals (routes/agent.ts).- The AI calls function tools handled in the browser —
load_val,navigate_page,reload_site,set_scene,slide_next/prev/goto,set_face_tuning,end_call— and theval_town_mcptools for file edits. - The preview iframe reloads on demand (via the
reload_sitetool or the "↻ Reload site" button) — auto-refresh polling is intentionally disabled.
The deployed studio runs on the owner's OPENAI_API_KEY. If that key is ever
rejected or out of quota, the UI shows a "remix this val & add your own
OPENAI_API_KEY" prompt instead of a cryptic error.
To run your own copy:
- Remix this val (the link in the error, or the Remix button on the val page).
- Set
OPENAI_API_KEY(an OpenAI key with Realtime API access) in the remixed val's environment variable settings. - Open the val's HTTP endpoint and start talking.
OPENAI_API_KEY(required) — OpenAI key with Realtime API access. The app no longer crashes at startup when this is missing; instead/rtcreturns a structured error the UI turns into the remix prompt above.
GET /— serves the studio (frontend/studio.html)POST /rtc?val=<handle/name>— create a new WebRTC Realtime session (valis optional). Returns{ code: "openai" | "upstream", ... }on failure.POST /observer/:callId— internal server-side session control (fire-and-forget)GET /me— current login stateGET /auth/token— OAuth token handoff (rarely needed; the studio is same-origin)GET /source— 302-redirects to this val's page on Val Town (used by the remix link, so it's correct even for remixed copies)GET /api/resolve-val?val=<handle/name>— resolve a val to its live HTTP URLGET /api/recent-vals— the signed-in user's most recent valsPOST /api/create-val— create a new public val with a minimal HTTP starter
├── main.tsx # Entrypoint: OAuth middleware, route mounting, /me, /source, serves studio
├── frontend/
│ └── studio.html # The Voice Edit Studio UI (WebRTC, waveform, sidebar, load/create)
└── routes/
├── agent.ts # Realtime session config: tools + Townie prompt + Studio addendum
├── rtc.ts # WebRTC session setup + OpenAI-error classification
├── observer.ts # Server-side session control (WebSocket to OpenAI)
├── api.ts # /api: resolve-val, recent-vals, create-val
└── utils.ts # Realtime API URL/header helpers + hasOpenAIKey()
Other files (routes/sip.ts, frontend/index.html, frontend/widget.html,
frontend/sdk.js, sdk.ts, oauth-test.tsx) are inherited from the
hello-realtime base this was built on and are not mounted by main.tsx.
- If the AI says it can't edit files, the Val Town OAuth token has gone stale — log out and back in from the sidebar to refresh it.
- The preview does not auto-refresh — the AI reloads it after finishing edits, or you can hit "↻ Reload site".