diff --git a/surfsense_web/features/artifacts-library/hooks/use-library-artifacts.ts b/surfsense_web/features/artifacts-library/hooks/use-library-artifacts.ts new file mode 100644 index 000000000..15f65d9b0 --- /dev/null +++ b/surfsense_web/features/artifacts-library/hooks/use-library-artifacts.ts @@ -0,0 +1,95 @@ +import { useQuery } from "@tanstack/react-query"; +import { imageGenerationsApiService } from "@/lib/apis/image-generations-api.service"; +import { podcastsApiService } from "@/lib/apis/podcasts-api.service"; +import { reportsApiService } from "@/lib/apis/reports-api.service"; +import { videoPresentationsApiService } from "@/lib/apis/video-presentations-api.service"; +import type { LibraryArtifact, LibraryArtifactStatus } from "../model/artifact"; + +function podcastStatus(status: string): LibraryArtifactStatus { + if (status === "ready") return "ready"; + if (status === "failed" || status === "cancelled") return "error"; + return "running"; +} + +function videoStatus(status: string): LibraryArtifactStatus { + if (status === "ready") return "ready"; + if (status === "failed") return "error"; + return "running"; +} + +// Each list is fetched independently; one failing source shouldn't blank the +// whole library, so failures degrade to an empty slice. +async function fetchLibraryArtifacts(searchSpaceId: number): Promise { + const [reports, podcasts, videos, images] = await Promise.all([ + reportsApiService.list(searchSpaceId).catch(() => []), + podcastsApiService.list(searchSpaceId).catch(() => []), + videoPresentationsApiService.list(searchSpaceId).catch(() => []), + imageGenerationsApiService.list(searchSpaceId).catch(() => []), + ]); + + const artifacts: LibraryArtifact[] = []; + + for (const report of reports) { + const isResume = report.content_type === "typst"; + artifacts.push({ + key: `report-${report.id}`, + kind: isResume ? "resume" : "report", + entityId: report.id, + title: report.title, + status: report.report_metadata?.status === "failed" ? "error" : "ready", + createdAt: report.created_at, + contentType: isResume ? "typst" : "markdown", + }); + } + + for (const podcast of podcasts) { + artifacts.push({ + key: `podcast-${podcast.id}`, + kind: "podcast", + entityId: podcast.id, + title: podcast.title, + status: podcastStatus(podcast.status), + createdAt: podcast.created_at, + contentType: "markdown", + }); + } + + for (const video of videos) { + artifacts.push({ + key: `video-${video.id}`, + kind: "video", + entityId: video.id, + title: video.title, + status: videoStatus(video.status), + createdAt: video.created_at, + contentType: "markdown", + }); + } + + for (const image of images) { + artifacts.push({ + key: `image-${image.id}`, + kind: "image", + entityId: image.id, + title: image.prompt, + status: image.is_success ? "ready" : "error", + createdAt: image.created_at, + contentType: "markdown", + }); + } + + return artifacts.sort( + (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); +} + +export function useLibraryArtifacts(searchSpaceId: number) { + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ["artifacts-library", searchSpaceId], + queryFn: () => fetchLibraryArtifacts(searchSpaceId), + enabled: Number.isFinite(searchSpaceId) && searchSpaceId > 0, + staleTime: 60 * 1000, + }); + + return { artifacts: data ?? [], loading: isLoading, error, refresh: refetch }; +}