Skip to content

feat(voice): dynamic voice list with preview#2

Draft
heavygee wants to merge 13 commits into
mainfrom
feat/voice-picker
Draft

feat(voice): dynamic voice list with preview#2
heavygee wants to merge 13 commits into
mainfrom
feat/voice-picker

Conversation

@heavygee
Copy link
Copy Markdown
Owner

@heavygee heavygee commented May 25, 2026

Summary

  • Adds a GET /api/voice/voices hub route that proxies the ElevenLabs /v1/voices endpoint, exposing all voices available to the configured API key — including user voice clones
  • Settings page voice picker now loads voices dynamically on mount instead of a hardcoded list
  • Each voice row now always shows a preview control:
    • enabled when preview_url exists
    • disabled with tooltip when preview is unavailable (including no-key fallback state)
  • Cloned voices are labelled with a "clone" badge so users can distinguish them
  • Falls back to a static built-in voice list if the hub has no API key configured
  • Voice selection routing is done via per-agent voice at token/agent creation (no runtime override)

Refs: tiann#686

Test plan

  • Open Settings → Voice Assistant → Voice picker
  • Confirm full ElevenLabs voice list loads (including cloned voices from your account)
  • Confirm cloned voices appear with a "clone" badge
  • Click ▶ on a voice with preview — audio preview plays/stops
  • Select a voice — next voice session uses that voice
  • With no ELEVENLABS_API_KEY set — picker falls back to static list, preview buttons are visible but disabled with tooltip, and a 400 toast appears on start
  • web/src/routes/settings/index.test.tsx passes (21/21)

heavygee and others added 13 commits May 25, 2026 10:46
Single canonical guide at docs/operator/AGENTS.md; merge=ours keeps
AGENTS.md deleted when syncing tiann/hapi. Upstream PR branches must
still be cut from upstream/main only.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a Voice dropdown in the Voice Assistant settings section,
mirroring the existing language picker pattern.

- New `web/src/lib/voices.ts` — curated list of 10 pre-made ElevenLabs
  voices with name, gender, and short description
- `VoiceSessionConfig` gains optional `voiceId` field
- `startRealtimeSession` and `VoiceContext.startVoice` read
  `hapi-voice-id` from localStorage and thread it through to the
  ElevenLabs `startSession` call via `overrides.tts.voice_id`
- `buildVoiceAgentConfig` enables `tts.voice_id` override in
  `platform_settings` alongside the existing `language` override
- Settings page renders the picker with Default + 10 named options;
  selection persists to localStorage immediately

Default path: when no voice is stored the existing `cgSgspJ2msm6clMCkdW9`
(Jessica) baked into the agent config is used unchanged.

NOTE: branch cut from fork main — re-cut from upstream/main before
opening a PR to tiann/hapi.

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
- Add GET /api/voice/voices hub route proxying ElevenLabs /v1/voices
  (returns empty list gracefully when no API key configured)
- Add fetchVoices() to ApiClient and web API layer with VoiceInfo type
- Settings voice picker now fetches account voices dynamically on mount,
  including user's cloned voices (shown with "clone" badge)
- Falls back to static built-in voice list if API returns empty
- Add play/stop preview button per voice using ElevenLabs preview_url
- Fix voice_id → voiceId in ElevenLabs override (SDK camelCase)

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <noreply@hapi.run>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant