chore: revert markdown to original component + new artifacts rendering

This commit is contained in:
Danny Avila 2024-08-22 18:15:27 -04:00
parent 801b0de49b
commit 85df66265d

View file

@ -1,10 +1,9 @@
import React, { memo, RefObject, useMemo, useRef } from 'react'; import React, { memo, useMemo } from 'react';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
import supersub from 'remark-supersub'; import supersub from 'remark-supersub';
import rehypeKatex from 'rehype-katex'; import rehypeKatex from 'rehype-katex';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { visit } from 'unist-util-visit';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight'; import rehypeHighlight from 'rehype-highlight';
import remarkDirective from 'remark-directive'; import remarkDirective from 'remark-directive';
@ -14,7 +13,6 @@ import { CodeBlockArtifact, CodeMarkdown } from '~/components/Artifacts/Code';
import { artifact, artifactPlugin } from '~/components/Artifacts/Artifact'; import { artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
import CodeBlock from '~/components/Messages/Content/CodeBlock'; import CodeBlock from '~/components/Messages/Content/CodeBlock';
import { useFileDownload } from '~/data-provider'; import { useFileDownload } from '~/data-provider';
import { filenameMap } from '~/utils/artifacts';
import useLocalize from '~/hooks/useLocalize'; import useLocalize from '~/hooks/useLocalize';
import { useToastContext } from '~/Providers'; import { useToastContext } from '~/Providers';
import store from '~/store'; import store from '~/store';
@ -23,14 +21,9 @@ type TCodeProps = {
inline: boolean; inline: boolean;
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
isLatestMessage: boolean;
showCursor?: boolean;
artifactId: string;
codeBlocksRef: RefObject<number | null>;
}; };
export const code: React.ElementType = memo(({ inline, className, children, ...props }: TCodeProps) => { export const code: React.ElementType = memo(({ inline, className, children }: TCodeProps) => {
const codeArtifacts = useRecoilValue(store.codeArtifacts);
const match = /language-(\w+)/.exec(className ?? ''); const match = /language-(\w+)/.exec(className ?? '');
const lang = match && match[1]; const lang = match && match[1];
@ -40,104 +33,83 @@ export const code: React.ElementType = memo(({ inline, className, children, ...p
{children} {children}
</code> </code>
); );
} else {
return <CodeBlock lang={lang ?? 'text'} codeChildren={children} />;
} }
const codeString = Array.isArray(children) ? children.join('') : children;
console.log('code lang, children, props', lang, children, props);
const isNonArtifact = filenameMap[lang ?? ''] === undefined;
if (codeArtifacts && typeof codeString === 'string' && isNonArtifact) {
return <CodeMarkdown content={`\`\`\`${lang}\n${codeString}\n\`\`\``} {...props}/>;
} else if (codeArtifacts && typeof codeString === 'string') {
return <CodeBlockArtifact lang={lang ?? 'text'} codeString={codeString} {...props}/>;
}
return <CodeBlock lang={lang ?? 'text'} codeChildren={children} />;
}); });
export const a: React.ElementType = memo(({ href, children }: { href: string; children: React.ReactNode }) => { export const a: React.ElementType = memo(
const user = useRecoilValue(store.user); ({ href, children }: { href: string; children: React.ReactNode }) => {
const { showToast } = useToastContext(); const user = useRecoilValue(store.user);
const localize = useLocalize(); const { showToast } = useToastContext();
const localize = useLocalize();
const { file_id, filename, filepath } = useMemo(() => { const { file_id, filename, filepath } = useMemo(() => {
const pattern = new RegExp(`(?:files|outputs)/${user?.id}/([^\\s]+)`); const pattern = new RegExp(`(?:files|outputs)/${user?.id}/([^\\s]+)`);
const match = href.match(pattern); const match = href.match(pattern);
if (match && match[0]) { if (match && match[0]) {
const path = match[0]; const path = match[0];
const parts = path.split('/'); const parts = path.split('/');
const name = parts.pop(); const name = parts.pop();
const file_id = parts.pop(); const file_id = parts.pop();
return { file_id, filename: name, filepath: path }; return { file_id, filename: name, filepath: path };
}
return { file_id: '', filename: '', filepath: '' };
}, [user?.id, href]);
const { refetch: downloadFile } = useFileDownload(user?.id ?? '', file_id);
const props: { target?: string; onClick?: React.MouseEventHandler } = { target: '_new' };
if (!file_id || !filename) {
return (
<a href={href} {...props}>
{children}
</a>
);
} }
return { file_id: '', filename: '', filepath: '' };
}, [user?.id, href]);
const { refetch: downloadFile } = useFileDownload(user?.id ?? '', file_id); const handleDownload = async (event: React.MouseEvent<HTMLAnchorElement>) => {
const props: { target?: string; onClick?: React.MouseEventHandler } = { target: '_new' }; event.preventDefault();
try {
const stream = await downloadFile();
if (stream.data == null || stream.data === '') {
console.error('Error downloading file: No data found');
showToast({
status: 'error',
message: localize('com_ui_download_error'),
});
return;
}
const link = document.createElement('a');
link.href = stream.data;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(stream.data);
} catch (error) {
console.error('Error downloading file:', error);
}
};
props.onClick = handleDownload;
props.target = '_blank';
if (!file_id || !filename) {
return ( return (
<a href={href} {...props}> <a
href={filepath.startsWith('files/') ? `/api/${filepath}` : `/api/files/${filepath}`}
{...props}
>
{children} {children}
</a> </a>
); );
} },
);
const handleDownload = async (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
try {
const stream = await downloadFile();
if (!stream.data) {
console.error('Error downloading file: No data found');
showToast({
status: 'error',
message: localize('com_ui_download_error'),
});
return;
}
const link = document.createElement('a');
link.href = stream.data;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(stream.data);
} catch (error) {
console.error('Error downloading file:', error);
}
};
props.onClick = handleDownload;
props.target = '_blank';
return (
<a
href={filepath.startsWith('files/') ? `/api/${filepath}` : `/api/files/${filepath}`}
{...props}
>
{children}
</a>
);
});
export const p: React.ElementType = memo(({ children }: { children: React.ReactNode }) => { export const p: React.ElementType = memo(({ children }: { children: React.ReactNode }) => {
return <p className="mb-2 whitespace-pre-wrap">{children}</p>; return <p className="mb-2 whitespace-pre-wrap">{children}</p>;
}); });
export const div: React.ElementType = memo(({ node, ...props }) => {
if (props.className === 'artifact') {
return (
<div className="artifact">
<h3>{props['data-identifier']}</h3>
<p>Type: {props['data-type']}</p>
{props.children}
</div>
);
}
return <div {...props} />;
});
const cursor = ' '; const cursor = ' ';
type TContentProps = { type TContentProps = {
@ -150,7 +122,6 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
const artifactIdRef = useRef<string | null>(null); const artifactIdRef = useRef<string | null>(null);
const codeBlocksRef = useRef<number | null>(null); const codeBlocksRef = useRef<number | null>(null);
const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing); const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing);
const codeArtifacts = useRecoilValue<boolean>(store.codeArtifacts);
const isInitializing = content === ''; const isInitializing = content === '';
@ -160,25 +131,7 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
currentContent = LaTeXParsing ? preprocessLaTeX(currentContent) : currentContent; currentContent = LaTeXParsing ? preprocessLaTeX(currentContent) : currentContent;
} }
if (artifactIdRef.current === null) { const rehypePlugins = [
artifactIdRef.current = new Date().toISOString();
}
const codePlugin: Pluggable = () => {
return (tree) => {
visit(tree, { tagName: 'code' }, (node) => {
node.properties = {
...node.properties,
isLatestMessage,
showCursor,
artifactId: artifactIdRef.current,
codeBlocksRef: codeBlocksRef.current,
};
});
};
};
const rehypePlugins: PluggableList = codeArtifacts ? [[rehypeKatex, { output: 'mathml' }], [codePlugin], [rehypeRaw]] : [
[rehypeKatex, { output: 'mathml' }], [rehypeKatex, { output: 'mathml' }],
[ [
rehypeHighlight, rehypeHighlight,
@ -203,10 +156,14 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
return ( return (
<ReactMarkdown <ReactMarkdown
remarkPlugins={[ remarkPlugins={[
/* @ts-ignore */
remarkDirective, remarkDirective,
supersub, remarkGfm, [remarkMath, { singleDollarTextMath: true }], supersub,
remarkGfm,
[remarkMath, { singleDollarTextMath: true }],
artifactPlugin, artifactPlugin,
]} ]}
/* @ts-ignore */
rehypePlugins={rehypePlugins} rehypePlugins={rehypePlugins}
// linkTarget="_new" // linkTarget="_new"
components={ components={
@ -215,6 +172,8 @@ const Markdown = memo(({ content = '', showCursor, isLatestMessage }: TContentPr
a, a,
p, p,
artifact, artifact,
} as {
[nodeType: string]: React.ElementType;
} }
} }
> >