Download src/app/chat/_components/FileUploadButton.tsx from meljendy/proteinea: direct link, hf CLI and curl.
- Browser
- Download file 3.26 kB
-
https://huggingface.co/spaces/meljendy/proteinea/resolve/main/src/app/chat/_components/FileUploadButton.tsx
- Command line
-
hf download hf://spaces/meljendy/proteinea/src/app/chat/_components/FileUploadButton.tsx
-
curl -L -o FileUploadButton.tsx https://huggingface.co/spaces/meljendy/proteinea/resolve/main/src/app/chat/_components/FileUploadButton.tsx
3.26 kB
| "use client"; | |
| import { useRef } from "react"; | |
| import type { Artifact } from "@/lib/types"; | |
| export interface PendingFile { | |
| /** Client-side temp id for keying */ | |
| tempId: string; | |
| name: string; | |
| size: number; | |
| status: "uploading" | "done" | "error"; | |
| artifact?: Artifact; | |
| error?: string; | |
| } | |
| interface FileUploadButtonProps { | |
| taskId: string | null; | |
| onFileStart: (file: PendingFile) => void; | |
| onFileComplete: (tempId: string, artifact: Artifact) => void; | |
| onFileError: (tempId: string, error: string) => void; | |
| disabled?: boolean; | |
| } | |
| const ACCEPTED = ".fasta,.pdb,.csv,.nwk"; | |
| let tempCounter = 0; | |
| 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`; | |
| } | |
| export { formatSize }; | |
| export default function FileUploadButton({ | |
| taskId, | |
| onFileStart, | |
| onFileComplete, | |
| onFileError, | |
| disabled, | |
| }: FileUploadButtonProps) { | |
| const inputRef = useRef<HTMLInputElement>(null); | |
| const handleClick = () => { | |
| if (disabled || !taskId) return; | |
| inputRef.current?.click(); | |
| }; | |
| const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const files = e.target.files; | |
| if (!files || !taskId) return; | |
| for (const file of Array.from(files)) { | |
| const tempId = `upload_${++tempCounter}_${Date.now()}`; | |
| const pending: PendingFile = { | |
| tempId, | |
| name: file.name, | |
| size: file.size, | |
| status: "uploading", | |
| }; | |
| onFileStart(pending); | |
| try { | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| formData.append("taskId", taskId); | |
| const res = await fetch("/api/upload", { | |
| method: "POST", | |
| body: formData, | |
| }); | |
| if (!res.ok) { | |
| const body = await res.json().catch(() => ({ error: `HTTP ${res.status}` })); | |
| throw new Error(body.error || `Upload failed (${res.status})`); | |
| } | |
| const { artifact } = (await res.json()) as { artifact: Artifact }; | |
| onFileComplete(tempId, artifact); | |
| } catch (err) { | |
| const msg = err instanceof Error ? err.message : "Upload failed"; | |
| onFileError(tempId, msg); | |
| } | |
| } | |
| // Reset the input so the same file can be re-uploaded | |
| if (inputRef.current) inputRef.current.value = ""; | |
| }; | |
| return ( | |
| <> | |
| <input | |
| ref={inputRef} | |
| type="file" | |
| accept={ACCEPTED} | |
| multiple | |
| className="hidden" | |
| onChange={handleChange} | |
| /> | |
| <button | |
| onClick={handleClick} | |
| disabled={disabled || !taskId} | |
| title="Attach file (.fasta, .pdb, .csv, .nwk)" | |
| className="p-2.5 rounded-xl text-muted-fg hover:text-foreground hover:bg-muted transition-colors 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> | |
| </> | |
| ); | |
| } | |