@sentry/junior-dashboard 0.274.0 → 0.276.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 +28 -9
- package/dist/client.js +115 -92
- package/dist/index.js +28 -9
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/app.ts +8 -3
- package/src/client/App.tsx +11 -0
- package/src/client/components/Metric.tsx +0 -32
- package/src/client/components/ProfileMenu.tsx +56 -4
- package/src/client/components/layout/DashboardChrome.tsx +3 -3
- package/src/client/components/layout/DashboardHeader.tsx +41 -4
- package/src/client/components/layout/NavRail.tsx +43 -40
- package/src/client/conversations/ConversationComposer.tsx +42 -26
- package/src/client/conversations/ConversationDetails.tsx +149 -166
- package/src/client/conversations/ConversationHeader.tsx +126 -132
- package/src/client/conversations/ConversationHeaderActions.tsx +92 -120
- package/src/client/conversations/ConversationListStatusIcon.tsx +16 -11
- package/src/client/conversations/ConversationMemories.tsx +118 -78
- package/src/client/conversations/ConversationMeta.tsx +94 -84
- package/src/client/conversations/ConversationOverflowMenu.tsx +59 -0
- package/src/client/conversations/ConversationPage.tsx +197 -422
- package/src/client/conversations/ConversationReplyFooter.tsx +342 -0
- package/src/client/conversations/ConversationSidebar.tsx +5 -7
- package/src/client/conversations/ConversationTranscript.tsx +37 -8
- package/src/client/conversations/ConversationWatches.tsx +37 -15
- package/src/client/conversations/PendingMailboxStack.tsx +11 -1
- package/src/client/conversations/QuickQuestion.tsx +476 -0
- package/src/client/conversations/TelemetryMetrics.tsx +2 -15
- package/src/client/conversations/TranscriptActivityGroup.tsx +5 -0
- package/src/client/conversations/TranscriptEventView.tsx +25 -2
- package/src/client/conversations/TranscriptMemoryPopover.tsx +179 -0
- package/src/client/conversations/TranscriptMessageView.tsx +46 -16
- package/src/client/conversations/TranscriptRows.tsx +9 -2
- package/src/client/conversations/TranscriptView.tsx +15 -32
- package/src/client/conversations/UsageCard.tsx +4 -18
- package/src/client/conversations/conversationMemory.ts +138 -0
- package/src/client/conversations/conversationSections.ts +18 -14
- package/src/client/conversations/eventTranscript.ts +8 -0
- package/src/client/review-desk/ConversationReviewItems.tsx +99 -0
- package/src/client/review-desk/ReviewItemActions.tsx +1 -1
- package/src/client/review-desk/ReviewItemChangeRow.tsx +85 -0
- package/src/client/review-desk/ReviewItemContext.tsx +41 -23
- package/src/client/review-desk/reviewItemFormat.ts +16 -0
- package/src/client/sentry.ts +77 -0
- package/src/client/types.ts +6 -0
- package/src/client.tsx +3 -0
- package/src/mock-reporting/watches.ts +2 -0
- package/src/shell.ts +27 -2
- package/src/tailwind.css +12 -19
- package/src/client/components/ActiveIndicator.tsx +0 -14
- package/src/client/conversations/ConversationHeaderMeta.tsx +0 -20
- package/src/client/review-desk/ConversationReviewBar.tsx +0 -85
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
isValidElement,
|
|
4
|
-
useEffect,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
type ReactElement,
|
|
8
|
-
type ReactNode,
|
|
9
|
-
} from "react";
|
|
10
|
-
import { Ellipsis, Menu } from "lucide-react";
|
|
1
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { Menu, PanelRight, ScrollText, X } from "lucide-react";
|
|
11
3
|
|
|
12
4
|
import { SearchInput } from "../components/SearchInput";
|
|
13
5
|
import { Button } from "../components/ui/button";
|
|
@@ -20,29 +12,41 @@ import {
|
|
|
20
12
|
import {
|
|
21
13
|
ConversationHeaderActions,
|
|
22
14
|
HeaderIconButton,
|
|
23
|
-
|
|
15
|
+
TranscriptSearchToggle,
|
|
24
16
|
type ConversationArchiveAction,
|
|
25
17
|
} from "./ConversationHeaderActions";
|
|
18
|
+
import { ConversationOverflowMenu } from "./ConversationOverflowMenu";
|
|
19
|
+
import { ShareConversationButton } from "./ShareConversationButton";
|
|
26
20
|
import type { TranscriptViewMode } from "./transcriptRenderModel";
|
|
27
21
|
|
|
28
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Show the conversation title bar: where it runs, the title, its cost, and
|
|
24
|
+
* actions. Other run facts, linked work, and the Brief live in Conversation
|
|
25
|
+
* details.
|
|
26
|
+
*/
|
|
29
27
|
export function ConversationHeader(props: {
|
|
30
28
|
/** Parent chain of a child Conversation. Root Conversations omit it. */
|
|
31
29
|
agentTree?: ReactNode;
|
|
30
|
+
/** A `menu` layout copy action for the `⋯` menu. */
|
|
32
31
|
copyAction?: ReactNode;
|
|
32
|
+
/** Estimated cost. Desktop only; mobile shows it in Conversation details. */
|
|
33
|
+
cost?: ReactNode;
|
|
33
34
|
archive: ConversationArchiveAction;
|
|
34
35
|
conversationId: string;
|
|
35
36
|
detailsOpen: boolean;
|
|
36
37
|
onDetailsClick(): void;
|
|
37
38
|
/** Links to the fork source and to forks of this conversation. */
|
|
38
39
|
forks?: ReactNode;
|
|
40
|
+
/** Mobile only. Desktop shows linked work in Conversation details. */
|
|
39
41
|
linkedWork?: ReactNode;
|
|
40
42
|
live: boolean;
|
|
41
|
-
|
|
43
|
+
/** Where the conversation runs, such as a Slack channel. */
|
|
44
|
+
location?: ReactNode;
|
|
42
45
|
onSearchChange(value: string): void;
|
|
43
46
|
onViewChange(value: TranscriptViewMode): void;
|
|
44
47
|
privacy: ReactNode;
|
|
45
48
|
search: string;
|
|
49
|
+
sentryConversationUrl?: string;
|
|
46
50
|
title: string;
|
|
47
51
|
view: TranscriptViewMode;
|
|
48
52
|
}) {
|
|
@@ -51,9 +55,7 @@ export function ConversationHeader(props: {
|
|
|
51
55
|
const menuButtonRef = useRef<HTMLButtonElement>(null);
|
|
52
56
|
const wasDetailsOpen = useRef(detailsOpen);
|
|
53
57
|
const [searchOpen, setSearchOpen] = useState(false);
|
|
54
|
-
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
|
55
58
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
56
|
-
const mobileMenuRef = useRef<HTMLDivElement>(null);
|
|
57
59
|
const searchOpenVisible =
|
|
58
60
|
props.view === "rich" && (searchOpen || props.search.length > 0);
|
|
59
61
|
const openMobileNavigation = useOpenMobileNavigation();
|
|
@@ -77,27 +79,6 @@ export function ConversationHeader(props: {
|
|
|
77
79
|
searchInputRef.current?.focus();
|
|
78
80
|
}, [searchOpenVisible]);
|
|
79
81
|
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (!mobileMenuOpen) return;
|
|
82
|
-
|
|
83
|
-
function closeOnOutsideClick(event: PointerEvent) {
|
|
84
|
-
if (!mobileMenuRef.current?.contains(event.target as Node)) {
|
|
85
|
-
setMobileMenuOpen(false);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function closeOnEscape(event: KeyboardEvent) {
|
|
90
|
-
if (event.key === "Escape") setMobileMenuOpen(false);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
document.addEventListener("pointerdown", closeOnOutsideClick);
|
|
94
|
-
document.addEventListener("keydown", closeOnEscape);
|
|
95
|
-
return () => {
|
|
96
|
-
document.removeEventListener("pointerdown", closeOnOutsideClick);
|
|
97
|
-
document.removeEventListener("keydown", closeOnEscape);
|
|
98
|
-
};
|
|
99
|
-
}, [mobileMenuOpen]);
|
|
100
|
-
|
|
101
82
|
const toggleSearch = () => {
|
|
102
83
|
if (searchOpenVisible) {
|
|
103
84
|
setSearchOpen(false);
|
|
@@ -107,64 +88,29 @@ export function ConversationHeader(props: {
|
|
|
107
88
|
setSearchOpen(true);
|
|
108
89
|
};
|
|
109
90
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
{
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
conversationId={props.conversationId}
|
|
134
|
-
copyAction={menuCopyAction}
|
|
135
|
-
detailsOpen={detailsOpen}
|
|
136
|
-
layout="menu"
|
|
137
|
-
onDetailsClick={() => {
|
|
138
|
-
setMobileMenuOpen(false);
|
|
139
|
-
props.onDetailsClick();
|
|
140
|
-
}}
|
|
141
|
-
onSearchClick={() => {
|
|
142
|
-
setMobileMenuOpen(false);
|
|
143
|
-
toggleSearch();
|
|
144
|
-
}}
|
|
145
|
-
onViewChange={(value) => {
|
|
146
|
-
setMobileMenuOpen(false);
|
|
147
|
-
props.onViewChange(value);
|
|
148
|
-
}}
|
|
149
|
-
searchOpen={searchOpenVisible}
|
|
150
|
-
view={props.view}
|
|
151
|
-
/>
|
|
152
|
-
{openMobileNavigation ? (
|
|
153
|
-
<Button
|
|
154
|
-
className="mt-0.5"
|
|
155
|
-
onClick={() => {
|
|
156
|
-
setMobileMenuOpen(false);
|
|
157
|
-
openMobileNavigation();
|
|
158
|
-
}}
|
|
159
|
-
size="menu"
|
|
160
|
-
>
|
|
161
|
-
<Menu aria-hidden="true" size={16} strokeWidth={2} />
|
|
162
|
-
App menu
|
|
163
|
-
</Button>
|
|
164
|
-
) : null}
|
|
165
|
-
</div>
|
|
166
|
-
) : null}
|
|
167
|
-
</div>
|
|
91
|
+
const menuActions = (placement: "desktop" | "mobile", close: () => void) => (
|
|
92
|
+
<ConversationHeaderActions
|
|
93
|
+
archive={props.archive}
|
|
94
|
+
conversationId={props.conversationId}
|
|
95
|
+
copyAction={props.copyAction}
|
|
96
|
+
detailsOpen={detailsOpen}
|
|
97
|
+
onDetailsClick={() => {
|
|
98
|
+
close();
|
|
99
|
+
props.onDetailsClick();
|
|
100
|
+
}}
|
|
101
|
+
onSearchClick={() => {
|
|
102
|
+
close();
|
|
103
|
+
toggleSearch();
|
|
104
|
+
}}
|
|
105
|
+
onViewChange={(value) => {
|
|
106
|
+
close();
|
|
107
|
+
props.onViewChange(value);
|
|
108
|
+
}}
|
|
109
|
+
placement={placement}
|
|
110
|
+
searchOpen={searchOpenVisible}
|
|
111
|
+
sentryConversationUrl={props.sentryConversationUrl}
|
|
112
|
+
view={props.view}
|
|
113
|
+
/>
|
|
168
114
|
);
|
|
169
115
|
|
|
170
116
|
const showMobileHeader =
|
|
@@ -184,48 +130,103 @@ export function ConversationHeader(props: {
|
|
|
184
130
|
|
|
185
131
|
return (
|
|
186
132
|
<>
|
|
187
|
-
<MobileHeaderActionsPortal>
|
|
133
|
+
<MobileHeaderActionsPortal>
|
|
134
|
+
<ConversationOverflowMenu buttonRef={menuButtonRef}>
|
|
135
|
+
{(close) => (
|
|
136
|
+
<>
|
|
137
|
+
{menuActions("mobile", close)}
|
|
138
|
+
{openMobileNavigation ? (
|
|
139
|
+
<Button
|
|
140
|
+
className="mt-0.5"
|
|
141
|
+
onClick={() => {
|
|
142
|
+
close();
|
|
143
|
+
openMobileNavigation();
|
|
144
|
+
}}
|
|
145
|
+
size="menu"
|
|
146
|
+
>
|
|
147
|
+
<Menu aria-hidden="true" size={16} strokeWidth={2} />
|
|
148
|
+
App menu
|
|
149
|
+
</Button>
|
|
150
|
+
) : null}
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
</ConversationOverflowMenu>
|
|
154
|
+
</MobileHeaderActionsPortal>
|
|
188
155
|
<MobileHeaderLivePortal>{liveIndicator}</MobileHeaderLivePortal>
|
|
156
|
+
{/* Desktop: above the event log's sticky toolbar, so the `⋯` menu
|
|
157
|
+
covers it. Mobile: below the app header, which holds the menu. */}
|
|
189
158
|
<header
|
|
190
159
|
className={cn(
|
|
191
|
-
"relative z-10 border-b border-dashboard-border bg-dashboard-bg",
|
|
160
|
+
"relative z-10 border-b md:z-30 border-dashboard-border bg-dashboard-bg",
|
|
192
161
|
!showMobileHeader && "hidden md:block",
|
|
193
162
|
)}
|
|
194
163
|
>
|
|
195
|
-
<div className="hidden min-w-0 items-
|
|
196
|
-
<div className="min-w-0">
|
|
164
|
+
<div className="hidden h-12 min-w-0 items-center justify-between gap-4 pl-7 pr-4 md:flex">
|
|
165
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
197
166
|
{props.agentTree ? (
|
|
198
|
-
<div className="
|
|
167
|
+
<div className="min-w-0 shrink-[2]">{props.agentTree}</div>
|
|
168
|
+
) : props.location ? (
|
|
169
|
+
<span className="shrink-0 text-sm text-dashboard-text-muted">
|
|
170
|
+
{props.location}
|
|
171
|
+
</span>
|
|
199
172
|
) : null}
|
|
200
|
-
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
</
|
|
204
|
-
{liveIndicator}
|
|
205
|
-
<span className="shrink-0">{props.privacy}</span>
|
|
206
|
-
</div>
|
|
207
|
-
{props.meta ? (
|
|
208
|
-
<div className="mt-2 min-w-0 text-xs leading-snug text-dashboard-text-muted">
|
|
209
|
-
{props.meta}
|
|
210
|
-
</div>
|
|
173
|
+
{props.agentTree || props.location ? (
|
|
174
|
+
<span aria-hidden="true" className="text-dashboard-text-faint">
|
|
175
|
+
›
|
|
176
|
+
</span>
|
|
211
177
|
) : null}
|
|
212
|
-
|
|
213
|
-
|
|
178
|
+
<h2 className="m-0 line-clamp-1 min-w-0 font-display text-base font-medium leading-tight tracking-tight">
|
|
179
|
+
{props.title}
|
|
180
|
+
</h2>
|
|
181
|
+
{liveIndicator}
|
|
182
|
+
<span className="shrink-0">{props.privacy}</span>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
185
|
+
{props.cost ? (
|
|
186
|
+
<span className="mr-2 text-xs tabular-nums text-dashboard-text-muted">
|
|
187
|
+
{props.cost}
|
|
188
|
+
</span>
|
|
214
189
|
) : null}
|
|
190
|
+
{props.view === "raw" ? (
|
|
191
|
+
// Event log is a debug view reached from the menu. Say which
|
|
192
|
+
// view this is, and give one click back.
|
|
193
|
+
<Button
|
|
194
|
+
aria-label="Close event log"
|
|
195
|
+
className="mr-1 h-8 px-2.5"
|
|
196
|
+
onClick={() => props.onViewChange("rich")}
|
|
197
|
+
variant="quiet"
|
|
198
|
+
>
|
|
199
|
+
<ScrollText aria-hidden="true" size={14} strokeWidth={2} />
|
|
200
|
+
Event log
|
|
201
|
+
<X aria-hidden="true" size={14} strokeWidth={2} />
|
|
202
|
+
</Button>
|
|
203
|
+
) : (
|
|
204
|
+
<TranscriptSearchToggle
|
|
205
|
+
onClick={toggleSearch}
|
|
206
|
+
open={searchOpenVisible}
|
|
207
|
+
/>
|
|
208
|
+
)}
|
|
209
|
+
<ShareConversationButton
|
|
210
|
+
key={props.conversationId}
|
|
211
|
+
conversationId={props.conversationId}
|
|
212
|
+
layout="icon"
|
|
213
|
+
/>
|
|
214
|
+
<ConversationOverflowMenu>
|
|
215
|
+
{(close) => menuActions("desktop", close)}
|
|
216
|
+
</ConversationOverflowMenu>
|
|
217
|
+
<HeaderIconButton
|
|
218
|
+
ref={detailsButtonRef}
|
|
219
|
+
label="Conversation details"
|
|
220
|
+
onClick={props.onDetailsClick}
|
|
221
|
+
pressed={detailsOpen}
|
|
222
|
+
>
|
|
223
|
+
<PanelRight aria-hidden="true" size={15} strokeWidth={2} />
|
|
224
|
+
</HeaderIconButton>
|
|
215
225
|
</div>
|
|
216
|
-
<ConversationHeaderActions
|
|
217
|
-
archive={props.archive}
|
|
218
|
-
conversationId={props.conversationId}
|
|
219
|
-
copyAction={props.copyAction}
|
|
220
|
-
detailsOpen={detailsOpen}
|
|
221
|
-
detailsButtonRef={detailsButtonRef}
|
|
222
|
-
onDetailsClick={props.onDetailsClick}
|
|
223
|
-
onSearchClick={toggleSearch}
|
|
224
|
-
onViewChange={props.onViewChange}
|
|
225
|
-
searchOpen={searchOpenVisible}
|
|
226
|
-
view={props.view}
|
|
227
|
-
/>
|
|
228
226
|
</div>
|
|
227
|
+
{props.forks ? (
|
|
228
|
+
<div className="hidden px-7 pb-2.5 md:block">{props.forks}</div>
|
|
229
|
+
) : null}
|
|
229
230
|
{props.agentTree ? (
|
|
230
231
|
<div className="px-4 py-2 md:hidden">{props.agentTree}</div>
|
|
231
232
|
) : null}
|
|
@@ -235,18 +236,11 @@ export function ConversationHeader(props: {
|
|
|
235
236
|
{props.linkedWork ? (
|
|
236
237
|
<div
|
|
237
238
|
aria-label="Linked work"
|
|
238
|
-
className="border-t border-dashboard-border-subtle px-4 py-2.5 md:
|
|
239
|
+
className="border-t border-dashboard-border-subtle px-4 py-2.5 md:hidden"
|
|
239
240
|
>
|
|
240
241
|
{props.linkedWork}
|
|
241
242
|
</div>
|
|
242
243
|
) : null}
|
|
243
|
-
<div className="hidden border-t border-dashboard-border-subtle px-7 md:flex">
|
|
244
|
-
<TranscriptViewToggle
|
|
245
|
-
labelled
|
|
246
|
-
onChange={props.onViewChange}
|
|
247
|
-
value={props.view}
|
|
248
|
-
/>
|
|
249
|
-
</div>
|
|
250
244
|
{searchOpenVisible ? (
|
|
251
245
|
<div className="px-4 py-3 md:px-7">
|
|
252
246
|
<SearchInput
|
|
@@ -3,14 +3,13 @@ import {
|
|
|
3
3
|
Archive,
|
|
4
4
|
ArchiveRestore,
|
|
5
5
|
Info,
|
|
6
|
-
PanelRight,
|
|
7
6
|
MessagesSquare,
|
|
8
7
|
ScrollText,
|
|
9
8
|
Search,
|
|
10
9
|
X,
|
|
11
10
|
} from "lucide-react";
|
|
12
11
|
|
|
13
|
-
import { Button } from "../components/ui/button";
|
|
12
|
+
import { Button, buttonVariants } from "../components/ui/button";
|
|
14
13
|
import {
|
|
15
14
|
Tooltip,
|
|
16
15
|
TooltipContent,
|
|
@@ -29,59 +28,74 @@ export type ConversationArchiveAction = {
|
|
|
29
28
|
pending: boolean;
|
|
30
29
|
};
|
|
31
30
|
|
|
32
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Show conversation actions in the `⋯` menu. On desktop the title bar shows
|
|
33
|
+
* search, share, and details, so the menu leaves them out.
|
|
34
|
+
*/
|
|
33
35
|
export function ConversationHeaderActions(props: {
|
|
34
36
|
archive: ConversationArchiveAction;
|
|
35
37
|
conversationId: string;
|
|
36
38
|
copyAction?: ReactNode;
|
|
37
39
|
detailsOpen: boolean;
|
|
38
|
-
detailsButtonRef?: Ref<HTMLButtonElement>;
|
|
39
|
-
/** `bar` is the desktop icon row. `menu` stacks actions for mobile overflow. */
|
|
40
|
-
layout?: "bar" | "menu";
|
|
41
40
|
onDetailsClick(): void;
|
|
42
41
|
onSearchClick(): void;
|
|
43
42
|
onViewChange(value: TranscriptViewMode): void;
|
|
43
|
+
placement: "desktop" | "mobile";
|
|
44
44
|
searchOpen: boolean;
|
|
45
|
+
sentryConversationUrl?: string;
|
|
45
46
|
view: TranscriptViewMode;
|
|
46
47
|
}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{props.view === "rich" ? (
|
|
52
|
-
<MenuActionButton
|
|
53
|
-
label={props.searchOpen ? "Hide search" : "Search transcript"}
|
|
54
|
-
onClick={props.onSearchClick}
|
|
55
|
-
pressed={props.searchOpen}
|
|
56
|
-
>
|
|
57
|
-
{props.searchOpen ? (
|
|
58
|
-
<X aria-hidden="true" size={16} strokeWidth={2} />
|
|
59
|
-
) : (
|
|
60
|
-
<Search aria-hidden="true" size={16} strokeWidth={2} />
|
|
61
|
-
)}
|
|
62
|
-
</MenuActionButton>
|
|
63
|
-
) : null}
|
|
64
|
-
<MenuActionButton
|
|
65
|
-
label="Conversation"
|
|
66
|
-
onClick={() => props.onViewChange("rich")}
|
|
67
|
-
pressed={props.view === "rich"}
|
|
68
|
-
>
|
|
69
|
-
<MessagesSquare aria-hidden="true" size={16} strokeWidth={2} />
|
|
70
|
-
</MenuActionButton>
|
|
48
|
+
const mobile = props.placement === "mobile";
|
|
49
|
+
return (
|
|
50
|
+
<div className="grid gap-0.5">
|
|
51
|
+
{mobile && props.view === "rich" ? (
|
|
71
52
|
<MenuActionButton
|
|
72
|
-
label="
|
|
73
|
-
onClick={
|
|
74
|
-
pressed={props.
|
|
53
|
+
label={props.searchOpen ? "Hide search" : "Search transcript"}
|
|
54
|
+
onClick={props.onSearchClick}
|
|
55
|
+
pressed={props.searchOpen}
|
|
75
56
|
>
|
|
76
|
-
|
|
57
|
+
{props.searchOpen ? (
|
|
58
|
+
<X aria-hidden="true" size={16} strokeWidth={2} />
|
|
59
|
+
) : (
|
|
60
|
+
<Search aria-hidden="true" size={16} strokeWidth={2} />
|
|
61
|
+
)}
|
|
77
62
|
</MenuActionButton>
|
|
63
|
+
) : null}
|
|
64
|
+
<MenuActionButton
|
|
65
|
+
label="Conversation"
|
|
66
|
+
onClick={() => props.onViewChange("rich")}
|
|
67
|
+
pressed={props.view === "rich"}
|
|
68
|
+
>
|
|
69
|
+
<MessagesSquare aria-hidden="true" size={16} strokeWidth={2} />
|
|
70
|
+
</MenuActionButton>
|
|
71
|
+
<MenuActionButton
|
|
72
|
+
label="Event log"
|
|
73
|
+
onClick={() => props.onViewChange("raw")}
|
|
74
|
+
pressed={props.view === "raw"}
|
|
75
|
+
>
|
|
76
|
+
<ScrollText aria-hidden="true" size={16} strokeWidth={2} />
|
|
77
|
+
</MenuActionButton>
|
|
78
|
+
{mobile ? (
|
|
78
79
|
<ShareConversationButton
|
|
79
80
|
key={props.conversationId}
|
|
80
81
|
conversationId={props.conversationId}
|
|
81
82
|
layout="menu"
|
|
82
83
|
/>
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
) : null}
|
|
85
|
+
{props.copyAction}
|
|
86
|
+
<ArchiveConversationButton {...props.archive} />
|
|
87
|
+
{props.sentryConversationUrl ? (
|
|
88
|
+
<a
|
|
89
|
+
className={buttonVariants({ size: "menu" })}
|
|
90
|
+
href={props.sentryConversationUrl}
|
|
91
|
+
rel="noopener noreferrer"
|
|
92
|
+
target="_blank"
|
|
93
|
+
>
|
|
94
|
+
<SentryGlyph />
|
|
95
|
+
Open in Sentry
|
|
96
|
+
</a>
|
|
97
|
+
) : null}
|
|
98
|
+
{mobile ? (
|
|
85
99
|
<MenuActionButton
|
|
86
100
|
label="Conversation details"
|
|
87
101
|
onClick={props.onDetailsClick}
|
|
@@ -89,57 +103,29 @@ export function ConversationHeaderActions(props: {
|
|
|
89
103
|
>
|
|
90
104
|
<Info aria-hidden="true" size={16} strokeWidth={2} />
|
|
91
105
|
</MenuActionButton>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return (
|
|
97
|
-
<div className="flex shrink-0 items-center gap-0.5">
|
|
98
|
-
{props.view === "rich" ? (
|
|
99
|
-
<TranscriptSearchToggle
|
|
100
|
-
onClick={props.onSearchClick}
|
|
101
|
-
open={props.searchOpen}
|
|
102
|
-
/>
|
|
103
106
|
) : null}
|
|
104
|
-
{props.copyAction}
|
|
105
|
-
<ArchiveConversationButton {...props.archive} layout="bar" />
|
|
106
|
-
<Button
|
|
107
|
-
ref={props.detailsButtonRef}
|
|
108
|
-
aria-label="Conversation details"
|
|
109
|
-
aria-pressed={props.detailsOpen}
|
|
110
|
-
className="ml-2"
|
|
111
|
-
onClick={props.onDetailsClick}
|
|
112
|
-
>
|
|
113
|
-
<PanelRight aria-hidden="true" size={15} strokeWidth={2} />
|
|
114
|
-
Details
|
|
115
|
-
</Button>
|
|
116
107
|
</div>
|
|
117
108
|
);
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
/** Switch between the conversation and event log. */
|
|
121
112
|
export function TranscriptViewToggle(props: {
|
|
122
|
-
labelled?: boolean;
|
|
123
113
|
onChange(value: TranscriptViewMode): void;
|
|
124
114
|
value: TranscriptViewMode;
|
|
125
115
|
}) {
|
|
126
116
|
return (
|
|
127
117
|
<ToggleGroup
|
|
128
118
|
aria-label="Transcript view"
|
|
129
|
-
className=
|
|
130
|
-
props.labelled
|
|
131
|
-
? "inline-flex gap-6"
|
|
132
|
-
: "inline-flex gap-0.5 rounded-md border border-dashboard-border bg-dashboard-overlay-soft p-0.5"
|
|
133
|
-
}
|
|
119
|
+
className="inline-flex gap-0.5 rounded-md border border-dashboard-border bg-dashboard-overlay-soft p-0.5"
|
|
134
120
|
onValueChange={([next]) => {
|
|
135
121
|
if (next) props.onChange(next);
|
|
136
122
|
}}
|
|
137
123
|
value={[props.value]}
|
|
138
124
|
>
|
|
139
|
-
<ViewModeItem label="Conversation"
|
|
125
|
+
<ViewModeItem label="Conversation" value="rich">
|
|
140
126
|
<MessagesSquare aria-hidden="true" size={14} strokeWidth={2} />
|
|
141
127
|
</ViewModeItem>
|
|
142
|
-
<ViewModeItem label="Event log"
|
|
128
|
+
<ViewModeItem label="Event log" value="raw">
|
|
143
129
|
<ScrollText aria-hidden="true" size={14} strokeWidth={2} />
|
|
144
130
|
</ViewModeItem>
|
|
145
131
|
</ToggleGroup>
|
|
@@ -149,28 +135,21 @@ export function TranscriptViewToggle(props: {
|
|
|
149
135
|
function ViewModeItem(props: {
|
|
150
136
|
children: ReactNode;
|
|
151
137
|
label: string;
|
|
152
|
-
labelled?: boolean;
|
|
153
138
|
value: TranscriptViewMode;
|
|
154
139
|
}) {
|
|
155
|
-
|
|
156
|
-
<ToggleGroupItem
|
|
157
|
-
aria-label={props.label}
|
|
158
|
-
className={
|
|
159
|
-
props.labelled
|
|
160
|
-
? "relative h-11 min-w-0 gap-2 rounded-none border-0 px-0 text-sm hover:bg-transparent after:absolute after:inset-x-0 after:bottom-0 after:h-0.5 data-pressed:border-0 data-pressed:bg-transparent data-pressed:after:bg-dashboard-text-solid"
|
|
161
|
-
: "size-7 min-w-0 border-0 px-0 data-pressed:border-0 data-pressed:bg-dashboard-fill-hover"
|
|
162
|
-
}
|
|
163
|
-
value={props.value}
|
|
164
|
-
>
|
|
165
|
-
{props.children}
|
|
166
|
-
{props.labelled ? props.label : null}
|
|
167
|
-
</ToggleGroupItem>
|
|
168
|
-
);
|
|
169
|
-
return props.labelled ? (
|
|
170
|
-
item
|
|
171
|
-
) : (
|
|
140
|
+
return (
|
|
172
141
|
<Tooltip>
|
|
173
|
-
<TooltipTrigger
|
|
142
|
+
<TooltipTrigger
|
|
143
|
+
render={
|
|
144
|
+
<ToggleGroupItem
|
|
145
|
+
aria-label={props.label}
|
|
146
|
+
className="size-7 min-w-0 border-0 px-0 data-pressed:border-0 data-pressed:bg-dashboard-fill-hover"
|
|
147
|
+
value={props.value}
|
|
148
|
+
>
|
|
149
|
+
{props.children}
|
|
150
|
+
</ToggleGroupItem>
|
|
151
|
+
}
|
|
152
|
+
/>
|
|
174
153
|
<TooltipContent size="compact">{props.label}</TooltipContent>
|
|
175
154
|
</Tooltip>
|
|
176
155
|
);
|
|
@@ -183,9 +162,11 @@ export function HeaderIconButton(props: {
|
|
|
183
162
|
label: string;
|
|
184
163
|
onClick(): void;
|
|
185
164
|
pressed?: boolean;
|
|
165
|
+
/** Hide the tooltip, such as while the menu this button opens covers it. */
|
|
166
|
+
tooltipDisabled?: boolean;
|
|
186
167
|
}) {
|
|
187
168
|
return (
|
|
188
|
-
<Tooltip>
|
|
169
|
+
<Tooltip disabled={props.tooltipDisabled}>
|
|
189
170
|
<TooltipTrigger
|
|
190
171
|
render={
|
|
191
172
|
<Button
|
|
@@ -248,45 +229,36 @@ function MenuActionButton(props: {
|
|
|
248
229
|
);
|
|
249
230
|
}
|
|
250
231
|
|
|
251
|
-
function ArchiveConversationButton(
|
|
252
|
-
props: ConversationArchiveAction & { layout: "bar" | "menu" },
|
|
253
|
-
) {
|
|
232
|
+
function ArchiveConversationButton(props: ConversationArchiveAction) {
|
|
254
233
|
const label = props.pending
|
|
255
234
|
? "Saving archive state"
|
|
256
235
|
: props.archived
|
|
257
236
|
? "Unarchive"
|
|
258
237
|
: "Archive";
|
|
259
238
|
const Icon = props.archived ? ArchiveRestore : Archive;
|
|
260
|
-
if (props.layout === "menu") {
|
|
261
|
-
return (
|
|
262
|
-
<Button
|
|
263
|
-
aria-label={label}
|
|
264
|
-
disabled={props.disabled}
|
|
265
|
-
onClick={props.onClick}
|
|
266
|
-
size="menu"
|
|
267
|
-
>
|
|
268
|
-
<Icon aria-hidden="true" size={16} strokeWidth={2} />
|
|
269
|
-
<span>{label}</span>
|
|
270
|
-
</Button>
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
239
|
return (
|
|
274
|
-
<
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
240
|
+
<Button
|
|
241
|
+
aria-label={label}
|
|
242
|
+
disabled={props.disabled}
|
|
243
|
+
onClick={props.onClick}
|
|
244
|
+
size="menu"
|
|
245
|
+
>
|
|
246
|
+
<Icon aria-hidden="true" size={16} strokeWidth={2} />
|
|
247
|
+
<span>{label}</span>
|
|
248
|
+
</Button>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Draw the Sentry glyph in the current text color, sized like a menu icon. */
|
|
253
|
+
function SentryGlyph() {
|
|
254
|
+
return (
|
|
255
|
+
<svg
|
|
256
|
+
aria-hidden="true"
|
|
257
|
+
className="size-4 shrink-0"
|
|
258
|
+
fill="currentColor"
|
|
259
|
+
viewBox="0 0 24 24"
|
|
260
|
+
>
|
|
261
|
+
<path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z" />
|
|
262
|
+
</svg>
|
|
291
263
|
);
|
|
292
264
|
}
|