Render code output files with code references in reference section

- Improve rendering code reference with better icons, smaller text and
  different line clamps for better visibility
- Show code output files as sub card of code card in reference section
- Allow downloading files generated by code instead of rendering it in
  chat message directly
- Show executed code before online references in reference panel
This commit is contained in:
Debanjum 2024-11-11 00:22:23 -08:00
parent 92c1efe6ee
commit 8e9f4262a9
3 changed files with 118 additions and 27 deletions

View file

@ -40,7 +40,6 @@ import {
Leaf, Leaf,
NewspaperClipping, NewspaperClipping,
OrangeSlice, OrangeSlice,
Rainbow,
SmileyMelting, SmileyMelting,
YinYang, YinYang,
SneakerMove, SneakerMove,
@ -247,6 +246,13 @@ function getIconFromFilename(
case "doc": case "doc":
case "docx": case "docx":
return <MicrosoftWordLogo className={className} />; return <MicrosoftWordLogo className={className} />;
case "csv":
case "json":
return <MathOperations className={className} />;
case "txt":
return <Notebook className={className} />;
case "py":
return <Code className={className} />;
case "jpg": case "jpg":
case "jpeg": case "jpeg":
case "png": case "png":

View file

@ -433,14 +433,6 @@ const ChatMessage = forwardRef<HTMLDivElement, ChatMessageProps>((props, ref) =>
if (!message.includes(`![${file.filename}](`)) { if (!message.includes(`![${file.filename}](`)) {
message += `\n\n![${file.filename}](data:image/png;base64,${file.b64_data})`; message += `\n\n![${file.filename}](data:image/png;base64,${file.b64_data})`;
} }
} else if (
file.filename.endsWith(".txt") ||
file.filename.endsWith(".org") ||
file.filename.endsWith(".md") ||
file.filename.endsWith(".csv") ||
file.filename.endsWith(".json")
) {
message += `\n\n## ${file.filename}\n\`\`\`\n${file.b64_data}\n\`\`\`\n`;
} }
}); });
}); });

View file

