@sentry/junior-dashboard 0.247.0 → 0.248.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ import { cn } from "../styles";
2
+ import { ShimmerText } from "../components/ShimmerText";
3
+
4
+ export type UsageCardRow = {
5
+ label: string;
6
+ live?: boolean;
7
+ /** Render the label in the code face, such as a model id. */
8
+ code?: boolean;
9
+ value: string;
10
+ };
11
+
12
+ export type UsageCardSection = {
13
+ /** Rows rolled up into one "N more" line, with their combined value. */
14
+ more?: { count: number; value: string };
15
+ rows: UsageCardRow[];
16
+ title: string;
17
+ /** Section total shown after the title, such as all auxiliary cost. */
18
+ total?: string;
19
+ };
20
+
21
+ export type UsageCardSplit = {
22
+ count: number;
23
+ self: string;
24
+ /** Share of the total that belongs to this agent, from 0 to 1. */
25
+ selfShare: number;
26
+ subagents: string;
27
+ };
28
+
29
+ export type UsageCardProps = {
30
+ /** Closing hint, such as where to find the full breakdown. */
31
+ footer?: string;
32
+ /** Muted detail after the title, such as a compaction count. */
33
+ note?: string;
34
+ sections: UsageCardSection[];
35
+ split?: UsageCardSplit;
36
+ title: string;
37
+ total: string;
38
+ };
39
+
40
+ const SELF_FILL = "bg-dashboard-focus/80";
41
+ const SUBAGENT_FILL = "bg-dashboard-focus/30";
42
+
43
+ function Swatch(props: { className: string }) {
44
+ return (
45
+ <span
46
+ aria-hidden="true"
47
+ className={cn("size-2 shrink-0 rounded-xs", props.className)}
48
+ />
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Summarize one usage total: the total up top, how it splits between this
54
+ * agent and its subagents, then the largest contributors. Long lists roll
55
+ * their tail into one "N more" row so the card never needs to scroll.
56
+ */
57
+ export function UsageCard(props: UsageCardProps) {
58
+ const selfPercent = Math.min(
59
+ 100,
60
+ Math.max(0, (props.split?.selfShare ?? 1) * 100),
61
+ );
62
+ return (
63
+ <span className="grid gap-3">
64
+ <span
65
+ className={cn(
66
+ "grid gap-2.5",
67
+ (props.sections.length > 0 || props.footer) &&
68
+ "border-b border-dashboard-border pb-3",
69
+ )}
70
+ >
71
+ <span className="flex items-baseline justify-between gap-3">
72
+ <span className="min-w-0 truncate">
73
+ {props.title}
74
+ {props.note ? (
75
+ <span className="text-dashboard-text-faint"> · {props.note}</span>
76
+ ) : null}
77
+ </span>
78
+ <span className="whitespace-nowrap text-lg font-semibold tabular-nums text-dashboard-text">
79
+ {props.total}
80
+ </span>
81
+ </span>
82
+ {props.split ? (
83
+ <>
84
+ <span
85
+ aria-hidden="true"
86
+ className="flex h-1.5 overflow-hidden rounded-full bg-dashboard-fill-hover"
87
+ >
88
+ <span
89
+ className={SELF_FILL}
90
+ style={{ width: `${selfPercent}%` }}
91
+ />
92
+ <span
93
+ className={SUBAGENT_FILL}
94
+ style={{ width: `${100 - selfPercent}%` }}
95
+ />
96
+ </span>
97
+ <span className="flex flex-wrap justify-between gap-x-3 gap-y-1 tabular-nums">
98
+ <span className="inline-flex items-center gap-1.5 whitespace-nowrap">
99
+ <Swatch className={SELF_FILL} />
100
+ this agent
101
+ <span className="font-medium text-dashboard-text">
102
+ {props.split.self}
103
+ </span>
104
+ </span>
105
+ <span className="inline-flex items-center gap-1.5 whitespace-nowrap">
106
+ <Swatch className={SUBAGENT_FILL} />
107
+ {props.split.count} subagent{props.split.count === 1 ? "" : "s"}
108
+ <span className="font-medium text-dashboard-text">
109
+ {props.split.subagents}
110
+ </span>
111
+ </span>
112
+ </span>
113
+ </>
114
+ ) : null}
115
+ </span>
116
+ {props.sections.map((section) => (
117
+ <span
118
+ className="grid grid-cols-[minmax(0,1fr)_auto] gap-x-3 gap-y-2"
119
+ key={section.title}
120
+ >
121
+ <span className="col-span-2 font-mono uppercase tracking-[0.08em] text-dashboard-text-faint">
122
+ {section.title}
123
+ {section.total ? ` · ${section.total}` : null}
124
+ </span>
125
+ {section.rows.map((row) => (
126
+ <span className="contents" key={row.label}>
127
+ <span
128
+ className={cn(
129
+ "flex min-w-0 items-baseline gap-2",
130
+ row.code && "font-mono text-dashboard-text",
131
+ )}
132
+ >
133
+ <span className="min-w-0 truncate" title={row.label}>
134
+ {row.label}
135
+ </span>
136
+ {row.live ? (
137
+ <span className="shrink-0 font-sans text-2xs text-dashboard-text-muted">
138
+ <ShimmerText active>in progress</ShimmerText>
139
+ </span>
140
+ ) : null}
141
+ </span>
142
+ <span className="whitespace-nowrap text-right tabular-nums text-dashboard-text">
143
+ {row.value}
144
+ </span>
145
+ </span>
146
+ ))}
147
+ {section.more ? (
148
+ <>
149
+ <span className="text-dashboard-text-faint">
150
+ {section.more.count} more
151
+ </span>
152
+ <span className="whitespace-nowrap text-right tabular-nums">
153
+ {section.more.value}
154
+ </span>
155
+ </>
156
+ ) : null}
157
+ </span>
158
+ ))}
159
+ {props.footer ? (
160
+ <span
161
+ className={cn(
162
+ "text-dashboard-text-faint",
163
+ props.sections.length > 0 &&
164
+ "border-t border-dashboard-border pt-3",
165
+ )}
166
+ >
167
+ {props.footer}
168
+ </span>
169
+ ) : null}
170
+ </span>
171
+ );
172
+ }
@@ -919,6 +919,7 @@ export function buildConversations(
919
919
  channel: summary.channel,
920
920
  channelName: summary.channelName,
921
921
  channelNameRedacted: summary.channelNameRedacted,
922
+ childAgentTotals: summary.childAgentTotals,
922
923
  cumulativeDurationMs: summary.cumulativeDurationMs,
923
924
  cumulativeUsage: summary.cumulativeUsage,
924
925
  displayTitle: summary.displayTitle,
@@ -156,6 +156,7 @@ export type Conversation = {
156
156
  channel?: string;
157
157
  channelName?: string;
158
158
  channelNameRedacted?: boolean;
159
+ childAgentTotals?: ConversationSummaryReport["childAgentTotals"];
159
160
  cumulativeDurationMs: number;
160
161
  cumulativeUsage?: ConversationSummaryReport["cumulativeUsage"];
161
162
  displayTitle: string;
@@ -129,6 +129,8 @@ export const DASHBOARD_QA_CONVERSATION_ID = "internal:dashboard-qa";
129
129
  export const ARCHIVED_CONVERSATION_ID = "internal:archived-restore-qa";
130
130
  const DASHBOARD_QA_PLAN_ID = "junior:internal:dashboard-qa:advisor-plan";
131
131
  const DASHBOARD_QA_REVIEW_ID = "junior:internal:dashboard-qa:advisor-review";
132
+ const DASHBOARD_QA_PLAN_CHECK_ID =
133
+ "junior:internal:dashboard-qa:advisor-plan:claim-check";
132
134
  /** Process-local archive state so mock restore can be exercised in visual QA. */
133
135
  const mockArchivedConversationIds = new Set<string>([ARCHIVED_CONVERSATION_ID]);
134
136
 
@@ -672,7 +674,24 @@ function dashboardQaConversation(nowMs: number): ConversationDetailReport {
672
674
  updatedAt: iso(Date.parse(startedAt), 20_000),
673
675
  },
674
676
  ],
675
- cumulativeDurationMs: 98_000,
677
+ // Totals include all three subagents, like the API.
678
+ childAgentTotals: {
679
+ count: 3,
680
+ cumulativeDurationMs: 88_400,
681
+ cumulativeUsage: {
682
+ inputTokens: 48_700,
683
+ outputTokens: 2_680,
684
+ cachedInputTokens: 21_600,
685
+ cost: { total: 0.172 },
686
+ },
687
+ },
688
+ cumulativeDurationMs: 186_400,
689
+ cumulativeUsage: {
690
+ inputTokens: 100_700,
691
+ outputTokens: 6_080,
692
+ cachedInputTokens: 51_600,
693
+ cost: { total: 0.382 },
694
+ },
676
695
  events: [
677
696
  reportEvent(0, startedAt, {
678
697
  type: "message",
@@ -973,8 +992,9 @@ function advisorConversation(
973
992
  text: string,
974
993
  metrics: Pick<
975
994
  ConversationDetailReport,
976
- "cumulativeDurationMs" | "cumulativeUsage"
995
+ "childAgentTotals" | "cumulativeDurationMs" | "cumulativeUsage"
977
996
  >,
997
+ parentConversationId = DASHBOARD_QA_CONVERSATION_ID,
978
998
  ): MockConversation {
979
999
  const startedAt = iso(nowMs, -10 * 60_000);
980
1000
  const conversation = detail(nowMs, {
@@ -999,10 +1019,7 @@ function advisorConversation(
999
1019
  }),
1000
1020
  ],
1001
1021
  });
1002
- return {
1003
- ...conversation,
1004
- parentConversationId: DASHBOARD_QA_CONVERSATION_ID,
1005
- };
1022
+ return { ...conversation, parentConversationId };
1006
1023
  }
1007
1024
 
1008
1025
  function longConversation(nowMs: number): ConversationDetailReport {
@@ -1404,14 +1421,37 @@ function mockConversations(nowMs: number): MockConversation[] {
1404
1421
  DASHBOARD_QA_PLAN_ID,
1405
1422
  "Review the dashboard plan before editing.",
1406
1423
  {
1407
- cumulativeDurationMs: 72_400,
1424
+ childAgentTotals: {
1425
+ count: 1,
1426
+ cumulativeDurationMs: 6_200,
1427
+ cumulativeUsage: {
1428
+ inputTokens: 4_100,
1429
+ outputTokens: 220,
1430
+ cost: { total: 0.011 },
1431
+ },
1432
+ },
1433
+ cumulativeDurationMs: 78_600,
1408
1434
  cumulativeUsage: {
1409
- inputTokens: 38_400,
1410
- outputTokens: 2_150,
1435
+ inputTokens: 42_500,
1436
+ outputTokens: 2_370,
1411
1437
  cachedInputTokens: 21_600,
1412
- cost: { input: 0.094, output: 0.048, total: 0.142 },
1438
+ cost: { input: 0.1, output: 0.053, total: 0.153 },
1439
+ },
1440
+ },
1441
+ ),
1442
+ advisorConversation(
1443
+ nowMs,
1444
+ DASHBOARD_QA_PLAN_CHECK_ID,
1445
+ "Check one claim in the dashboard plan.",
1446
+ {
1447
+ cumulativeDurationMs: 6_200,
1448
+ cumulativeUsage: {
1449
+ inputTokens: 4_100,
1450
+ outputTokens: 220,
1451
+ cost: { total: 0.011 },
1413
1452
  },
1414
1453
  },
1454
+ DASHBOARD_QA_PLAN_ID,
1415
1455
  ),
1416
1456
  advisorConversation(
1417
1457
  nowMs,
@@ -1855,20 +1895,39 @@ export function readMockConversationChildAgents(
1855
1895
  (candidate) => candidate.conversationId === conversationId,
1856
1896
  );
1857
1897
  if (!conversation) return undefined;
1858
- if (conversationId !== DASHBOARD_QA_CONVERSATION_ID) {
1859
- return { agents: [], conversationId };
1860
- }
1861
1898
  const startedAtMs = NOW_MS - 5 * 60 * 60_000;
1862
- // Match the API: each row has the usage of its own child Conversation.
1899
+ // Match the API: each row has the usage of its child Conversation and of
1900
+ // that child's own subagents.
1863
1901
  const childMetrics = (childConversationId: string) => {
1864
1902
  const child = conversations.find(
1865
1903
  (candidate) => candidate.conversationId === childConversationId,
1866
1904
  )!;
1867
1905
  return {
1906
+ ...(child.childAgentTotals
1907
+ ? { childAgentTotals: child.childAgentTotals }
1908
+ : undefined),
1868
1909
  cumulativeDurationMs: child.cumulativeDurationMs,
1869
1910
  cumulativeUsage: child.cumulativeUsage,
1870
1911
  };
1871
1912
  };
1913
+ if (conversationId === DASHBOARD_QA_PLAN_ID) {
1914
+ return {
1915
+ agents: [
1916
+ {
1917
+ ...childMetrics(DASHBOARD_QA_PLAN_CHECK_ID),
1918
+ conversationId: DASHBOARD_QA_PLAN_CHECK_ID,
1919
+ status: "completed",
1920
+ task: "Check one claim in the dashboard plan.",
1921
+ title: "Check one plan claim",
1922
+ updatedAt: iso(startedAtMs, 16_000),
1923
+ },
1924
+ ],
1925
+ conversationId,
1926
+ };
1927
+ }
1928
+ if (conversationId !== DASHBOARD_QA_CONVERSATION_ID) {
1929
+ return { agents: [], conversationId };
1930
+ }
1872
1931
  return {
1873
1932
  agents: [
1874
1933
  {