From e36d9862ece4af0edf2fe2e9c61797b828abb4f5 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 26 Jun 2026 03:22:14 -0500 Subject: [PATCH] feat(desktop): render embeds, fences and alerts in assistant markdown Wire the embeds module into the markdown surface: bare provider autolinks unfurl to inline embeds, ```mermaid/```svg fences route to the rich renderers, and `> [!NOTE]`-style blockquotes become alert callouts. Labeled links stay plain. --- .../components/assistant-ui/embeds/index.ts | 5 ++ .../components/assistant-ui/markdown-text.tsx | 50 ++++++++++++++++--- 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/components/assistant-ui/embeds/index.ts diff --git a/apps/desktop/src/components/assistant-ui/embeds/index.ts b/apps/desktop/src/components/assistant-ui/embeds/index.ts new file mode 100644 index 00000000000..37117b195a6 --- /dev/null +++ b/apps/desktop/src/components/assistant-ui/embeds/index.ts @@ -0,0 +1,5 @@ +export { extractAlert, MarkdownAlert } from './alert' +export type { EmbedDescriptor } from './providers' +export { detectEmbed, isEmbeddableUrl } from './providers' +export { RICH_FENCE_LANGUAGES, RichCodeBlock } from './registry' +export { UrlEmbed } from './url-embed' diff --git a/apps/desktop/src/components/assistant-ui/markdown-text.tsx b/apps/desktop/src/components/assistant-ui/markdown-text.tsx index 3da29aebbcc..beabcbf8cc2 100644 --- a/apps/desktop/src/components/assistant-ui/markdown-text.tsx +++ b/apps/desktop/src/components/assistant-ui/markdown-text.tsx @@ -40,6 +40,8 @@ import { previewTargetFromMarkdownHref } from '@/lib/preview-targets' import { tailBoundedRemend } from '@/lib/remend-tail' import { cn } from '@/lib/utils' +import { detectEmbed, extractAlert, MarkdownAlert, RichCodeBlock, UrlEmbed } from './embeds' + // Math rendering plugin (KaTeX). Configured once at module scope — the // plugin is stateless beyond its internal cache so re-creating per-render // would needlessly thrash. We use a memoizing wrapper around rehype-katex @@ -270,6 +272,17 @@ function MarkdownLink({ children, className, href, ...props }: ComponentProps<'a } const text = childrenToText(children) + + // Bare autolink → inline rich embed when a provider matches. Labeled links + // (`[watch](url)`) stay plain. Desktop only (webview / iframe renderers). + if (window.hermesDesktop && text && normalizeExternalUrl(text) === target) { + const embed = detectEmbed(target) + + if (embed) { + return + } + } + const fallbackLabel = text && normalizeExternalUrl(text) !== target ? text : undefined return ( @@ -535,13 +548,25 @@ function MarkdownTextSurface({ containerClassName, containerProps }: MarkdownTex // owning per-line text direction. Inline code carries `dir="ltr"` // (see the `code` override) so it doesn't vote here either, same // contract as the CSS isolate. - blockquote: ({ className, ...props }: ComponentProps<'blockquote'>) => ( -
- ), + // A `> [!NOTE]`/`[!WARNING]`/... blockquote renders as a GFM alert + // callout; everything else stays a plain quote. + blockquote: ({ children, className, ...props }: ComponentProps<'blockquote'>) => { + const alert = extractAlert(children) + + if (alert) { + return {alert.body} + } + + return ( +
+ {children} +
+ ) + }, ul: ({ className, ...props }: ComponentProps<'ul'>) => (
    ), @@ -578,7 +603,16 @@ function MarkdownTextSurface({ containerClassName, containerProps }: MarkdownTex ), img: MarkdownImage, - SyntaxHighlighter: (props: SyntaxHighlighterProps) => + // ```mermaid / ```svg fences route to their lazy renderers; every other + // language falls back to the Shiki-highlighted code block. + SyntaxHighlighter: (props: SyntaxHighlighterProps) => ( + } + language={props.language} + streaming={isStreaming} + /> + ) }) as StreamdownTextComponents, [isStreaming] )