Skip to content

H-6763: Integrate Voice mode into the AI assistant - #9467

Merged
kostandinang merged 16 commits into
mainfrom
kostandin/h-6763-inline-voice-mode
Sep 2, 2026
Merged

H-6763: Integrate Voice mode into the AI assistant#9467
kostandinang merged 16 commits into
mainfrom
kostandin/h-6763-inline-voice-mode

Conversation

@kostandinang

@kostandinang kostandinang commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

🌟 What is the purpose of this PR?

Complete H-6763 by integrating Voice mode into Petrinaut's existing AI assistant instead of maintaining a separate interview surface. Text and voice share one canonical Brunch conversation and transcript, while the merged GPT-Realtime-2 transport remains a disposable media plane for microphone input, interruption, and speech.

🔗 Related links

  • H-6763 (internal)
  • #9399 — merged GPT-Realtime-2 transport foundation

🚫 Blocked by

🔍 What does this change?

  • Replaces separate Chat/Interview presentation with provider-neutral Voice controls in the existing assistant transcript.
  • Starts Voice from the empty composer and replaces the composer with a compact live dock while a session runs.
  • Keeps finalized spoken turns in the normal conversation with Voice provenance, while provisional transcription and audio remain ephemeral.
  • Adds one-time consent, live microphone feedback, mute, pause/resume, reconnect, automatic barge-in, privacy-safe errors, and accessible reduced-motion behavior.
  • Preserves canonical Brunch messages, pending-brunch_ask correlation, ordered answer submission, host/programmatic text handoff, draft recovery, and exactly-once semantics.
  • Pauses media when the panel closes and restores the mounted session safely when the panel reopens.
  • Updates Petrinaut's user guide, Brunch integration documentation, changelog, tests, and changeset.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • Voice is a non-production preview and is disabled by server policy in production.
  • Physical microphone quality and interruption behavior still need validation in a preview deployment.

🐾 Next steps

  • Validate microphone quality, barge-in, pause/resume, reconnect, mute, and recovery on a physical device in preview.
  • Continue response-quality work in the stacked FE-1570 PR.

🛡 What tests cover this?

  • Petrinaut and Petrinaut website unit suites cover the shared composer, consent, session lifecycle, transcript continuity, answer correlation, provenance, pause/resume, reconnect, mute, accessibility, and recovery paths.
  • Petrinaut architecture validation, package builds, TypeScript checks, ESLint, formatting, and integration tests cover the affected boundaries.
  • GitHub CI previously passed on this restored head.

❓ How to test this?

  1. In apps/petrinaut-website/.env.local, configure a real VITE_BRUNCH_CHAT_ENDPOINT, PETRINAUT_OPENAI_VOICE_ENABLED=true, and a dedicated OPENAI_VOICE_API_KEY.
  2. Run yarn workspace @apps/petrinaut-website dev and open the local URL.
  3. Open the AI assistant with an empty composer, select Start voice mode, review the disclosure, and start the session.
  4. Confirm the live dock reports microphone activity and listening/speaking state, and finalized spoken turns enter the existing transcript once with Voice provenance.
  5. Exercise barge-in, mute, panel-close pause/resume, reconnect, error recovery, and End voice mode.
  6. Confirm ending Voice restores the text composer without duplicating or losing any accepted answer.

📹 Demo

A short physical-device preview recording is still needed because the primary behavior is audio- and motion-dependent.

@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 2, 2026 1:34pm UTC
petrinaut Ready Ready Preview Sep 2, 2026 1:34pm UTC
petrinaut-docs Ready Ready Preview Sep 2, 2026 1:34pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 2, 2026 1:34pm UTC

Request Review

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps area/apps > hash.design Affects the `hash.design` design site (app) labels Aug 31, 2026
lunelson
lunelson previously approved these changes Sep 2, 2026
kostandinang and others added 15 commits September 2, 2026 10:17
Keep text and voice in one conversation with inline controls, provenance, lifecycle recovery, and accessible live feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve failed speech for recovery and ensure every text-mode transition safely ends the active voice session before submission.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep realtime submissions current without render-time ref access and avoid querying the POST-only local chat fallback for history.

Co-authored-by: Cursor <cursoragent@cursor.com>
Hosts report a session snapshot instead of rendering the live Voice UI, so
Petrinaut owns the dock, indicator and canvas controls, defers spoken turns
until a session ends, and shows recovery failures as toasts.

