Skip to content

feat(AIMessage): add audio content block for inline playback#285

Merged
garrity-miepub merged 9 commits into
mainfrom
feat/ai-message-audio-block
Jun 29, 2026
Merged

feat(AIMessage): add audio content block for inline playback#285
garrity-miepub merged 9 commits into
mainfrom
feat/ai-message-audio-block

Conversation

@abroa01

@abroa01 abroa01 commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an audio content block to AIMessage so chat bubbles can render an inline waveform AudioPlayer. This mirrors the existing image/file content-block pattern.

Changes

  • types.ts — add 'audio' to the AIMessageContent.type union and optional audioUrl, mimeType, duration fields. The existing text field doubles as the audio label.
  • AIMessage.tsx — render <AudioPlayer variant="waveform" showTime showPlaybackRate /> when a content block of type audio carries an audioUrl.

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 AudioPlayer component already supports this; this PR just lets AIChat/AIMessage surface it as a content block.

Notes

  • Backwards compatible — purely additive to the union and props; existing blocks render unchanged.
  • No new dependencies (AudioPlayer is 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 AudioPlayer component 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 through AIChat/AIMessage:

    <div className="rounded-2xl bg-neutral-100 ...">
      <AudioPlayer src={...} variant="compact" showTime />
    </div>

    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:

    <AIChat messages={messages} ... />

    AIChat owns the message list and renders each message through AIMessage, which switches on content.type (text/tool_use/thinking/code/image/file) and returns null for 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 for text blocks only. There is:

  • no renderMessage / renderContent prop,
  • no children per message,
  • no generic per-block override.

So while ozwell renders via AIChat, there is no supported way to put an <AudioPlayer> inside a bubble unless AIMessage understands an audio block. That's the whole reason for the mieweb/ui change — and my change mirrors the already-merged image/file block pattern, so it's exactly how the library is designed to grow.

Options

Option What it means Cost Verdict
A. Keep PR #285 (audio content block in AIMessage) +1 additive block type, identical to image/file tiny, reusable, library-consistent ✅ Recommended
B. Stop using AIChat, hand-roll our own bubbles in ozwell (like the story / bluehive) Re-implement the message list ourselves Large rewrite; lose AIChat streaming, tool calls, composer, suggestions, timestamps, avatars ❌ Throws away why we adopted AIChat
C. Abuse renderTextContent to sniff a marker and inject AudioPlayer Hack on the text render-prop Fragile, fires only for text, violates the contract

Bottom line: Yes, we genuinely need the mieweb/ui change as long as ozwell renders the session via AIChat. The "Chat Message" story isn't using AIChat at all — it's the hand-rolled path, which is option B. Reusing the existing AudioPlayer is exactly what PR #285 does; it just makes AIChat able to host it.

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.
Copilot AI review requested due to automatic review settings June 24, 2026 23:23
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 24, 2026

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

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

View logs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.type with a new 'audio' block type and adds audio-related fields (audioUrl, mimeType, duration).
  • Updates AIMessage content rendering to show an inline waveform AudioPlayer when an audio block includes an audioUrl.

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.

Comment thread src/components/AI/types.ts Outdated
@abroa01 abroa01 requested a review from garrity-miepub June 24, 2026 23:36
abroa01 added 2 commits June 24, 2026 19:36
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
Copilot AI review requested due to automatic review settings June 24, 2026 23:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/components/AI/AIMessage.tsx
abroa01 and others added 2 commits June 25, 2026 09:59
- 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).
Copilot AI review requested due to automatic review settings June 25, 2026 14:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread src/components/AudioPlayer/sampleAudio.ts Outdated
Comment thread src/components/AudioPlayer/sampleAudio.ts
Comment thread src/components/AudioPlayer/sampleAudio.ts Outdated
… (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
garrity-miepub previously approved these changes Jun 25, 2026

@garrity-miepub garrity-miepub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! Nice addition to the ai messaging! Just need to fix the CI lint errors

@garrity-miepub garrity-miepub self-requested a review June 25, 2026 20:33
Copilot AI review requested due to automatic review settings June 26, 2026 13:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread src/components/AudioPlayer/sampleAudio.ts
Copilot AI review requested due to automatic review settings June 26, 2026 14:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread src/components/AI/AIMessage.tsx Outdated
Comment thread src/components/AudioPlayer/sampleAudio.ts
@abroa01

abroa01 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@garrity-miepub , Re-review please.

@garrity-miepub garrity-miepub left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@garrity-miepub garrity-miepub merged commit c730ef8 into main Jun 29, 2026
10 checks passed
@garrity-miepub garrity-miepub deleted the feat/ai-message-audio-block branch June 29, 2026 19:34
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.

3 participants