feat(AIMessage): add audio content block for inline playback#285
Merged
Conversation
Add an 'audio' content block type to AIMessage so chat bubbles can render an inline waveform AudioPlayer. Mirrors the existing image/file block pattern. - types.ts: add 'audio' to AIMessageContent.type union plus optional audioUrl, mimeType, duration fields. - AIMessage.tsx: render <AudioPlayer variant="waveform"> when a block of type 'audio' carries an audioUrl.
Deploying ui with
|
| Latest commit: |
fe8a971
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://297626fa.ui-6d0.pages.dev |
| Branch Preview URL: | https://feat-ai-message-audio-block.ui-6d0.pages.dev |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds support for rendering inline audio playback inside AI chat messages by introducing a new audio content-block type and mapping it to the existing AudioPlayer component.
Changes:
- Extends
AIMessageContent.typewith a new'audio'block type and adds audio-related fields (audioUrl,mimeType,duration). - Updates
AIMessagecontent rendering to show an inline waveformAudioPlayerwhen anaudioblock includes anaudioUrl.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/components/AI/types.ts | Adds an audio content-block type and associated metadata fields to the message content model. |
| src/components/AI/AIMessage.tsx | Renders AudioPlayer for audio content blocks that include an audioUrl. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Match the rest of the codebase (MessageAttachment.duration, AudioRecorder onRecordingComplete, AudioPlayer fallbackDuration) which represent audio duration as a number of seconds. Addresses review feedback on #285.
…-block # Conflicts: # src/components/AI/AIMessage.tsx # src/components/AI/types.ts
- Guard audioUrl against javascript: scheme (mirrors image/file blocks). - Pass content.duration through as AudioPlayer fallbackDuration so the time shows before audio metadata loads. Addresses review on #285. - Add WithAudioBlock story to AIMessage stories and document the audio block in the docs table, so the inline player is visible in Storybook. - Extract the sample-audio WAV generator into a shared sampleAudio.ts reused by AudioPlayer and AIMessage stories (DRY).
… (review #285) - Throw a clear error when Web Audio API/AudioContext is unavailable - Type audioBufferToWav param as AudioBuffer (drop any + eslint-disable) - Close the AudioContext in finally to release resources - Register AudioBuffer as an eslint global
garrity-miepub
previously approved these changes
Jun 25, 2026
Contributor
Author
|
@garrity-miepub , Re-review please. |
garrity-miepub
approved these changes
Jun 29, 2026
garrity-miepub
left a comment
Collaborator
There was a problem hiding this comment.
We might eventually want to update the waveColor to use a primary color so it renders batter in dark mode, but i think we can get this merged as is and do a fast follow to explore wave color in dark mode.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an
audiocontent block toAIMessageso chat bubbles can render an inline waveformAudioPlayer. This mirrors the existing image/file content-block pattern.Changes
types.ts— add'audio'to theAIMessageContent.typeunion and optionalaudioUrl,mimeType,durationfields. The existingtextfield doubles as the audio label.AIMessage.tsx— render<AudioPlayer variant="waveform" showTime showPlaybackRate />when a content block of typeaudiocarries anaudioUrl.Motivation
Consuming apps (e.g. Ozwell) capture audio during dictation and want users to play back the original recording inline next to its transcription to verify accuracy. The existing
AudioPlayercomponent already supports this; this PR just letsAIChat/AIMessagesurface it as a content block.Notes
AudioPlayeris already part of the package).Why are we doing it ?
Why bluehive-ai "just worked" but ozwell didn't
The difference is who owns the chat rendering, not the
AudioPlayercomponent itself.The AudioPlayer docs "Chat Message" story you saw is hand-rolled — it builds the bubbles itself with raw
<div>+<Avatar>+<AudioPlayer>. It does not go throughAIChat/AIMessage:bluehive-ai rendered its own bespoke message bubbles the same way, so it could drop
<AudioPlayer>anywhere it liked. Nothing stopped it.ozwell does not hand-roll bubbles. SessionPage.tsx delegates the entire conversation to one component:
AIChatowns the message list and renders each message throughAIMessage, whichswitches oncontent.type(text/tool_use/thinking/code/image/file) and returnsnullfor anything it doesn't recognize.The crux: AIChat has no escape hatch for custom content
I checked AIChat.tsx. The only render hook it exposes is
renderTextContent, and that fires fortextblocks only. There is:renderMessage/renderContentprop,childrenper message,So while ozwell renders via
AIChat, there is no supported way to put an<AudioPlayer>inside a bubble unlessAIMessageunderstands an audio block. That's the whole reason for the mieweb/ui change — and my change mirrors the already-mergedimage/fileblock pattern, so it's exactly how the library is designed to grow.Options
image/filerenderTextContentto sniff a marker and inject AudioPlayerBottom line: Yes, we genuinely need the mieweb/ui change as long as ozwell renders the session via
AIChat. The "Chat Message" story isn't usingAIChatat all — it's the hand-rolled path, which is option B. Reusing the existingAudioPlayeris exactly what PR #285 does; it just makesAIChatable to host it.