mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-22 11:20:15 +01:00
markdown library change
This commit is contained in:
parent
d56aa2edef
commit
0b47218cd5
13 changed files with 4838 additions and 63 deletions
57
client/src/components/Messages/CodeBlock.jsx
Normal file
57
client/src/components/Messages/CodeBlock.jsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { useRef, useState } from 'react';
|
||||
import Clipboard from '../svg/Clipboard';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
const CodeBlock = ({ lang, codeChildren }) => {
|
||||
const codeRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div className="rounded-md bg-black">
|
||||
<CodeBar
|
||||
lang={lang}
|
||||
codeRef={codeRef}
|
||||
/>
|
||||
<div className="overflow-y-auto p-4">
|
||||
<code
|
||||
ref={codeRef}
|
||||
className={`hljs !whitespace-pre language-${lang}`}
|
||||
>
|
||||
{codeChildren}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CodeBar = React.memo(({ lang, codeRef }) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
return (
|
||||
<div className="relative flex items-center rounded-tl-md rounded-tr-md bg-gray-800 px-4 py-2 font-sans text-xs text-gray-200">
|
||||
<span className="">{lang}</span>
|
||||
<button
|
||||
className="ml-auto flex gap-2"
|
||||
onClick={async () => {
|
||||
const codeString = codeRef.current?.textContent;
|
||||
if (codeString)
|
||||
navigator.clipboard.writeText(codeString).then(() => {
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 3000);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<CheckMark />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Clipboard />
|
||||
Copy code
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export default CodeBlock;
|
||||
71
client/src/components/Messages/Content.jsx
Normal file
71
client/src/components/Messages/Content.jsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import React from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import rehypeHighlight from 'rehype-highlight';
|
||||
import remarkMath from 'remark-math';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import CodeBlock from './CodeBlock';
|
||||
import { langSubset } from '~/utils/languages';
|
||||
|
||||
const Content = React.memo(({ content }) => {
|
||||
return (
|
||||
<>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, [remarkMath, { singleDollarTextMath: false }]]}
|
||||
rehypePlugins={[
|
||||
[rehypeKatex, { output: 'mathml' }],
|
||||
[
|
||||
rehypeHighlight,
|
||||
{
|
||||
detect: true,
|
||||
ignoreMissing: true,
|
||||
subset: langSubset
|
||||
}
|
||||
]
|
||||
]}
|
||||
linkTarget="_new"
|
||||
components={{
|
||||
code,
|
||||
p,
|
||||
text: blinker,
|
||||
// li,
|
||||
// ul,
|
||||
// ol
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const code = React.memo((props) => {
|
||||
const { inline, className, children } = props;
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const lang = match && match[1];
|
||||
|
||||
if (inline) {
|
||||
return <code className={className}>{children}</code>;
|
||||
} else {
|
||||
return (
|
||||
<CodeBlock
|
||||
lang={lang || 'text'}
|
||||
codeChildren={children}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const p = React.memo((props) => {
|
||||
return <p className="whitespace-pre-wrap ">{props?.children}</p>;
|
||||
});
|
||||
|
||||
const blinker = ({ node }) => {
|
||||
if (node.type === 'text' && node.value === '█') {
|
||||
return <span className="result-streaming">{node.value}</span>
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default Content;
|
||||
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
|||
import Clipboard from '../svg/Clipboard';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
export default function Embed({ children, language = '', code, matched }) {
|
||||
export default function Embed({ children, lang = '', code, matched }) {
|
||||
const [buttonText, setButtonText] = useState('Copy code');
|
||||
const isClicked = buttonText === 'Copy code';
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ export default function Embed({ children, language = '', code, matched }) {
|
|||
<pre>
|
||||
<div className="mb-4 rounded-md bg-black">
|
||||
<div className="relative flex items-center rounded-tl-md rounded-tr-md bg-gray-800 px-4 py-2 font-sans text-xs text-gray-200">
|
||||
<span className="">{language === 'javascript' && !matched ? '' : language}</span>
|
||||
<span className="">{lang === 'javascript' && !matched ? '' : lang}</span>
|
||||
<button
|
||||
className="ml-auto flex gap-2"
|
||||
onClick={clickHandler}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import hljs from 'highlight.js';
|
||||
import languages from '~/utils/languages';
|
||||
import { languages } from '~/utils/languages';
|
||||
|
||||
export default function Highlight({language, code}) {
|
||||
const [highlightedCode, setHighlightedCode] = useState(code);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import TextWrapper from './TextWrapper';
|
||||
import Content from './Content';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import HoverButtons from './HoverButtons';
|
||||
|
|
@ -131,7 +132,7 @@ export default function Message({
|
|||
<div
|
||||
{...props}
|
||||
onWheel={handleWheel}
|
||||
// onClick={clickSearchResult}
|
||||
onClick={clickSearchResult}
|
||||
>
|
||||
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
||||
|
|
@ -188,14 +189,15 @@ export default function Message({
|
|||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||
{!isCreatedByUser && !searchResult ? (
|
||||
{/* {!isCreatedByUser && !searchResult ? (
|
||||
<TextWrapper
|
||||
text={text}
|
||||
generateCursor={generateCursor}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
)}
|
||||
)} */}
|
||||
<Content content={text} generateCursor={generateCursor}/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export default function Messages({ messages, messageTree }) {
|
|||
<div className="flex w-full items-center justify-center gap-1 border-b border-black/10 bg-gray-50 p-3 text-sm text-gray-500 dark:border-gray-900/50 dark:bg-gray-700 dark:text-gray-300">
|
||||
Model: {modelName} {customModel ? `(${customModel})` : null}
|
||||
</div>
|
||||
{messageTree.length === 0 ? (
|
||||
{(messageTree.length === 0) ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue