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'>) => (