diff --git a/surfsense_web/app/dashboard/[search_space_id]/artifacts/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/artifacts/page.tsx
new file mode 100644
index 000000000..8f8109156
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/artifacts/page.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import { useParams } from "next/navigation";
+import { ArtifactsLibrary } from "@/features/artifacts-library";
+
+export default function ArtifactsPage() {
+ const params = useParams();
+ const searchSpaceId = Number(params.search_space_id);
+
+ return ;
+}
diff --git a/surfsense_web/features/artifacts-library/index.ts b/surfsense_web/features/artifacts-library/index.ts
new file mode 100644
index 000000000..f086f50ae
--- /dev/null
+++ b/surfsense_web/features/artifacts-library/index.ts
@@ -0,0 +1 @@
+export { ArtifactsLibrary } from "./ui/artifacts-library";
diff --git a/surfsense_web/features/artifacts-library/ui/artifacts-library.tsx b/surfsense_web/features/artifacts-library/ui/artifacts-library.tsx
new file mode 100644
index 000000000..0c354c331
--- /dev/null
+++ b/surfsense_web/features/artifacts-library/ui/artifacts-library.tsx
@@ -0,0 +1,137 @@
+"use client";
+
+import { useSetAtom } from "jotai";
+import { Boxes, RefreshCw, TriangleAlert } from "lucide-react";
+import { useMemo, useState } from "react";
+import { openReportPanelAtom } from "@/atoms/chat/report-panel.atom";
+import { MobileReportPanel } from "@/components/report-panel/report-panel";
+import { Button } from "@/components/ui/button";
+import { useLibraryArtifacts } from "../hooks/use-library-artifacts";
+import type { LibraryArtifact, LibraryArtifactKind } from "../model/artifact";
+import { ArtifactCard } from "./artifact-card";
+import { KIND_META, KIND_ORDER } from "./kind-meta";
+import { MediaViewerDialog } from "./media-viewer-dialog";
+
+const SKELETON_KEYS = ["s1", "s2", "s3", "s4", "s5", "s6"];
+
+function LoadingState() {
+ return (
+
+ {SKELETON_KEYS.map((key) => (
+
+ ))}
+
+ );
+}
+
+function ErrorState({ onRetry }: { onRetry: () => void }) {
+ return (
+
+
+
+
+
+
Couldn't load artifacts
+
+ Something went wrong fetching this search space's deliverables.
+
+
+
+
+ );
+}
+
+function EmptyState() {
+ return (
+
+
+
+
+
+
No artifacts yet
+
+ Reports, resumes, podcasts, presentations, and images you generate appear here.
+
+
+
+ );
+}
+
+export function ArtifactsLibrary({ searchSpaceId }: { searchSpaceId: number }) {
+ const { artifacts, loading, error, refresh } = useLibraryArtifacts(searchSpaceId);
+ const openReportPanel = useSetAtom(openReportPanelAtom);
+ const [selectedMedia, setSelectedMedia] = useState(null);
+
+ const grouped = useMemo(() => {
+ const map = new Map();
+ for (const artifact of artifacts) {
+ const bucket = map.get(artifact.kind);
+ if (bucket) bucket.push(artifact);
+ else map.set(artifact.kind, [artifact]);
+ }
+ return map;
+ }, [artifacts]);
+
+ const handleOpen = (artifact: LibraryArtifact) => {
+ // Reports/resumes reuse the shared report panel; the rest open in the dialog.
+ if (artifact.kind === "report" || artifact.kind === "resume") {
+ openReportPanel({
+ reportId: artifact.entityId,
+ title: artifact.title,
+ contentType: artifact.contentType,
+ });
+ return;
+ }
+ setSelectedMedia(artifact);
+ };
+
+ return (
+
+
+
+ {loading ? (
+
+ ) : error ? (
+
refresh()} />
+ ) : artifacts.length === 0 ? (
+
+ ) : (
+
+ {KIND_ORDER.map((kind) => {
+ const items = grouped.get(kind);
+ if (!items || items.length === 0) return null;
+ return (
+
+
+ {KIND_META[kind].group}
+ {items.length}
+
+
+ {items.map((artifact) => (
+
+ ))}
+
+
+ );
+ })}
+
+ )}
+
+ setSelectedMedia(null)} />
+
+
+ );
+}