Public
Remixed
Sample app for the OpenAI Realtime API
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

hey-townie — Voice Edit Studio + face trigger

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.

Face-triggered sessions

  • 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 via end_call when 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.

Per-user memory (one ledger, pinned = always-loaded)

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 me for 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.

What it does

  • 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/name and 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 the set_scene tool and can drive slideshows.

How it works

  1. The browser logs in with Val Town OAuth and opens a WebRTC mic connection.
  2. POST /rtc negotiates 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).
  3. 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 the val_town_mcp tools for file edits.
  4. The preview iframe reloads on demand (via the reload_site tool or the "↻ Reload site" button) — auto-refresh polling is intentionally disabled.

Running your own / OpenAI key

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:

  1. Remix this val (the link in the error, or the Remix button on the val page).
  2. Set OPENAI_API_KEY (an OpenAI key with Realtime API access) in the remixed val's environment variable settings.
  3. Open the val's HTTP endpoint and start talking.

Environment variables

  • OPENAI_API_KEY (required) — OpenAI key with Realtime API access. The app no longer crashes at startup when this is missing; instead /rtc returns a structured error the UI turns into the remix prompt above.

API endpoints

  • GET / — serves the studio (frontend/studio.html)
  • POST /rtc?val=<handle/name> — create a new WebRTC Realtime session (val is optional). Returns { code: "openai" | "upstream", ... } on failure.
  • POST /observer/:callId — internal server-side session control (fire-and-forget)
  • GET /me — current login state
  • GET /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 URL
  • GET /api/recent-vals — the signed-in user's most recent vals
  • POST /api/create-val — create a new public val with a minimal HTTP starter

Project structure

├── 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.

Notes

  • 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".