mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-14 19:56:34 +01:00
chore: revert markdown to original component + new artifacts rendering
This commit is contained in:
parent
801b0de49b
commit
85df66265d
1 changed files with 71 additions and 112 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue