"use client"; import { useEffect, useState } from "react"; import { ArrowCircleDown, ArrowRight, Code, Note } from "@phosphor-icons/react"; import markdownIt from "markdown-it"; const md = new markdownIt({ html: true, linkify: true, typographer: true, }); import { Context, WebPage, OnlineContext, CodeContext, CodeContextFile, } from "../chatMessage/chatMessage"; import { Card } from "@/components/ui/card"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import DOMPurify from "dompurify"; import { getIconFromFilename } from "@/app/common/iconUtils"; import Link from "next/link"; interface NotesContextReferenceData { title: string; content: string; } interface NotesContextReferenceCardProps extends NotesContextReferenceData { showFullContent: boolean; } function extractSnippet(props: NotesContextReferenceCardProps): string { const hierarchicalFileExtensions = ["org", "md", "markdown"]; const extension = props.title.split(".").pop() || ""; const cleanContent = hierarchicalFileExtensions.includes(extension) ? props.content.split("\n").slice(1).join("\n") : props.content; return props.showFullContent ? DOMPurify.sanitize(md.render(cleanContent)) : DOMPurify.sanitize(cleanContent); } function NotesContextReferenceCard(props: NotesContextReferenceCardProps) { const fileIcon = getIconFromFilename( props.title || ".txt", "w-6 h-6 text-muted-foreground inline-flex mr-2", ); const fileName = props.title.split("/").pop() || props.title; const snippet = extractSnippet(props); const [isHovering, setIsHovering] = useState(false); return ( <> setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className={`${props.showFullContent ? "w-auto bg-muted" : "w-auto"} overflow-hidden break-words text-balance rounded-lg border-none p-2 shadow-none`} > { !props.showFullContent ? : <>

{fileIcon} {props.showFullContent ? props.title : fileName}

}

{fileIcon} {props.title}

); } interface CodeContextReferenceCardProps { code: string; output: string; output_files: CodeContextFile[]; error: string; showFullContent: boolean; } function CodeContextReferenceCard(props: CodeContextReferenceCardProps) { const fileIcon = getIconFromFilename(".py", "!w-4 h-4 text-muted-foreground flex-shrink-0"); const sanitizedCodeSnippet = DOMPurify.sanitize(props.code); 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); }; const renderOutputFiles = (files: CodeContextFile[], hoverCard: boolean) => { if (files?.length == 0) return null; return (
{files.slice(0, props.showFullContent ? undefined : 1).map((file, index) => { return (

{file.filename}

{file.filename.match(/\.(txt|org|md|csv|json)$/) ? (
                                    {file.b64_data}
                                
) : file.filename.match(/\.(png|jpg|jpeg|webp)$/) ? ( {file.filename} ) : null}
); })}
); }; return ( <> setIsHovering(true)} onMouseLeave={() => setIsHovering(false)} className={`${props.showFullContent ? "w-auto bg-muted" : "w-auto"} overflow-hidden break-words text-balance rounded-lg border-none p-2 shadow-none`} > { !props.showFullContent ? :
{fileIcon}

code {props.output_files?.length > 0 ? "artifacts" : ""}

 0 ? "hidden" : "overflow-hidden line-clamp-3"}`}
                                    >
                                        {sanitizedCodeSnippet}
                                    
{props.output_files?.length > 0 && renderOutputFiles(props.output_files, false)}
}
{fileIcon}

code {props.output_files?.length > 0 ? "artifact" : ""}

{(props.output_files?.length > 0 && renderOutputFiles(props.output_files?.slice(0, 1), true)) || (
                                    {sanitizedCodeSnippet}
                                
)}
); } export interface CodeReferenceData { code: string; output: string; output_files: CodeContextFile[]; error: string; } interface OnlineReferenceData { title: string; description: string; link: string; } interface OnlineReferenceCardProps extends OnlineReferenceData { showFullContent: boolean; } function GenericOnlineReferenceCard(props: OnlineReferenceCardProps) { const [isHovering, setIsHovering] = useState(false); if (!props.link || props.link.split(" ").length > 1) { return null; } let favicon = `https://www.google.com/s2/favicons?domain=globe`; let domain = "unknown"; try { domain = new URL(props.link).hostname; favicon = `https://www.google.com/s2/favicons?domain=${domain}`; } catch (error) { console.warn(`Error parsing domain from link: ${props.link}`); return null; } const handleMouseEnter = () => { setIsHovering(true); }; const handleMouseLeave = () => { setIsHovering(false); }; return ( <> { !props.showFullContent ? :
{

{domain}

}

{props.title}

{props.description}

}

{domain}

{props.title}

