mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
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.
This commit is contained in:
parent
0c190083cd
commit
e36d9862ec
2 changed files with 47 additions and 8 deletions
5
apps/desktop/src/components/assistant-ui/embeds/index.ts
Normal file
5
apps/desktop/src/components/assistant-ui/embeds/index.ts
Normal file
|
|
@ -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'
|
||||
|
|
@ -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 <UrlEmbed descriptor={embed} />
|
||||
}
|
||||
}
|
||||
|
||||
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'>) => (
|
||||
<blockquote
|
||||
className={cn('border-s-2 border-border ps-3 text-muted-foreground italic', className)}
|
||||
dir="auto"
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
// 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 <MarkdownAlert type={alert.type}>{alert.body}</MarkdownAlert>
|
||||
}
|
||||
|
||||
return (
|
||||
<blockquote
|
||||
className={cn('border-s-2 border-border ps-3 text-muted-foreground italic', className)}
|
||||
dir="auto"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</blockquote>
|
||||
)
|
||||
},
|
||||
ul: ({ className, ...props }: ComponentProps<'ul'>) => (
|
||||
<ul className={cn('my-1 gap-0', className)} dir="auto" {...props} />
|
||||
),
|
||||
|
|
@ -578,7 +603,16 @@ function MarkdownTextSurface({ containerClassName, containerProps }: MarkdownTex
|
|||
<td className={cn('px-2.5 py-1.5 align-top text-[0.8125rem] leading-snug', className)} {...props} />
|
||||
),
|
||||
img: MarkdownImage,
|
||||
SyntaxHighlighter: (props: SyntaxHighlighterProps) => <SyntaxHighlighter {...props} defer={isStreaming} />
|
||||
// ```mermaid / ```svg fences route to their lazy renderers; every other
|
||||
// language falls back to the Shiki-highlighted code block.
|
||||
SyntaxHighlighter: (props: SyntaxHighlighterProps) => (
|
||||
<RichCodeBlock
|
||||
code={props.code}
|
||||
fallback={<SyntaxHighlighter {...props} defer={isStreaming} />}
|
||||
language={props.language}
|
||||
streaming={isStreaming}
|
||||
/>
|
||||
)
|
||||
}) as StreamdownTextComponents,
|
||||
[isStreaming]
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue