mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-12 22:42:13 +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
|
status: PodcastStatus
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
search_space_id: int
|
search_space_id: int
|
||||||
|
thread_id: int | None = None
|
||||||
|
|
||||||
|
|
||||||
class PodcastDetail(BaseModel):
|
class PodcastDetail(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class ReportRead(BaseModel):
|
||||||
report_metadata: dict[str, Any] | None = None
|
report_metadata: dict[str, Any] | None = None
|
||||||
report_group_id: int | None = None
|
report_group_id: int | None = None
|
||||||
content_type: str = "markdown"
|
content_type: str = "markdown"
|
||||||
|
thread_id: int | None = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ class VideoPresentationRead(VideoPresentationBase):
|
||||||
status: VideoPresentationStatusEnum = VideoPresentationStatusEnum.READY
|
status: VideoPresentationStatusEnum = VideoPresentationStatusEnum.READY
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
slide_count: int | None = None
|
slide_count: int | None = None
|
||||||
|
thread_id: int | None = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
@ -68,6 +69,7 @@ class VideoPresentationRead(VideoPresentationBase):
|
||||||
"status": obj.status,
|
"status": obj.status,
|
||||||
"created_at": obj.created_at,
|
"created_at": obj.created_at,
|
||||||
"slide_count": len(obj.slides) if obj.slides else None,
|
"slide_count": len(obj.slides) if obj.slides else None,
|
||||||
|
"thread_id": obj.thread_id,
|
||||||
}
|
}
|
||||||
return cls(**data)
|
return cls(**data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ export const podcastSummary = z.object({
|
||||||
status: podcastStatus,
|
status: podcastStatus,
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
search_space_id: z.number(),
|
search_space_id: z.number(),
|
||||||
|
thread_id: z.number().nullish(),
|
||||||
});
|
});
|
||||||
export type PodcastSummary = z.infer<typeof podcastSummary>;
|
export type PodcastSummary = z.infer<typeof podcastSummary>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ export const reportListItem = z.object({
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
content_type: z.string().default("markdown"),
|
content_type: z.string().default("markdown"),
|
||||||
report_metadata: reportMetadata,
|
report_metadata: reportMetadata,
|
||||||
|
thread_id: z.number().nullish(),
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
});
|
});
|
||||||
export type ReportListItem = z.infer<typeof reportListItem>;
|
export type ReportListItem = z.infer<typeof reportListItem>;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export const videoPresentationListItem = z.object({
|
||||||
status: videoPresentationStatus.default("ready"),
|
status: videoPresentationStatus.default("ready"),
|
||||||
created_at: z.string(),
|
created_at: z.string(),
|
||||||
search_space_id: z.number(),
|
search_space_id: z.number(),
|
||||||
|
thread_id: z.number().nullish(),
|
||||||
});
|
});
|
||||||
export type VideoPresentationListItem = z.infer<typeof videoPresentationListItem>;
|
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",
|
status: report.report_metadata?.status === "failed" ? "error" : "ready",
|
||||||
createdAt: report.created_at,
|
createdAt: report.created_at,
|
||||||
contentType: isResume ? "typst" : "markdown",
|
contentType: isResume ? "typst" : "markdown",
|
||||||
|
sourceThreadId: report.thread_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,6 +52,7 @@ async function fetchLibraryArtifacts(searchSpaceId: number): Promise<LibraryArti
|
||||||
status: podcastStatus(podcast.status),
|
status: podcastStatus(podcast.status),
|
||||||
createdAt: podcast.created_at,
|
createdAt: podcast.created_at,
|
||||||
contentType: "markdown",
|
contentType: "markdown",
|
||||||
|
sourceThreadId: podcast.thread_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,6 +65,7 @@ async function fetchLibraryArtifacts(searchSpaceId: number): Promise<LibraryArti
|
||||||
status: videoStatus(video.status),
|
status: videoStatus(video.status),
|
||||||
createdAt: video.created_at,
|
createdAt: video.created_at,
|
||||||
contentType: "markdown",
|
contentType: "markdown",
|
||||||
|
sourceThreadId: video.thread_id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,6 @@ export interface LibraryArtifact {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** Report panel content type — "typst" for resumes, "markdown" otherwise. */
|
/** Report panel content type — "typst" for resumes, "markdown" otherwise. */
|
||||||
contentType: "markdown" | "typst";
|
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 { formatRelativeDate } from "@/lib/format-date";
|
||||||
import type { LibraryArtifact } from "../model/artifact";
|
import type { LibraryArtifact } from "../model/artifact";
|
||||||
import { KIND_META } from "./kind-meta";
|
import { KIND_META } from "./kind-meta";
|
||||||
|
|
||||||
export function ArtifactCard({
|
export function ArtifactCard({
|
||||||
artifact,
|
artifact,
|
||||||
|
searchSpaceId,
|
||||||
onOpen,
|
onOpen,
|
||||||
}: {
|
}: {
|
||||||
artifact: LibraryArtifact;
|
artifact: LibraryArtifact;
|
||||||
|
searchSpaceId: number;
|
||||||
onOpen: (artifact: LibraryArtifact) => void;
|
onOpen: (artifact: LibraryArtifact) => void;
|
||||||
}) {
|
}) {
|
||||||
const meta = KIND_META[artifact.kind];
|
const meta = KIND_META[artifact.kind];
|
||||||
|
|
@ -20,11 +24,16 @@ export function ArtifactCard({
|
||||||
: meta.label;
|
: meta.label;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<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">
|
||||||
type="button"
|
{/* Stretched overlay makes the whole card open the viewer; sibling controls sit above it via z-10. */}
|
||||||
onClick={() => onOpen(artifact)}
|
<button
|
||||||
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"
|
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">
|
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
||||||
<Icon className="size-4" />
|
<Icon className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -38,6 +47,17 @@ export function ArtifactCard({
|
||||||
<span>{formatRelativeDate(artifact.createdAt)}</span>
|
<span>{formatRelativeDate(artifact.createdAt)}</span>
|
||||||
</span>
|
</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>
|
</h2>
|
||||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
{items.map((artifact) => (
|
{items.map((artifact) => (
|
||||||
<ArtifactCard key={artifact.key} artifact={artifact} onOpen={handleOpen} />
|
<ArtifactCard
|
||||||
|
key={artifact.key}
|
||||||
|
artifact={artifact}
|
||||||
|
searchSpaceId={searchSpaceId}
|
||||||
|
onOpen={handleOpen}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue