update markdown streaming

This commit is contained in:
Nate Kelley 2025-03-10 12:20:54 -06:00
parent c218565339
commit 9819ea57fa
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
10 changed files with 124 additions and 133 deletions

View File

@ -33,7 +33,7 @@ export const PulseLoader: React.FC<{
);
};
export const TextPulseLoader: React.FC<{
export const TextDotLoader: React.FC<{
showPulseLoader?: boolean;
size?: number;
className?: string;
@ -50,9 +50,8 @@ export const TextPulseLoader: React.FC<{
height: size,
backgroundColor: 'var(--color-text-default)',
borderRadius: '100%'
}}>
{/* Pulse animation can be added here if needed */}
</span>
}}
/>
)}
</>
);

View File

@ -11,7 +11,7 @@ import { Text } from '@/components/ui/typography';
import pluralize from 'pluralize';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { StreamingMessageCodeTitle } from './StreamingMessageCodeTitle';
import { TextPulseLoader } from '../../loaders/PulseLoader';
import { TextDotLoader } from '../../loaders/PulseLoader';
const style = SyntaxHighlighterLightTheme;
@ -141,7 +141,7 @@ export const StreamingMessageCode: React.FC<
)}
</motion.div>
))}
{showLoader && <TextPulseLoader className="pl-2" />}
{showLoader && <TextDotLoader className="pl-2" />}
</motion.div>
</AnimatePresence>
</AppCodeBlockWrapper>

View File

