feat: link artifacts to source chat

This commit is contained in:
CREDO23 2026-06-23 15:47:21 +02:00
parent 9c622ae3f3
commit 8b0a2f8964
10 changed files with 44 additions and 7 deletions

View file

@ -39,6 +39,7 @@ async function fetchLibraryArtifacts(searchSpaceId: number): Promise<LibraryArti
status: report.report_metadata?.status === "failed" ? "error" : "ready",
createdAt: report.created_at,
contentType: isResume ? "typst" : "markdown",
sourceThreadId: report.thread_id,
});
}
@ -51,6 +52,7 @@ async function fetchLibraryArtifacts(searchSpaceId: number): Promise<LibraryArti
status: podcastStatus(podcast.status),
createdAt: podcast.created_at,
contentType: "markdown",
sourceThreadId: podcast.thread_id,
});
}
@ -63,6 +65,7 @@ async function fetchLibraryArtifacts(searchSpaceId: number): Promise<LibraryArti
status: videoStatus(video.status),
createdAt: video.created_at,
contentType: "markdown",
sourceThreadId: video.thread_id,
});
}

View file

@ -18,4 +18,6 @@ export interface LibraryArtifact {
createdAt: string;
/** Report panel content type — "typst" for resumes, "markdown" otherwise. */
contentType: "markdown" | "typst";
/** Chat thread that produced this artifact, when the source recorded one. */
sourceThreadId?: number | null;
}

View file

@ -1,12 +1,16 @@
import { MessageSquareText } from "lucide-react";
import Link from "next/link";
import { formatRelativeDate } from "@/lib/format-date";
import type { LibraryArtifact } from "../model/artifact";
import { KIND_META } from "./kind-meta";
export function ArtifactCard({
artifact,
searchSpaceId,
onOpen,
}: {
artifact: LibraryArtifact;
searchSpaceId: number;
onOpen: (artifact: LibraryArtifact) => void;
}) {
const meta = KIND_META[artifact.kind];
@ -20,11 +24,16 @@ export function ArtifactCard({
: meta.label;
return (
<button
type="button"
onClick={() => onOpen(artifact)}
className="group flex w-full items-start gap-3 rounded-xl border bg-card p-3 text-left transition-colors hover:border-primary/40 hover:bg-accent/50"
>
<div className="group relative flex items-start gap-3 rounded-xl border bg-card p-3 transition-colors hover:border-primary/40 hover:bg-accent/50">
{/* Stretched overlay makes the whole card open the viewer; sibling controls sit above it via z-10. */}
<button
type="button"
onClick={() => onOpen(artifact)}
className="absolute inset-0 rounded-xl"
>
<span className="sr-only">Open {artifact.title}</span>
</button>
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
<Icon className="size-4" />
</span>
@ -38,6 +47,17 @@ export function ArtifactCard({
<span>{formatRelativeDate(artifact.createdAt)}</span>
</span>
</span>
</button>
{artifact.sourceThreadId ? (
<Link
href={`/dashboard/${searchSpaceId}/new-chat/${artifact.sourceThreadId}`}
title="Open source chat"
className="relative z-10 flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground focus-visible:opacity-100 group-hover:opacity-100"
>
<MessageSquareText className="size-4" />
<span className="sr-only">Open source chat</span>
</Link>
) : null}
</div>
);
}

View file

@ -121,7 +121,12 @@ export function ArtifactsLibrary({ searchSpaceId }: { searchSpaceId: number }) {
</h2>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{items.map((artifact) => (
<ArtifactCard key={artifact.key} artifact={artifact} onOpen={handleOpen} />
<ArtifactCard
key={artifact.key}
artifact={artifact}
searchSpaceId={searchSpaceId}
onOpen={handleOpen}
/>
))}
</div>
</section>