mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-26 21:39:43 +02:00
feat: link artifacts to source chat
This commit is contained in:
parent
9c622ae3f3
commit
8b0a2f8964
10 changed files with 44 additions and 7 deletions
|
|
@ -84,6 +84,7 @@ class PodcastSummary(BaseModel):
|
|||
status: PodcastStatus
|
||||
created_at: datetime
|
||||
search_space_id: int
|
||||
thread_id: int | None = None
|
||||
|
||||
|
||||
class PodcastDetail(BaseModel):
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class ReportRead(BaseModel):
|
|||
report_metadata: dict[str, Any] | None = None
|
||||
report_group_id: int | None = None
|
||||
content_type: str = "markdown"
|
||||
thread_id: int | None = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ class VideoPresentationRead(VideoPresentationBase):
|
|||
status: VideoPresentationStatusEnum = VideoPresentationStatusEnum.READY
|
||||
created_at: datetime
|
||||
slide_count: int | None = None
|
||||
thread_id: int | None = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -68,6 +69,7 @@ class VideoPresentationRead(VideoPresentationBase):
|
|||
"status": obj.status,
|
||||
"created_at": obj.created_at,
|
||||
"slide_count": len(obj.slides) if obj.slides else None,
|
||||
"thread_id": obj.thread_id,
|
||||
}
|
||||
return cls(**data)
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ export const podcastSummary = z.object({
|
|||
status: podcastStatus,
|
||||
created_at: z.string(),
|
||||
search_space_id: z.number(),
|
||||
thread_id: z.number().nullish(),
|
||||
});
|
||||
export type PodcastSummary = z.infer<typeof podcastSummary>;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const reportListItem = z.object({
|
|||
title: z.string(),
|
||||
content_type: z.string().default("markdown"),
|
||||
report_metadata: reportMetadata,
|
||||
thread_id: z.number().nullish(),
|
||||
created_at: z.string(),
|
||||
});
|
||||
export type ReportListItem = z.infer<typeof reportListItem>;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export const videoPresentationListItem = z.object({
|
|||
status: videoPresentationStatus.default("ready"),
|
||||
created_at: z.string(),
|
||||
search_space_id: z.number(),
|
||||
thread_id: z.number().nullish(),
|
||||
});
|
||||
export type VideoPresentationListItem = z.infer<typeof videoPresentationListItem>;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue