@sentry/junior-dashboard 0.265.0 → 0.266.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 +2 -2
- package/dist/client.js +48 -48
- package/dist/index.js +2 -2
- package/dist/tailwind.css +1 -1
- package/package.json +3 -3
- package/src/client/components/ActorAvatar.tsx +10 -22
- package/src/client/components/ConnectionBanner.tsx +4 -3
- package/src/client/components/CopyButton.tsx +3 -4
- package/src/client/components/EmptyTelemetry.tsx +5 -3
- package/src/client/components/ImageAttachment.tsx +49 -63
- package/src/client/components/InlineError.tsx +8 -4
- package/src/client/components/Notice.tsx +19 -26
- package/src/client/components/Pagination.tsx +26 -26
- package/src/client/components/ParticipantAvatarStack.tsx +10 -10
- package/src/client/components/README.md +17 -1
- package/src/client/components/SearchInput.tsx +51 -41
- package/src/client/components/VersionDriftBanner.tsx +9 -7
- package/src/client/components/charts/CacheWrites.tsx +52 -43
- package/src/client/components/ui/alert.tsx +64 -0
- package/src/client/components/ui/avatar.tsx +53 -0
- package/src/client/components/ui/button.tsx +2 -1
- package/src/client/components/ui/dialog.tsx +66 -0
- package/src/client/components/ui/empty.tsx +32 -0
- package/src/client/components/ui/input-group.tsx +96 -0
- package/src/client/components/ui/pagination.tsx +60 -0
- package/src/client/components/ui/spinner.tsx +21 -0
- package/src/client/components/ui/table.tsx +22 -1
- package/src/client/components/ui/tooltip.tsx +1 -1
- package/src/client/conversations/ConversationChildAgents.tsx +5 -5
- package/src/client/conversations/ConversationHeader.tsx +5 -4
- package/src/client/conversations/ConversationHeaderActions.tsx +7 -11
- package/src/client/conversations/ConversationSidebar.tsx +5 -4
- package/src/client/conversations/ConversationUsageBreakdown.tsx +108 -65
- package/src/client/conversations/ConversationWatches.tsx +67 -59
- package/src/client/conversations/DetailsList.tsx +44 -29
- package/src/client/conversations/EventUsage.tsx +99 -74
- package/src/client/conversations/PendingMailboxStack.tsx +5 -10
- package/src/client/conversations/README.md +3 -0
- package/src/client/conversations/ShareConversationButton.tsx +3 -4
- package/src/client/conversations/TranscriptEventView.tsx +35 -16
- package/src/client/conversations/TranscriptMessageContextView.tsx +31 -20
- package/src/client/conversations/TranscriptStructuredEventView.tsx +25 -15
- package/src/client/conversations/TranscriptSummary.tsx +34 -2
- package/src/client/conversations/TranscriptTurnContextView.tsx +45 -38
- package/src/client/conversations/eventLog.ts +1 -0
- package/src/client/conversations/transcriptMentions.tsx +18 -5
- package/src/mock-reporting/watches.ts +1 -1
|
@@ -7,8 +7,8 @@ import { cn, dashboardDisclosureChevronClass } from "../styles";
|
|
|
7
7
|
const COLLAPSED_ROW_COUNT = 3;
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Show rows in a details card.
|
|
11
|
-
*
|
|
10
|
+
* Show rows in a details card. A list with more than three rows shows the
|
|
11
|
+
* first three and a row that shows the rest.
|
|
12
12
|
*/
|
|
13
13
|
export function DetailsList<T>(props: {
|
|
14
14
|
items: readonly T[];
|
|
@@ -23,26 +23,21 @@ export function DetailsList<T>(props: {
|
|
|
23
23
|
: props.items.slice(0, COLLAPSED_ROW_COUNT);
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
// Rows own their padding, so hover
|
|
27
|
-
//
|
|
28
|
-
<ul className="-
|
|
26
|
+
// Rows own their padding, so a hover fill sits inside the card with an
|
|
27
|
+
// even inset. The negative margins keep row text on the card padding.
|
|
28
|
+
<ul className="-mx-1.5 -mb-1.5 -mt-1 grid list-none gap-0.5 p-0">
|
|
29
29
|
{visible.map((item) => (
|
|
30
|
-
<li
|
|
31
|
-
className="border-t border-dashboard-border-subtle first:border-t-0"
|
|
32
|
-
key={props.itemKey(item)}
|
|
33
|
-
>
|
|
34
|
-
{props.renderItem(item)}
|
|
35
|
-
</li>
|
|
30
|
+
<li key={props.itemKey(item)}>{props.renderItem(item)}</li>
|
|
36
31
|
))}
|
|
37
32
|
{hidden > 0 ? (
|
|
38
|
-
<li
|
|
33
|
+
<li>
|
|
39
34
|
<button
|
|
40
35
|
aria-expanded={expanded}
|
|
41
|
-
className="group
|
|
36
|
+
className="group flex min-h-8 w-full items-center gap-2.5 rounded-md px-1.5 py-1.5 text-left text-xs font-medium leading-4 text-dashboard-text-muted transition-colors hover:bg-dashboard-surface-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
42
37
|
onClick={() => setExpanded((value) => !value)}
|
|
43
38
|
type="button"
|
|
44
39
|
>
|
|
45
|
-
<span className="grid size-
|
|
40
|
+
<span className="grid size-5 shrink-0 place-items-center">
|
|
46
41
|
<ChevronRight
|
|
47
42
|
aria-hidden="true"
|
|
48
43
|
className={dashboardDisclosureChevronClass}
|
|
@@ -59,20 +54,24 @@ export function DetailsList<T>(props: {
|
|
|
59
54
|
/**
|
|
60
55
|
* Lay out one details list row: an icon, a title, and short trailing text on
|
|
61
56
|
* the first line, then optional lines under the title. A row with `href`
|
|
62
|
-
* opens that link in a new tab.
|
|
57
|
+
* opens that link in a new tab. A row with `onToggle` is a disclosure button
|
|
58
|
+
* that shows or hides content the caller renders under it.
|
|
63
59
|
*/
|
|
64
60
|
export function DetailsRow(props: {
|
|
65
61
|
children?: ReactNode;
|
|
62
|
+
expanded?: boolean;
|
|
66
63
|
href?: string;
|
|
67
64
|
icon: ReactNode;
|
|
68
65
|
meta?: ReactNode;
|
|
66
|
+
onToggle?(): void;
|
|
69
67
|
title: ReactNode;
|
|
70
68
|
/** Native hover text with detail that the row does not show. */
|
|
71
69
|
tooltip?: string;
|
|
72
70
|
}) {
|
|
71
|
+
const interactive = props.href !== undefined || props.onToggle !== undefined;
|
|
73
72
|
const className = cn(
|
|
74
|
-
"-
|
|
75
|
-
|
|
73
|
+
"grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-x-2.5 gap-y-1 rounded-md px-1.5 py-1.5 text-left text-inherit no-underline",
|
|
74
|
+
interactive &&
|
|
76
75
|
"group transition-colors hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus",
|
|
77
76
|
);
|
|
78
77
|
const content = (
|
|
@@ -80,7 +79,7 @@ export function DetailsRow(props: {
|
|
|
80
79
|
<span className="grid size-5 shrink-0 place-items-center text-dashboard-text-muted">
|
|
81
80
|
{props.icon}
|
|
82
81
|
</span>
|
|
83
|
-
<span className="min-w-0 truncate text-sm font-medium leading-5 text-dashboard-text
|
|
82
|
+
<span className="min-w-0 truncate text-sm font-medium leading-5 text-dashboard-text">
|
|
84
83
|
{props.title}
|
|
85
84
|
</span>
|
|
86
85
|
<span className="shrink-0 text-xs leading-5 tabular-nums text-dashboard-text-muted">
|
|
@@ -89,17 +88,33 @@ export function DetailsRow(props: {
|
|
|
89
88
|
{props.children}
|
|
90
89
|
</>
|
|
91
90
|
);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
91
|
+
if (props.href) {
|
|
92
|
+
return (
|
|
93
|
+
<a
|
|
94
|
+
className={className}
|
|
95
|
+
href={props.href}
|
|
96
|
+
rel="noreferrer"
|
|
97
|
+
target="_blank"
|
|
98
|
+
title={props.tooltip}
|
|
99
|
+
>
|
|
100
|
+
{content}
|
|
101
|
+
</a>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (props.onToggle) {
|
|
105
|
+
return (
|
|
106
|
+
<button
|
|
107
|
+
aria-expanded={props.expanded ?? false}
|
|
108
|
+
className={className}
|
|
109
|
+
onClick={props.onToggle}
|
|
110
|
+
title={props.tooltip}
|
|
111
|
+
type="button"
|
|
112
|
+
>
|
|
113
|
+
{content}
|
|
114
|
+
</button>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return (
|
|
103
118
|
<div className={className} title={props.tooltip}>
|
|
104
119
|
{content}
|
|
105
120
|
</div>
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { ConversationUsage } from "@sentry/junior/api/schema";
|
|
2
2
|
|
|
3
3
|
import { formatCostBreakdown, summarizeCost } from "../format";
|
|
4
|
+
import {
|
|
5
|
+
Table,
|
|
6
|
+
TableBody,
|
|
7
|
+
TableCell,
|
|
8
|
+
TableHead,
|
|
9
|
+
TableFooter,
|
|
10
|
+
TableHeader,
|
|
11
|
+
TableRow,
|
|
12
|
+
} from "../components/ui/table";
|
|
4
13
|
import { cn } from "../styles";
|
|
5
14
|
|
|
6
15
|
/** Align recorded token counts with their costs without adding overlapping counters. */
|
|
@@ -31,83 +40,53 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
|
|
|
31
40
|
);
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
const total = rows.find((row) => row.label === "Total");
|
|
34
44
|
return (
|
|
35
45
|
<>
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
>
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"py-2 text-left font-normal",
|
|
73
|
-
row.label === "Total"
|
|
74
|
-
? "font-semibold text-dashboard-text"
|
|
75
|
-
: "text-dashboard-text-muted",
|
|
76
|
-
)}
|
|
77
|
-
>
|
|
78
|
-
{row.label}
|
|
79
|
-
</th>
|
|
80
|
-
<td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
|
|
81
|
-
{row.tokens === undefined ? (
|
|
82
|
-
<span
|
|
83
|
-
aria-label="Not recorded"
|
|
84
|
-
className="text-dashboard-text-faint"
|
|
85
|
-
>
|
|
86
|
-
—
|
|
87
|
-
</span>
|
|
88
|
-
) : (
|
|
89
|
-
row.tokens.toLocaleString("en-US")
|
|
90
|
-
)}
|
|
91
|
-
</td>
|
|
92
|
-
<td className="py-2 text-right font-sans tabular-nums text-dashboard-text">
|
|
93
|
-
{row.cost === undefined ? (
|
|
94
|
-
<span
|
|
95
|
-
aria-label="Not recorded separately"
|
|
96
|
-
className="text-dashboard-text-faint"
|
|
97
|
-
>
|
|
98
|
-
—
|
|
99
|
-
</span>
|
|
100
|
-
) : (
|
|
101
|
-
<span title={`${row.cost} USD`}>
|
|
102
|
-
{formatCostBreakdown({ total: row.cost })}
|
|
103
|
-
</span>
|
|
104
|
-
)}
|
|
105
|
-
</td>
|
|
106
|
-
</tr>
|
|
46
|
+
<Table
|
|
47
|
+
aria-label="Model call token and cost breakdown"
|
|
48
|
+
className="min-w-72 table-fixed text-xs leading-relaxed"
|
|
49
|
+
>
|
|
50
|
+
<colgroup>
|
|
51
|
+
<col />
|
|
52
|
+
<col className="w-24" />
|
|
53
|
+
<col className="w-24" />
|
|
54
|
+
</colgroup>
|
|
55
|
+
<TableHeader>
|
|
56
|
+
<TableRow className="border-dashboard-border">
|
|
57
|
+
<TableHead
|
|
58
|
+
scope="col"
|
|
59
|
+
className="px-0 py-2 leading-relaxed font-normal"
|
|
60
|
+
>
|
|
61
|
+
Usage
|
|
62
|
+
</TableHead>
|
|
63
|
+
<TableHead
|
|
64
|
+
scope="col"
|
|
65
|
+
className="px-0 py-2 text-right leading-relaxed font-normal"
|
|
66
|
+
>
|
|
67
|
+
Tokens
|
|
68
|
+
</TableHead>
|
|
69
|
+
<TableHead
|
|
70
|
+
scope="col"
|
|
71
|
+
className="px-0 py-2 text-right leading-relaxed font-normal"
|
|
72
|
+
>
|
|
73
|
+
Cost (USD)
|
|
74
|
+
</TableHead>
|
|
75
|
+
</TableRow>
|
|
76
|
+
</TableHeader>
|
|
77
|
+
<TableBody>
|
|
78
|
+
{rows
|
|
79
|
+
.filter((row) => row !== total)
|
|
80
|
+
.map((row) => (
|
|
81
|
+
<UsageRow key={row.label} {...row} />
|
|
107
82
|
))}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
83
|
+
</TableBody>
|
|
84
|
+
{total ? (
|
|
85
|
+
<TableFooter className="font-semibold">
|
|
86
|
+
<UsageRow {...total} total />
|
|
87
|
+
</TableFooter>
|
|
88
|
+
) : null}
|
|
89
|
+
</Table>
|
|
111
90
|
<p className="m-0 text-xs leading-relaxed text-dashboard-text-muted">
|
|
112
91
|
This call only · Estimated costs
|
|
113
92
|
<br />
|
|
@@ -116,3 +95,49 @@ export function EventUsage({ usage }: { usage?: ConversationUsage }) {
|
|
|
116
95
|
</>
|
|
117
96
|
);
|
|
118
97
|
}
|
|
98
|
+
|
|
99
|
+
function UsageRow(props: {
|
|
100
|
+
cost?: number;
|
|
101
|
+
label: string;
|
|
102
|
+
tokens?: number;
|
|
103
|
+
total?: boolean;
|
|
104
|
+
}) {
|
|
105
|
+
return (
|
|
106
|
+
<TableRow>
|
|
107
|
+
<TableHead
|
|
108
|
+
scope="row"
|
|
109
|
+
className={cn(
|
|
110
|
+
"px-0 py-2 leading-relaxed",
|
|
111
|
+
props.total
|
|
112
|
+
? "font-semibold text-dashboard-text"
|
|
113
|
+
: "font-normal text-dashboard-text-muted",
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
{props.label}
|
|
117
|
+
</TableHead>
|
|
118
|
+
<TableCell className="px-0 py-2 text-right font-sans tabular-nums">
|
|
119
|
+
{props.tokens === undefined ? (
|
|
120
|
+
<span aria-label="Not recorded" className="text-dashboard-text-faint">
|
|
121
|
+
—
|
|
122
|
+
</span>
|
|
123
|
+
) : (
|
|
124
|
+
props.tokens.toLocaleString("en-US")
|
|
125
|
+
)}
|
|
126
|
+
</TableCell>
|
|
127
|
+
<TableCell className="px-0 py-2 text-right font-sans tabular-nums">
|
|
128
|
+
{props.cost === undefined ? (
|
|
129
|
+
<span
|
|
130
|
+
aria-label="Not recorded separately"
|
|
131
|
+
className="text-dashboard-text-faint"
|
|
132
|
+
>
|
|
133
|
+
—
|
|
134
|
+
</span>
|
|
135
|
+
) : (
|
|
136
|
+
<span title={`${props.cost} USD`}>
|
|
137
|
+
{formatCostBreakdown({ total: props.cost })}
|
|
138
|
+
</span>
|
|
139
|
+
)}
|
|
140
|
+
</TableCell>
|
|
141
|
+
</TableRow>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
import {
|
|
10
10
|
AlertCircle,
|
|
11
11
|
Clock3,
|
|
12
|
-
LoaderCircle,
|
|
13
12
|
SkipForward,
|
|
14
13
|
X,
|
|
15
14
|
type LucideIcon,
|
|
@@ -20,6 +19,7 @@ import {
|
|
|
20
19
|
TooltipContent,
|
|
21
20
|
TooltipTrigger,
|
|
22
21
|
} from "../components/ui/tooltip";
|
|
22
|
+
import { Spinner } from "../components/ui/spinner";
|
|
23
23
|
import { actorLabel } from "../format";
|
|
24
24
|
import type { ConversationMailboxMessage } from "./conversationOutbox";
|
|
25
25
|
import {
|
|
@@ -33,12 +33,12 @@ const COLLAPSED_PENDING_ROW_COUNT = 2;
|
|
|
33
33
|
|
|
34
34
|
function pendingDeliveryMeta(
|
|
35
35
|
message: Pick<ConversationMailboxMessage, "clientStatus" | "delivery">,
|
|
36
|
-
): { icon: LucideIcon; label: string
|
|
36
|
+
): { icon: LucideIcon | typeof Spinner; label: string } {
|
|
37
37
|
if (message.clientStatus === "failed") {
|
|
38
38
|
return { icon: AlertCircle, label: "Failed to send" };
|
|
39
39
|
}
|
|
40
40
|
if (message.clientStatus === "sending") {
|
|
41
|
-
return { icon:
|
|
41
|
+
return { icon: Spinner, label: "Sending" };
|
|
42
42
|
}
|
|
43
43
|
if (message.delivery === "interrupt") {
|
|
44
44
|
return { icon: SkipForward, label: "Interrupt" };
|
|
@@ -92,8 +92,7 @@ function PendingMetaIcons(props: {
|
|
|
92
92
|
<PendingMetaIcon label={delivery.label}>
|
|
93
93
|
<DeliveryIcon
|
|
94
94
|
aria-hidden="true"
|
|
95
|
-
className=
|
|
96
|
-
size={13}
|
|
95
|
+
className="size-3.25"
|
|
97
96
|
strokeWidth={2.2}
|
|
98
97
|
/>
|
|
99
98
|
</PendingMetaIcon>
|
|
@@ -113,11 +112,7 @@ function PendingMetaIcons(props: {
|
|
|
113
112
|
type="button"
|
|
114
113
|
>
|
|
115
114
|
{props.cancelPending ? (
|
|
116
|
-
<
|
|
117
|
-
aria-hidden="true"
|
|
118
|
-
className="animate-spin"
|
|
119
|
-
size={13}
|
|
120
|
-
/>
|
|
115
|
+
<Spinner aria-hidden="true" className="size-3.25" />
|
|
121
116
|
) : (
|
|
122
117
|
<X aria-hidden="true" size={14} strokeWidth={2.2} />
|
|
123
118
|
)}
|
|
@@ -16,6 +16,9 @@ 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
|
+
`TranscriptSummaryTrigger` gives a `ui/collapsible` trigger the same look.
|
|
20
|
+
Its caller owns the `Collapsible` and its content. Transcript search holds
|
|
21
|
+
event and context rows open.
|
|
19
22
|
- Activity rows (tools, reasoning, and grouped rail events) share one
|
|
20
23
|
layout: a 1rem icon column, an 8px gap, then the title and metadata.
|
|
21
24
|
Tool and reasoning rows use `ui/trace`. Transcript search forces them open.
|
|
@@ -96,15 +96,14 @@ export function ShareConversationButton(props: {
|
|
|
96
96
|
</TooltipContent>
|
|
97
97
|
</Tooltip>
|
|
98
98
|
) : props.layout === "menu" ? (
|
|
99
|
-
<
|
|
99
|
+
<Button
|
|
100
100
|
aria-label={label}
|
|
101
|
-
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-hover focus-visible:bg-dashboard-fill-hover focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
102
101
|
disabled={status === "sharing"}
|
|
103
102
|
onClick={() => void shareLink()}
|
|
104
|
-
|
|
103
|
+
size="menu"
|
|
105
104
|
>
|
|
106
105
|
{content}
|
|
107
|
-
</
|
|
106
|
+
</Button>
|
|
108
107
|
) : (
|
|
109
108
|
<Button
|
|
110
109
|
aria-label={label}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { ChevronRight } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
|
|
4
|
+
import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
|
|
5
|
+
import { cn, dashboardDisclosureChevronClass } from "../styles";
|
|
3
6
|
import { MentionText } from "./transcriptMentions";
|
|
4
7
|
import { messageRawText } from "./transcriptRenderModel";
|
|
5
|
-
import { HighlightText } from "./transcriptSearch";
|
|
8
|
+
import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
|
|
6
9
|
import { RedactedMarker } from "./TranscriptRedacted";
|
|
7
10
|
import type { TranscriptViewMessage } from "../types";
|
|
8
|
-
import {
|
|
11
|
+
import { TranscriptSummaryTrigger } from "./TranscriptSummary";
|
|
9
12
|
|
|
10
13
|
function EventTypeChip(props: { eventType: string }) {
|
|
11
14
|
return (
|
|
@@ -25,6 +28,8 @@ function batchSummary(events: readonly { label?: string }[]): string {
|
|
|
25
28
|
|
|
26
29
|
/** Render an Event Message whose heading expands its full text. */
|
|
27
30
|
export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
|
|
31
|
+
const { active: searchActive } = useTranscriptSearch();
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
28
33
|
const text = messageRawText(props.message);
|
|
29
34
|
const redacted = props.message.parts.some(
|
|
30
35
|
(part) => part.type === "text" && part.redacted,
|
|
@@ -35,33 +40,38 @@ export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
|
|
|
35
40
|
const summary = isBatch
|
|
36
41
|
? batchSummary(events)
|
|
37
42
|
: (props.message.trustedSummary ?? props.message.eventType ?? "");
|
|
43
|
+
// With text, the heading sits in the disclosure button, which cannot hold
|
|
44
|
+
// links.
|
|
45
|
+
const links = !text;
|
|
38
46
|
const heading = (
|
|
39
47
|
<span className="flex min-w-0 flex-col gap-1">
|
|
40
48
|
<span className="flex min-h-6 min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
41
49
|
<span className="min-w-0 break-words font-display text-sm font-semibold leading-6 text-dashboard-text">
|
|
42
|
-
<MentionText mentions={mentions} text={summary} />
|
|
50
|
+
<MentionText links={links} mentions={mentions} text={summary} />
|
|
43
51
|
</span>
|
|
44
52
|
{!isBatch && props.message.eventType ? (
|
|
45
53
|
<EventTypeChip eventType={props.message.eventType} />
|
|
46
54
|
) : null}
|
|
47
55
|
</span>
|
|
48
56
|
{isBatch ? (
|
|
49
|
-
<
|
|
57
|
+
<span className="flex min-w-0 flex-col gap-1" role="list">
|
|
50
58
|
{events.map((event, index) => (
|
|
51
|
-
<
|
|
59
|
+
<span
|
|
52
60
|
key={index}
|
|
61
|
+
role="listitem"
|
|
53
62
|
className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 text-xs leading-5 text-dashboard-text-muted"
|
|
54
63
|
>
|
|
55
64
|
<span className="min-w-0 break-words">
|
|
56
65
|
<MentionText
|
|
66
|
+
links={links}
|
|
57
67
|
mentions={mentions}
|
|
58
68
|
text={event.trustedSummary ?? event.eventType}
|
|
59
69
|
/>
|
|
60
70
|
</span>
|
|
61
71
|
<EventTypeChip eventType={event.eventType} />
|
|
62
|
-
</
|
|
72
|
+
</span>
|
|
63
73
|
))}
|
|
64
|
-
</
|
|
74
|
+
</span>
|
|
65
75
|
) : null}
|
|
66
76
|
</span>
|
|
67
77
|
);
|
|
@@ -69,22 +79,31 @@ export function TranscriptEventView(props: { message: TranscriptViewMessage }) {
|
|
|
69
79
|
return (
|
|
70
80
|
<div className="min-w-0 py-1">
|
|
71
81
|
{text ? (
|
|
72
|
-
<
|
|
73
|
-
|
|
82
|
+
<Collapsible
|
|
83
|
+
className="min-w-0"
|
|
84
|
+
// Search holds the text open, so the heading button is off until
|
|
85
|
+
// search ends.
|
|
86
|
+
disabled={searchActive}
|
|
87
|
+
onOpenChange={setOpen}
|
|
88
|
+
open={searchActive || open}
|
|
89
|
+
>
|
|
90
|
+
<TranscriptSummaryTrigger
|
|
74
91
|
flush
|
|
75
|
-
className="flex items-start gap-2 text-dashboard-text-muted"
|
|
92
|
+
className="group flex items-start gap-2 text-dashboard-text-muted"
|
|
76
93
|
>
|
|
77
94
|
{heading}
|
|
78
95
|
<ChevronRight
|
|
79
96
|
aria-hidden="true"
|
|
80
|
-
className="mt-1.5
|
|
97
|
+
className={cn(dashboardDisclosureChevronClass, "mt-1.5")}
|
|
81
98
|
strokeWidth={2.2}
|
|
82
99
|
/>
|
|
83
|
-
</
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
</TranscriptSummaryTrigger>
|
|
101
|
+
<CollapsibleContent hiddenUntilFound>
|
|
102
|
+
<pre className="mt-2 max-h-80 overflow-auto whitespace-pre-wrap break-words rounded-lg border border-dashboard-border bg-dashboard-surface-panel px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted">
|
|
103
|
+
<HighlightText text={text} />
|
|
104
|
+
</pre>
|
|
105
|
+
</CollapsibleContent>
|
|
106
|
+
</Collapsible>
|
|
88
107
|
) : (
|
|
89
108
|
<>
|
|
90
109
|
{heading}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { ChevronRight } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
2
3
|
|
|
4
|
+
import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
|
|
5
|
+
import { dashboardDisclosureChevronClass } from "../styles";
|
|
3
6
|
import { formatMessageTimestamp, transcriptMessageActorLabel } from "../format";
|
|
4
7
|
import type { ConversationTranscript, TranscriptViewMessage } from "../types";
|
|
5
8
|
import {
|
|
6
9
|
TranscriptHeadingMeta,
|
|
7
10
|
TranscriptHeadingRow,
|
|
8
11
|
} from "./TranscriptHeadingRow";
|
|
9
|
-
import { HighlightText } from "./transcriptSearch";
|
|
12
|
+
import { HighlightText, useTranscriptSearch } from "./transcriptSearch";
|
|
10
13
|
import { messageRawText } from "./transcriptRenderModel";
|
|
11
14
|
import { RedactedMarker } from "./TranscriptRedacted";
|
|
12
15
|
import { TranscriptTurnContextView } from "./TranscriptTurnContextView";
|
|
13
|
-
import {
|
|
16
|
+
import { TranscriptSummaryTrigger } from "./TranscriptSummary";
|
|
14
17
|
|
|
15
18
|
/** Render ambient conversation context that is not a primary chat bubble. */
|
|
16
19
|
export function TranscriptMessageContextView(props: {
|
|
@@ -18,6 +21,8 @@ export function TranscriptMessageContextView(props: {
|
|
|
18
21
|
message: TranscriptViewMessage;
|
|
19
22
|
redacted?: boolean;
|
|
20
23
|
}) {
|
|
24
|
+
const { active: searchActive } = useTranscriptSearch();
|
|
25
|
+
const [open, setOpen] = useState(false);
|
|
21
26
|
const actor = transcriptMessageActorLabel(props.conversation, props.message);
|
|
22
27
|
const timestamp = formatMessageTimestamp(props.message.timestamp);
|
|
23
28
|
const text = messageRawText(props.message);
|
|
@@ -30,33 +35,39 @@ export function TranscriptMessageContextView(props: {
|
|
|
30
35
|
|
|
31
36
|
return (
|
|
32
37
|
<>
|
|
33
|
-
<
|
|
34
|
-
className="
|
|
38
|
+
<Collapsible
|
|
39
|
+
className="min-w-0 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 md:hidden"
|
|
35
40
|
data-transcript-message-context
|
|
41
|
+
// Search holds the text open, so the heading button is off until
|
|
42
|
+
// search ends.
|
|
43
|
+
disabled={searchActive}
|
|
44
|
+
onOpenChange={setOpen}
|
|
45
|
+
open={searchActive || open}
|
|
36
46
|
>
|
|
37
|
-
<
|
|
47
|
+
<TranscriptSummaryTrigger
|
|
38
48
|
flush
|
|
39
|
-
className="flex items-center justify-between gap-2 font-sans text-xs font-medium text-dashboard-text-muted"
|
|
49
|
+
className="group flex items-center justify-between gap-2 font-sans text-xs font-medium text-dashboard-text-muted"
|
|
40
50
|
>
|
|
41
51
|
<span className="min-w-0 truncate">Context from {actor}</span>
|
|
42
52
|
<ChevronRight
|
|
43
53
|
aria-hidden="true"
|
|
44
|
-
className=
|
|
45
|
-
size={14}
|
|
54
|
+
className={dashboardDisclosureChevronClass}
|
|
46
55
|
/>
|
|
47
|
-
</
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{!props.redacted && props.message.contexts?.length ? (
|
|
52
|
-
<div className="mt-2">
|
|
53
|
-
<TranscriptTurnContextView
|
|
54
|
-
contexts={props.message.contexts}
|
|
55
|
-
messageId={props.message.messageId}
|
|
56
|
-
/>
|
|
56
|
+
</TranscriptSummaryTrigger>
|
|
57
|
+
<CollapsibleContent hiddenUntilFound>
|
|
58
|
+
<div className="mt-2 whitespace-pre-wrap pt-2 text-sm leading-relaxed text-dashboard-text-subtle">
|
|
59
|
+
{content}
|
|
57
60
|
</div>
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
{!props.redacted && props.message.contexts?.length ? (
|
|
62
|
+
<div className="mt-2">
|
|
63
|
+
<TranscriptTurnContextView
|
|
64
|
+
contexts={props.message.contexts}
|
|
65
|
+
messageId={props.message.messageId}
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
) : null}
|
|
69
|
+
</CollapsibleContent>
|
|
70
|
+
</Collapsible>
|
|
60
71
|
<article
|
|
61
72
|
className="hidden min-w-0 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel px-3 py-2.5 md:block"
|
|
62
73
|
data-transcript-message-context
|