Skip to content

feat(AI): custom content blocks with a renderCustomContent render-prop - #759

Open
abroa01 wants to merge 2 commits into
mainfrom
feat/ai-custom-content-block
Open

abroa01 wants to merge 2 commits into
mainfrom
feat/ai-custom-content-block

Conversation

@abroa01

@abroa01 abroa01 commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #760

Lets a host pass a typed object to AIChat / AIMessageDisplay and render it itself, instead of encoding structured data into a text block.

Requested in review of mieweb/ozwell-workspace#521 (comment).

What & why

Ozwell shows a speaker-labelled recording with TranscriptPlayer. The only way to put a host widget inside a message today is renderTextContent, so the app JSON-encodes the transcript data behind a sentinel prefix, sends it as a text block, and decodes it again in renderTextContent. That round trip is fragile, and it means every text block, including what a user types, is checked for the marker.

This PR adds a small, generic hook that follows the existing renderTextContent / renderMessageFooter pattern:

// Host
content.push({ type: 'custom', data: { kind: 'speaker-transcript', chatId, audioUrl } });

<AIChat
  messages={messages}
  renderCustomContent={(data, { messageId, streaming, role }) =>
    isSpeakerTranscript(data) ? <SpeakerTranscript payload={data} /> : null
  }
/>
File Change
types.ts 'custom' block type, data?: unknown, and AIRenderCustomContent = (data, ctx) => ReactNode (reuses AITextRenderContext).
AIMessage.tsx ContentBlock calls renderCustomContent(data, ctx) for custom blocks, without the prose wrapper (widgets aren't prose). Threaded through MessageContentBlocks / AIMessageDisplay.
AIChat.tsx New renderCustomContent prop, passed to every message. AIChatModal / FloatingAIChat already spread props.
Docs AIMessage / AIChat story descriptions and argTypes, a WithCustomContentBlock story, and a MAINTAINERS.md note. The AIChat Limitations list which surfaces support custom blocks and that widgets remount when a new turn starts.

Behaviour:

  • data is opaque to the library; hosts use their own discriminator (e.g. data.kind).
  • No renderCustomContent → a custom block renders nothing (same as any unknown block today).
  • Text blocks never reach renderCustomContent.
  • Purely additive: existing block types render exactly as before. SuperChat renders AIMessageContent itself and ignores unknown types, so it is unaffected. OzwellChat does not forward the new prop (no consumer needs it yet).

Tests

New AIMessageDisplay custom block tests in AIMessage.test.tsx:

  • renderCustomContent receives data and { messageId, streaming, role }, and its output is mounted outside .prose.
  • A custom block without a renderer renders nothing.
  • A text block containing the old sentinel-encoded payload renders as plain text and never reaches renderCustomContent.

Checklist

  • pnpm typecheck && pnpm lint && pnpm format && pnpm test pass (190 files, 2724 tests)
  • Visual tests: existing rendering is unchanged; the new story is not in the curated visual set

RTL

  • No new physical-direction classes or inline styles — pnpm rtl:scan passes
  • No directional icons
  • No arrow-key handlers
  • No new class strings in components (story-only classes)
  • No direction-sensitive rendering

…-prop

Hosts can now pass a typed payload to AIChat/AIMessageDisplay as { type: 'custom', data } and render it through renderCustomContent(data, ctx), instead of encoding structured data into a sentinel-prefixed text block and decoding it in renderTextContent. Custom blocks render without the prose wrapper and render nothing when no renderer is passed. Text blocks never reach renderCustomContent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 12, 2026 17:24
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Deploying ui with  Cloudflare Pages  Cloudflare Pages

Latest commit: 94852d8
Status: ✅  Deploy successful!
Preview URL: https://7b65e5b7.ui-6d0.pages.dev
Branch Preview URL: https://feat-ai-custom-content-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.

🟡 Changes recommended

The documentation omits which chat surfaces support custom widgets and how hosts should preserve widget state when new turns cause remounting.

2 open issues
What changed in this PR

Adds host-rendered custom content blocks to AI chat messages, letting applications pass structured widget data directly instead of encoding it as text.

Changes:

  • Adds the custom block type and renderCustomContent callback to AIChat and AIMessageDisplay.
  • Renders custom widgets without a prose wrapper; blocks without a renderer produce no output.
  • Documents the extension point and adds a Storybook example and focused unit tests.
File Description
src/​components/​AI/​types.ts Defines custom block payloads and the renderer callback type.
src/​components/​AI/​MAINTAINERS.md Documents the extension point and updates the block-type inventory.
src/​components/​AI/​AIMessage.tsx Invokes and forwards the custom renderer through message content.
src/​components/​AI/​AIMessage.test.tsx Tests callback context, wrapper behavior, missing renderers, and text isolation.
src/​components/​AI/​AIMessage.stories.tsx Documents custom blocks and demonstrates a structured-data widget.
src/​components/​AI/​AIChat.tsx Exposes the callback and forwards it to every message.
src/​components/​AI/​AIChat.stories.tsx Adds consumer-facing documentation for the new prop.

Review effort: Balanced

Comment thread src/components/AI/AIChat.stories.tsx Resolved
Comment thread src/components/AI/AIChat.stories.tsx Resolved
…ets remount between turns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 12, 2026 18:07

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.

🟢 Approval recommended

The custom-content rendering extension has no active review issues.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

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.

AI chat: let hosts render typed custom content blocks (renderCustomContent)

2 participants