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>
    </>
  );
}