diff --git a/src/interface/web/app/common/iconUtils.tsx b/src/interface/web/app/common/iconUtils.tsx index f266cac5..4fd7d443 100644 --- a/src/interface/web/app/common/iconUtils.tsx +++ b/src/interface/web/app/common/iconUtils.tsx @@ -40,7 +40,6 @@ import { Leaf, NewspaperClipping, OrangeSlice, - Rainbow, SmileyMelting, YinYang, SneakerMove, @@ -247,6 +246,13 @@ function getIconFromFilename( case "doc": case "docx": return ; + case "csv": + case "json": + return ; + case "txt": + return ; + case "py": + return ; case "jpg": case "jpeg": case "png": diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index a8fb2f6f..2d63d58c 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -433,14 +433,6 @@ const ChatMessage = forwardRef((props, ref) => if (!message.includes(`![${file.filename}](`)) { 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`; } }); }); diff --git a/src/interface/web/app/components/referencePanel/referencePanel.tsx b/src/interface/web/app/components/referencePanel/referencePanel.tsx index 53016cfc..0237f18f 100644 --- a/src/interface/web/app/components/referencePanel/referencePanel.tsx +++ b/src/interface/web/app/components/referencePanel/referencePanel.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; -import { ArrowRight } from "@phosphor-icons/react"; +import { ArrowCircleDown, ArrowRight } from "@phosphor-icons/react"; import markdownIt from "markdown-it"; const md = new markdownIt({ @@ -11,7 +11,13 @@ const md = new markdownIt({ 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 { @@ -97,6 +103,7 @@ function NotesContextReferenceCard(props: NotesContextReferenceCardProps) { interface CodeContextReferenceCardProps { code: string; output: string; + output_files: CodeContextFile[]; error: string; 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 sanitizedCodeSnippet = DOMPurify.sanitize(props.code.replace(/\n/g, "
")); 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 ( <> @@ -122,9 +161,66 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) { Code

+ {props.output_files && props.output_files.length > 0 && ( +
+ {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 ( +
+

+ {fileIcon} + + {file.filename} + + +

+ {file.filename.match(/\.(txt|org|md|csv|json)$/) ? ( +
+                                                        {file.b64_data}
+                                                    
+ ) : file.filename.match( + /\.(png|jpg|jpeg|webp)$/, + ) ? ( + {file.filename} + ) : null} +
+ ); + })} +
+ )} @@ -136,7 +232,7 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) { Code

@@ -146,14 +242,10 @@ function CodeContextReferenceCard(props: CodeContextReferenceCardProps) { ); } -export interface ReferencePanelData { - notesReferenceCardData: NotesContextReferenceData[]; - onlineReferenceCardData: OnlineReferenceData[]; -} - export interface CodeReferenceData { code: string; output: string; + output_files: CodeContextFile[]; error: string; } @@ -289,6 +381,7 @@ export function constructAllReferences( codeReferences.push({ code: value.code, output: value.results.std_out, + output_files: value.results.output_files, error: value.results.std_err, }); } @@ -497,15 +590,6 @@ export default function ReferencePanel(props: ReferencePanelDataProps) { /> ); })} - {props.onlineReferenceCardData.map((online, index) => { - return ( - - ); - })} {props.codeReferenceCardData.map((code, index) => { return ( ); })} + {props.onlineReferenceCardData.map((online, index) => { + return ( + + ); + })}