Skip to content

Commit 9a9fe1d

Browse files
kapetrPetrBulanek
authored andcommitted
feat(ui): render markdown in API error messages (i-am-bee#1593)
Signed-off-by: Petr Kadlec <[email protected]> Signed-off-by: Petr Bulánek <[email protected]> Co-authored-by: Petr Bulánek <[email protected]> Signed-off-by: Eden Gilbert <[email protected]>
1 parent a041e69 commit 9a9fe1d

File tree

17 files changed

+79
-34
lines changed

17 files changed

+79
-34
lines changed

apps/agentstack-ui/src/components/ErrorMessage/ErrorMessage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import { ActionableNotification, Button, InlineLoading } from '@carbon/react';
77
import type { ReactNode } from 'react';
88

9+
import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
10+
911
import classes from './ErrorMessage.module.scss';
1012

1113
interface Props {
@@ -21,7 +23,7 @@ export function ErrorMessage({ title, subtitle, onRetry, isRefetching, children
2123
<ActionableNotification title={title} kind="error" lowContrast hideCloseButton>
2224
{(subtitle || onRetry) && (
2325
<div className={classes.body}>
24-
{subtitle && <p>{subtitle}</p>}
26+
{subtitle && <MarkdownContent>{subtitle}</MarkdownContent>}
2527

2628
{onRetry && (
2729
<Button size="sm" onClick={() => onRetry()} disabled={isRefetching}>

apps/agentstack-ui/src/components/MarkdownContent/MarkdownContent.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,48 @@ import 'katex/dist/katex.min.css';
77

88
import clsx from 'clsx';
99
import { useMemo } from 'react';
10+
import type { Components } from 'react-markdown';
1011
import Markdown from 'react-markdown';
11-
12-
import type { UISourcePart } from '#modules/messages/types.ts';
12+
import type { PluggableList } from 'unified';
1313

1414
import { components, type ExtendedComponents } from './components';
15-
import { CitationLink } from './components/CitationLink/CitationLink';
1615
import { Code } from './components/Code';
1716
import classes from './MarkdownContent.module.scss';
1817
import { rehypePlugins } from './rehype';
1918
import { remarkPlugins } from './remark';
2019
import { urlTransform } from './utils';
2120

22-
interface Props {
23-
isPending?: boolean;
24-
sources?: UISourcePart[];
21+
export interface MarkdownContentProps {
22+
codeBlocksExpanded?: boolean;
2523
children?: string;
2624
className?: string;
25+
remarkPlugins?: PluggableList;
26+
components?: Components;
2727
}
2828

29-
export function MarkdownContent({ isPending, sources, className, children }: Props) {
29+
export function MarkdownContent({
30+
codeBlocksExpanded,
31+
className,
32+
remarkPlugins: remarkPluginsProps,
33+
components: componentsProps,
34+
children,
35+
}: MarkdownContentProps) {
3036
const extendedComponents: ExtendedComponents = useMemo(
3137
() => ({
3238
...components,
33-
citationLink: ({ ...props }) => <CitationLink {...props} sources={sources} />,
34-
code: ({ ...props }) => <Code {...props} forceExpand={isPending} />,
39+
code: ({ ...props }) => <Code {...props} forceExpand={codeBlocksExpanded} />,
40+
...componentsProps,
3541
}),
36-
[isPending, sources],
42+
[codeBlocksExpanded, componentsProps],
3743
);
3844

45+
const extendedRemarkPlugins = useMemo(() => [...remarkPlugins, ...(remarkPluginsProps ?? [])], [remarkPluginsProps]);
46+
3947
return (
4048
<div className={clsx(classes.root, className)}>
4149
<Markdown
4250
rehypePlugins={rehypePlugins}
43-
remarkPlugins={remarkPlugins}
51+
remarkPlugins={extendedRemarkPlugins}
4452
components={extendedComponents}
4553
urlTransform={urlTransform}
4654
>

apps/agentstack-ui/src/components/MarkdownContent/components/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import type { JSX } from 'react';
77
import type { Components } from 'react-markdown';
88

9-
import type { CitationLinkBaseProps } from './CitationLink/CitationLink';
109
import { Code } from './Code';
1110
import { ExternalLink, type ExternalLinkProps } from './ExternalLink';
1211
import { Img } from './Img';
@@ -15,7 +14,6 @@ import { MermaidDiagram } from './MermaidDiagram';
1514
import { Table } from './Table';
1615

1716
export interface ExtendedComponents extends Components {
18-
citationLink?: (props: CitationLinkBaseProps) => JSX.Element;
1917
externalLink?: (props: ExternalLinkProps) => JSX.Element;
2018
mermaidDiagram?: (props: MermaidDiagramProps) => JSX.Element;
2119
}

apps/agentstack-ui/src/components/MarkdownContent/remark/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import remarkGfm from 'remark-gfm';
77
import remarkMath from 'remark-math';
88
import type { PluggableList } from 'unified';
99

10-
import { remarkCitationLink } from './remarkCitationLink';
1110
import { remarkExternalLink } from './remarkExternalLink';
1211
import { remarkMermaid } from './remarkMermaid';
1312

1413
export const remarkPlugins = [
1514
remarkGfm,
1615
[remarkMath, { singleDollarTextMath: false }],
1716
remarkMermaid,
18-
remarkCitationLink,
1917
remarkExternalLink,
2018
] satisfies PluggableList;

apps/agentstack-ui/src/contexts/Toast/ToastProvider.module.scss

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,3 @@
3939
background-color: $layer;
4040
}
4141
}
42-
43-
.apiError {
44-
@include line-clamp(10);
45-
margin-block-start: $spacing-02;
46-
font-size: rem(12px);
47-
font-family: font-family('mono');
48-
padding-block: $spacing-03;
49-
max-block-size: 10.2rem;
50-
}

apps/agentstack-ui/src/contexts/Toast/ToastProvider.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import type { PropsWithChildren } from 'react';
1010
import { useCallback, useEffect, useMemo, useState } from 'react';
1111
import { v4 as uuid } from 'uuid';
1212

13+
import { LineClampText } from '#components/LineClampText/LineClampText.tsx';
14+
import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
15+
1316
import type { Toast, ToastWithKey } from './toast-context';
1417
import { ToastContext } from './toast-context';
1518
import classes from './ToastProvider.module.scss';
@@ -22,7 +25,7 @@ export function ToastProvider({ children }: PropsWithChildren) {
2225
setToasts((existing) => {
2326
const defaults = {
2427
lowContrast: true,
25-
timeout: 10000,
28+
timeout: 10_000,
2629
key: uuid(),
2730
date: new Date(),
2831
};
@@ -85,8 +88,12 @@ function Toast({ toast, onClose }: { toast: ToastWithKey; onClose: () => void })
8588

8689
{(subtitle || apiError) && (
8790
<div className="cds--toast-notification__subtitle">
88-
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
89-
{apiError && <div className={classes.apiError}>{apiError}</div>}
91+
{subtitle && <div>{subtitle}</div>}
92+
{apiError && (
93+
<LineClampText lines={8} useBlockElement>
94+
<MarkdownContent>{apiError}</MarkdownContent>
95+
</LineClampText>
96+
)}
9097
</div>
9198
)}
9299
</ToastNotification>

apps/agentstack-ui/src/modules/messages/components/MessageContent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import clsx from 'clsx';
77
import { memo } from 'react';
88

9-
import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
109
import type { UIMessage } from '#modules/messages/types.ts';
10+
import { ChatMarkdownContent } from '#modules/runs/components/ChatMarkdownContent/ChatMarkdownContent.tsx';
1111

1212
import { useAgentRun } from '../../runs/contexts/agent-run';
1313
import { Role } from '../api/types';
@@ -42,9 +42,9 @@ export const MessageContent = memo(function MessageContent({ message }: Props) {
4242
}
4343

4444
return (
45-
<MarkdownContent className={classes.root} sources={sources} isPending={isPending}>
45+
<ChatMarkdownContent className={classes.root} sources={sources} codeBlocksExpanded={isPending}>
4646
{content}
47-
</MarkdownContent>
47+
</ChatMarkdownContent>
4848
);
4949
} else if (secretPart || status?.isError) {
5050
return null;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import type { JSX } from 'react';
7+
import { useMemo } from 'react';
8+
import type { Components } from 'react-markdown';
9+
10+
import type { MarkdownContentProps } from '#components/MarkdownContent/MarkdownContent.tsx';
11+
import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
12+
import type { UISourcePart } from '#modules/messages/types.ts';
13+
import type { CitationLinkBaseProps } from '#modules/runs/components/ChatMarkdownContent/CitationLink/CitationLink.tsx';
14+
import { CitationLink } from '#modules/runs/components/ChatMarkdownContent/CitationLink/CitationLink.tsx';
15+
16+
import { remarkCitationLink } from './CitationLink/remarkCitationLink';
17+
18+
interface ChatComponents extends Components {
19+
citationLink?: (props: CitationLinkBaseProps) => JSX.Element;
20+
}
21+
22+
interface Props extends Omit<MarkdownContentProps, 'remarkPlugins' | 'components'> {
23+
sources?: UISourcePart[];
24+
}
25+
26+
const remarkPlugins = [remarkCitationLink];
27+
28+
export function ChatMarkdownContent({ sources, ...props }: Props) {
29+
const components: ChatComponents = useMemo(
30+
() => ({
31+
citationLink: ({ ...props }) => <CitationLink {...props} sources={sources} />,
32+
}),
33+
[sources],
34+
);
35+
36+
return <MarkdownContent {...props} components={components} remarkPlugins={remarkPlugins} />;
37+
}

apps/agentstack-ui/src/components/MarkdownContent/components/CitationLink/CitationLink.tsx renamed to apps/agentstack-ui/src/modules/runs/components/ChatMarkdownContent/CitationLink/CitationLink.tsx

File renamed without changes.

apps/agentstack-ui/src/components/MarkdownContent/components/CitationLink/InlineCitationButton.module.scss renamed to apps/agentstack-ui/src/modules/runs/components/ChatMarkdownContent/CitationLink/InlineCitationButton.module.scss

File renamed without changes.

0 commit comments

Comments
 (0)