Skip to content

Commit 555b77e

Browse files
committed
refactor(webapp): split the agent gallery into pages and prune its states
The card catalogs move out of Chat UI onto their own storybook pages (view blocks, report view, investigation card, watch card), all sharing the manifest and the page shell. Cuts near-duplicate states: 97 sections down to 52, section ids unchanged. The screenshot script now walks every page.
1 parent 6650b94 commit 555b77e

10 files changed

Lines changed: 1200 additions & 1439 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {
2+
INVESTIGATION_CAPABILITIES_VERSION,
3+
type InvestigationCapabilities,
4+
} from "@internal/dashboard-agent-contracts";
5+
import { demoFixtures } from "~/components/dashboard-agent/demo";
6+
import { InvestigationCard } from "~/components/dashboard-agent/InvestigationCard";
7+
import { investigationBlock } from "../storybook.agent-ui/fixtures";
8+
import { fixtureResolveUri, GalleryPage, noop } from "../storybook.agent-ui/gallery";
9+
10+
/**
11+
* The investigation card, one state per ending it can reach. The shell and the
12+
* manifest live in `../storybook.agent-ui`.
13+
*
14+
* The card only, so the unfinished state shows no progress line here: progress
15+
* belongs to the transcript, which mounts one line for the whole turn — see
16+
* `messages-investigation-live` on the Chat UI page.
17+
*/
18+
19+
const { demoInvestigations } = demoFixtures;
20+
21+
/**
22+
* The actions the executor would attach to each settled card — written out here
23+
* because they are server-decided and the fixtures deliberately don't carry
24+
* them. "Show code" hangs on a source citation the agent read at the pinned
25+
* commit, which is exactly what separates the two concluded states below. Both
26+
* actions point at URIs the card already cites, the way the executor builds
27+
* them: an action can only ever target evidence the investigation resolved.
28+
*/
29+
const citedUri = (
30+
fixture: (typeof demoInvestigations)[keyof typeof demoInvestigations],
31+
kind: string
32+
) => fixture.evidence.find((evidence) => evidence.kind === kind)!.uri;
33+
34+
const codeGroundedCapabilities: InvestigationCapabilities = {
35+
version: INVESTIGATION_CAPABILITIES_VERSION,
36+
actions: [
37+
{
38+
kind: "show_code",
39+
label: "Show code",
40+
intent: {
41+
kind: "ask",
42+
prompt:
43+
"Show me the code behind this and propose the minimal fix as a fenced diff, anchored to the file, line and commit you read.",
44+
},
45+
},
46+
{
47+
kind: "view_similar",
48+
label: "View similar failures",
49+
intent: { kind: "navigate", target: citedUri(demoInvestigations.concluded, "error") },
50+
},
51+
],
52+
};
53+
54+
// Nothing was read, so there is no code to show — only the follow-up that needs
55+
// no source at all.
56+
const notCodeGroundedCapabilities: InvestigationCapabilities = {
57+
version: INVESTIGATION_CAPABILITIES_VERSION,
58+
actions: [
59+
{
60+
kind: "view_similar",
61+
label: "View the queue",
62+
intent: { kind: "navigate", target: citedUri(demoInvestigations.concludedNoCode, "queue") },
63+
},
64+
],
65+
};
66+
67+
const STATES: Record<string, React.ReactNode> = {
68+
"investigation-card-streaming-rev1": (
69+
<InvestigationCard block={investigationBlock(demoInvestigations.streamingRev1)} />
70+
),
71+
"investigation-card-concluded": (
72+
<InvestigationCard block={investigationBlock(demoInvestigations.concluded)} />
73+
),
74+
"investigation-card-concluded-code-grounded": (
75+
<InvestigationCard
76+
block={investigationBlock(demoInvestigations.concluded, codeGroundedCapabilities)}
77+
resolveUri={fixtureResolveUri}
78+
onIntent={noop}
79+
/>
80+
),
81+
"investigation-card-concluded-not-code-grounded": (
82+
<InvestigationCard
83+
block={investigationBlock(demoInvestigations.concludedNoCode, notCodeGroundedCapabilities)}
84+
resolveUri={fixtureResolveUri}
85+
onIntent={noop}
86+
/>
87+
),
88+
"investigation-card-inconclusive": (
89+
<InvestigationCard
90+
block={investigationBlock(demoInvestigations.inconclusive)}
91+
defaultExpanded
92+
resolveUri={fixtureResolveUri}
93+
/>
94+
),
95+
"investigation-card-degraded": (
96+
<InvestigationCard
97+
block={investigationBlock(demoInvestigations.degraded)}
98+
defaultExpanded
99+
resolveUri={fixtureResolveUri}
100+
/>
101+
),
102+
};
103+
104+
export default function Story() {
105+
return <GalleryPage page="investigation" states={STATES} />;
106+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { ReportViewModelPayload } from "@internal/dashboard-agent-contracts";
2+
import { demoFixtures, demoId } from "~/components/dashboard-agent/demo";
3+
import { ReportView } from "~/components/dashboard-agent/ReportView";
4+
import { chatItems } from "../storybook.agent-ui/fixtures";
5+
import { fixtureResolveUri, GalleryPage, noop } from "../storybook.agent-ui/gallery";
6+
7+
/**
8+
* The health report, one state per verdict. The shell and the manifest live in
9+
* `../storybook.agent-ui`.
10+
*/
11+
12+
// The report items of the demo conversations, for the source URI each card cites.
13+
const reportItems = chatItems(demoId("report-healthy"), "report").concat(
14+
chatItems(demoId("report-degraded"), "report")
15+
);
16+
17+
/**
18+
* The two demo VMs cover healthy and degraded; the third is derived here because
19+
* no fixture conversation shows it: when telemetry goes stale the interpreter
20+
* marks flow AND execution "unknown", strips every actionable field, and flags
21+
* the snapshot `trustworthy: false` — the one state where the card must show
22+
* numbers while refusing to advise on them. Derived exactly the way
23+
* `applyStaleGuard` does it, so the shape is real.
24+
*/
25+
const untrustworthyReport: ReportViewModelPayload = {
26+
...demoFixtures.demoDegradedReport,
27+
summary: {
28+
severity: "crit",
29+
statements: [
30+
{ findingType: "flow", severity: "crit", reason: "unknown" },
31+
{ findingType: "execution", severity: "crit", reason: "unknown" },
32+
{ findingType: "liveness", severity: "crit" },
33+
],
34+
},
35+
findings: demoFixtures.demoDegradedReport.findings.map((finding) =>
36+
finding.type === "liveness"
37+
? {
38+
...finding,
39+
severity: "crit",
40+
reason: "stale",
41+
recommendation: { code: "check_control_plane", link: "status" },
42+
}
43+
: {
44+
...finding,
45+
severity: "crit",
46+
reason: "unknown",
47+
recommendation: undefined,
48+
attribution: undefined,
49+
exclusions: undefined,
50+
observations: undefined,
51+
hedge: undefined,
52+
anomalyWindow: undefined,
53+
}
54+
),
55+
metrics: demoFixtures.demoDegradedReport.metrics.map((metric) =>
56+
metric.id === "liveness"
57+
? { ...metric, value: 21 * 60_000, severity: "crit" }
58+
: { ...metric, annotation: undefined }
59+
),
60+
facts: { trustworthy: false, staleReason: "telemetry_stale" },
61+
links: [{ key: "status", label: "status.trigger.dev", url: "https://status.trigger.dev" }],
62+
footer: [{ code: "check_control_plane", link: "status" }],
63+
};
64+
65+
const STATES: Record<string, React.ReactNode> = {
66+
"report-view-healthy": (
67+
<ReportView
68+
vm={demoFixtures.demoHealthyReport}
69+
reportUri={reportItems[0]?.sourceUri}
70+
onIntent={noop}
71+
resolveUri={fixtureResolveUri}
72+
/>
73+
),
74+
"report-view-degraded": (
75+
<ReportView
76+
vm={demoFixtures.demoDegradedReport}
77+
reportUri={reportItems[1]?.sourceUri}
78+
onIntent={noop}
79+
resolveUri={fixtureResolveUri}
80+
/>
81+
),
82+
"report-view-untrustworthy": (
83+
<ReportView vm={untrustworthyReport} onIntent={noop} resolveUri={fixtureResolveUri} />
84+
),
85+
};
86+
87+
export default function Story() {
88+
return <GalleryPage page="report" states={STATES} />;
89+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { UIMessage } from "@ai-sdk/react";
2+
import {
3+
VIEW_BLOCK_VERSION,
4+
type InvestigationBlock,
5+
type InvestigationCapabilities,
6+
} from "@internal/dashboard-agent-contracts";
7+
import { demoChatById, type demoFixtures, type DemoItem } from "~/components/dashboard-agent/demo";
8+
9+
/**
10+
* Fixture readers shared by the gallery pages.
11+
*
12+
* The message-level states already exist as demo chats, so the harnesses pull
13+
* their items rather than inventing transcripts.
14+
*/
15+
16+
export function chatItems<K extends DemoItem["kind"]>(
17+
chatId: string,
18+
kind: K
19+
): Extract<DemoItem, { kind: K }>[] {
20+
const chat = demoChatById(chatId);
21+
return (chat?.items ?? []).filter(
22+
(item): item is Extract<DemoItem, { kind: K }> => item.kind === kind
23+
);
24+
}
25+
26+
export function chatMessages(chatId: string, take?: number): UIMessage[] {
27+
const items = chatItems(chatId, "messages");
28+
return (take === undefined ? items : items.slice(0, take)).flatMap((item) => item.messages);
29+
}
30+
31+
/**
32+
* A demo investigation fixture as the real `investigation` block: the demo type
33+
* carries its identity inline (`investigationId` + `revision`) where the block
34+
* carries it in the envelope, so the mapping is a move, not a rewrite — which is
35+
* the point of having reviewed the demo payload.
36+
*/
37+
export function investigationBlock(
38+
fixture: (typeof demoFixtures.demoInvestigations)[keyof typeof demoFixtures.demoInvestigations],
39+
capabilities?: InvestigationCapabilities
40+
): InvestigationBlock {
41+
const { investigationId, revision, ...investigation } = fixture;
42+
return {
43+
type: "investigation",
44+
id: investigationId,
45+
revision,
46+
version: VIEW_BLOCK_VERSION,
47+
investigation,
48+
...(capabilities ? { capabilities } : {}),
49+
};
50+
}

0 commit comments

Comments
 (0)