"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)$/) ? (
.pop()};base64,${file.b64_data}`})
) : 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}
}
>
);
}
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 (
);
})}
);
}