@sentry/junior-dashboard 0.247.0 → 0.249.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.
- package/dist/app.js +69 -15
- package/dist/client.js +51 -50
- package/dist/index.js +69 -15
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
- package/src/client/App.tsx +1 -5
- package/src/client/components/ActorAvatar.tsx +7 -4
- package/src/client/components/AutomationCard.tsx +2 -2
- package/src/client/components/Button.tsx +12 -11
- package/src/client/components/ConversationList.tsx +3 -3
- package/src/client/components/ConversationSummary.tsx +2 -1
- package/src/client/components/DetailList.tsx +3 -3
- package/src/client/components/Drawer.tsx +2 -2
- package/src/client/components/EmptyTelemetry.tsx +1 -1
- package/src/client/components/Field.tsx +1 -2
- package/src/client/components/FilterBar.tsx +6 -6
- package/src/client/components/Metric.tsx +11 -16
- package/src/client/components/Notice.tsx +7 -7
- package/src/client/components/PageContentSkeleton.tsx +2 -2
- package/src/client/components/Pagination.tsx +2 -2
- package/src/client/components/ParticipantAvatarStack.tsx +4 -4
- package/src/client/components/ProfileMenu.tsx +5 -7
- package/src/client/components/README.md +122 -58
- package/src/client/components/SearchInput.tsx +4 -3
- package/src/client/components/SegmentedNav.tsx +10 -17
- package/src/client/components/SegmentedTabs.tsx +3 -3
- package/src/client/components/Select.tsx +1 -1
- package/src/client/components/SelectableRow.tsx +2 -2
- package/src/client/components/Skeleton.tsx +1 -1
- package/src/client/components/StatusChip.tsx +1 -1
- package/src/client/components/StatusDot.tsx +1 -1
- package/src/client/components/TextInput.tsx +1 -1
- package/src/client/components/Tooltip.tsx +2 -2
- package/src/client/components/VersionDriftBanner.tsx +2 -2
- package/src/client/components/charts/ActivityChart.tsx +5 -6
- package/src/client/components/charts/CacheWrites.tsx +3 -3
- package/src/client/components/charts/ChartHeader.tsx +4 -4
- package/src/client/components/charts/ChartLegend.tsx +1 -1
- package/src/client/components/charts/InputCacheChart.tsx +5 -5
- package/src/client/components/charts/WorkspaceUsageChart.tsx +4 -11
- package/src/client/components/controls/DirectorySortSelect.tsx +2 -2
- package/src/client/components/directory/DirectoryMetric.tsx +2 -2
- package/src/client/components/directory/DirectoryRowsSkeleton.tsx +5 -5
- package/src/client/components/directory/DirectoryTable.tsx +3 -3
- package/src/client/components/layout/CardHeader.tsx +4 -4
- package/src/client/components/layout/DashboardChrome.tsx +9 -3
- package/src/client/components/layout/DashboardHeader.tsx +7 -7
- package/src/client/components/layout/PageHeader.tsx +2 -2
- package/src/client/components/layout/SecondaryNavigation.tsx +7 -14
- package/src/client/components/layout/SectionIntro.tsx +1 -1
- package/src/client/components/layout/SectionTitle.tsx +1 -1
- package/src/client/components/metrics/StatCard.tsx +2 -2
- package/src/client/conversations/ConversationArchiveNotices.tsx +1 -1
- package/src/client/conversations/ConversationBrief.tsx +4 -4
- package/src/client/conversations/ConversationChildAgents.tsx +13 -4
- package/src/client/conversations/ConversationComposer.tsx +8 -7
- package/src/client/conversations/ConversationHeader.tsx +5 -5
- package/src/client/conversations/ConversationHeaderActions.tsx +9 -9
- package/src/client/conversations/ConversationHomeList.tsx +3 -3
- package/src/client/conversations/ConversationListStatusIcon.tsx +1 -1
- package/src/client/conversations/ConversationMemories.tsx +4 -4
- package/src/client/conversations/ConversationMeta.tsx +30 -11
- package/src/client/conversations/ConversationPage.tsx +13 -5
- package/src/client/conversations/ConversationSidebar.tsx +6 -6
- package/src/client/conversations/ConversationUsageBreakdown.tsx +278 -0
- package/src/client/conversations/ConversationWorkspace.tsx +1 -1
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +4 -4
- package/src/client/conversations/CopyMarkdownButton.tsx +1 -1
- package/src/client/conversations/EventUsage.tsx +2 -2
- package/src/client/conversations/PendingMailboxStack.tsx +5 -5
- package/src/client/conversations/README.md +9 -0
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +39 -23
- package/src/client/conversations/TelemetryMetrics.tsx +327 -180
- package/src/client/conversations/ToolFrame.tsx +36 -14
- package/src/client/conversations/TranscriptActivityGroup.tsx +10 -3
- package/src/client/conversations/TranscriptAttachmentsDeliveredView.tsx +4 -6
- package/src/client/conversations/TranscriptContextEventView.tsx +2 -2
- package/src/client/conversations/TranscriptEventView.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +3 -3
- package/src/client/conversations/TranscriptMessageContextView.tsx +7 -7
- package/src/client/conversations/TranscriptMessageView.tsx +2 -6
- package/src/client/conversations/TranscriptRailEvent.tsx +20 -8
- package/src/client/conversations/TranscriptReasoningView.tsx +12 -12
- package/src/client/conversations/TranscriptStructuredEventView.tsx +16 -11
- package/src/client/conversations/TranscriptSubagentView.tsx +64 -39
- package/src/client/conversations/TranscriptText.tsx +5 -4
- package/src/client/conversations/TranscriptToolView.tsx +9 -11
- package/src/client/conversations/TranscriptTurnContextView.tsx +3 -3
- package/src/client/conversations/TranscriptView.tsx +3 -3
- package/src/client/conversations/UsageCard.tsx +172 -0
- package/src/client/conversations/eventTranscript.ts +7 -0
- package/src/client/conversations/transcriptMarkdownLinks.ts +2 -4
- package/src/client/conversations/transcriptStyles.ts +1 -1
- package/src/client/format.ts +1 -0
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/admin/AdminPage.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +2 -2
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +5 -10
- package/src/client/pages/automations/AutomationEditor.tsx +1 -1
- package/src/client/pages/automations/AutomationExecutionChart.tsx +2 -2
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +2 -2
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +4 -4
- package/src/client/pages/automations/AutomationRow.tsx +4 -4
- package/src/client/pages/automations/AutomationRunsPage.tsx +3 -3
- package/src/client/pages/automations/AutomationScheduleFields.tsx +1 -1
- package/src/client/pages/automations/AutomationVersionsPage.tsx +1 -1
- package/src/client/pages/code/CodeActivityChart.tsx +7 -6
- package/src/client/pages/code/CodeChangeRow.tsx +1 -1
- package/src/client/pages/code/CodePage.tsx +5 -5
- package/src/client/pages/code/RepositoryPage.tsx +2 -2
- package/src/client/pages/dev/ComponentsPage.tsx +40 -146
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +206 -0
- package/src/client/pages/locations/LocationDetailPage.tsx +4 -4
- package/src/client/pages/locations/LocationDirectory.tsx +1 -1
- package/src/client/pages/locations/PrivateActivityCard.tsx +3 -3
- package/src/client/pages/memory/MemoryCostChart.tsx +3 -5
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +5 -5
- package/src/client/pages/memory/MemoryPage.tsx +15 -15
- package/src/client/pages/memory/MemoryTimeline.tsx +3 -5
- package/src/client/pages/people/ContributionGrid.tsx +8 -5
- package/src/client/pages/people/PersonProfilePage.tsx +3 -6
- package/src/client/pages/people/ProfilePluginReports.tsx +2 -2
- package/src/client/pages/system/GuardianActivity.tsx +9 -9
- package/src/client/pages/system/PluginBarChart.tsx +4 -6
- package/src/client/pages/system/PluginDetails.tsx +11 -7
- package/src/client/pages/system/PluginPanels.tsx +8 -8
- package/src/client/pages/system/PluginReportRecordSet.tsx +14 -14
- package/src/client/pages/system/PluginReports.tsx +7 -11
- package/src/client/pages/system/SkillInventory.tsx +9 -9
- package/src/client/pages/system/SystemActivity.tsx +1 -1
- package/src/client/pages/system/SystemPage.tsx +3 -6
- package/src/client/pages/system/WorkspaceDetails.tsx +2 -5
- package/src/client/pages/system/WorkspaceEditor.tsx +2 -2
- package/src/client/pages/system/WorkspaceList.tsx +2 -2
- package/src/client/pages/user/PluginUserPage.tsx +3 -3
- package/src/client/styles.ts +5 -4
- package/src/client/types.ts +3 -0
- package/src/mock-reporting/fixtures.ts +73 -14
- package/src/tailwind.css +36 -38
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConversationDetailReport,
|
|
3
|
+
ConversationModelUsage,
|
|
4
|
+
} from "@sentry/junior/api/schema";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
formatCompactNumber,
|
|
9
|
+
summarizeCost,
|
|
10
|
+
summarizeModelCost,
|
|
11
|
+
summarizeModelUsage,
|
|
12
|
+
summarizeUsage,
|
|
13
|
+
totalConversationCost,
|
|
14
|
+
} from "../format";
|
|
15
|
+
import { ShimmerText } from "../components/ShimmerText";
|
|
16
|
+
import type { Conversation } from "../types";
|
|
17
|
+
import { liveModelId } from "./ConversationMeta";
|
|
18
|
+
import {
|
|
19
|
+
auxiliaryOperationLabel,
|
|
20
|
+
costFormatter,
|
|
21
|
+
modelLabel,
|
|
22
|
+
subagentShare,
|
|
23
|
+
subagentUsage,
|
|
24
|
+
tokenTypeRows,
|
|
25
|
+
} from "./TelemetryMetrics";
|
|
26
|
+
|
|
27
|
+
type BreakdownRow = {
|
|
28
|
+
cost: string;
|
|
29
|
+
count: string;
|
|
30
|
+
/** Muted second line, such as the token types of one model. */
|
|
31
|
+
detail?: string;
|
|
32
|
+
key: string;
|
|
33
|
+
label: ReactNode;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const NOT_RECORDED = "—";
|
|
37
|
+
|
|
38
|
+
/** Name each recorded token type, such as `42k input · 4.6k output`. */
|
|
39
|
+
function tokenTypes(
|
|
40
|
+
usage: ConversationModelUsage["usage"],
|
|
41
|
+
): string | undefined {
|
|
42
|
+
const summary = summarizeUsage(usage);
|
|
43
|
+
const text = summary
|
|
44
|
+
? tokenTypeRows(summary)
|
|
45
|
+
.map((row) => `${row.value} ${row.label}`)
|
|
46
|
+
.join(" · ")
|
|
47
|
+
: "";
|
|
48
|
+
return text || undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* One usage table. Every table in the breakdown shares column widths, so the
|
|
53
|
+
* cost column lines up from table to table.
|
|
54
|
+
*/
|
|
55
|
+
function BreakdownTable(props: {
|
|
56
|
+
/** Header of the count column, such as Tokens or Calls. */
|
|
57
|
+
countLabel: string;
|
|
58
|
+
label: string;
|
|
59
|
+
rows: BreakdownRow[];
|
|
60
|
+
total?: { cost: string; count: string; label: string };
|
|
61
|
+
}) {
|
|
62
|
+
return (
|
|
63
|
+
<div className="min-w-0 overflow-x-auto">
|
|
64
|
+
<table
|
|
65
|
+
aria-label={props.label}
|
|
66
|
+
className="w-full min-w-72 table-fixed border-collapse text-xs leading-relaxed"
|
|
67
|
+
>
|
|
68
|
+
<colgroup>
|
|
69
|
+
<col />
|
|
70
|
+
<col className="w-20" />
|
|
71
|
+
<col className="w-20" />
|
|
72
|
+
</colgroup>
|
|
73
|
+
<thead>
|
|
74
|
+
<tr className="border-b border-dashboard-border text-dashboard-text-muted">
|
|
75
|
+
<th scope="col" className="py-2 text-left font-normal">
|
|
76
|
+
{props.label}
|
|
77
|
+
</th>
|
|
78
|
+
<th scope="col" className="py-2 text-right font-normal">
|
|
79
|
+
{props.countLabel}
|
|
80
|
+
</th>
|
|
81
|
+
<th scope="col" className="py-2 text-right font-normal">
|
|
82
|
+
Cost
|
|
83
|
+
</th>
|
|
84
|
+
</tr>
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody>
|
|
87
|
+
{props.rows.map((row) => (
|
|
88
|
+
<tr
|
|
89
|
+
className="border-b border-dashboard-border-subtle last:border-b-0"
|
|
90
|
+
key={row.key}
|
|
91
|
+
>
|
|
92
|
+
<th scope="row" className="py-2 pr-3 text-left font-normal">
|
|
93
|
+
<span className="block truncate text-dashboard-text">
|
|
94
|
+
{row.label}
|
|
95
|
+
</span>
|
|
96
|
+
{row.detail ? (
|
|
97
|
+
<span className="mt-0.5 block text-dashboard-text-faint">
|
|
98
|
+
{row.detail}
|
|
99
|
+
</span>
|
|
100
|
+
) : null}
|
|
101
|
+
</th>
|
|
102
|
+
<td className="py-2 text-right align-top font-sans tabular-nums text-dashboard-text">
|
|
103
|
+
{row.count}
|
|
104
|
+
</td>
|
|
105
|
+
<td className="py-2 text-right align-top font-sans tabular-nums text-dashboard-text">
|
|
106
|
+
{row.cost}
|
|
107
|
+
</td>
|
|
108
|
+
</tr>
|
|
109
|
+
))}
|
|
110
|
+
{props.total ? (
|
|
111
|
+
<tr className="border-t border-dashboard-border font-semibold text-dashboard-text">
|
|
112
|
+
<th scope="row" className="py-2 text-left font-semibold">
|
|
113
|
+
{props.total.label}
|
|
114
|
+
</th>
|
|
115
|
+
<td className="py-2 text-right font-sans tabular-nums">
|
|
116
|
+
{props.total.count}
|
|
117
|
+
</td>
|
|
118
|
+
<td className="py-2 text-right font-sans tabular-nums">
|
|
119
|
+
{props.total.cost}
|
|
120
|
+
</td>
|
|
121
|
+
</tr>
|
|
122
|
+
) : null}
|
|
123
|
+
</tbody>
|
|
124
|
+
</table>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Break a Conversation's usage down by agent, model, and auxiliary
|
|
131
|
+
* operation. The header tooltips show only the largest items; this lists
|
|
132
|
+
* every one with its token types. Render nothing without model or auxiliary
|
|
133
|
+
* usage.
|
|
134
|
+
*/
|
|
135
|
+
export function ConversationUsageBreakdown(props: {
|
|
136
|
+
conversation: Conversation | undefined;
|
|
137
|
+
detail?: ConversationDetailReport;
|
|
138
|
+
}) {
|
|
139
|
+
if (!props.conversation) return null;
|
|
140
|
+
const modelUsage = props.detail?.modelUsage ?? [];
|
|
141
|
+
const auxiliaryCosts =
|
|
142
|
+
props.detail?.auxiliaryCosts ?? props.conversation.auxiliaryCosts;
|
|
143
|
+
const usage =
|
|
144
|
+
props.detail?.cumulativeUsage ?? props.conversation.cumulativeUsage;
|
|
145
|
+
const childAgentTotals =
|
|
146
|
+
props.detail?.childAgentTotals ?? props.conversation.childAgentTotals;
|
|
147
|
+
const tokens =
|
|
148
|
+
summarizeModelUsage(modelUsage)?.totalTokens ??
|
|
149
|
+
summarizeUsage(usage)?.totalTokens;
|
|
150
|
+
const modelCost = summarizeModelCost(modelUsage) ?? summarizeCost(usage);
|
|
151
|
+
const cost = totalConversationCost(modelCost, auxiliaryCosts)?.total;
|
|
152
|
+
const operations = [...(auxiliaryCosts?.operations ?? [])].sort(
|
|
153
|
+
(a, b) => b.costUsd - a.costUsd,
|
|
154
|
+
);
|
|
155
|
+
if (!modelUsage.length && !operations.length) return null;
|
|
156
|
+
|
|
157
|
+
const formatCost = costFormatter(cost ?? 0);
|
|
158
|
+
const formatTokens = (value: number | undefined) =>
|
|
159
|
+
value === undefined ? NOT_RECORDED : formatCompactNumber(value);
|
|
160
|
+
const formatMaybeCost = (value: number | undefined) =>
|
|
161
|
+
value === undefined ? NOT_RECORDED : formatCost(value);
|
|
162
|
+
const activeModelId = liveModelId(props.detail);
|
|
163
|
+
|
|
164
|
+
const models = modelUsage
|
|
165
|
+
.map((item) => ({
|
|
166
|
+
cost: summarizeCost(item.usage)?.total,
|
|
167
|
+
item,
|
|
168
|
+
tokens: summarizeUsage(item.usage)?.totalTokens,
|
|
169
|
+
}))
|
|
170
|
+
.sort(
|
|
171
|
+
(a, b) =>
|
|
172
|
+
(b.cost ?? 0) - (a.cost ?? 0) || (b.tokens ?? 0) - (a.tokens ?? 0),
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const subagents = subagentUsage(childAgentTotals);
|
|
176
|
+
const subagentCost = subagentShare(subagents.cost, cost ?? subagents.cost);
|
|
177
|
+
const subagentTokens = subagentShare(
|
|
178
|
+
subagents.tokens,
|
|
179
|
+
tokens ?? subagents.tokens,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div className="grid min-w-0 gap-5">
|
|
184
|
+
{childAgentTotals ? (
|
|
185
|
+
<BreakdownTable
|
|
186
|
+
countLabel="Tokens"
|
|
187
|
+
label="Agent"
|
|
188
|
+
rows={[
|
|
189
|
+
{
|
|
190
|
+
cost: formatMaybeCost(
|
|
191
|
+
cost === undefined ? undefined : cost - subagentCost,
|
|
192
|
+
),
|
|
193
|
+
count: formatTokens(
|
|
194
|
+
tokens === undefined ? undefined : tokens - subagentTokens,
|
|
195
|
+
),
|
|
196
|
+
key: "self",
|
|
197
|
+
label: "This agent",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
cost: formatCost(subagentCost),
|
|
201
|
+
count: formatTokens(subagentTokens),
|
|
202
|
+
key: "subagents",
|
|
203
|
+
label: `${formatCompactNumber(childAgentTotals.count)} subagent${childAgentTotals.count === 1 ? "" : "s"}`,
|
|
204
|
+
},
|
|
205
|
+
]}
|
|
206
|
+
total={{
|
|
207
|
+
cost: formatMaybeCost(cost),
|
|
208
|
+
count: formatTokens(tokens),
|
|
209
|
+
label: "Total",
|
|
210
|
+
}}
|
|
211
|
+
/>
|
|
212
|
+
) : null}
|
|
213
|
+
{models.length ? (
|
|
214
|
+
<BreakdownTable
|
|
215
|
+
countLabel="Tokens"
|
|
216
|
+
label="Model"
|
|
217
|
+
rows={models.map(({ cost: itemCost, item, tokens: itemTokens }) => ({
|
|
218
|
+
cost: formatMaybeCost(itemCost),
|
|
219
|
+
count: formatTokens(itemTokens),
|
|
220
|
+
detail: tokenTypes(item.usage),
|
|
221
|
+
key: item.modelId,
|
|
222
|
+
label: (
|
|
223
|
+
<span className="flex min-w-0 items-baseline gap-2">
|
|
224
|
+
<span
|
|
225
|
+
className="min-w-0 truncate font-mono"
|
|
226
|
+
title={item.modelId}
|
|
227
|
+
>
|
|
228
|
+
{modelLabel(item.modelId)}
|
|
229
|
+
</span>
|
|
230
|
+
{item.modelId === activeModelId ? (
|
|
231
|
+
<span className="shrink-0 text-dashboard-text-muted">
|
|
232
|
+
<ShimmerText active>in progress</ShimmerText>
|
|
233
|
+
</span>
|
|
234
|
+
) : null}
|
|
235
|
+
</span>
|
|
236
|
+
),
|
|
237
|
+
}))}
|
|
238
|
+
total={
|
|
239
|
+
models.length > 1
|
|
240
|
+
? {
|
|
241
|
+
cost: formatMaybeCost(modelCost?.total),
|
|
242
|
+
count: formatTokens(tokens),
|
|
243
|
+
label: "All models",
|
|
244
|
+
}
|
|
245
|
+
: undefined
|
|
246
|
+
}
|
|
247
|
+
/>
|
|
248
|
+
) : null}
|
|
249
|
+
{auxiliaryCosts && operations.length ? (
|
|
250
|
+
<BreakdownTable
|
|
251
|
+
countLabel="Calls"
|
|
252
|
+
label="Auxiliary"
|
|
253
|
+
rows={operations.map((operation) => ({
|
|
254
|
+
cost: formatCost(operation.costUsd),
|
|
255
|
+
count: formatCompactNumber(operation.events),
|
|
256
|
+
key: `${operation.namespace}/${operation.name}`,
|
|
257
|
+
label: auxiliaryOperationLabel(operation),
|
|
258
|
+
}))}
|
|
259
|
+
total={
|
|
260
|
+
operations.length > 1
|
|
261
|
+
? {
|
|
262
|
+
cost: formatCost(auxiliaryCosts.costUsd),
|
|
263
|
+
count: formatCompactNumber(
|
|
264
|
+
operations.reduce((sum, item) => sum + item.events, 0),
|
|
265
|
+
),
|
|
266
|
+
label: "All auxiliary",
|
|
267
|
+
}
|
|
268
|
+
: undefined
|
|
269
|
+
}
|
|
270
|
+
/>
|
|
271
|
+
) : null}
|
|
272
|
+
<p className="m-0 text-xs leading-relaxed text-dashboard-text-muted">
|
|
273
|
+
Estimated costs.
|
|
274
|
+
{childAgentTotals ? " Totals include subagents at any depth." : null}
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
277
|
+
);
|
|
278
|
+
}
|
|
@@ -93,7 +93,7 @@ export function ConversationWorkspace() {
|
|
|
93
93
|
<main
|
|
94
94
|
className={cn(
|
|
95
95
|
dashboardContainerClass,
|
|
96
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6
|
|
96
|
+
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
97
97
|
)}
|
|
98
98
|
>
|
|
99
99
|
<div className="mx-auto grid w-full max-w-6xl gap-8">
|
|
@@ -12,7 +12,7 @@ export function ConversationWorkspaceLoading(props: { detail: boolean }) {
|
|
|
12
12
|
aria-live="polite"
|
|
13
13
|
className={cn(
|
|
14
14
|
dashboardContainerClass,
|
|
15
|
-
"grid h-full min-h-0 overflow-hidden md:grid-cols-[
|
|
15
|
+
"grid h-full min-h-0 overflow-hidden md:grid-cols-[20rem_minmax(0,1fr)]",
|
|
16
16
|
)}
|
|
17
17
|
role="status"
|
|
18
18
|
>
|
|
@@ -25,7 +25,7 @@ export function ConversationWorkspaceLoading(props: { detail: boolean }) {
|
|
|
25
25
|
|
|
26
26
|
function ConversationSidebarLoading() {
|
|
27
27
|
return (
|
|
28
|
-
<aside className="hidden h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] overflow-hidden border-r border-
|
|
28
|
+
<aside className="hidden h-full min-h-0 grid-rows-[auto_auto_minmax(0,1fr)] overflow-hidden border-r border-dashboard-border bg-dashboard-fill-faint md:grid">
|
|
29
29
|
<div className="flex items-center justify-between gap-2 px-3 pb-2 pt-3">
|
|
30
30
|
<h2 className="m-0 font-display text-lg font-medium leading-tight text-dashboard-text">
|
|
31
31
|
Conversations
|
|
@@ -60,7 +60,7 @@ function ConversationHomeLoading() {
|
|
|
60
60
|
aria-live="polite"
|
|
61
61
|
className={cn(
|
|
62
62
|
dashboardContainerClass,
|
|
63
|
-
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6
|
|
63
|
+
"h-full min-h-0 overflow-y-auto overscroll-contain px-4 py-4 sm:px-6 sm:py-6",
|
|
64
64
|
)}
|
|
65
65
|
role="status"
|
|
66
66
|
>
|
|
@@ -89,7 +89,7 @@ function ConversationDetailLoading() {
|
|
|
89
89
|
return (
|
|
90
90
|
<section
|
|
91
91
|
aria-label="Selected conversation"
|
|
92
|
-
className="grid min-h-0 grid-rows-[minmax(0,1fr)] overflow-hidden bg-
|
|
92
|
+
className="grid min-h-0 grid-rows-[minmax(0,1fr)] overflow-hidden bg-dashboard-fill-faint"
|
|
93
93
|
>
|
|
94
94
|
<ChatLayout
|
|
95
95
|
scrollAriaLabel="Conversation transcript"
|
|
@@ -39,7 +39,7 @@ export function CopyMarkdownButton(props: {
|
|
|
39
39
|
return (
|
|
40
40
|
<button
|
|
41
41
|
aria-label={label}
|
|
42
|
-
className="flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-
|
|
42
|
+
className="flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
43
43
|
disabled={!props.getMarkdown || status === "copying"}
|
|
44
44
|
onClick={() => void copyMarkdown()}
|
|
45
45
|
type="button"
|
|
@@ -77,7 +77,7 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
|
|
|
77
77
|
>
|
|
78
78
|
{row.label}
|
|
79
79
|
</th>
|
|
80
|
-
<td className="py-2 text-right font-
|
|
80
|
+
<td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
|
|
81
81
|
{row.tokens === undefined ? (
|
|
82
82
|
<span
|
|
83
83
|
aria-label="Not recorded"
|
|
@@ -89,7 +89,7 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
|
|
|
89
89
|
row.tokens.toLocaleString("en-US")
|
|
90
90
|
)}
|
|
91
91
|
</td>
|
|
92
|
-
<td className="py-2 text-right font-
|
|
92
|
+
<td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
|
|
93
93
|
{row.cost === undefined ? (
|
|
94
94
|
<span
|
|
95
95
|
aria-label="Not recorded separately"
|
|
@@ -91,7 +91,7 @@ function PendingMetaIcons(props: {
|
|
|
91
91
|
? "Could not remove. Try again."
|
|
92
92
|
: "Remove queued message"
|
|
93
93
|
}
|
|
94
|
-
className="inline-flex size-5 cursor-pointer items-center justify-center rounded border-0 bg-transparent p-0 text-dashboard-text-muted transition-colors hover:bg-
|
|
94
|
+
className="inline-flex size-5 cursor-pointer items-center justify-center rounded-md border-0 bg-transparent p-0 text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-hover hover:text-amber-50 focus-visible:outline focus-visible:outline-1 focus-visible:outline-amber-200/55 disabled:cursor-default disabled:opacity-50"
|
|
95
95
|
disabled={props.cancelDisabled}
|
|
96
96
|
onClick={props.onCancel}
|
|
97
97
|
type="button"
|
|
@@ -141,7 +141,7 @@ function PendingRow(props: {
|
|
|
141
141
|
<article className="grid min-w-0 grid-cols-[minmax(0,1fr)] gap-1 px-3 py-2 text-dashboard-text md:px-3.5">
|
|
142
142
|
<TranscriptHeadingRow
|
|
143
143
|
left={
|
|
144
|
-
<span className="inline-block max-w-full truncate font-
|
|
144
|
+
<span className="inline-block max-w-full truncate font-sans text-sm font-semibold leading-tight text-dashboard-text">
|
|
145
145
|
{roleLabel}
|
|
146
146
|
</span>
|
|
147
147
|
}
|
|
@@ -160,11 +160,11 @@ function PendingRow(props: {
|
|
|
160
160
|
}
|
|
161
161
|
/>
|
|
162
162
|
{redacted ? (
|
|
163
|
-
<p className="m-0 font-
|
|
163
|
+
<p className="m-0 font-sans text-sm leading-snug text-dashboard-text-muted">
|
|
164
164
|
[redacted]
|
|
165
165
|
</p>
|
|
166
166
|
) : (
|
|
167
|
-
<p className="m-0 line-clamp-3 font-
|
|
167
|
+
<p className="m-0 line-clamp-3 font-sans text-sm leading-snug text-dashboard-text-subtle">
|
|
168
168
|
{text}
|
|
169
169
|
</p>
|
|
170
170
|
)}
|
|
@@ -174,7 +174,7 @@ function PendingRow(props: {
|
|
|
174
174
|
Could not send.
|
|
175
175
|
</p>
|
|
176
176
|
<button
|
|
177
|
-
className="cursor-pointer border-0 bg-transparent p-0 font-sans text-xs font-medium text-
|
|
177
|
+
className="cursor-pointer border-0 bg-transparent p-0 font-sans text-xs font-medium text-dashboard-accent-text/90 underline-offset-2 hover:underline focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
|
|
178
178
|
onClick={() => props.onRetry?.(props.message)}
|
|
179
179
|
type="button"
|
|
180
180
|
>
|
|
@@ -16,6 +16,15 @@ shared controls and overlays.
|
|
|
16
16
|
`flush` when the text already sits on the transcript edge. It extends the
|
|
17
17
|
padded control around that text. Do not repeat negative margins at call sites.
|
|
18
18
|
The caller owns the native `details` element and its content.
|
|
19
|
+
- Activity rows (tools, reasoning, and grouped rail events) share one
|
|
20
|
+
layout: a 1rem icon column, an 8px gap, then the title and metadata.
|
|
21
|
+
Expanded content sits in `transcriptActivityBodyClass` under the title.
|
|
22
|
+
`TranscriptActivityNesting` tells a rail event that it is inside an activity
|
|
23
|
+
group. Outside a group, the rail icon sits in the avatar column so the text
|
|
24
|
+
lines up with message text.
|
|
25
|
+
- `TranscriptSubagentView` renders a child agent as a card with its name,
|
|
26
|
+
status, run time, and title. It matches the child agent rows in the details
|
|
27
|
+
drawer and opens the child transcript.
|
|
19
28
|
- `TranscriptAttachments` owns wrapping for both message attachments and file
|
|
20
29
|
deliveries. `ImageAttachment` owns image previews.
|
|
21
30
|
- `TranscriptTurnContextView` owns context content. The shared `Drawer` owns
|
|
@@ -4,7 +4,11 @@ import { Link } from "react-router";
|
|
|
4
4
|
|
|
5
5
|
import { useConversationData } from "./queries";
|
|
6
6
|
import { conversationPath } from "./conversationRoutes";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
conversationDetailTitle,
|
|
9
|
+
formatMessageTimestamp,
|
|
10
|
+
formatRelativeMessageTimestamp,
|
|
11
|
+
} from "../format";
|
|
8
12
|
import { buildConversationMarkdown } from "../markdownExport";
|
|
9
13
|
import { Drawer } from "../components/Drawer";
|
|
10
14
|
import { SearchInput } from "../components/SearchInput";
|
|
@@ -22,6 +26,8 @@ import { transcriptEmptyClass } from "./transcriptStyles";
|
|
|
22
26
|
|
|
23
27
|
/** One child Conversation to show, with what its parent already knows. */
|
|
24
28
|
export interface SubagentTranscriptTarget {
|
|
29
|
+
/** Agent name, such as "planner". Shown beside the status. */
|
|
30
|
+
agentName?: string;
|
|
25
31
|
conversationId: string;
|
|
26
32
|
/** Task title or agent name, shown before the child loads. */
|
|
27
33
|
label?: string;
|
|
@@ -58,6 +64,11 @@ function SubagentTranscriptDrawerContent(props: {
|
|
|
58
64
|
const label =
|
|
59
65
|
props.target.label ?? conversationDetailTitle(detail) ?? "Subagent";
|
|
60
66
|
const status = statusLabel(props.target.status, detail?.status);
|
|
67
|
+
const agentName =
|
|
68
|
+
props.target.agentName && props.target.agentName !== label
|
|
69
|
+
? props.target.agentName
|
|
70
|
+
: undefined;
|
|
71
|
+
const startedAt = detail ? Date.parse(detail.startedAt) : undefined;
|
|
61
72
|
const searchOpenVisible = searchOpen || search.length > 0;
|
|
62
73
|
|
|
63
74
|
const titleId = "subagent-transcript-drawer-title";
|
|
@@ -100,42 +111,47 @@ function SubagentTranscriptDrawerContent(props: {
|
|
|
100
111
|
aria-hidden="true"
|
|
101
112
|
className="shrink-0 text-cyan-300"
|
|
102
113
|
size={16}
|
|
103
|
-
strokeWidth={2
|
|
114
|
+
strokeWidth={2}
|
|
104
115
|
/>
|
|
105
116
|
<h2
|
|
106
|
-
className="m-0 min-w-0 break-words text-lg font-
|
|
117
|
+
className="m-0 min-w-0 break-words font-display text-lg font-medium leading-tight tracking-tight text-dashboard-text"
|
|
107
118
|
id={titleId}
|
|
108
119
|
>
|
|
109
120
|
{label}
|
|
110
121
|
</h2>
|
|
111
122
|
</div>
|
|
112
|
-
<div className="mt-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-5 text-dashboard-text-muted">
|
|
124
|
+
{agentName ? (
|
|
125
|
+
<span className="font-medium text-dashboard-text">
|
|
126
|
+
{agentName}
|
|
127
|
+
</span>
|
|
128
|
+
) : null}
|
|
129
|
+
{status ? (
|
|
130
|
+
<StatusChip size="compact" tone={subagentStatusTone(status)}>
|
|
131
|
+
{status}
|
|
132
|
+
</StatusChip>
|
|
133
|
+
) : null}
|
|
134
|
+
{detail && startedAt !== undefined ? (
|
|
135
|
+
<time
|
|
136
|
+
className="tabular-nums"
|
|
137
|
+
dateTime={detail.startedAt}
|
|
138
|
+
title={formatMessageTimestamp(startedAt, true)}
|
|
139
|
+
>
|
|
140
|
+
Started {formatRelativeMessageTimestamp(startedAt)}
|
|
141
|
+
</time>
|
|
142
|
+
) : null}
|
|
116
143
|
<Link
|
|
117
|
-
className="inline-flex w-fit items-center gap-1 font-
|
|
144
|
+
className="inline-flex w-fit items-center gap-1 font-medium text-dashboard-accent-text no-underline hover:underline"
|
|
118
145
|
onClick={props.onClose}
|
|
119
146
|
to={conversationPath(props.target.conversationId)}
|
|
120
147
|
>
|
|
121
148
|
Open conversation
|
|
122
|
-
<ExternalLink aria-hidden="true" size={12} strokeWidth={2
|
|
149
|
+
<ExternalLink aria-hidden="true" size={12} strokeWidth={2} />
|
|
123
150
|
</Link>
|
|
124
151
|
</div>
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
<StatusChip size="compact" tone={subagentStatusTone(status)}>
|
|
129
|
-
{status}
|
|
130
|
-
</StatusChip>
|
|
131
|
-
) : null}
|
|
132
|
-
{detail ? (
|
|
133
|
-
<time className="font-mono" dateTime={detail.startedAt}>
|
|
134
|
-
{formatMessageTimestamp(Date.parse(detail.startedAt))}
|
|
135
|
-
</time>
|
|
136
|
-
) : null}
|
|
137
|
-
</div>
|
|
138
|
-
) : null}
|
|
152
|
+
<code className="mt-1 block min-w-0 break-all font-mono text-xs text-dashboard-text-faint">
|
|
153
|
+
{props.target.conversationId}
|
|
154
|
+
</code>
|
|
139
155
|
{searchOpenVisible ? (
|
|
140
156
|
<div className="mt-3 min-w-0">
|
|
141
157
|
<SearchInput
|