"use client"; import * as React from "react"; import { Upload } from "lucide-react"; import { Button, type ButtonProps } from "@/components/ui/button"; import { toast } from "@/components/ui/toast"; import { errorMessage } from "@/lib/client/humanize"; import { ClientApiError } from "@/lib/client/api"; import { LIBRARY_ACCEPT } from "./format"; import type { PublicProjectFile } from "@/lib/client/types"; export const LIBRARY_MAX_BYTES = 10 * 1024 * 1024; /** Uploads files one by one to the library (multipart, same limits as chat attachments). Returns saved files + errors. */ export async function uploadLibraryFiles(files: File[], projectId?: string | null, onProgress?: (done: number, total: number) => void): Promise<{ saved: PublicProjectFile[]; errors: { name: string; message: string }[] }> { const saved: PublicProjectFile[] = []; const errors: { name: string; message: string }[] = []; let done = 0; for (const f of files) { if (f.size > LIBRARY_MAX_BYTES) { errors.push({ name: f.name, message: "Larger than 10 MB" }); } else { const form = new FormData(); form.append("file", f); if (projectId) form.append("projectId", projectId); try { const res = await fetch("/api/library/files", { method: "POST", body: form, credentials: "same-origin" }); if (!res.ok) { const body = (await res.json().catch(() => ({}))) as { error?: { code?: string; message?: string } }; throw new ClientApiError(res.status, body.error?.code ?? "HTTP_ERROR", body.error?.message ?? `Upload failed (${res.status})`); } const body = (await res.json()) as { file: PublicProjectFile }; saved.push(body.file); } catch (e) { errors.push({ name: f.name, message: errorMessage(e) }); } } done += 1; onProgress?.(done, files.length); } return { saved, errors }; } /** Summarises an upload batch in toasts. */ export function reportUpload(result: { saved: PublicProjectFile[]; errors: { name: string; message: string }[] }) { if (result.saved.length) toast.success(result.saved.length === 1 ? `Added “${result.saved[0].name}”` : `Added ${result.saved.length} files to the library`); for (const e of result.errors.slice(0, 3)) toast.error(`Could not upload ${e.name}`, e.message); if (result.errors.length > 3) toast.error(`${result.errors.length - 3} more files failed`); } export function UploadButton({ projectId, onUploaded, children, size = "md", variant = "primary", className, ...rest }: { projectId?: string | null; onUploaded?: (files: PublicProjectFile[]) => unknown } & Omit) { const inputRef = React.useRef(null); const [busy, setBusy] = React.useState<{ done: number; total: number } | null>(null); const onFiles = async (list: FileList | null) => { const files = list ? Array.from(list) : []; if (!files.length) return; setBusy({ done: 0, total: files.length }); try { const result = await uploadLibraryFiles(files, projectId, (done, total) => setBusy({ done, total })); reportUpload(result); if (result.saved.length) await onUploaded?.(result.saved); } finally { setBusy(null); if (inputRef.current) inputRef.current.value = ""; } }; return ( <> void onFiles(e.target.files)} /> ); }