Also carries the local-storage demo's chat-history refinement: an explicit
endpoint parameter, URL construction through `new URL()`, and comments for
why the generic route has no history door.

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep pending question audio untouched when the panel closes during connection, then start the bridge after the user explicitly resumes.

Co-authored-by: Cursor <cursoragent@cursor.com>
Remember the notified voice failure until the session recovers so repeated snapshots and callback changes cannot enqueue the same toast again.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidate the session controls onto the assistant dock: a transcription
toggle, a round microphone button that mutes input without ending the turn,
and a red stop button. Replace the bar indicator with a canvas ribbon driven
by the live microphone level, and mark spoken turns with an inline Voice chip.

Co-authored-by: Cursor <cursoragent@cursor.com>
The ribbon's shape came from two sine waves beating against each other, which
read as decoration rather than as speech. Keep a couple of seconds of sampled
levels instead and draw the envelope from that contour, so the newest sound
enters at one edge and the shape of what was said drifts across. Ease against
the frame delta so the motion is identical at any refresh rate, and drop to one
stroke and a faint echo so the dock stays quiet.

Co-authored-by: Cursor <cursoragent@cursor.com>
Draw the ribbon as three travelling waves that fade out at both ends,
replacing the rolling contour. Curve the raw microphone level before it
drives them: an RMS reading puts ordinary speech near 0.15, which the
previous linear gain turned into under two pixels of deflection, so a
whole sentence barely lifted off the midline.

Give the inline Voice chip a legible blue. It was pale blue text on a
four percent tint, which at ten pixels read as nothing at all.

Quieten the dock to ghost icons at one size, and end a session with a
close mark rather than a filled stop. Draw the transcription toggle as
lines of transcript; the shared set only offers a serif T, which promises
inserting text rather than showing it. Drop the error tone from the muted
microphone, so red belongs to the one action that cannot be undone.

Keep the composer's trailing action on a single button and swap only the
glyph inside it. Rendering a different element per state tore the button
down and built it again, so the box blinked on every change, including
the send-to-voice swap that happens on the first character typed.

Hold the composer and the dock to one height and fade them through each
other in place, so trading one for the other leaves the transcript above
untouched. Restore the reader's distance from the end for the cases that
cannot cover, such as prompt chips returning with the composer.

Stop the composer raising a scrollbar on a box that visibly fits.
`scrollHeight` is a rounded integer, so the height it yields can land a
fraction of a pixel under the real content; allow scrolling only once
the content passes the cap. Recompute the height when the panel opens,
too, since a measurement taken while it is hidden reads zero and was
never revisited.
Keep shared session state types in the React layer so state providers do not depend on the UI components that render them.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread apps/petrinaut-website/src/main/app/voice-interview/realtime-brunch-bridge.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b5eba96. Configure here.

Comment thread apps/petrinaut-website/src/main/app/voice-interview/voice-turn-controller.ts Outdated
lunelson
lunelson previously approved these changes Sep 2, 2026
Report muting independently from the active turn phase so the dock remains accurate and can unmute while the assistant speaks or thinks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Large public API and UX change to AI composer, voice lifecycle, and chat submission paths; mitigated by extensive tests and unchanged Brunch authority, but edge cases around handoff, pause, and hydration remain sensitive.

Overview
Replaces the separate Chat/Interview interview-stage UX with a unified text + Voice assistant that shares one transcript and composer. Hosts plug in via renderVoiceMode (replacing renderInterviewStage) and export PetrinautAiInputMode; they report live session state through reportVoiceSessionState and register lifecycle controls while Petrinaut owns the waveform dock, transcript ordering, provenance chips, consent handoff, and error toasts.

The Brunch website voice control is slimmed to inline consent + Realtime/session orchestration—the large VoiceInterviewControlView stage, mini bar, and Chat/Interview tabs are removed. VoiceTurnController gains mute-without-interrupt, pause-while-connecting, and deferred bridge start; useFlueChatHistory only hydrates from a configured Brunch URL so the generic chat route is not polled.

Typed send ends Voice once before shared submission (draft restored on failure); closing the panel pauses media and reopens the same mounted session paused. Assistant failures surface as toasts instead of transcript lines. Docs, ADR-0009, integration spec, changeset, and tests align with the new contract.

Reviewed by Cursor Bugbot for commit 2a1f1bb. Bugbot is set up for automated code reviews on this repo. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

2 participants