From 5d385c5f92370ce6d9af62cc04f5dd45434d704a Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Wed, 1 Apr 2026 23:30:29 +0000 Subject: [PATCH] feat(25-03): create ChatFilePreview and ChatFileCard components - ChatFileCard: icon, filename, size, download button with theme-aware bg-muted styling - ChatFilePreview: inline image rendering with constrained max-h-[300px], ChatFileCard for all other types - formatFileSize helper (B, KB, MB) - lucide icons: ImageIcon, FileCode, FileText, File per category --- ui/src/components/ChatFileCard.tsx | 67 +++++++++++++++++++++++++++ ui/src/components/ChatFilePreview.tsx | 29 ++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 ui/src/components/ChatFileCard.tsx create mode 100644 ui/src/components/ChatFilePreview.tsx diff --git a/ui/src/components/ChatFileCard.tsx b/ui/src/components/ChatFileCard.tsx new file mode 100644 index 00000000..6fe241a3 --- /dev/null +++ b/ui/src/components/ChatFileCard.tsx @@ -0,0 +1,67 @@ +import { Download, File, FileCode, FileText, ImageIcon } from "lucide-react"; +import { cn } from "../lib/utils"; +import type { ChatFile } from "@paperclipai/shared"; + +interface ChatFileCardProps { + file: ChatFile; + contentPath: string; + className?: string; +} + +export function formatFileSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + +function FileIcon({ category }: { category: ChatFile["category"] }) { + const cls = "h-5 w-5 shrink-0 text-muted-foreground"; + switch (category) { + case "image": + return ; + case "code": + return ; + case "document": + return ; + default: + return ; + } +} + +export function ChatFileCard({ file, contentPath, className }: ChatFileCardProps) { + return ( +
+ + +
+ + {file.originalFilename} + + + {formatFileSize(file.sizeBytes)} + +
+ + e.stopPropagation()} + > + + +
+ ); +} diff --git a/ui/src/components/ChatFilePreview.tsx b/ui/src/components/ChatFilePreview.tsx new file mode 100644 index 00000000..8e821aa8 --- /dev/null +++ b/ui/src/components/ChatFilePreview.tsx @@ -0,0 +1,29 @@ +import { ChatFileCard } from "./ChatFileCard"; +import type { ChatFile } from "@paperclipai/shared"; + +interface ChatFilePreviewProps { + file: ChatFile; + contentPath: string; +} + +export function ChatFilePreview({ file, contentPath }: ChatFilePreviewProps) { + if (file.category === "image") { + return ( +
+ + {file.originalFilename} + + {/* Always show card below image for download button */} + +
+ ); + } + + // For all non-image types (code, document, other): render the file card with download + return ; +}