add codeblock support for prompt replies and historical messages (#55)

* add codeblock support for prompt replies and historical messages
add markdown-it

* Fix spacing for HTML rendering
This commit is contained in:
Timothy Carambat 2023-06-14 13:35:55 -07:00 committed by GitHub
parent bd32f97a21
commit 2403806949
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 8098 additions and 362 deletions

7662
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
"@metamask/jazzicon": "^2.0.0", "@metamask/jazzicon": "^2.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"he": "^1.2.0", "he": "^1.2.0",
"markdown-it": "^13.0.1",
"pluralize": "^8.0.0", "pluralize": "^8.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-device-detect": "^2.2.2", "react-device-detect": "^2.2.2",

View File

@ -119,9 +119,7 @@ export default function ManageWorkspace({ hideModal = noop, workspace }) {
setSelectFiles([...new Set(updatedDocs)]); setSelectFiles([...new Set(updatedDocs)]);
} else { } else {
var newDocs = []; var newDocs = [];
var parentDirs = directories.items.find( var parentDirs = directories.items.find((item) => item.name === parent);
(item) => item.name === parent
)
if (isFolder && parentDirs) { if (isFolder && parentDirs) {
const folderItems = parentDirs.items; const folderItems = parentDirs.items;
newDocs = folderItems.map((item) => parent + "/" + item.name); newDocs = folderItems.map((item) => parent + "/" + item.name);

View File

@ -3,6 +3,7 @@ import { AlertTriangle } from "react-feather";
import Jazzicon from "../../../../UserIcon"; import Jazzicon from "../../../../UserIcon";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { decode as HTMLDecode } from "he"; import { decode as HTMLDecode } from "he";
import renderMarkdown from "../../../../../utils/chat/markdown";
function HistoricalMessage({ function HistoricalMessage({
message, message,
@ -52,9 +53,10 @@ function HistoricalMessage({
<div ref={replyRef} className="flex justify-start items-end mb-4"> <div ref={replyRef} className="flex justify-start items-end mb-4">
<Jazzicon size={30} user={{ uid: workspace.slug }} /> <Jazzicon size={30} user={{ uid: workspace.slug }} />
<div className="ml-2 py-3 px-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm"> <div className="ml-2 py-3 px-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm">
<span className="whitespace-pre-line text-slate-800 dark:text-slate-200 font-semibold"> <span
{message} className="whitespace-pre-line text-slate-800 dark:text-slate-200 font-semibold flex flex-col gap-y-1"
</span> dangerouslySetInnerHTML={{ __html: renderMarkdown(message) }}
/>
<Citations sources={sources} /> <Citations sources={sources} />
</div> </div>
</div> </div>

View File

@ -1,8 +1,9 @@
import { memo, useEffect, useRef, useState } from "react"; import { memo, useEffect, useRef, useState } from "react";
import { AlertTriangle } from "react-feather"; import { AlertTriangle } from "react-feather";
import Jazzicon from "../../../../UserIcon"; import Jazzicon from "../../../../UserIcon";
import { decode as HTMLDecode } from "he";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { decode as HTMLDecode } from "he";
import renderMarkdown from "../../../../../utils/chat/markdown";
function PromptReply({ function PromptReply({
uuid, uuid,
@ -54,14 +55,14 @@ function PromptReply({
<div <div
key={uuid} key={uuid}
ref={replyRef} ref={replyRef}
className="chat__message mb-4 flex justify-start items-end" className="mb-4 flex justify-start items-end"
> >
<Jazzicon size={30} user={{ uid: workspace.slug }} /> <Jazzicon size={30} user={{ uid: workspace.slug }} />
<div className="ml-2 py-3 px-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm"> <div className="ml-2 py-3 px-4 max-w-[75%] bg-orange-100 dark:bg-stone-700 rounded-t-2xl rounded-br-2xl rounded-bl-sm">
<p className="text-[15px] whitespace-pre-line break-words text-slate-800 dark:text-slate-200 font-semibold"> <span
{reply} className="whitespace-pre-line text-slate-800 dark:text-slate-200 font-semibold flex flex-col gap-y-1"
{!closed && <i className="not-italic blink">|</i>} dangerouslySetInnerHTML={{ __html: renderMarkdown(reply) }}
</p> />
<Citations sources={sources} /> <Citations sources={sources} />
</div> </div>
</div> </div>

View File

@ -0,0 +1,36 @@
import { encode as HTMLEncode } from "he";
import markdownIt from "markdown-it";
const markdown = markdownIt({
html: true,
typographer: true,
highlight: function (str) {
return `<div class="whitespace-pre-line w-fit rounded-lg bg-black-900 px-4 pt-10 pb-4 relative font-mono font-normal text-sm text-slate-200"><div class="w-full flex items-center absolute top-0 left-0 text-slate-200 bg-stone-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md"><button onclick='window.copySnippet();' class="flex ml-auto gap-2"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="h-4 w-4" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><pre class='markdown'>${HTMLEncode(
str
)}<pre></div>`;
},
});
window.copySnippet = function () {
if (!event?.target) return false;
const target = event?.target;
const markdown =
target.parentElement?.parentElement?.querySelector(".markdown")?.innerText;
if (!markdown) return false;
window.navigator.clipboard.writeText(markdown);
target.classList.add("text-green-500");
const originalText = target.innerHTML;
target.innerText = "Copied!";
target.setAttribute("disabled", true);
setTimeout(() => {
target.classList.remove("text-green-500");
target.innerHTML = originalText;
target.removeAttribute("disabled");
}, 5000);
};
export default function renderMarkdown(text) {
return markdown.render(text);
}

File diff suppressed because it is too large Load Diff

View File

@ -61,11 +61,13 @@ apiRouter.post("/v/:command", async (request, response) => {
app.use("/api", apiRouter); app.use("/api", apiRouter);
if (process.env.NODE_ENV !== "development") { if (process.env.NODE_ENV !== "development") {
app.use(express.static(path.resolve(__dirname, 'public'), {extensions: ["js"]})); app.use(
express.static(path.resolve(__dirname, "public"), { extensions: ["js"] })
);
app.use("/", function (_, response) { app.use("/", function (_, response) {
response.sendFile(path.join(__dirname, "public", "index.html")); response.sendFile(path.join(__dirname, "public", "index.html"));
}) });
} }
app.all("*", function (_, response) { app.all("*", function (_, response) {

View File

@ -26,7 +26,8 @@ function curateLanceSources(sources = []) {
} }
const LanceDb = { const LanceDb = {
uri: `${!!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "./storage/" uri: `${
!!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "./storage/"
}lancedb`, }lancedb`,
name: "LanceDb", name: "LanceDb",
connect: async function () { connect: async function () {