"use client"; import { memo, type FC } from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { FileIcon, FileTextIcon, ImageIcon, MusicIcon, VideoIcon, BracesIcon, DownloadIcon, } from "lucide-react"; import type { FileMessagePartComponent } from "@assistant-ui/react"; import { cn } from '../../lib/utils'; const fileVariants = cva( "aui-file-root inline-flex items-center gap-3 rounded-lg transition-colors", { variants: { variant: { outline: "border-border hover:bg-muted/50 border", ghost: "hover:bg-muted/50", muted: "bg-muted/50 hover:bg-muted/70", }, size: { sm: "px-2.5 py-1.5 text-xs", default: "px-3 py-2 text-sm", lg: "px-4 py-3 text-base", }, }, defaultVariants: { variant: "outline", size: "default", }, }, ); function getMimeTypeIcon(mimeType: string): FC<{ className?: string }> { const type = mimeType.toLowerCase(); if (type.startsWith("image/")) { return ImageIcon; } if (type === "application/pdf") { return FileTextIcon; } if (type === "application/json") { return BracesIcon; } if (type.startsWith("text/")) { return FileTextIcon; } if (type.startsWith("audio/")) { return MusicIcon; } if (type.startsWith("video/")) { return VideoIcon; } return FileIcon; } export type FileDataKind = "data-uri" | "url" | "base64" | "id"; function getFileDataKind( data: string, sourceType?: "url" | "id", ): FileDataKind { if (sourceType === "url" && /^data:/i.test(data)) return "data-uri"; if (sourceType) return sourceType; if (/^data:/i.test(data)) return "data-uri"; if (/^https?:\/\//i.test(data)) return "url"; return "base64"; } function getBase64Size(base64: string): number { const commaIndex = base64.indexOf(","); const base64Data = commaIndex >= 0 ? base64.slice(commaIndex + 1) : base64; const padding = (base64Data.match(/=/g) || []).length; return Math.floor((base64Data.length * 3) / 4) - padding; } 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`; } export type FileRootProps = React.ComponentProps<"div"> & VariantProps; function FileRoot({ className, variant, size, children, ...props }: FileRootProps) { return (
{children}
); } type FileIconDisplayProps = React.ComponentProps<"span"> & { mimeType?: string; }; function FileIconDisplay({ mimeType, className, children, ...props }: FileIconDisplayProps) { const IconComponent = mimeType ? getMimeTypeIcon(mimeType) : FileIcon; return ( {children ?? } ); } function FileName({ className, children, ...props }: React.ComponentProps<"span">) { return ( {children || "Unnamed file"} ); } type FileSizeProps = React.ComponentProps<"span"> & { bytes: number; }; function FileSize({ bytes, className, ...props }: FileSizeProps) { return ( {formatFileSize(bytes)} ); } type FileDownloadProps = Omit, "href"> & { data: string; mimeType: string; filename?: string; sourceType?: "url" | "id"; }; function FileDownload({ data, mimeType, filename, sourceType, className, children, ...props }: FileDownloadProps) { if (typeof data !== "string") return null; const kind = getFileDataKind(data, sourceType); if (kind === "id") return null; if (kind === "url" && !/^(https?:\/\/|blob:)/i.test(data)) return null; const href = kind === "base64" ? `data:${mimeType};base64,${data}` : data; return ( {children || } ); } const FileImpl: FileMessagePartComponent = ({ filename, data, mimeType, sourceType, }) => { const kind = getFileDataKind(data, sourceType); const showSize = typeof data === "string" && (kind === "base64" || kind === "data-uri"); return (
{filename} {showSize && ( )}
); }; const File = memo(FileImpl) as unknown as FileMessagePartComponent & { Root: typeof FileRoot; Icon: typeof FileIconDisplay; Name: typeof FileName; Size: typeof FileSize; Download: typeof FileDownload; }; File.displayName = "File"; File.Root = FileRoot; File.Icon = FileIconDisplay; File.Name = FileName; File.Size = FileSize; File.Download = FileDownload; export { File, FileRoot, FileIconDisplay, FileName, FileSize, FileDownload, fileVariants, getMimeTypeIcon, getFileDataKind, getBase64Size, formatFileSize, };