diff --git a/web/src/app/(app)/notes/[noteId]/page.tsx b/web/src/app/(app)/notes/[noteId]/page.tsx index 0e0763b..ace9b28 100644 --- a/web/src/app/(app)/notes/[noteId]/page.tsx +++ b/web/src/app/(app)/notes/[noteId]/page.tsx @@ -9,7 +9,7 @@ import { LinkedNotesPanel } from "@/components/LinkedNotesPanel"; import { TaskReviewPanel } from "@/components/TaskReviewPanel"; import { ArtifactPanel } from "@/components/ArtifactPanel"; import { AgentTimeline } from "@/components/AgentTimeline"; -import { getNoteDetail, updateNoteDetail } from "@/lib/notes-client"; +import { createNoteArtifact, getNoteDetail, updateNoteDetail } from "@/lib/notes-client"; import type { NoteDetail } from "@/lib/types"; export default function NoteDetailPage() { @@ -17,6 +17,7 @@ export default function NoteDetailPage() { const noteId = params.noteId; const [note, setNote] = useState(null); const [isSaving, setIsSaving] = useState(false); + const [isCreatingArtifact, setIsCreatingArtifact] = useState(false); const [error, setError] = useState(null); useEffect(() => { @@ -48,6 +49,38 @@ export default function NoteDetailPage() { } } + async function handleCreateArtifact(input: { + title: string; + artifactType: "file" | "summary" | "extraction" | "citation" | "export"; + description?: string; + blobPath?: string; + }) { + if (!note) { + return; + } + + setIsCreatingArtifact(true); + + try { + await createNoteArtifact({ + id: crypto.randomUUID(), + workspaceId: note.workspaceId, + noteId: note.id, + artifactType: input.artifactType, + title: input.title, + description: input.description, + blobPath: input.blobPath, + }); + const refreshed = await getNoteDetail(note.id); + setNote(refreshed); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : "Unable to create artifact"); + } finally { + setIsCreatingArtifact(false); + } + } + if (!note) { return ( - + diff --git a/web/src/components/ArtifactPanel.tsx b/web/src/components/ArtifactPanel.tsx index d6ec891..4167f75 100644 --- a/web/src/components/ArtifactPanel.tsx +++ b/web/src/components/ArtifactPanel.tsx @@ -4,8 +4,25 @@ import { useState } from "react"; import { getArtifactReadUrl } from "@/lib/blob-client"; import type { ArtifactSummary } from "@/lib/types"; -export function ArtifactPanel({ artifacts }: { artifacts: ArtifactSummary[] }) { +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 [title, setTitle] = useState(""); + const [artifactType, setArtifactType] = useState<"file" | "summary" | "extraction" | "citation" | "export">("file"); + const [description, setDescription] = useState(""); + const [blobPath, setBlobPath] = useState(""); async function handleOpenArtifact(artifact: ArtifactSummary) { if (!artifact.blobPath) { @@ -21,12 +38,89 @@ export function ArtifactPanel({ artifacts }: { artifacts: ArtifactSummary[] }) { } } + 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(""); + } + return (
Artifacts
blob-backed view
+
+
+ + setTitle(event.target.value)} + placeholder="Artifact title" + /> +
+
+ + setBlobPath(event.target.value)} + placeholder="Optional blob path" + /> +
+