"use client"; import { useCallback, useRef, useState } from "react"; import { ACCEPTED_FILE_TYPES, addLink, coverageWarning, describeKind, removeDocument, uploadFile, type Workspace, } from "@/lib/workspace"; interface Props { workspace: Workspace | null; onChange: (workspace: Workspace) => void; } /** * The upload surface: drop, browse, or paste a link. * * An empty screen is an invitation to act, so when there is nothing here yet this is the * largest target on the page and it names what it takes in plain words. Once a document * is in, it shrinks to a list and gets out of the way of the question box. */ export function Dropzone({ workspace, onChange }: Props) { const [dragging, setDragging] = useState(false); const [busy, setBusy] = useState(null); const [error, setError] = useState(null); const [url, setUrl] = useState(""); const fileInput = useRef(null); const documents = workspace?.documents ?? []; const full = workspace ? documents.length >= workspace.limits.max_documents : false; const run = useCallback( async (label: string, action: () => Promise) => { setBusy(label); setError(null); try { onChange(await action()); } catch (cause) { // The server writes these messages for the person who uploaded the file — a // generic "something went wrong" would throw away the only useful part. setError(cause instanceof Error ? cause.message : "That file could not be read."); } finally { setBusy(null); } }, [onChange], ); const send = useCallback( (files: FileList | null) => { const file = files?.[0]; if (file) void run(`Reading ${file.name}`, () => uploadFile(file)); }, [run], ); return (

Add a document

{ event.preventDefault(); setDragging(true); }} onDragLeave={() => setDragging(false)} onDrop={(event) => { event.preventDefault(); setDragging(false); send(event.dataTransfer.files); }} > { send(event.target.files); event.target.value = ""; }} /> {busy ? (

{busy}…

) : ( <>

Drop a document here

PDF, Word, an image or a photo of a page — scans are read automatically

or
{ event.preventDefault(); const target = url.trim(); if (!target) return; void run(`Fetching ${target}`, () => addLink(target)).then(() => setUrl("")); }} > setUrl(event.target.value)} placeholder="paste a link" disabled={full} className="w-52 rounded-lg border rule bg-raised px-3 py-2 text-sm outline-none focus-visible:border-indigo" />
)}
{full && (

That is the maximum of {workspace?.limits.max_documents} documents. Remove one to add another.

)} {error && (

{error}

)} {documents.length > 0 && (
    {documents.map((document) => (
  • {document.title}

    {describeKind(document)}

    {coverageWarning(document) && (

    {coverageWarning(document)}

    )}
  • ))}
)}
); }