mirror of
https://github.com/usestrix/strix.git
synced 2026-08-17 09:29:49 +02:00
Open agent details in a modal
This commit is contained in:
+91
-86
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Strix Results</title>
|
||||
<script type="module" crossorigin src="./assets/index-C0ezPxZx.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-nWrlYz6N.css">
|
||||
<script type="module" crossorigin src="./assets/index-BQfxrWUY.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-Di2TwyAB.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -16,7 +16,8 @@ import VulnerabilityDetail from "@/components/vulnerability/VulnerabilityDetail"
|
||||
import { ContentSection } from "@/components/vulnerability/ContentSection";
|
||||
import { IssueSeveritySummary } from "@/components/IssueSeveritySummary";
|
||||
import AgentGraph from "@/components/live/AgentGraph";
|
||||
import { AgentTranscript, buildGraphAgents } from "@/components/live/AgentTranscript";
|
||||
import { buildGraphAgents } from "@/components/live/AgentTranscript";
|
||||
import AgentDetailModal from "@/components/live/AgentDetailModal";
|
||||
import { severityCounts, type ParsedRunSummary } from "@/lib/local-run-parser";
|
||||
import { fetchAll, fetchRunSummary, fetchTranscript, fetchVulnerabilities, type LoadedRun } from "@/data/serverSource";
|
||||
|
||||
@@ -456,12 +457,10 @@ function UpsellRow() {
|
||||
function AgentsTab({ run }: { run: LoadedRun }) {
|
||||
const { agents, events } = run.transcript;
|
||||
const graphAgents = useMemo(() => buildGraphAgents(agents, events), [agents, events]);
|
||||
// Default the transcript to the root agent (no parent) so something is always
|
||||
// shown; the graph selection then drives which agent's transcript renders.
|
||||
const rootId = useMemo(() => agents.find((a) => !a.parent_id)?.id ?? agents[0]?.id ?? null, [agents]);
|
||||
// Clicking a graph node opens the agent's transcript in a modal (matching the
|
||||
// cloud app); no node selected means no modal.
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null);
|
||||
const activeId = selectedId ?? rootId;
|
||||
const activeAgent = agents.find((a) => a.id === activeId) ?? null;
|
||||
const selectedAgent = selectedId ? (agents.find((a) => a.id === selectedId) ?? null) : null;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@@ -474,12 +473,12 @@ function AgentsTab({ run }: { run: LoadedRun }) {
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 mb-4 text-xs text-[#666]">
|
||||
Select an agent to view its full transcript below.
|
||||
Click an agent to open its full transcript.
|
||||
</p>
|
||||
<div className="h-[480px] rounded-lg border border-[#1a1a1a] overflow-hidden">
|
||||
<AgentGraph
|
||||
agents={graphAgents}
|
||||
selectedAgentId={activeId}
|
||||
selectedAgentId={selectedId}
|
||||
onSelectAgent={(id) => setSelectedId(id)}
|
||||
eventsLoaded
|
||||
eventsEmpty={graphAgents.size === 0}
|
||||
@@ -488,10 +487,12 @@ function AgentsTab({ run }: { run: LoadedRun }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeAgent && (
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<AgentTranscript agent={activeAgent} events={events} />
|
||||
</div>
|
||||
{selectedAgent && (
|
||||
<AgentDetailModal
|
||||
agent={selectedAgent}
|
||||
events={events}
|
||||
onClose={() => setSelectedId(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useEffect } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { AgentTranscript } from "./AgentTranscript";
|
||||
import type { TranscriptAgent, TranscriptEvent } from "@/data/serverSource";
|
||||
|
||||
/**
|
||||
* Overlay modal showing a single agent's full transcript. Matches the cloud
|
||||
* app, where clicking a node in the agent graph opens a modal (rather than
|
||||
* revealing a section beneath the graph). Closes on backdrop click, the X
|
||||
* button, or Escape.
|
||||
*/
|
||||
export function AgentDetailModal({
|
||||
agent,
|
||||
events,
|
||||
onClose,
|
||||
}: {
|
||||
agent: TranscriptAgent;
|
||||
events: TranscriptEvent[];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("keydown", onKey);
|
||||
const prevOverflow = document.body.style.overflow;
|
||||
document.body.style.overflow = "hidden";
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKey);
|
||||
document.body.style.overflow = prevOverflow;
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-start justify-center overflow-y-auto bg-black/70 p-4 sm:p-8"
|
||||
onClick={onClose}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={`Agent ${agent.name}`}
|
||||
>
|
||||
<div
|
||||
className="relative my-auto w-full max-w-3xl rounded-xl border border-[#222] bg-[#0a0a0a] shadow-2xl"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="absolute right-3 top-3 z-10 rounded-md p-1 text-[#888] transition-colors hover:bg-[#1a1a1a] hover:text-white"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
<div className="max-h-[85vh] overflow-y-auto p-5">
|
||||
<AgentTranscript agent={agent} events={events} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AgentDetailModal;
|
||||
Reference in New Issue
Block a user