diff --git a/ui/src/components/agents/ported/Markdown.tsx b/ui/src/components/agents/ported/Markdown.tsx index a4e37c44..cdcb0864 100644 --- a/ui/src/components/agents/ported/Markdown.tsx +++ b/ui/src/components/agents/ported/Markdown.tsx @@ -1,4 +1,4 @@ -import { memo, useMemo } from "react"; +import { Component, memo, useMemo } from "react"; import { Streamdown } from "streamdown"; import type { ReactNode } from "react"; import "streamdown/styles.css"; @@ -76,6 +76,46 @@ const STREAMDOWN_COMPONENTS = { const SHIKI_THEME: ["github-light", "github-dark"] = ["github-light", "github-dark"]; +interface BoundaryProps { + content: string; + children: ReactNode; +} + +interface BoundaryState { + failed: boolean; + key: string; +} + +// Streamdown bundles Mermaid and renders ```mermaid blocks itself; a diagram it +// can't parse throws during render and, with no boundary, white-screens the +// whole page. Contain it and fall back to the raw markdown text. +class MarkdownErrorBoundary extends Component { + state: BoundaryState = { failed: false, key: this.props.content }; + + static getDerivedStateFromError(): Partial { + return { failed: true }; + } + + static getDerivedStateFromProps( + props: BoundaryProps, + state: BoundaryState + ): Partial | null { + if (props.content !== state.key) return { failed: false, key: props.content }; + return null; + } + + render(): ReactNode { + if (this.state.failed) { + return ( +
+          {this.props.content}
+        
+ ); + } + return this.props.children; + } +} + export const Markdown = memo(function Markdown({ content, isLive = false, @@ -99,17 +139,19 @@ export const Markdown = memo(function Markdown({ return (
- - {content} - + + + {content} + +
); });