mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-26 23:51:14 +02:00
feat: open runs in the citation panel
This commit is contained in:
parent
979ad02ef3
commit
b86eadb847
3 changed files with 88 additions and 13 deletions
|
|
@ -1,14 +1,19 @@
|
|||
import { atom } from "jotai";
|
||||
import { atom, type Getter, type Setter } from "jotai";
|
||||
import { rightPanelCollapsedAtom, rightPanelTabAtom } from "@/atoms/layout/right-panel.atom";
|
||||
|
||||
/** The source the citation panel is showing: a KB chunk or a scraper run. */
|
||||
export type CitationTarget =
|
||||
| { kind: "chunk"; chunkId: number }
|
||||
| { kind: "run"; runId: string };
|
||||
|
||||
interface CitationPanelState {
|
||||
isOpen: boolean;
|
||||
chunkId: number | null;
|
||||
target: CitationTarget | null;
|
||||
}
|
||||
|
||||
const initialState: CitationPanelState = {
|
||||
isOpen: false,
|
||||
chunkId: null,
|
||||
target: null,
|
||||
};
|
||||
|
||||
export const citationPanelAtom = atom<CitationPanelState>(initialState);
|
||||
|
|
@ -17,16 +22,21 @@ export const citationPanelOpenAtom = atom((get) => get(citationPanelAtom).isOpen
|
|||
|
||||
const preCitationCollapsedAtom = atom<boolean | null>(null);
|
||||
|
||||
export const openCitationPanelAtom = atom(null, (get, set, payload: { chunkId: number }) => {
|
||||
function openWithTarget(get: Getter, set: Setter, target: CitationTarget) {
|
||||
if (!get(citationPanelAtom).isOpen) {
|
||||
set(preCitationCollapsedAtom, get(rightPanelCollapsedAtom));
|
||||
}
|
||||
set(citationPanelAtom, {
|
||||
isOpen: true,
|
||||
chunkId: payload.chunkId,
|
||||
});
|
||||
set(citationPanelAtom, { isOpen: true, target });
|
||||
set(rightPanelTabAtom, "citation");
|
||||
set(rightPanelCollapsedAtom, false);
|
||||
}
|
||||
|
||||
export const openCitationPanelAtom = atom(null, (get, set, payload: { chunkId: number }) => {
|
||||
openWithTarget(get, set, { kind: "chunk", chunkId: payload.chunkId });
|
||||
});
|
||||
|
||||
export const openRunCitationPanelAtom = atom(null, (get, set, payload: { runId: string }) => {
|
||||
openWithTarget(get, set, { kind: "run", runId: payload.runId });
|
||||
});
|
||||
|
||||
export const closeCitationPanelAtom = atom(null, (get, set) => {
|
||||
|
|
|
|||
47
surfsense_web/components/citations/run-citation-panel.tsx
Normal file
47
surfsense_web/components/citations/run-citation-panel.tsx
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import { XIcon } from "lucide-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import type { FC } from "react";
|
||||
import { RunDetail } from "@/app/dashboard/[workspace_id]/playground/components/run-detail";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
/** Right-panel viewer for a cited scraper run. `runId` is the `run_<uuid>` handle. */
|
||||
export const RunCitationPanelContent: FC<{ runId: string; onClose?: () => void }> = ({
|
||||
runId,
|
||||
onClose,
|
||||
}) => {
|
||||
const params = useParams();
|
||||
const rawWorkspaceId = Array.isArray(params?.workspace_id)
|
||||
? params.workspace_id[0]
|
||||
: params?.workspace_id;
|
||||
const workspaceId = Number(rawWorkspaceId);
|
||||
const scraperRunId = runId.replace(/^run_/, "");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="shrink-0 flex h-12 items-center justify-between px-3 border-b">
|
||||
<h2 className="select-none text-lg font-semibold">Scraper run</h2>
|
||||
{onClose && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onClose}
|
||||
className="h-8 w-8 rounded-full shrink-0 text-muted-foreground hover:text-accent-foreground"
|
||||
>
|
||||
<XIcon className="h-4 w-4" />
|
||||
<span className="sr-only">Close run panel</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
{Number.isFinite(workspaceId) ? (
|
||||
<RunDetail workspaceId={workspaceId} runId={scraperRunId} />
|
||||
) : (
|
||||
<p className="p-4 text-sm text-muted-foreground">Open a workspace to view this run.</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -35,6 +35,14 @@ const CitationPanelContent = dynamic(
|
|||
{ ssr: false, loading: () => null }
|
||||
);
|
||||
|
||||
const RunCitationPanelContent = dynamic(
|
||||
() =>
|
||||
import("@/components/citations/run-citation-panel").then((m) => ({
|
||||
default: m.RunCitationPanelContent,
|
||||
})),
|
||||
{ ssr: false, loading: () => null }
|
||||
);
|
||||
|
||||
const HitlEditPanelContent = dynamic(
|
||||
() =>
|
||||
import("@/features/chat-messages/hitl").then((m) => ({
|
||||
|
|
@ -89,7 +97,7 @@ export function RightPanelToggleButton({
|
|||
? !!editorState.memoryScope
|
||||
: !!editorState.localFilePath);
|
||||
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
|
||||
const citationOpen = citationState.isOpen && citationState.chunkId != null;
|
||||
const citationOpen = citationState.isOpen && citationState.target != null;
|
||||
const hasContent = reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen;
|
||||
const label = collapsed ? "Expand panel" : "Collapse panel";
|
||||
|
||||
|
|
@ -141,7 +149,7 @@ export function RightPanelExpandButton() {
|
|||
? !!editorState.memoryScope
|
||||
: !!editorState.localFilePath);
|
||||
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
|
||||
const citationOpen = citationState.isOpen && citationState.chunkId != null;
|
||||
const citationOpen = citationState.isOpen && citationState.target != null;
|
||||
const hasContent = reportOpen || editorOpen || hitlEditOpen || citationOpen || artifactsOpen;
|
||||
|
||||
if (!collapsed || !hasContent) return null;
|
||||
|
|
@ -212,7 +220,7 @@ export function RightPanel({ showTopBorder = false }: RightPanelProps) {
|
|||
? !!editorState.memoryScope
|
||||
: !!editorState.localFilePath);
|
||||
const hitlEditOpen = hitlEditState.isOpen && !!hitlEditState.onSave;
|
||||
const citationOpen = citationState.isOpen && citationState.chunkId != null;
|
||||
const citationOpen = citationState.isOpen && citationState.target != null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!reportOpen && !editorOpen && !hitlEditOpen && !citationOpen && !artifactsOpen) return;
|
||||
|
|
@ -306,9 +314,19 @@ export function RightPanel({ showTopBorder = false }: RightPanelProps) {
|
|||
/>
|
||||
</div>
|
||||
)}
|
||||
{effectiveTab === "citation" && citationOpen && citationState.chunkId != null && (
|
||||
{effectiveTab === "citation" && citationOpen && citationState.target && (
|
||||
<div className="h-full flex flex-col">
|
||||
<CitationPanelContent chunkId={citationState.chunkId} onClose={closeCitation} />
|
||||
{citationState.target.kind === "run" ? (
|
||||
<RunCitationPanelContent
|
||||
runId={citationState.target.runId}
|
||||
onClose={closeCitation}
|
||||
/>
|
||||
) : (
|
||||
<CitationPanelContent
|
||||
chunkId={citationState.target.chunkId}
|
||||
onClose={closeCitation}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{effectiveTab === "artifacts" && artifactsOpen && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue