"use client"; import { useRef, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { FileText, Upload, ExternalLink, Trash2, Paperclip } from "lucide-react"; import { api } from "@/lib/client/api"; import { Button } from "@/components/ui/button"; import { Badge, Spinner, Empty } from "@/components/ui/primitives"; import { toast } from "@/components/ui/toast"; import { fmtBytes } from "./ui"; import { refsKey, attKey } from "./ReferencesTab"; import { timeAgo } from "@/lib/utils"; export function FilesTab({ pageId }: { pageId: string }) { const qc = useQueryClient(); const input = useRef(null); const [busy, setBusy] = useState(0); const { data: files = [], isLoading } = useQuery({ queryKey: attKey(pageId), queryFn: () => api(`/api/attachments?pageId=${pageId}`) }); const { data: refs = [] } = useQuery({ queryKey: refsKey(pageId), queryFn: () => api(`/api/research/references?pageId=${pageId}`) }); const refresh = () => { qc.invalidateQueries({ queryKey: attKey(pageId) }); qc.invalidateQueries({ queryKey: ["research-overview"] }); }; async function upload(list: FileList | null) { const arr = Array.from(list ?? []); if (!arr.length) return; setBusy(arr.length); for (const f of arr) { try { const fd = new FormData(); fd.append("file", f); fd.append("pageId", pageId); const r = await api("/api/attachments", { method: "POST", body: fd }); toast(`Uploaded ${r.name}`, { description: r.hasText ? `${r.pages ?? "?"} pages · text extracted, indexing…` : "No text extracted", tone: "success" }); } catch (e: any) { toast(`Upload failed: ${f.name}`, { description: e.message, tone: "danger" }); } finally { setBusy((b) => b - 1); } } refresh(); } async function del(f: any) { if (!confirm(`Delete ${f.filename}?`)) return; await api(`/api/attachments?id=${f.id}`, { method: "DELETE" }); toast("File deleted"); refresh(); qc.invalidateQueries({ queryKey: refsKey(pageId) }); } const linked = (id: string) => refs.filter((r) => r.attachmentId === id); return (
e.preventDefault()} onDrop={(e) => { e.preventDefault(); upload(e.dataTransfer.files); }}>
{ upload(e.target.files); e.target.value = ""; }} /> PDF text is extracted and indexed for search and AI synthesis. Drop files here.
{isLoading ?
: !files.length ? } title="No files on this page" hint="Upload the PDFs of the papers you cite; attach them to references from the References tab." /> : (
{files.map((f) => { const l = linked(f.id); return (
{f.filename}
{fmtBytes(f.size)}{f.pageCount != null && · {f.pageCount} p.}· {timeAgo(f.createdAt)}{f.hasText ? text : no text}
{l.length > 0 &&
{l.map((r) => {r.citeKey})}
}
); })}
)}
); }