Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import { ActionableNotification, Button, InlineLoading } from '@carbon/react';
import type { ReactNode } from 'react';

import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';

import classes from './ErrorMessage.module.scss';

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

{onRetry && (
<Button size="sm" onClick={() => onRetry()} disabled={isRefetching}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,48 @@ import 'katex/dist/katex.min.css';

import clsx from 'clsx';
import { useMemo } from 'react';
import type { Components } from 'react-markdown';
import Markdown from 'react-markdown';

import type { UISourcePart } from '#modules/messages/types.ts';
import type { PluggableList } from 'unified';

import { components, type ExtendedComponents } from './components';
import { CitationLink } from './components/CitationLink/CitationLink';
import { Code } from './components/Code';
import classes from './MarkdownContent.module.scss';
import { rehypePlugins } from './rehype';
import { remarkPlugins } from './remark';
import { urlTransform } from './utils';

interface Props {
isPending?: boolean;
sources?: UISourcePart[];
export interface MarkdownContentProps {
codeBlocksExpanded?: boolean;
children?: string;
className?: string;
remarkPlugins?: PluggableList;
components?: Components;
}

export function MarkdownContent({ isPending, sources, className, children }: Props) {
export function MarkdownContent({
codeBlocksExpanded,
className,
remarkPlugins: remarkPluginsProps,
components: componentsProps,
children,
}: MarkdownContentProps) {
const extendedComponents: ExtendedComponents = useMemo(
() => ({
...components,
citationLink: ({ ...props }) => <CitationLink {...props} sources={sources} />,
code: ({ ...props }) => <Code {...props} forceExpand={isPending} />,
code: ({ ...props }) => <Code {...props} forceExpand={codeBlocksExpanded} />,
...componentsProps,
}),
[isPending, sources],
[codeBlocksExpanded, componentsProps],
);

const extendedRemarkPlugins = useMemo(() => [...remarkPlugins, ...(remarkPluginsProps ?? [])], [remarkPluginsProps]);

return (
<div className={clsx(classes.root, className)}>
<Markdown
rehypePlugins={rehypePlugins}
remarkPlugins={remarkPlugins}
remarkPlugins={extendedRemarkPlugins}
components={extendedComponents}
urlTransform={urlTransform}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import type { JSX } from 'react';
import type { Components } from 'react-markdown';

import type { CitationLinkBaseProps } from './CitationLink/CitationLink';
import { Code } from './Code';
import { ExternalLink, type ExternalLinkProps } from './ExternalLink';
import { Img } from './Img';
Expand All @@ -15,7 +14,6 @@ import { MermaidDiagram } from './MermaidDiagram';
import { Table } from './Table';

export interface ExtendedComponents extends Components {
citationLink?: (props: CitationLinkBaseProps) => JSX.Element;
externalLink?: (props: ExternalLinkProps) => JSX.Element;
mermaidDiagram?: (props: MermaidDiagramProps) => JSX.Element;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import type { PluggableList } from 'unified';

import { remarkCitationLink } from './remarkCitationLink';
import { remarkExternalLink } from './remarkExternalLink';
import { remarkMermaid } from './remarkMermaid';

export const remarkPlugins = [
remarkGfm,
[remarkMath, { singleDollarTextMath: false }],
remarkMermaid,
remarkCitationLink,
remarkExternalLink,
] satisfies PluggableList;
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
}
}

.apiError {
.apiError > div {
@include line-clamp(10);
margin-block-start: $spacing-02;
font-size: rem(12px);
Expand Down
8 changes: 7 additions & 1 deletion apps/agentstack-ui/src/contexts/Toast/ToastProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type { PropsWithChildren } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { v4 as uuid } from 'uuid';

import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';

import type { Toast, ToastWithKey } from './toast-context';
import { ToastContext } from './toast-context';
import classes from './ToastProvider.module.scss';
Expand Down Expand Up @@ -86,7 +88,11 @@ function Toast({ toast, onClose }: { toast: ToastWithKey; onClose: () => void })
{(subtitle || apiError) && (
<div className="cds--toast-notification__subtitle">
{subtitle && <div className={classes.subtitle}>{subtitle}</div>}
{apiError && <div className={classes.apiError}>{apiError}</div>}
{apiError && (
<div className={classes.apiError}>
<MarkdownContent>{apiError}</MarkdownContent>
</div>
)}
</div>
)}
</ToastNotification>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
import clsx from 'clsx';
import { memo } from 'react';

import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
import type { UIMessage } from '#modules/messages/types.ts';
import { ChatMarkdownContent } from '#modules/runs/components/ChatMarkdownContent/ChatMarkdownContent.tsx';

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

return (
<MarkdownContent className={classes.root} sources={sources} isPending={isPending}>
<ChatMarkdownContent className={classes.root} sources={sources} codeBlocksExpanded={isPending}>
{content}
</MarkdownContent>
</ChatMarkdownContent>
);
} else if (secretPart || status?.isError) {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Copyright 2025 © BeeAI a Series of LF Projects, LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { JSX } from 'react';
import { useMemo } from 'react';
import type { Components } from 'react-markdown';

import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
import type { UISourcePart } from '#modules/messages/types.ts';
import type { CitationLinkBaseProps } from '#modules/runs/components/ChatMarkdownContent/CitationLink/CitationLink.tsx';
import { CitationLink } from '#modules/runs/components/ChatMarkdownContent/CitationLink/CitationLink.tsx';

import { remarkCitationLink } from './CitationLink/remarkCitationLink';

interface Props {
codeBlocksExpanded?: boolean;
sources?: UISourcePart[];
children?: string;
className?: string;
}

export function ChatMarkdownContent({ sources, ...props }: Props) {
const components: ChatComponents = useMemo(
() => ({
citationLink: ({ ...props }) => <CitationLink {...props} sources={sources} />,
}),
[sources],
);

return <MarkdownContent {...props} components={components} remarkPlugins={remarkPlugins} />;
}

const remarkPlugins = [remarkCitationLink];

export interface ChatComponents extends Components {
citationLink?: (props: CitationLinkBaseProps) => JSX.Element;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { visit } from 'unist-util-visit';

import { CITATION_LINK_PREFIX } from '#modules/sources/types.ts';

import type { CitationLinkBaseProps } from '../components/CitationLink/CitationLink';
import type { CitationLinkBaseProps } from './CitationLink';

export function remarkCitationLink() {
return (tree: Root) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { CopyButton } from '@carbon/react';
import type { PropsWithChildren } from 'react';

import { DownloadButton } from '#components/DownloadButton/DownloadButton.tsx';
import { MarkdownContent } from '#components/MarkdownContent/MarkdownContent.tsx';
import type { UISourcePart } from '#modules/messages/types.ts';

import { ChatMarkdownContent } from './ChatMarkdownContent/ChatMarkdownContent';
import classes from './RunOutputBox.module.scss';

interface Props {
Expand All @@ -30,7 +30,11 @@ export function RunOutputBox({ isPending, text, downloadFileName, sources, child
</div>
)}

{text && <MarkdownContent sources={sources}>{text}</MarkdownContent>}
{text && (
<ChatMarkdownContent sources={sources} codeBlocksExpanded={isPending}>
{text}
</ChatMarkdownContent>
)}

<div className={classes.holder}>{children}</div>
</div>
Expand Down
Loading