proteinea / src /app /chat /_components /FileUpload.tsx
Mahmoud Eljendy
feat: Antibody Studio — AI-native antibody design workspace by Proteinea
30cc31a
"use client";
import { useRef } from "react";
/** File types accepted by the scientist file upload. */
const ACCEPTED_EXTENSIONS = ".fasta,.fa,.pdb,.cif";
function formatSize(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`;
}
interface FileUploadProps {
file: File | null;
onFileChange: (file: File | null) => void;
disabled?: boolean;
}
/**
* Paperclip-style file upload button for scientist-provided PDB/FASTA input.
* Shows a small chip with filename + size when a file is selected.
*/
export default function FileUpload({ file, onFileChange, disabled }: FileUploadProps) {
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (disabled) return;
inputRef.current?.click();
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selected = e.target.files?.[0] ?? null;
onFileChange(selected);
// Reset so the same file can be re-selected
if (inputRef.current) inputRef.current.value = "";
};
return (
<div className="flex items-center gap-1.5">
<input
ref={inputRef}
type="file"
accept={ACCEPTED_EXTENSIONS}
className="hidden"
onChange={handleChange}
/>
{/* Paperclip button */}
<button
onClick={handleClick}
disabled={disabled}
title="Upload PDB/FASTA file (.fasta, .fa, .pdb, .cif)"
className={`p-2.5 rounded-xl transition-colors ${
file
? "text-accent bg-accent/10"
: "text-muted-fg hover:text-foreground hover:bg-muted"
} disabled:opacity-30`}
>
<svg
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" />
</svg>
</button>
{/* File chip */}
{file && (
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-accent/10 text-accent text-[11px] font-medium max-w-[200px]">
<span className="truncate">{file.name}</span>
<span className="text-accent/60 shrink-0">({formatSize(file.size)})</span>
<button
onClick={(e) => {
e.stopPropagation();
onFileChange(null);
}}
className="ml-0.5 hover:text-red-500 transition-colors shrink-0"
title="Remove file"
>
<svg className="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</span>
)}
</div>
);
}