@ -2,7 +2,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { ArrowRight } from "@phosphor-icons/react"; import { ArrowCircleDown, ArrowRight } from "@phosphor-icons/react";
import markdownIt from "markdown-it"; import markdownIt from "markdown-it";
const md = new markdownIt({ const md = new markdownIt({
@ -11,7 +11,13 @@ const md = new markdownIt({
typographer: true, typographer: true,
}); });
import { Context, WebPage, OnlineContext, CodeContext } from "../chatMessage/chatMessage"; import {
Context,
WebPage,
OnlineContext,
CodeContext,
CodeContextFile,
} from "../chatMessage/chatMessage";
import { Card } from "@/components/ui/card"; import { Card } from "@/components/ui/card";
import { import {
@ -97,6 +103,7 @@ function NotesContextReferenceCard(props: NotesContextReferenceCardProps) {
interface CodeContextReferenceCardProps { interface CodeContextReferenceCardProps {
code: string; code: string;
output: string; output: string;
output_files: CodeContextFile[];
error: string; error: string;
showFullContent: boolean; showFullContent: boolean;
} }
@ -105,6 +112,38 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) {
const fileIcon = getIconFromFilename(".py", "w-6 h-6 text-muted-foreground inline-flex mr-2"); const fileIcon = getIconFromFilename(".py", "w-6 h-6 text-muted-foreground inline-flex mr-2");
const sanitizedCodeSnippet = DOMPurify.sanitize(props.code.replace(/\n/g, "<br/>")); const sanitizedCodeSnippet = DOMPurify.sanitize(props.code.replace(/\n/g, "<br/>"));
const [isHovering, setIsHovering] = useState(false); const [isHovering, setIsHovering] = useState(false);
const [isDownloadHover, setIsDownloadHover] = useState(false);
const handleDownload = (file: CodeContextFile) => {
// Determine MIME type
let mimeType = "text/plain";
let byteString = file.b64_data;
if (file.filename.match(/\.(png|jpg|jpeg|webp)$/)) {
mimeType = `image/${file.filename.split(".").pop()}`;
byteString = atob(file.b64_data);
} else if (file.filename.endsWith(".json")) {
mimeType = "application/json";
} else if (file.filename.endsWith(".csv")) {
mimeType = "text/csv";
}
const arrayBuffer = new ArrayBuffer(byteString.length);
const bytes = new Uint8Array(arrayBuffer);
for (let i = 0; i < byteString.length; i++) {
bytes[i] = byteString.charCodeAt(i);
}
const blob = new Blob([arrayBuffer], { type: mimeType });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = file.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
};
return ( return (
<> <>
@ -122,9 +161,66 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) {
Code Code
</h3> </h3>
<p <p
className={`${props.showFullContent ? "block" : "overflow-hidden line-clamp-2"}`} className={`text-xs ${props.showFullContent ? "block" : "overflow-hidden line-clamp-3"}`}
dangerouslySetInnerHTML={{ __html: sanitizedCodeSnippet }} dangerouslySetInnerHTML={{ __html: sanitizedCodeSnippet }}
></p> ></p>
{props.output_files && props.output_files.length > 0 && (
<div className={`mt-2 border-t pt-2`}>
{props.output_files
.slice(0, props.showFullContent ? undefined : 1)
.map((file, index) => {
const fileIcon = getIconFromFilename(
file.filename,
"w-4 h-4 text-muted-foreground inline-flex",
);
return (
<div key={`${file.filename}-${index}`}>
<h4 className="text-sm text-muted-foreground flex items-center">
{fileIcon}
<span className="flex items-center overflow-hidden mx-2">
{file.filename}
</span>
<button
onClick={(e) => {
e.preventDefault();
handleDownload(file);
}}
onMouseEnter={() =>
setIsDownloadHover(true)
}
onMouseLeave={() =>
setIsDownloadHover(false)
}
title={`Download file: ${file.filename}`}
>
<ArrowCircleDown
className="w-4 h-4"
weight={
isDownloadHover ? "fill" : "regular"
}
/>
</button>
</h4>
{file.filename.match(/\.(txt|org|md|csv|json)$/) ? (
<pre
className={`${props.showFullContent ? "block" : "line-clamp-2"} text-sm mt-1 p-1 bg-background rounded overflow-x-auto`}
>
{file.b64_data}
</pre>
) : file.filename.match(
/\.(png|jpg|jpeg|webp)$/,
) ? (
<img
src={`data:image/${file.filename.split(".").pop()};base64,${file.b64_data}`}
alt={file.filename}
className="mt-1 max-h-32 rounded"
/>
) : null}
</div>
);
})}
</div>
)}
</Card> </Card>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="w-[400px] mx-2"> <PopoverContent className="w-[400px] mx-2">
@ -136,7 +232,7 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) {
Code Code
</h3> </h3>
<p <p
className={`overflow-hidden line-clamp-5`} className={`text-xs overflow-hidden line-clamp-10`}
dangerouslySetInnerHTML={{ __html: sanitizedCodeSnippet }} dangerouslySetInnerHTML={{ __html: sanitizedCodeSnippet }}
></p> ></p>
</Card> </Card>
@ -146,14 +242,10 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) {
); );
} }
export interface ReferencePanelData {
notesReferenceCardData: NotesContextReferenceData[];
onlineReferenceCardData: OnlineReferenceData[];
}
export interface CodeReferenceData { export interface CodeReferenceData {
code: string; code: string;
output: string; output: string;
output_files: CodeContextFile[];
error: string; error: string;
} }
@ -289,6 +381,7 @@ export function constructAllReferences(
codeReferences.push({ codeReferences.push({
code: value.code, code: value.code,
output: value.results.std_out, output: value.results.std_out,
output_files: value.results.output_files,
error: value.results.std_err, error: value.results.std_err,
}); });
} }
@ -497,15 +590,6 @@ export default function ReferencePanel(props: ReferencePanelDataProps) {
/> />
); );
})} })}
{props.onlineReferenceCardData.map((online, index) => {
return (
<GenericOnlineReferenceCard
showFullContent={true}
{...online}
key={`${online.title}-${index}`}
/>
);
})}
{props.codeReferenceCardData.map((code, index) => { {props.codeReferenceCardData.map((code, index) => {
return ( return (
<CodeContextReferenceCard <CodeContextReferenceCard
@ -515,6 +599,15 @@ export default function ReferencePanel(props: ReferencePanelDataProps) {
/> />
); );
})} })}
{props.onlineReferenceCardData.map((online, index) => {
return (
<GenericOnlineReferenceCard
showFullContent={true}
{...online}
key={`${online.title}-${index}`}
/>
);
})}
</div> </div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>