mirror of https://github.com/buster-so/buster.git
update markdown streaming
This commit is contained in:
parent
c218565339
commit
9819ea57fa
|
@ -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>
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>;
|
||||
};
|
||||
|
|
|
@ -45,5 +45,5 @@ body {
|
|||
}
|
||||
|
||||
p {
|
||||
@apply text-base leading-[1.2];
|
||||
@apply leading-1.3 text-base;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue