File size: 3,262 Bytes
30cc31a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | "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>
</>
);
}
|