mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
adds markdown support, fix code wrapping
This commit is contained in:
parent
c58a9bbe93
commit
fd01fd3334
9 changed files with 1545 additions and 115 deletions
|
|
@ -37,7 +37,8 @@ Currently, this project is only functional with the `text-davinci-003` model.
|
||||||
- [x] AI Model Selection
|
- [x] AI Model Selection
|
||||||
- [x] Bing AI integration
|
- [x] Bing AI integration
|
||||||
- [x] Remember last selected model
|
- [x] Remember last selected model
|
||||||
- [ ] Highlight.js for code blocks
|
- [x] Highlight.js for code blocks
|
||||||
|
- [ ] Markdown handling
|
||||||
- [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo)
|
- [ ] AI model change handling (whether to pseudo-persist convos or start new convos within existing convo)
|
||||||
- [ ] Server convo pagination (limit fetch and load more with 'show more' button)
|
- [ ] Server convo pagination (limit fetch and load more with 'show more' button)
|
||||||
- [ ] Prompt Templates
|
- [ ] Prompt Templates
|
||||||
|
|
|
||||||
1448
package-lock.json
generated
1448
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -40,9 +40,11 @@
|
||||||
"openai": "^3.1.0",
|
"openai": "^3.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-markdown": "^8.0.5",
|
||||||
"react-redux": "^8.0.5",
|
"react-redux": "^8.0.5",
|
||||||
"react-textarea-autosize": "^8.4.0",
|
"react-textarea-autosize": "^8.4.0",
|
||||||
"react-transition-group": "^4.4.5",
|
"react-transition-group": "^4.4.5",
|
||||||
|
"remark-supersub": "^1.0.0",
|
||||||
"swr": "^2.0.3",
|
"swr": "^2.0.3",
|
||||||
"tailwind-merge": "^1.9.1",
|
"tailwind-merge": "^1.9.1",
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
pre code.hljs {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding: 1em;
|
|
||||||
}
|
|
||||||
code.hljs {
|
|
||||||
padding: 3px 5px;
|
|
||||||
}
|
|
||||||
.hljs {
|
|
||||||
color: #abb2bf;
|
|
||||||
background: #282c34;
|
|
||||||
}
|
|
||||||
.hljs-comment,
|
|
||||||
.hljs-quote {
|
|
||||||
color: #5c6370;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.hljs-doctag,
|
|
||||||
.hljs-formula,
|
|
||||||
.hljs-keyword {
|
|
||||||
color: #c678dd;
|
|
||||||
}
|
|
||||||
.hljs-deletion,
|
|
||||||
.hljs-name,
|
|
||||||
.hljs-section,
|
|
||||||
.hljs-selector-tag,
|
|
||||||
.hljs-subst {
|
|
||||||
color: #e06c75;
|
|
||||||
}
|
|
||||||
.hljs-literal {
|
|
||||||
color: #56b6c2;
|
|
||||||
}
|
|
||||||
.hljs-addition,
|
|
||||||
.hljs-attribute,
|
|
||||||
.hljs-meta .hljs-string,
|
|
||||||
.hljs-regexp,
|
|
||||||
.hljs-string {
|
|
||||||
color: #98c379;
|
|
||||||
}
|
|
||||||
.hljs-attr,
|
|
||||||
.hljs-number,
|
|
||||||
.hljs-selector-attr,
|
|
||||||
.hljs-selector-class,
|
|
||||||
.hljs-selector-pseudo,
|
|
||||||
.hljs-template-variable,
|
|
||||||
.hljs-type,
|
|
||||||
.hljs-variable {
|
|
||||||
color: #d19a66;
|
|
||||||
}
|
|
||||||
.hljs-bullet,
|
|
||||||
.hljs-link,
|
|
||||||
.hljs-meta,
|
|
||||||
.hljs-selector-id,
|
|
||||||
.hljs-symbol,
|
|
||||||
.hljs-title {
|
|
||||||
color: #61aeee;
|
|
||||||
}
|
|
||||||
.hljs-built_in,
|
|
||||||
.hljs-class .hljs-title,
|
|
||||||
.hljs-title.class_ {
|
|
||||||
color: #e6c07b;
|
|
||||||
}
|
|
||||||
.hljs-emphasis {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
.hljs-strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.hljs-link {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// import '~/atom-one-dark.css';
|
|
||||||
|
|
||||||
export default function Embed({ children, language = ''}) {
|
export default function Embed({ children, language = ''}) {
|
||||||
return (
|
return (
|
||||||
<pre>
|
<pre>
|
||||||
<div className="mb-2 rounded-md bg-black">
|
<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 rounded-tl-md rounded-tr-md">
|
<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>
|
<span className="">{ language }</span>
|
||||||
<button className="ml-auto flex gap-2">
|
<button className="ml-auto flex gap-2">
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,60 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import supersub from 'remark-supersub'
|
||||||
import Embed from './Embed';
|
import Embed from './Embed';
|
||||||
import Highlight from './Highlight';
|
import Highlight from './Highlight';
|
||||||
import regexSplit from '~/utils/regexSplit';
|
import regexSplit from '~/utils/regexSplit';
|
||||||
// const codeRegex = /(```[^`]+?```)/g;
|
import { languages, wrapperRegex } from '~/utils';
|
||||||
const codeRegex = /(```[\s\S]*?```)/g;
|
const { codeRegex, inLineRegex, matchRegex, languageMatch, newLineMatch } = wrapperRegex;
|
||||||
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) =>
|
// original function
|
||||||
parts.map((part, i) => {
|
// 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;
|
||||||
|
// return part.includes('`') ? part : <ReactMarkdown key={i}>{part}</ReactMarkdown>;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const inLineWrap = (parts) => {
|
||||||
|
let previousElement = null;
|
||||||
|
return parts.map((part, i) => {
|
||||||
if (part.match(matchRegex)) {
|
if (part.match(matchRegex)) {
|
||||||
return <code key={i}>{part.slice(1, -1)}</code>;
|
const codeElement = <code key={i}>{part.slice(1, -1)}</code>;
|
||||||
|
if (previousElement && typeof previousElement !== 'string') {
|
||||||
|
// Append code element as a child to previous non-code element
|
||||||
|
previousElement = (
|
||||||
|
<ReactMarkdown remarkPlugins={[supersub]} key={i}>
|
||||||
|
{previousElement}
|
||||||
|
{codeElement}
|
||||||
|
</ReactMarkdown>
|
||||||
|
);
|
||||||
|
return previousElement;
|
||||||
} else {
|
} else {
|
||||||
// return <p key={i}>{part}</p>;
|
return codeElement;
|
||||||
return part;
|
}
|
||||||
|
} else {
|
||||||
|
previousElement = part;
|
||||||
|
return previousElement;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function CodeWrapper({ text }) {
|
export default function TextWrapper({ text }) {
|
||||||
if (text.includes('```')) {
|
// append triple backticks to the end of the text if only singular found and language found
|
||||||
|
if (text.match(/```/g)?.length === 1 && text.match(languageMatch)) {
|
||||||
|
text += '```';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.match(codeRegex)) {
|
||||||
|
// if (text.includes('```')) {
|
||||||
// const parts = text.split(codeRegex);
|
// const parts = text.split(codeRegex);
|
||||||
|
|
||||||
const parts = regexSplit(text);
|
const parts = regexSplit(text);
|
||||||
console.log(parts);
|
// console.log(parts);
|
||||||
const codeParts = parts.map((part, i) => {
|
const codeParts = parts.map((part, i) => {
|
||||||
if (part.match(codeRegex)) {
|
if (part.match(codeRegex)) {
|
||||||
let language = 'javascript';
|
let language = 'javascript';
|
||||||
|
|
@ -73,7 +83,8 @@ export default function CodeWrapper({ text }) {
|
||||||
const innerParts = part.split(inLineRegex);
|
const innerParts = part.split(inLineRegex);
|
||||||
return inLineWrap(innerParts);
|
return inLineWrap(innerParts);
|
||||||
} else {
|
} else {
|
||||||
return part;
|
// return part;
|
||||||
|
return <ReactMarkdown remarkPlugins={[supersub]} key={i}>{part}</ReactMarkdown>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
outline: 1px solid limegreen !important;
|
outline: 1px solid limegreen !important;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.scroll-down-enter {
|
.scroll-down-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +246,7 @@
|
||||||
.prose :where(code):not(:where([class~=not-prose] *)) {
|
.prose :where(code):not(:where([class~=not-prose] *)) {
|
||||||
color:var(--tw-prose-code);
|
color:var(--tw-prose-code);
|
||||||
font-size:.875em;
|
font-size:.875em;
|
||||||
font-weight:600
|
font-weight:600;
|
||||||
}
|
}
|
||||||
.prose :where(code):not(:where([class~=not-prose] *)):before {
|
.prose :where(code):not(:where([class~=not-prose] *)):before {
|
||||||
content:"`"
|
content:"`"
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,41 @@ import { twMerge } from 'tailwind-merge';
|
||||||
export function cn(...inputs) {
|
export function cn(...inputs) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const languages = [
|
||||||
|
'java',
|
||||||
|
'c',
|
||||||
|
'markdown',
|
||||||
|
'css',
|
||||||
|
'html',
|
||||||
|
'xml',
|
||||||
|
'bash',
|
||||||
|
'json',
|
||||||
|
'yaml',
|
||||||
|
'jsx',
|
||||||
|
'python',
|
||||||
|
'c++',
|
||||||
|
'javascript',
|
||||||
|
'csharp',
|
||||||
|
'php',
|
||||||
|
'typescript',
|
||||||
|
'swift',
|
||||||
|
'objectivec',
|
||||||
|
'sql',
|
||||||
|
'r',
|
||||||
|
'kotlin',
|
||||||
|
'ruby',
|
||||||
|
'go',
|
||||||
|
'x86asm',
|
||||||
|
'matlab',
|
||||||
|
'perl',
|
||||||
|
'pascal'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const wrapperRegex = {
|
||||||
|
codeRegex: /(```[\s\S]*?```)/g,
|
||||||
|
inLineRegex: /(`[^`]+?`)/g,
|
||||||
|
matchRegex: /(`[^`]+?`)/g,
|
||||||
|
languageMatch: /^```(\w+)/,
|
||||||
|
newLineMatch: /^```(\n+)/
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// const string = 'Some text before\n```python\nThis is some `Python` code with ```backticks``` inside the enclosure\n```\nSome text after\n```javascript\nThis is some JavaScript code\n```\n';
|
|
||||||
const regex = /```([^`\n]*?)\n([\s\S]*?)\n```/g;
|
const regex = /```([^`\n]*?)\n([\s\S]*?)\n```/g;
|
||||||
|
|
||||||
export default function regexSplit(string) {
|
export default function regexSplit(string) {
|
||||||
|
|
@ -8,8 +7,7 @@ export default function regexSplit(string) {
|
||||||
for (let i = 0; i < matches.length; i++) {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
const [fullMatch, language, code] = matches[i];
|
const [fullMatch, language, code] = matches[i];
|
||||||
// const formattedCode = code.replace(/`+/g, '\\`');
|
// const formattedCode = code.replace(/`+/g, '\\`');
|
||||||
const formattedCode = code;
|
output.push(`\`\`\`${language}\n${code}\n\`\`\``);
|
||||||
output.push(`\`\`\`${language}\n${formattedCode}\n\`\`\``);
|
|
||||||
if (i < matches.length - 1) {
|
if (i < matches.length - 1) {
|
||||||
const nextText = string.slice(matches[i].index + fullMatch.length, matches[i + 1].index);
|
const nextText = string.slice(matches[i].index + fullMatch.length, matches[i + 1].index);
|
||||||
output.push(nextText.trim());
|
output.push(nextText.trim());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue