"use client"; import { useRef, useState } from "react"; import { StateNotice } from "@/components/StateNotice"; import { Badge, Button, Card, Input, Select, Textarea } from "@/components/ui/Primitives"; import { getArtifactReadUrl, uploadArtifact } from "@/lib/blob-client"; import { toast } from "@/lib/toast"; import { getEmptyState, toUserFacingState, type UserFacingState } from "@/lib/user-facing-states"; import type { ArtifactSummary } from "@/lib/types"; export function ArtifactPanel({ artifacts, onCreate, isCreating = false, }: { artifacts: ArtifactSummary[]; onCreate: (input: { title: string; artifactType: "file" | "summary" | "extraction" | "citation" | "export"; description?: string; blobPath?: string; }) => Promise; isCreating?: boolean; }) { const [openingId, setOpeningId] = useState(null); const [uploading, setUploading] = useState(false); const fileInputRef = useRef(null); const [title, setTitle] = useState(""); const [artifactType, setArtifactType] = useState<"file" | "summary" | "extraction" | "citation" | "export">("file"); const [description, setDescription] = useState(""); const [blobPath, setBlobPath] = useState(""); const [noticeState, setNoticeState] = useState(null); async function handleOpenArtifact(artifact: ArtifactSummary) { if (!artifact.blobPath) { return; } try { setOpeningId(artifact.id); const url = await getArtifactReadUrl(artifact.blobPath); window.open(url, "_blank", "noopener,noreferrer"); } finally { setOpeningId(null); } } async function handleFileUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setUploading(true); setNoticeState(null); try { const path = `artifacts/${crypto.randomUUID()}/${file.name}`; const result = await uploadArtifact(file, path); await onCreate({ title: file.name, artifactType: "file", description: `Uploaded file (${(result.sizeBytes / 1024).toFixed(1)} KB)`, blobPath: result.blobPath, }); toast.success("File uploaded"); } catch (err) { const state = toUserFacingState(err, "blob"); setNoticeState(state); toast.error(state.message); } finally { setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ""; } } async function handleCreateArtifact() { if (!title.trim()) { return; } await onCreate({ title: title.trim(), artifactType, description: description.trim() || undefined, blobPath: blobPath.trim() || undefined, }); setTitle(""); setArtifactType("file"); setDescription(""); setBlobPath(""); setNoticeState(null); } return (
Artifacts
blob-backed view
setTitle(event.target.value)} placeholder="Artifact title" aria-label="Artifact title" />
setBlobPath(event.target.value)} placeholder="Optional blob path" aria-label="Artifact blob path" />