{props.description}

); } export function constructAllReferences( contextData: Context[], onlineData: OnlineContext, codeContext: CodeContext, ) { const onlineReferences: OnlineReferenceData[] = []; const contextReferences: NotesContextReferenceData[] = []; const codeReferences: CodeReferenceData[] = []; if (codeContext) { for (const [key, value] of Object.entries(codeContext)) { if (!value.results) { continue; } codeReferences.push({ code: value.code, output: value.results.std_out, output_files: value.results.output_files, error: value.results.std_err, }); } } if (onlineData) { let localOnlineReferences = []; for (const [key, value] of Object.entries(onlineData)) { if (value.answerBox) { localOnlineReferences.push({ title: value.answerBox.title, description: value.answerBox.answer, link: value.answerBox.source, }); } if (value.knowledgeGraph) { localOnlineReferences.push({ title: value.knowledgeGraph.title, description: value.knowledgeGraph.description, link: value.knowledgeGraph.descriptionLink, }); } if (value.webpages) { // If webpages is of type Array, iterate through it and add each webpage to the localOnlineReferences array if (value.webpages instanceof Array) { let webPageResults = value.webpages.map((webPage) => { return { title: webPage.query, description: webPage.snippet, link: webPage.link, }; }); localOnlineReferences.push(...webPageResults); } else { let singleWebpage = value.webpages as WebPage; // If webpages is an object, add the object to the localOnlineReferences array localOnlineReferences.push({ title: singleWebpage.query, description: singleWebpage.snippet, link: singleWebpage.link, }); } } if (value.organic) { let organicResults = value.organic.map((organicContext) => { return { title: organicContext.title, description: organicContext.snippet, link: organicContext.link, }; }); localOnlineReferences.push(...organicResults); } } onlineReferences.push(...localOnlineReferences); } if (contextData) { let localContextReferences = contextData.map((context) => { if (!context.compiled) { const fileContent = context as unknown as string; const title = fileContent.split("\n")[0]; const content = fileContent.split("\n").slice(1).join("\n"); return { title: title, content: content, }; } return { title: context.file, content: context.compiled, }; }); contextReferences.push(...localContextReferences); } return { notesReferenceCardData: contextReferences, onlineReferenceCardData: onlineReferences, codeReferenceCardData: codeReferences, }; } interface SimpleIconProps { type: string; link?: string; } function SimpleIcon(props: SimpleIconProps) { let favicon = ``; let domain = "unknown"; if (props.link) { try { domain = new URL(props.link).hostname; favicon = `https://www.google.com/s2/favicons?domain=${domain}`; } catch (error) { console.warn(`Error parsing domain from link: ${props.link}`); return null; } } let symbol = null; const itemClasses = "!w-4 !h-4 text-muted-foreground inline-flex mr-2 rounded-lg"; switch (props.type) { case "code": symbol = ; break; case "online": symbol = ; break; case "notes": symbol = ; break; default: symbol = null; } if (!symbol) { return null; } return (
{symbol}
); } export interface TeaserReferenceSectionProps { notesReferenceCardData: NotesContextReferenceData[]; onlineReferenceCardData: OnlineReferenceData[]; codeReferenceCardData: CodeReferenceData[]; isMobileWidth: boolean; } export function TeaserReferencesSection(props: TeaserReferenceSectionProps) { const shouldShowShowMoreButton = props.notesReferenceCardData.length > 0 || props.codeReferenceCardData.length > 0 || props.onlineReferenceCardData.length > 0; const numReferences = props.notesReferenceCardData.length + props.codeReferenceCardData.length + props.onlineReferenceCardData.length; if (numReferences === 0) { return null; } return (

{numReferences} sources
{shouldShowShowMoreButton && ( )}

); } interface ReferencePanelDataProps { notesReferenceCardData: NotesContextReferenceData[]; onlineReferenceCardData: OnlineReferenceData[]; codeReferenceCardData: CodeReferenceData[]; isMobileWidth: boolean; } export default function ReferencePanel(props: ReferencePanelDataProps) { const [numTeaserSlots, setNumTeaserSlots] = useState(3); useEffect(() => { setNumTeaserSlots(props.isMobileWidth ? 1 : 3); }, [props.isMobileWidth]); if (!props.notesReferenceCardData && !props.onlineReferenceCardData) { return null; } const codeDataToShow = props.codeReferenceCardData.slice(0, numTeaserSlots); const notesDataToShow = props.notesReferenceCardData.slice( 0, numTeaserSlots - codeDataToShow.length, ); const onlineDataToShow = notesDataToShow.length + codeDataToShow.length < numTeaserSlots ? props.onlineReferenceCardData.filter((online) => online.link).slice( 0, numTeaserSlots - codeDataToShow.length - notesDataToShow.length, ) : []; return ( {codeDataToShow.map((code, index) => { return ( ); })} {notesDataToShow.map((note, index) => { return ( ); })} {onlineDataToShow.map((online, index) => { return ( ); })} References View all references for this response
{props.codeReferenceCardData.map((code, index) => { return ( ); })} {props.notesReferenceCardData.map((note, index) => { return ( ); })} {props.onlineReferenceCardData.map((online, index) => { return ( ); })}
); }