mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-03 17:18:51 +01:00
finish highlight.js styling (for chatgpt)
This commit is contained in:
parent
e95e22de15
commit
c58a9bbe93
12 changed files with 851 additions and 150 deletions
|
|
@ -1,55 +0,0 @@
|
|||
import React from 'react';
|
||||
import Embed from './Embed';
|
||||
import hljs from 'highlight.js';
|
||||
import Highlight from 'react-highlight';
|
||||
|
||||
export default function CodeWrapper({ text }) {
|
||||
if (text.includes('```')) {
|
||||
const codeRegex = /(```[^`]+?```)/g;
|
||||
const inLineRegex = /(`[^`]+?`)/g;
|
||||
const parts = text.split(codeRegex);
|
||||
// console.log(parts);
|
||||
const codeParts = parts.map((part, i) => {
|
||||
if (part.match(codeRegex)) {
|
||||
return (
|
||||
<Embed
|
||||
key={i}
|
||||
language="javascript"
|
||||
>
|
||||
{hljs.highlightAuto(part.slice(1, -1)).value}
|
||||
{/* <Highlight className="!whitespace-pre">{part.slice(1, -1)}</Highlight> */}
|
||||
|
||||
</Embed>
|
||||
);
|
||||
} else if (part.match(inLineRegex)) {
|
||||
const innerParts = part.split(inLineRegex);
|
||||
return innerParts.map((part, i) => {
|
||||
if (part.match(inLineRegex)) {
|
||||
return <code key={i}>{part.slice(1, -1)}</code>;
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
});
|
||||
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
} else {
|
||||
const matchRegex = /(`[^`]+?`)/g;
|
||||
const parts = text.split(matchRegex);
|
||||
// console.log('parts', parts);
|
||||
|
||||
// map over the parts and wrap any text between tildes with <code> tags
|
||||
const codeParts = parts.map((part, i) => {
|
||||
if (part.match(matchRegex)) {
|
||||
return <code key={i}>{part.slice(1, -1)}</code>;
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
});
|
||||
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import React from 'react';
|
|||
export default function Embed({ children, language = ''}) {
|
||||
return (
|
||||
<pre>
|
||||
<div className="mb-4 rounded-md bg-black">
|
||||
<div className="relative flex items-center bg-gray-800 px-4 py-2 font-sans text-xs text-gray-200">
|
||||
<div className="mb-2 rounded-md bg-black">
|
||||
<div className="relative flex items-center bg-gray-800 px-4 py-2 font-sans text-xs text-gray-200 rounded-tl-md rounded-tr-md">
|
||||
<span className="">{ language }</span>
|
||||
<button className="ml-auto flex gap-2">
|
||||
<svg
|
||||
|
|
@ -35,26 +35,6 @@ export default function Embed({ children, language = ''}) {
|
|||
</div>
|
||||
<div className="overflow-y-auto p-4">
|
||||
{ children }
|
||||
{/* <span className="hljs-keyword">export</span> <span className="hljs-keyword">default</span> <span className="hljs-keyword">function</span> <span className="hljs-title function_">CodeWrapper</span>(<span className="hljs-params">{ text }</span>) {
|
||||
<span className="hljs-keyword">const</span> matchRegex = <span className="hljs-regexp">/(`[^`]+?`)/g</span>; <span className="hljs-comment">// regex to match backticks and text between them</span>
|
||||
<span className="hljs-keyword">const</span> parts = text.<span className="hljs-title function_">split</span>(matchRegex);
|
||||
<span className="hljs-variable language_">console</span>.<span className="hljs-title function_">log</span>(<span className="hljs-string">'parts'</span>, parts);
|
||||
|
||||
<span className="hljs-comment">// map over the parts and wrap any backticked text with <code> tags</span>
|
||||
<span className="hljs-keyword">const</span> codeParts = parts.<span className="hljs-title function_">map</span>(<span className="hljs-function">(<span className="hljs-params">part, index</span>) =></span> {
|
||||
<span className="hljs-keyword">if</span> (part.<span className="hljs-title function_">match</span>(matchRegex)) {
|
||||
<>
|
||||
<span className="hljs-keyword">return</span> <span className="xml"><span className="hljs-tag"><<span className="hljs-name">code</span> <span className="hljs-attr">key</span>=<span className="hljs-string">{index}</span>></span>{part}<span className="hljs-tag"></<span className="hljs-name">code</span>></span></span>;
|
||||
</>
|
||||
} <span className="hljs-keyword">else</span> {
|
||||
<>
|
||||
<span className="hljs-keyword">return</span> part.<span className="hljs-title function_">trim</span>(); <span className="hljs-comment">// remove leading/trailing whitespace from non-backticked text</span>
|
||||
</>
|
||||
}
|
||||
});
|
||||
|
||||
<span className="hljs-keyword">return</span> <span className="xml"><span className="hljs-tag"><></span>{codeParts}<span className="hljs-tag"></></span></span>; <span className="hljs-comment">// return the wrapped text</span>
|
||||
} */}
|
||||
</div>
|
||||
</div>
|
||||
</pre>
|
||||
|
|
|
|||
16
src/components/Messages/Highlight.jsx
Normal file
16
src/components/Messages/Highlight.jsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
export default function Highlight({language, code}) {
|
||||
const [highlightedCode, setHighlightedCode] = useState(code);
|
||||
|
||||
useEffect(() => {
|
||||
setHighlightedCode(hljs.highlight(code, { language }).value);
|
||||
}, [code, language]);
|
||||
|
||||
return (
|
||||
<pre>
|
||||
<code className={`language-${language}`} dangerouslySetInnerHTML={{__html: highlightedCode}}/>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import CodeWrapper from './CodeWrapper';
|
||||
import TextWrapper from './TextWrapper';
|
||||
import { useSelector } from 'react-redux';
|
||||
import GPTIcon from '../svg/GPTIcon';
|
||||
import BingIcon from '../svg/BingIcon';
|
||||
|
|
@ -13,7 +13,8 @@ export default function Message({
|
|||
}) {
|
||||
const { isSubmitting } = useSelector((state) => state.submit);
|
||||
const [abortScroll, setAbort] = useState(false);
|
||||
const blinker = isSubmitting && last && sender.toLowerCase() !== 'user';
|
||||
const notUser = sender.toLowerCase() !== 'user';
|
||||
const blinker = isSubmitting && last && notUser;
|
||||
|
||||
useEffect(() => {
|
||||
if (blinker && !abortScroll) {
|
||||
|
|
@ -34,15 +35,12 @@ export default function Message({
|
|||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group dark:bg-gray-800'
|
||||
};
|
||||
|
||||
if (sender.toLowerCase() !== 'user') {
|
||||
props.className =
|
||||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
|
||||
}
|
||||
|
||||
let icon = `${sender}:`;
|
||||
const isGPT = sender === 'chatgpt' || sender === 'davinci' || sender === 'GPT';
|
||||
|
||||
if (sender.toLowerCase() !== 'user') {
|
||||
if (notUser) {
|
||||
props.className =
|
||||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
|
||||
icon = (
|
||||
<div
|
||||
style={isGPT ? { backgroundColor: 'rgb(16, 163, 127)' } : {}}
|
||||
|
|
@ -53,7 +51,7 @@ export default function Message({
|
|||
);
|
||||
}
|
||||
|
||||
const wrapText = (text) => <CodeWrapper text={text} />;
|
||||
const wrapText = (text) => <TextWrapper text={text} />;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -67,13 +65,13 @@ export default function Message({
|
|||
{error ? (
|
||||
<div className="flex flex min-h-[20px] flex-row flex-col items-start gap-4 gap-2 whitespace-pre-wrap text-red-500">
|
||||
<div className="rounded-md border border-red-500 bg-red-500/10 py-2 px-3 text-sm text-gray-600 dark:text-gray-100">
|
||||
{wrapText(text)}
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-[20px] flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||
{wrapText(text)}
|
||||
{notUser ? wrapText(text) : text}
|
||||
{blinker && <span className="result-streaming">█</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
87
src/components/Messages/TextWrapper.jsx
Normal file
87
src/components/Messages/TextWrapper.jsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React from 'react';
|
||||
import Embed from './Embed';
|
||||
import Highlight from './Highlight';
|
||||
import regexSplit from '~/utils/regexSplit';
|
||||
// const codeRegex = /(```[^`]+?```)/g;
|
||||
const codeRegex = /(```[\s\S]*?```)/g;
|
||||
const inLineRegex = /(`[^`]+?`)/g;
|
||||
const matchRegex = /(`[^`]+?`)/g;
|
||||
const languageMatch = /^```(\w+)/;
|
||||
const newLineMatch = /^```(\n+)/;
|
||||
const languages = [
|
||||
'java',
|
||||
'c',
|
||||
'python',
|
||||
'c++',
|
||||
'javascript',
|
||||
'csharp',
|
||||
'php',
|
||||
'typescript',
|
||||
'swift',
|
||||
'objectivec',
|
||||
'sql',
|
||||
'r',
|
||||
'kotlin',
|
||||
'ruby',
|
||||
'go',
|
||||
'x86asm',
|
||||
'matlab',
|
||||
'perl',
|
||||
'pascal'
|
||||
];
|
||||
|
||||
const inLineWrap = (parts) =>
|
||||
parts.map((part, i) => {
|
||||
if (part.match(matchRegex)) {
|
||||
return <code key={i}>{part.slice(1, -1)}</code>;
|
||||
} else {
|
||||
// return <p key={i}>{part}</p>;
|
||||
return part;
|
||||
}
|
||||
});
|
||||
|
||||
export default function CodeWrapper({ text }) {
|
||||
if (text.includes('```')) {
|
||||
// const parts = text.split(codeRegex);
|
||||
const parts = regexSplit(text);
|
||||
console.log(parts);
|
||||
const codeParts = parts.map((part, i) => {
|
||||
if (part.match(codeRegex)) {
|
||||
let language = 'javascript';
|
||||
|
||||
if (part.match(languageMatch)) {
|
||||
language = part.match(languageMatch)[1].toLowerCase();
|
||||
const validLanguage = languages.some((lang) => language === lang);
|
||||
part = validLanguage ? part.replace(languageMatch, '```') : part;
|
||||
language = validLanguage ? language : 'javascript';
|
||||
}
|
||||
|
||||
part = part.replace(newLineMatch, '```');
|
||||
|
||||
return (
|
||||
<Embed
|
||||
key={i}
|
||||
language={language}
|
||||
>
|
||||
<Highlight
|
||||
code={part.slice(3, -3)}
|
||||
language={language}
|
||||
/>
|
||||
</Embed>
|
||||
);
|
||||
} else if (part.match(inLineRegex)) {
|
||||
const innerParts = part.split(inLineRegex);
|
||||
return inLineWrap(innerParts);
|
||||
} else {
|
||||
return part;
|
||||
}
|
||||
});
|
||||
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
} else {
|
||||
// map over the parts and wrap any text between tildes with <code> tags
|
||||
const parts = text.split(matchRegex);
|
||||
const codeParts = inLineWrap(parts);
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue