diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx index b72e55feaea..e2773bd1ca0 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterOutput.tsx @@ -1,15 +1,23 @@ import React, { useState, useEffect } from "react"; -import { Collapse, Spin } from "antd"; import { - CodeOutlined, - DownloadOutlined, - FileImageOutlined, - FileTextOutlined, - LoadingOutlined, -} from "@ant-design/icons"; + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { + Code, + Download, + FileImage, + FileText, + Loader2, +} from "lucide-react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { coy } from "react-syntax-highlighter/dist/esm/styles/prism"; -import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking"; +import { + getProxyBaseUrl, + getGlobalLitellmHeaderName, +} from "@/components/networking"; interface ContainerFileCitation { type: "container_file_citation"; @@ -29,46 +37,49 @@ interface CodeInterpreterOutputProps { const CodeInterpreterOutput: React.FC = ({ code, - containerId, annotations = [], accessToken, }) => { const [imageUrls, setImageUrls] = useState>({}); - const [loadingImages, setLoadingImages] = useState>({}); + const [loadingImages, setLoadingImages] = useState>( + {}, + ); const proxyBaseUrl = getProxyBaseUrl(); - // Fetch images from container files API useEffect(() => { const fetchImages = async () => { for (const annotation of annotations) { - const isImage = annotation.filename?.toLowerCase().endsWith(".png") || - annotation.filename?.toLowerCase().endsWith(".jpg") || - annotation.filename?.toLowerCase().endsWith(".jpeg") || - annotation.filename?.toLowerCase().endsWith(".gif"); - + const isImage = + annotation.filename?.toLowerCase().endsWith(".png") || + annotation.filename?.toLowerCase().endsWith(".jpg") || + annotation.filename?.toLowerCase().endsWith(".jpeg") || + annotation.filename?.toLowerCase().endsWith(".gif"); + if (isImage && annotation.container_id && annotation.file_id) { - setLoadingImages(prev => ({ ...prev, [annotation.file_id]: true })); - + setLoadingImages((prev) => ({ ...prev, [annotation.file_id]: true })); + try { - // Fetch image content from container files API const response = await fetch( `${proxyBaseUrl}/v1/containers/${annotation.container_id}/files/${annotation.file_id}/content`, { headers: { [getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`, }, - } + }, ); - + if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); - setImageUrls(prev => ({ ...prev, [annotation.file_id]: url })); + setImageUrls((prev) => ({ ...prev, [annotation.file_id]: url })); } } catch (error) { console.error("Error fetching image:", error); } finally { - setLoadingImages(prev => ({ ...prev, [annotation.file_id]: false })); + setLoadingImages((prev) => ({ + ...prev, + [annotation.file_id]: false, + })); } } } @@ -78,10 +89,10 @@ const CodeInterpreterOutput: React.FC = ({ fetchImages(); } - // Cleanup URLs on unmount return () => { - Object.values(imageUrls).forEach(url => URL.revokeObjectURL(url)); + Object.values(imageUrls).forEach((url) => URL.revokeObjectURL(url)); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [annotations, accessToken, proxyBaseUrl]); const handleDownload = async (annotation: ContainerFileCitation) => { @@ -92,9 +103,9 @@ const CodeInterpreterOutput: React.FC = ({ headers: { [getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`, }, - } + }, ); - + if (response.ok) { const blob = await response.blob(); const url = URL.createObjectURL(blob); @@ -111,19 +122,20 @@ const CodeInterpreterOutput: React.FC = ({ } }; - // Separate images and other files - const imageAnnotations = annotations.filter(a => - a.filename?.toLowerCase().endsWith(".png") || - a.filename?.toLowerCase().endsWith(".jpg") || - a.filename?.toLowerCase().endsWith(".jpeg") || - a.filename?.toLowerCase().endsWith(".gif") + const imageAnnotations = annotations.filter( + (a) => + a.filename?.toLowerCase().endsWith(".png") || + a.filename?.toLowerCase().endsWith(".jpg") || + a.filename?.toLowerCase().endsWith(".jpeg") || + a.filename?.toLowerCase().endsWith(".gif"), ); - - const fileAnnotations = annotations.filter(a => - !a.filename?.toLowerCase().endsWith(".png") && - !a.filename?.toLowerCase().endsWith(".jpg") && - !a.filename?.toLowerCase().endsWith(".jpeg") && - !a.filename?.toLowerCase().endsWith(".gif") + + const fileAnnotations = annotations.filter( + (a) => + !a.filename?.toLowerCase().endsWith(".png") && + !a.filename?.toLowerCase().endsWith(".jpg") && + !a.filename?.toLowerCase().endsWith(".jpeg") && + !a.filename?.toLowerCase().endsWith(".gif"), ); if (!code && annotations.length === 0) { @@ -134,67 +146,71 @@ const CodeInterpreterOutput: React.FC = ({
{/* Executed Code - Collapsible */} {code && ( - - Python Code Executed - - ), - children: ( - - {code} - - ), - }, - ]} - /> + + + + + Python Code Executed + + + + + {code} + + + + )} {/* Generated Images */} {imageAnnotations.map((annotation) => ( -
+
{loadingImages[annotation.file_id] ? ( -
- } /> - Loading image... +
+ + + Loading image... +
) : imageUrls[annotation.file_id] ? (
+ {/* eslint-disable-next-line @next/next/no-img-element */} {annotation.filename -
- - {annotation.filename} +
+ + {annotation.filename}
) : ( -
- Image not available +
+ + Image not available +
)}
@@ -207,11 +223,11 @@ const CodeInterpreterOutput: React.FC = ({ ))}
@@ -221,4 +237,3 @@ const CodeInterpreterOutput: React.FC = ({ }; export default CodeInterpreterOutput; -