@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
import { AppCodeBlock } from './AppCodeBlock';
const meta: Meta<typeof AppCodeBlock> = {
title: 'UI/Typography/AppMarkdown/AppCodeBlock',
title: 'UI/Typography/AppCodeBlock',
component: AppCodeBlock,
tags: ['autodocs'],
args: {

View File

@ -9,6 +9,7 @@ import { cn } from '@/lib/classMerge';
export const AppCodeBlock: React.FC<{
language?: string;
className?: string;
wrapperClassName?: string;
children?: React.ReactNode;
style?: React.CSSProperties;
showLoader?: boolean;
@ -16,7 +17,15 @@ export const AppCodeBlock: React.FC<{
title?: string;
buttons?: React.ReactNode;
}> = React.memo(({ title, buttons, ...props }) => {
const { children, className = '', language, showLoader, showCopyButton = true, ...rest } = props;
const {
children,
className = '',
wrapperClassName = '',
language,
showLoader,
showCopyButton = true,
...rest
} = props;
const [style, setStyle] = useState<{
[key: string]: React.CSSProperties;
}>(lightTheme);
@ -28,7 +37,11 @@ export const AppCodeBlock: React.FC<{
}
return (
<AppCodeBlockWrapper code={code} language={title || language} showCopyButton={showCopyButton}>
<AppCodeBlockWrapper
className={wrapperClassName}
code={code}
language={title || language}
showCopyButton={showCopyButton}>
<div className="w-full overflow-x-auto">
<div className="code-wrapper">
{language ? (

View File

@ -13,42 +13,45 @@ export const AppCodeBlockWrapper: React.FC<{
showCopyButton?: boolean;
buttons?: React.ReactNode;
title?: string | React.ReactNode;
}> = React.memo(({ children, code, showCopyButton = true, language, buttons, title }) => {
const { openSuccessMessage } = useBusterNotifications();
className?: string;
}> = React.memo(
({ children, code, showCopyButton = true, language, buttons, title, className }) => {
const { openSuccessMessage } = useBusterNotifications();
const copyCode = useMemoizedFn(() => {
if (!code) return;
navigator.clipboard.writeText(code);
openSuccessMessage('Copied to clipboard');
});
const copyCode = useMemoizedFn(() => {
if (!code) return;
navigator.clipboard.writeText(code);
openSuccessMessage('Copied to clipboard');
});
return (
<div className={cn('overflow-hidden rounded border', 'max-h-fit')}>
<div
className={cn(
'bg-item-active border-border max-h-[32px] min-h-[32px] border-b px-2.5',
'flex items-center justify-between'
)}>
<Text className="pl-2">{title || language}</Text>
<div className="flex items-center space-x-1">
{showCopyButton && (
<Button
variant="ghost"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
copyCode();
}}
prefix={<Copy />}>
Copy
</Button>
)}
{buttons}
return (
<div className={cn('overflow-hidden rounded border', 'max-h-fit', className)}>
<div
className={cn(
'bg-item-active border-border max-h-[32px] min-h-[32px] border-b px-2.5',
'flex items-center justify-between'
)}>
<Text className="pl-2">{title || language}</Text>
<div className="flex items-center space-x-1">
{showCopyButton && (
<Button
variant="ghost"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
copyCode();
}}
prefix={<Copy />}>
Copy
</Button>
)}
{buttons}
</div>
</div>
</div>
{children}
</div>
);
});
{children}
</div>
);
}
);
AppCodeBlockWrapper.displayName = 'CodeBlockWrapper';

View File

@ -1,69 +1,17 @@
.container {
line-height: 1.25rem; /* leading-5 */
}
.container h1 {
font-size: var(--text-2xl);
line-height: var(--text-2xl--line-height);
}
.container h2 {
font-size: var(--text-xl);
line-height: var(--text-xl--line-height);
}
.container h3 {
font-size: var(--text-lg);
line-height: var(--text-lg--line-height);
}
.container h4 {
font-size: var(--text-md);
line-height: var(--text-md--line-height);
}
.container h5 {
font-size: var(--text-base);
line-height: var(--text-base--line-height);
}
.container p {
font-size: var(--text-base);
line-height: var(--text-base--line-height);
}
.container h1,
.container h2,
.container h3,
.container h4,
.container h5 {
margin-top: calc(var(--spacing) * 5);
display: flex;
width: fit-content;
align-items: center;
gap: var(--spacing);
}
.container h1:first-child,
.container h2:first-child,
.container h3:first-child,
.container h4:first-child,
.container h5:first-child {
margin-top: 0;
line-height: 1.3;
}
.container table {
border-color: var(--color-border);
border-radius: var(--radius-xs);
border-width: var(--default-border-width);
border-style: solid;
border-radius: var(--radius);
border-width: 0.5px;
}
.container th {
border-color: var(--color-border);
@apply border;
min-width: 30px;
border-right-width: var(--default-border-width);
border-bottom-width: var(--default-border-width);
border-right-width: 0.5px;
border-bottom-width: 0.5px;
border-style: solid;
padding: calc(var(--spacing) * 1.5);
}
@ -74,7 +22,7 @@
.container td {
border-color: var(--color-border);
border-right-width: var(--default-border-width);
border-right-width: 0.5px;
border-style: solid;
padding: calc(var(--spacing) * 1.5);
}
@ -84,9 +32,9 @@
}
.container tr {
border-bottom-width: var(--default-border-width);
border-bottom-width: 0.5px;
border-style: solid;
border-color: var(--color-gray-light);
border-color: var(--color-border);
}
.container tr:last-child {

View File

@ -16,6 +16,10 @@ const meta: Meta<typeof AppMarkdown> = {
className: {
control: 'text',
description: 'Additional CSS class names'
},
stripFormatting: {
control: 'boolean',
description: 'Whether to strip formatting'
}
}
};

View File

@ -12,21 +12,25 @@ import {
} from './AppMarkdownCommon';
import { useMemoizedFn } from '@/hooks';
import styles from './AppMarkdown.module.css';
import { cn } from '@/lib/classMerge';
const AppMarkdownBase: React.FC<{
markdown: string | null;
showLoader?: boolean;
className?: string;
}> = ({ markdown = '', showLoader = false, className = '' }) => {
stripFormatting?: boolean;
}> = ({ markdown = '', showLoader = false, className = '', stripFormatting = false }) => {
const currentMarkdown = markdown || '';
const commonProps = useMemo(() => {
const numberOfLineMarkdown = currentMarkdown.split('\n').length;
return {
markdown: currentMarkdown,
showLoader,
numberOfLineMarkdown
numberOfLineMarkdown,
stripFormatting
};
}, [currentMarkdown, showLoader]);
}, [currentMarkdown, showLoader, stripFormatting]);
const text = useMemoizedFn((props: React.SVGTextElementAttributes<SVGTextElement>) => (
<CustomParagraph {...props} {...commonProps} />
@ -38,10 +42,10 @@ const AppMarkdownBase: React.FC<{
const span = useMemoizedFn((props) => <CustomSpan {...props} {...commonProps} />);
const li = useMemoizedFn((props) => <CustomListItem {...props} {...commonProps} />);
const p = useMemoizedFn((props) => <CustomParagraph {...props} {...commonProps} />);
const h1 = useMemoizedFn((props) => <CustomHeading as="h1" {...props} {...commonProps} />);
const h2 = useMemoizedFn((props) => <CustomHeading as="h2" {...props} {...commonProps} />);
const h3 = useMemoizedFn((props) => <CustomHeading as="h3" {...props} {...commonProps} />);
const h4 = useMemoizedFn((props) => <CustomHeading as="h4" {...props} {...commonProps} />);
const h1 = useMemoizedFn((props) => <CustomHeading level={1} {...props} {...commonProps} />);
const h2 = useMemoizedFn((props) => <CustomHeading level={2} {...props} {...commonProps} />);
const h3 = useMemoizedFn((props) => <CustomHeading level={3} {...props} {...commonProps} />);
const h4 = useMemoizedFn((props) => <CustomHeading level={4} {...props} {...commonProps} />);
const h5 = useMemoizedFn((props) => <CustomHeading level={5} {...props} {...commonProps} />);
const h6 = useMemoizedFn((props) => <CustomHeading level={6} {...props} {...commonProps} />);
@ -66,15 +70,15 @@ const AppMarkdownBase: React.FC<{
}, []);
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
skipHtml={true}
components={memoizedComponents}
rehypePlugins={[rehypeRaw]} //rehypeSanitize we will assume that the markdown is safe? If we use it we cant do web components
// className={cn(styles.container, 'space-y-2.5', className)}
>
{currentMarkdown}
</ReactMarkdown>
<div className={cn('leading-1.3 gap-1', styles.container, className)}>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
skipHtml={true}
components={memoizedComponents}
rehypePlugins={[rehypeRaw]}>
{currentMarkdown}
</ReactMarkdown>
</div>
);
};

View File

@ -1,7 +1,8 @@
import React from 'react';
import { ExtraProps } from 'react-markdown';
import { AppCodeBlock } from '../AppCodeBlock/AppCodeBlock';
import { TextPulseLoader } from '@/components/ui/loaders';
import { TextDotLoader } from '@/components/ui/loaders';
import { cva } from 'class-variance-authority';
type Element = any; //TODO fix this after migration
@ -33,7 +34,7 @@ export const CommonPulseLoader: React.FC<{
);
if (showStreamingLoader) {
return <TextPulseLoader />;
return <TextDotLoader />;
}
return null;
};
@ -51,7 +52,11 @@ export const CustomCode: React.FC<
const showStreamingLoader = showLoader && node?.position?.end.line === rest.numberOfLineMarkdown;
return (
<AppCodeBlock language={language} showLoader={showStreamingLoader}>
<AppCodeBlock
wrapperClassName="my-2.5"
className="leading-1.3"
language={language}
showLoader={showStreamingLoader}>
{children}
</AppCodeBlock>
);
@ -66,7 +71,7 @@ export const CustomParagraph: React.FC<
> = ({ children, markdown, ...rest }) => {
if (Array.isArray(children)) {
return (
<p className="nate-rulez">
<p className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</p>
@ -80,13 +85,26 @@ export const CustomParagraph: React.FC<
}
return (
<p className="">
<p className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</p>
);
};
const headingVariants = cva('leading-1.3 my-2', {
variants: {
level: {
1: 'text-3xl ',
2: 'text-2xl',
3: 'text-xl',
4: 'text-lg',
5: 'text-md',
6: 'text-sm',
base: ''
}
}
});
export const CustomHeading: React.FC<
{
level: 1 | 2 | 3 | 4 | 5 | 6;
@ -94,11 +112,13 @@ export const CustomHeading: React.FC<
markdown: string;
showLoader: boolean;
numberOfLineMarkdown: number;
stripFormatting?: boolean;
} & ExtraPropsExtra
> = ({ level, children, markdown, ...rest }) => {
> = ({ level, children, markdown, stripFormatting = false, ...rest }) => {
const HeadingTag = `h${level}` as any;
console.log('heading', HeadingTag, level, children);
return (
<HeadingTag>
<HeadingTag className={headingVariants({ level: stripFormatting ? 'base' : level })}>
{children}
<CommonPulseLoader {...rest} />
</HeadingTag>
@ -115,7 +135,7 @@ export const CustomList: React.FC<
> = ({ ordered, children, markdown, ...rest }) => {
const ListTag = ordered ? 'ol' : 'ul';
return (
<ListTag>
<ListTag className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</ListTag>
@ -130,7 +150,7 @@ export const CustomListItem: React.FC<
} & ExtraPropsExtra
> = ({ children, markdown, ...rest }) => {
return (
<li>
<li className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</li>
@ -145,7 +165,7 @@ export const CustomBlockquote: React.FC<
} & ExtraPropsExtra
> = ({ children, markdown, ...rest }) => {
return (
<blockquote>
<blockquote className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</blockquote>
@ -160,7 +180,7 @@ export const CustomTable: React.FC<
} & ExtraPropsExtra
> = ({ children, markdown, ...rest }) => {
return (
<table>
<table className="leading-1.3">
{children}
<CommonPulseLoader {...rest} />
</table>
@ -174,5 +194,5 @@ export const CustomSpan: React.FC<
showLoader: boolean;
} & ExtraPropsExtra
> = ({ children, markdown, ...rest }) => {
return <span>{children}</span>;
return <span className="leading-1.3">{children}</span>;
};

View File

@ -45,5 +45,5 @@ body {
}
p {
@apply text-base leading-[1.2];
@apply leading-1.3 text-base;
}