adds markdown support, fix code wrapping

This commit is contained in:
Daniel Avila 2023-02-24 11:24:09 -05:00
parent c58a9bbe93
commit fd01fd3334
9 changed files with 1545 additions and 115 deletions

View file

@ -37,7 +37,8 @@ Currently, this project is only functional with the `text-davinci-003` model.
- [x] AI Model Selection
- [x] Bing AI integration
- [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)
- [ ] Server convo pagination (limit fetch and load more with 'show more' button)
- [ ] Prompt Templates

1448
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -40,9 +40,11 @@
"openai": "^3.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-markdown": "^8.0.5",
"react-redux": "^8.0.5",
"react-textarea-autosize": "^8.4.0",
"react-transition-group": "^4.4.5",
"remark-supersub": "^1.0.0",
"swr": "^2.0.3",
"tailwind-merge": "^1.9.1",
"tailwindcss-animate": "^1.0.5",

View file

@ -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;
}

View file

@ -1,10 +1,9 @@
import React from 'react';
// import '~/atom-one-dark.css';
export default function Embed({ children, language = ''}) {
return (
<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">
<span className="">{ language }</span>
<button className="ml-auto flex gap-2">

View file

@ -1,50 +1,60 @@
import React from 'react';
import ReactMarkdown from 'react-markdown';
import supersub from 'remark-supersub'
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'
];
import { languages, wrapperRegex } from '~/utils';
const { codeRegex, inLineRegex, matchRegex, languageMatch, newLineMatch } = wrapperRegex;
const inLineWrap = (parts) =>
parts.map((part, i) => {
// original function
// 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)) {
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 {
return codeElement;
}
} else {
// return <p key={i}>{part}</p>;
return part;
previousElement = part;
return previousElement;
}
});
};
export default function CodeWrapper({ text }) {
if (text.includes('```')) {
export default function TextWrapper({ text }) {
// 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 = regexSplit(text);
console.log(parts);
// console.log(parts);
const codeParts = parts.map((part, i) => {
if (part.match(codeRegex)) {
let language = 'javascript';
@ -73,7 +83,8 @@ export default function CodeWrapper({ text }) {
const innerParts = part.split(inLineRegex);
return inLineWrap(innerParts);
} else {
return part;
// return part;
return <ReactMarkdown remarkPlugins={[supersub]} key={i}>{part}</ReactMarkdown>;
}
});

View file

@ -7,6 +7,10 @@
outline: 1px solid limegreen !important;
} */
blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {
margin: 0;
}
.scroll-down-enter {
opacity: 0;
}
@ -242,7 +246,7 @@
.prose :where(code):not(:where([class~=not-prose] *)) {
color:var(--tw-prose-code);
font-size:.875em;
font-weight:600
font-weight:600;
}
.prose :where(code):not(:where([class~=not-prose] *)):before {
content:"`"

View file

@ -3,4 +3,42 @@ import { twMerge } from 'tailwind-merge';
export function cn(...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+)/
};

View file

@ -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;
export default function regexSplit(string) {
@ -8,8 +7,7 @@ export default function regexSplit(string) {
for (let i = 0; i < matches.length; i++) {
const [fullMatch, language, code] = matches[i];
// const formattedCode = code.replace(/`+/g, '\\`');
const formattedCode = code;
output.push(`\`\`\`${language}\n${formattedCode}\n\`\`\``);
output.push(`\`\`\`${language}\n${code}\n\`\`\``);
if (i < matches.length - 1) {
const nextText = string.slice(matches[i].index + fullMatch.length, matches[i + 1].index);
output.push(nextText.trim());