mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-05 17:06:54 +00:00
Merge pull request #23973 from BerriAI/litellm_/fervent-hypatia
[Refactor] UI - Playground: Extract FilePreviewCard from ChatUI
This commit is contained in:
@@ -60,6 +60,7 @@ import CodeInterpreterOutput from "./CodeInterpreterOutput";
|
||||
import CodeInterpreterTool from "./CodeInterpreterTool";
|
||||
import { generateCodeSnippet } from "./CodeSnippets";
|
||||
import EndpointSelector from "./EndpointSelector";
|
||||
import FilePreviewCard from "./FilePreviewCard";
|
||||
import MCPEventsDisplay from "./MCPEventsDisplay";
|
||||
import type { MCPEvent } from "../../mcp_tools/types";
|
||||
import { EndpointType, getEndpointType } from "./mode_endpoint_mapping";
|
||||
@@ -2231,67 +2232,19 @@ const ChatUI: React.FC<ChatUIProps> = ({
|
||||
|
||||
{/* Show file previews above input when files are uploaded */}
|
||||
{endpointType === EndpointType.RESPONSES && responsesUploadedImage && (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="relative inline-block">
|
||||
{responsesUploadedImage.name.toLowerCase().endsWith(".pdf") ? (
|
||||
<div className="w-10 h-10 rounded-md bg-red-500 flex items-center justify-center">
|
||||
<FilePdfOutlined style={{ fontSize: "16px", color: "white" }} />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={responsesImagePreviewUrl || ""}
|
||||
alt="Upload preview"
|
||||
className="w-10 h-10 rounded-md border border-gray-200 object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">{responsesUploadedImage.name}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{responsesUploadedImage.name.toLowerCase().endsWith(".pdf") ? "PDF" : "Image"}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center w-6 h-6 text-gray-400 hover:text-gray-600 hover:bg-gray-200 rounded-full transition-colors"
|
||||
onClick={handleRemoveResponsesImage}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "12px" }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<FilePreviewCard
|
||||
file={responsesUploadedImage}
|
||||
previewUrl={responsesImagePreviewUrl}
|
||||
onRemove={handleRemoveResponsesImage}
|
||||
/>
|
||||
)}
|
||||
|
||||
{endpointType === EndpointType.CHAT && chatUploadedImage && (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="relative inline-block">
|
||||
{chatUploadedImage.name.toLowerCase().endsWith(".pdf") ? (
|
||||
<div className="w-10 h-10 rounded-md bg-red-500 flex items-center justify-center">
|
||||
<FilePdfOutlined style={{ fontSize: "16px", color: "white" }} />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={chatImagePreviewUrl || ""}
|
||||
alt="Upload preview"
|
||||
className="w-10 h-10 rounded-md border border-gray-200 object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">{chatUploadedImage.name}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{chatUploadedImage.name.toLowerCase().endsWith(".pdf") ? "PDF" : "Image"}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center w-6 h-6 text-gray-400 hover:text-gray-600 hover:bg-gray-200 rounded-full transition-colors"
|
||||
onClick={handleRemoveChatImage}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "12px" }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<FilePreviewCard
|
||||
file={chatUploadedImage}
|
||||
previewUrl={chatImagePreviewUrl}
|
||||
onRemove={handleRemoveChatImage}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Code Interpreter indicator and sample prompts when enabled */}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import FilePreviewCard from "./FilePreviewCard";
|
||||
|
||||
function makeFile(name: string): File {
|
||||
return new File(["dummy"], name, { type: "application/octet-stream" });
|
||||
}
|
||||
|
||||
describe("FilePreviewCard", () => {
|
||||
it("should render", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("photo.png")} previewUrl={null} onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("photo.png")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display the file name", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("my-screenshot.jpg")} previewUrl={null} onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("my-screenshot.jpg")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'Image' label for non-PDF files", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("photo.png")} previewUrl="blob:http://localhost/abc" onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("Image")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show 'PDF' label for PDF files", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("report.pdf")} previewUrl={null} onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("PDF")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render an image preview when the file is not a PDF", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("photo.png")} previewUrl="blob:http://localhost/abc" onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByAltText("Upload preview")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render an image preview when the file is a PDF", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("doc.PDF")} previewUrl={null} onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.queryByAltText("Upload preview")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onRemove when the remove button is clicked", async () => {
|
||||
const onRemove = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("photo.png")} previewUrl={null} onRemove={onRemove} />
|
||||
);
|
||||
await user.click(screen.getByRole("button"));
|
||||
expect(onRemove).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("should treat .PDF (uppercase) as a PDF file", () => {
|
||||
render(
|
||||
<FilePreviewCard file={makeFile("REPORT.PDF")} previewUrl={null} onRemove={vi.fn()} />
|
||||
);
|
||||
expect(screen.getByText("PDF")).toBeInTheDocument();
|
||||
expect(screen.queryByAltText("Upload preview")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { DeleteOutlined, FilePdfOutlined } from "@ant-design/icons";
|
||||
|
||||
interface FilePreviewCardProps {
|
||||
file: File;
|
||||
previewUrl: string | null;
|
||||
onRemove: () => void;
|
||||
}
|
||||
|
||||
function FilePreviewCard({ file, previewUrl, onRemove }: FilePreviewCardProps) {
|
||||
const isPdf = file.name.toLowerCase().endsWith(".pdf");
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="relative inline-block">
|
||||
{isPdf ? (
|
||||
<div className="w-10 h-10 rounded-md bg-red-500 flex items-center justify-center">
|
||||
<FilePdfOutlined style={{ fontSize: "16px", color: "white" }} />
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={previewUrl || ""}
|
||||
alt="Upload preview"
|
||||
className="w-10 h-10 rounded-md border border-gray-200 object-cover"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 truncate">{file.name}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{isPdf ? "PDF" : "Image"}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="flex items-center justify-center w-6 h-6 text-gray-400 hover:text-gray-600 hover:bg-gray-200 rounded-full transition-colors"
|
||||
onClick={onRemove}
|
||||
>
|
||||
<DeleteOutlined style={{ fontSize: "12px" }} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilePreviewCard;
|
||||
Reference in New Issue
Block a user