Inline Tool Renderers
Inline tools let RapBotRito stream mixed content (markdown headings, gifs, SVGs, chain logos, music commands, etc.) inside a single assistant message. This page explains the architecture that powers those renderers so you know which file to touch when adding a new tag.
Scope & Goals
- Covers
MessageContent.tsx,parseContentWithMedia.ts,splitPartsAtAnchor.ts,RenderSegment.tsx, and the renderer helpers insidedapp/components/chatBot/ChatMessages. - Highlights the helpers that normalize inline markdown (
mdInline.ts), sanitize assets (imageHelpers.ts,gifHelpers.ts,svgHelpers.ts), and bridge to the sharedMusicPlayer. - Excludes ToolActivity chips, prompt stores, or the broader modal layout. Jump to the Chat Modal transcript section for scroll pinning, headers, and composer details.
Inline tools are scoped to assistant turns. splitPartsAtAnchor.ts (dapp/components/chatBot/ChatMessages/utils/splitPartsAtAnchor.ts) splits only assistant messages around the ToolActivity anchor so chips sit between the “before” and “after” content, while user parts bypass the parser unchanged.
Transcript Pipeline
The rendering path stacks vertically so it holds up on narrow viewports:
Key stops along the path:
- MessageContent (
dapp/.../MessageContent.tsx) loops everyMessagePart, callsparseContentWithMedia(part.text), and streams the resultingMediaSegment[]through<RenderSegment />. - Split around anchors (
splitPartsAtAnchor.ts) ensures tool chips for the current assistant tool are mounted between the parsed segments that came before vs after the anchor index. - RenderSegment.tsx is the registry that maps each
segment.typetoLinkRenderer,ImageRenderer,GifRenderer,SvgRenderer,ChainLogo,MusicCommandRenderer, orGoodbyeRenderer.
Segment Parsing Rules
parseContentWithMedia.ts turns raw strings into typed segments so renderers can stay dumb:
- Preprocessing: decodes entities, drops ``` fences, rewrites markdown images to
<img>, and strips inline backticks around<svg>/<img>/<gif>/<music>/<goodbye>tags so they survive streaming chunks. - Markdown emphasis: delegates inline bold/italic detection to
mdInline.ts, producingformattedTextorheadingsegments with normalizedInlineRun[]payloads. - Link detection: scans
[label](https://example)patterns ahead of other tags and emits{ type: 'link' }segments before formatted text fallback. - Media tags: matches literal HTML-like tokens for
<img>,<svg>,<gif>,<chain-logo>,<key-nft>,<music>, and<goodbye/>. Helpers extract safe attributes:imageHelpers.tsreadssrc,alt,width,heightfor<img />strings.gifHelpers.tsresolves local gifs to/gifs/...paths and normalizes dimensions.svgHelpers.tsadds namespaces, viewBox defaults, andpreserveAspectRatiobefore sanitization.
- Chain logos:
<chain-logo chainName="Base" size="160" />is parsed into{ type: 'chainLogo', chainName, size }soChainLogo.tsxcan resolve the right TrustWallet asset, complete with overrides and Fuse fuzzy matching. - Music cues:
<music song="demo" timeline="1:14" action="play" />map toMusicSegmentwithsong,ext,timeline,autoplay, andactionprops for the renderer. - Goodbye timer:
<goodbye width="360" seconds="8" />becomes a dedicatedGoodbyeSegment, arming the countdown component that reloads the page via sessionStorage-backed timers.
Renderer Registry
RenderSegment.tsx performs a strict type switch so the actual renderers never have to parse raw strings:
formattedText/headingmap to inline React trees usingInlineRuns. Everything else requires a renderer fromdapp/components/chatBot/ChatMessages/renderers.ImageRenderer.tsxhandles shimmer loading,store://hydration via@store/toolImageStore, and<img>fallbacks.GifRenderer.tsxsimply forwards toImageRendererwith a default width, so<gif>tags share the same shimmer/error handling.SvgRenderer.tsxwraps DOMPurify sanitization around the helper-massaged SVG string (including the inline SVG built for<key-nft>tokens).ChainLogo.tsxlazily fetches the TrustWallet repo directory listing, runs Fuse to pick a logo path, and falls back to an inline SVG placeholder.MusicCommandRenderer.tsxissues imperative commands to the shareduseMusichook (load song, seek, play/pause), scrolls to#music-bar, and renders a pill summarizing the action.GoodbyeRenderer.tsxschedules a hardwindow.location.reload()relative tosessionStorageso the timer survives unmounts.
Because the registry is the single point of truth, adding a new tag means:
- Teach
parseContentWithMedia.tshow to read attributes and emit a newMediaSegmentvariant. - Extend
RenderSegment.tsxto map that variant to a new renderer component. - Export the renderer via
renderers/index.tsso other tests/utilities can reference it.
Example: Plain Links
Link rendering is intentionally boring yet illustrative. When parseContentWithMedia hits [docs](https://example.com), it emits { type: 'link' }. RenderSegment passes that to LinkRenderer.tsx, which:
- Rejects any non-
http(s)href by falling back to#. - Colors the
<a>element differently for user vs assistant roles. - Forces
target="_blank"+rel="noopener noreferrer"for safety.
If you are contributing a new renderer, mimic this pattern: keep input props typed, gate external URLs, and avoid side effects in the render path.
Inline Tool Catalog
| Tag(s) | Renderer | Helper(s) | Doc |
|---|---|---|---|
[label](url) | LinkRenderer.tsx | built-in regex guard | — (covered above) |
<img .../> | ImageRenderer.tsx | imageHelpers.ts | Images, SVGs & KeyNFT |
raw <svg ...> / <key-nft .../> | SvgRenderer.tsx (the parser converts <key-nft> to SVG markup) | svgHelpers.ts, prepareSvg() | Images, SVGs & KeyNFT |
<gif .../> | GifRenderer.tsx (wraps ImageRenderer) | gifHelpers.ts | GIFs |
<chain-logo .../> | ChainLogo.tsx | ChainOverrides.ts, Fuse search, TrustWallet directory cache | Chain Logo |
<music .../> | MusicCommandRenderer.tsx | MusicPlayer/MusicProvider.tsx | Music Bar & Commands |
<goodbye .../> | GoodbyeRenderer.tsx | sessionStorage deadline helpers | Goodbye Timer |
Use this table as the hub: each doc dives into prop examples, styling, and troubleshooting for that renderer.
Music Commands & Player Bridge
MusicCommandRenderer.tsx lives with the other chat renderers but talks to the shared MusicPlayer located at dapp/components/chatBot/MusicPlayer. When a <music> segment arrives:
- The renderer calls
useMusic()and executes commands exactly once viauseEffect, respectingsong,ext,timeline, andactionvalues. - Clicking or pressing
Enter/Spaceon the pill scrolls to the persistent<MusicBar />so users can see what changed. - Loading a song plus seeking is a two-step operation (load without autoplay → seek with autoplay), mirroring the code path in the component.
Any future inline audio tool should reuse this bridge rather than touching the player directly.
Helper Responsibilities & Extension Points
mdInline.ts: supports headings + emphasis for agent-authored markdown. If you need more inline tokens (e.g., code spans), extend this file so every renderer benefits.imageHelpers.ts/gifHelpers.ts: single source for parsing attribute strings. Update them when you add new attributes so renderers stay stateless.svgHelpers.ts: guardrails that addxmlns,viewBox, and dimension defaults for inline<svg>strings (including the generated<key-nft>markup) before DOMPurify runs.renderers/__tests__: houses renderer-level tests. Add snapshots/behavior tests here whenever you ship a new renderer or adjust parsing logic.
Testing & Safety Checklist
- Parser coverage: when introducing a new tag, add unit tests for
parseContentWithMediaso streaming edge cases (e.g., fenced code removal, entity decoding) don’t regress. - Renderer tests: use the
renderers/__tests__sandbox to verify color, aria-labels, and behavior (music pills, countdown timers) without spinning up the whole modal. - Sanitization: never render untrusted markup without going through
prepareSvg+ DOMPurify or a similar sanitizer. - Streaming awareness: keep renderers pure. If you need side effects (
MusicCommandRenderer,GoodbyeRenderer), gate them insideuseEffectto avoid duplicate fires when React re-renders mid-stream.
Related Docs & Deep Dives
Layout, scroll pinning, and anchors that surround these renderers.
🧱Chat Modal TranscriptStart with the images/SVG/KeyNFT doc, then branch to GIFs, chain logos, goodbye, and music.
🧩Inline Tool SpecsExplains the persistent MusicBar UI that <music> commands control.
How Prompt Fragments teach the LLM to use these inline tags.
🧠System Prompts