@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,17 +1,12 @@
|
|
|
1
|
+
import { ArrowLeft } from "lucide-react";
|
|
1
2
|
import { useState, type ReactNode } from "react";
|
|
3
|
+
import type { ConversationDetailReport } from "@sentry/junior/api/schema";
|
|
2
4
|
|
|
3
5
|
import { DetailsPanel, useDetailsPanel } from "../components/DetailsPanel";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "../components/ui/tooltip";
|
|
9
|
-
import {
|
|
10
|
-
Tabs,
|
|
11
|
-
TabsContent,
|
|
12
|
-
TabsList,
|
|
13
|
-
TabsTrigger,
|
|
14
|
-
} from "../components/ui/tabs";
|
|
6
|
+
import { formatRelativeTime, formatTime } from "../format";
|
|
7
|
+
import { ConversationReviewItems } from "../review-desk/ConversationReviewItems";
|
|
8
|
+
import { Button } from "../components/ui/button";
|
|
9
|
+
import { ConversationBrief } from "./ConversationBrief";
|
|
15
10
|
import {
|
|
16
11
|
ConversationChildAgents,
|
|
17
12
|
useConversationChildAgents,
|
|
@@ -21,70 +16,94 @@ import {
|
|
|
21
16
|
type ConversationLink,
|
|
22
17
|
} from "./ConversationLinkedWork";
|
|
23
18
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
ConversationMemoryCard,
|
|
20
|
+
ConversationMemoryEntries,
|
|
26
21
|
} from "./ConversationMemories";
|
|
22
|
+
import {
|
|
23
|
+
conversationMemoryEntries,
|
|
24
|
+
useConversationMemoryState,
|
|
25
|
+
type ConversationMemoryEntry,
|
|
26
|
+
} from "./conversationMemory";
|
|
27
27
|
import {
|
|
28
28
|
ConversationWatches,
|
|
29
29
|
useConversationWatches,
|
|
30
30
|
} from "./ConversationWatches";
|
|
31
31
|
import { DetailsCard } from "./DetailsList";
|
|
32
|
-
import { ShareConversationButton } from "./ShareConversationButton";
|
|
33
32
|
import type { SubagentTranscriptTarget } from "./SubagentTranscriptDrawer";
|
|
34
33
|
|
|
35
|
-
type
|
|
36
|
-
|
|
37
|
-
/** Keep the tabs fixed while each panel scrolls to the frame edge. */
|
|
38
|
-
const detailsPanelClass =
|
|
39
|
-
"min-h-0 overflow-y-auto overscroll-contain rounded-none px-4 pb-[max(1rem,env(safe-area-inset-bottom))] focus-visible:-outline-offset-2";
|
|
34
|
+
type Brief = NonNullable<ConversationDetailReport["brief"]>;
|
|
40
35
|
|
|
41
36
|
/**
|
|
42
|
-
* Show conversation details
|
|
43
|
-
*
|
|
44
|
-
*
|
|
37
|
+
* Show conversation details in the order a reader asks about them: what the
|
|
38
|
+
* work is (the Brief), what needs them, what it changed or waits on, who did
|
|
39
|
+
* it, and what it cost. Each fact shows here only, not in the title bar.
|
|
45
40
|
*/
|
|
46
41
|
export function ConversationDetails(props: {
|
|
47
|
-
brief
|
|
42
|
+
brief?: Brief;
|
|
48
43
|
conversationId: string;
|
|
44
|
+
/** True when a sidebar review chip opened details. */
|
|
45
|
+
highlightReview: boolean;
|
|
49
46
|
identity: ReactNode;
|
|
50
47
|
links: ConversationLink[];
|
|
51
48
|
onClose(): void;
|
|
52
49
|
/** Open one child agent transcript. A details sheet closes first. */
|
|
53
50
|
onOpenSubagent(target: SubagentTranscriptTarget): void;
|
|
51
|
+
/** Show one memory where its Turn recalled or captured it. */
|
|
52
|
+
onSelectMemory(entry: ConversationMemoryEntry & { turnId: string }): void;
|
|
53
|
+
/** The prompt that started each loaded Turn. */
|
|
54
|
+
memoryPrompts: Map<string, string>;
|
|
54
55
|
overview: ReactNode;
|
|
55
|
-
sentryConversationUrl?: string;
|
|
56
56
|
usage: ReactNode;
|
|
57
57
|
}) {
|
|
58
|
-
const [tab, setTab] = useState<DetailsTab>("summary");
|
|
59
58
|
const panel = useDetailsPanel();
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
59
|
+
const memoryList = useConversationMemoryState()?.list;
|
|
60
|
+
const [memoriesOpen, setMemoriesOpen] = useState(false);
|
|
61
|
+
const [memoryConversationId, setMemoryConversationId] = useState(
|
|
62
|
+
props.conversationId,
|
|
63
|
+
);
|
|
64
|
+
if (memoryConversationId !== props.conversationId) {
|
|
65
|
+
setMemoryConversationId(props.conversationId);
|
|
66
|
+
setMemoriesOpen(false);
|
|
67
|
+
}
|
|
68
|
+
const agents = useConversationChildAgents(props.conversationId).data?.agents;
|
|
69
|
+
const watches =
|
|
70
|
+
useConversationWatches(props.conversationId).data?.watches ?? [];
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
72
|
+
if (memoriesOpen && memoryList) {
|
|
73
|
+
// The list replaces details in place, so nothing stacks over the panel.
|
|
74
|
+
return (
|
|
75
|
+
<DetailsPanel
|
|
76
|
+
label="Conversation details"
|
|
77
|
+
onClose={props.onClose}
|
|
78
|
+
title={
|
|
79
|
+
<div className="-ml-2 flex min-w-0 items-center gap-1">
|
|
80
|
+
<Button
|
|
81
|
+
aria-label="Back to details"
|
|
82
|
+
onClick={() => setMemoriesOpen(false)}
|
|
83
|
+
size="icon"
|
|
84
|
+
>
|
|
85
|
+
<ArrowLeft aria-hidden="true" size={15} />
|
|
86
|
+
</Button>
|
|
87
|
+
<h2 className="m-0 truncate font-sans text-sm font-semibold text-dashboard-text">
|
|
88
|
+
Memories
|
|
89
|
+
</h2>
|
|
90
|
+
</div>
|
|
91
|
+
}
|
|
92
|
+
>
|
|
93
|
+
<div className="px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3">
|
|
94
|
+
<ConversationMemoryEntries
|
|
95
|
+
entries={conversationMemoryEntries(memoryList)}
|
|
96
|
+
onSelect={(entry) => {
|
|
97
|
+
// The transcript sits under a details sheet. Close it first.
|
|
98
|
+
if (!panel) props.onClose();
|
|
99
|
+
props.onSelectMemory(entry);
|
|
100
|
+
}}
|
|
101
|
+
prompts={props.memoryPrompts}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
104
|
+
</DetailsPanel>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
88
107
|
|
|
89
108
|
return (
|
|
90
109
|
<DetailsPanel
|
|
@@ -92,126 +111,90 @@ export function ConversationDetails(props: {
|
|
|
92
111
|
title={
|
|
93
112
|
<h2
|
|
94
113
|
aria-label="Conversation details"
|
|
95
|
-
className="m-0
|
|
114
|
+
className="m-0 flex min-w-0 items-center gap-1.5 font-sans text-sm"
|
|
96
115
|
>
|
|
97
|
-
|
|
116
|
+
{props.brief ? (
|
|
117
|
+
<>
|
|
118
|
+
<span className="font-semibold text-dashboard-text">Brief</span>
|
|
119
|
+
<span aria-hidden="true" className="text-dashboard-text-faint">
|
|
120
|
+
·
|
|
121
|
+
</span>
|
|
122
|
+
<time
|
|
123
|
+
className="truncate font-normal text-dashboard-text-muted"
|
|
124
|
+
dateTime={props.brief.updatedAt}
|
|
125
|
+
title={formatTime(props.brief.updatedAt, {
|
|
126
|
+
dateStyle: "medium",
|
|
127
|
+
timeStyle: "short",
|
|
128
|
+
})}
|
|
129
|
+
>
|
|
130
|
+
Updated {formatRelativeTime(props.brief.updatedAt)}
|
|
131
|
+
</time>
|
|
132
|
+
</>
|
|
133
|
+
) : (
|
|
134
|
+
<span className="font-semibold text-dashboard-text">Details</span>
|
|
135
|
+
)}
|
|
98
136
|
</h2>
|
|
99
137
|
}
|
|
100
|
-
actions={
|
|
101
|
-
<>
|
|
102
|
-
<ShareConversationButton
|
|
103
|
-
key={props.conversationId}
|
|
104
|
-
conversationId={props.conversationId}
|
|
105
|
-
layout="icon"
|
|
106
|
-
/>
|
|
107
|
-
{props.sentryConversationUrl ? (
|
|
108
|
-
<Tooltip>
|
|
109
|
-
<TooltipTrigger
|
|
110
|
-
render={
|
|
111
|
-
<a
|
|
112
|
-
aria-label="Open in Sentry"
|
|
113
|
-
className="grid size-9 place-items-center rounded-md text-dashboard-text-muted transition-colors hover:bg-dashboard-fill-strong hover:text-dashboard-text focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-dashboard-focus"
|
|
114
|
-
href={props.sentryConversationUrl}
|
|
115
|
-
rel="noopener noreferrer"
|
|
116
|
-
target="_blank"
|
|
117
|
-
>
|
|
118
|
-
<SentryGlyph />
|
|
119
|
-
</a>
|
|
120
|
-
}
|
|
121
|
-
/>
|
|
122
|
-
<TooltipContent size="compact" side="bottom">
|
|
123
|
-
Open in Sentry
|
|
124
|
-
</TooltipContent>
|
|
125
|
-
</Tooltip>
|
|
126
|
-
) : null}
|
|
127
|
-
</>
|
|
128
|
-
}
|
|
129
138
|
onClose={props.onClose}
|
|
130
139
|
>
|
|
131
|
-
<
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
props.onOpenSubagent(target);
|
|
187
|
-
}}
|
|
140
|
+
<div className="grid min-w-0 content-start gap-3 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3">
|
|
141
|
+
{props.brief ? (
|
|
142
|
+
<div className="min-w-0 break-words px-1 pb-1 font-sans text-sm leading-relaxed text-dashboard-text-muted">
|
|
143
|
+
<ConversationBrief brief={props.brief} variant="summary" />
|
|
144
|
+
</div>
|
|
145
|
+
) : null}
|
|
146
|
+
<ConversationReviewItems
|
|
147
|
+
conversationId={props.conversationId}
|
|
148
|
+
highlight={props.highlightReview}
|
|
149
|
+
/>
|
|
150
|
+
{props.links.length > 0 || watches.length > 0 ? (
|
|
151
|
+
// A Watch waits on something. It shows with linked work, so the
|
|
152
|
+
// work and what Junior waits on read as one list.
|
|
153
|
+
<DetailsCard
|
|
154
|
+
count={props.links.length || watches.length}
|
|
155
|
+
title={props.links.length > 0 ? "Linked work" : "Waiting on"}
|
|
156
|
+
>
|
|
157
|
+
{props.links.length > 0 ? (
|
|
158
|
+
<ConversationLinkedWork links={props.links} />
|
|
159
|
+
) : null}
|
|
160
|
+
{props.links.length > 0 && watches.length > 0 ? (
|
|
161
|
+
<h4 className="m-0 mb-1.5 mt-3 font-sans text-xs font-medium text-dashboard-text-faint">
|
|
162
|
+
Also waiting on
|
|
163
|
+
</h4>
|
|
164
|
+
) : null}
|
|
165
|
+
{watches.length > 0 ? (
|
|
166
|
+
<ConversationWatches watches={watches} />
|
|
167
|
+
) : null}
|
|
168
|
+
</DetailsCard>
|
|
169
|
+
) : null}
|
|
170
|
+
{agents?.length ? (
|
|
171
|
+
<DetailsCard count={agents.length} title="Subagents">
|
|
172
|
+
<ConversationChildAgents
|
|
173
|
+
conversationId={props.conversationId}
|
|
174
|
+
onOpenAgent={(target) => {
|
|
175
|
+
// The panel stays beside the drawer. A modal sheet cannot.
|
|
176
|
+
if (!panel) props.onClose();
|
|
177
|
+
props.onOpenSubagent(target);
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
</DetailsCard>
|
|
181
|
+
) : null}
|
|
182
|
+
{props.overview != null ? (
|
|
183
|
+
<DetailsCard>{props.overview}</DetailsCard>
|
|
184
|
+
) : null}
|
|
185
|
+
{props.usage != null ? (
|
|
186
|
+
<DetailsCard title="Usage">{props.usage}</DetailsCard>
|
|
187
|
+
) : null}
|
|
188
|
+
{props.identity != null ? (
|
|
189
|
+
<DetailsCard title="Participants">{props.identity}</DetailsCard>
|
|
190
|
+
) : null}
|
|
191
|
+
{memoryList?.events.length ? (
|
|
192
|
+
<ConversationMemoryCard
|
|
193
|
+
list={memoryList}
|
|
194
|
+
onOpen={() => setMemoriesOpen(true)}
|
|
188
195
|
/>
|
|
189
|
-
|
|
190
|
-
</
|
|
196
|
+
) : null}
|
|
197
|
+
</div>
|
|
191
198
|
</DetailsPanel>
|
|
192
199
|
);
|
|
193
200
|
}
|
|
194
|
-
|
|
195
|
-
/** Show a panel's entry count. Zero and unknown counts are hidden. */
|
|
196
|
-
function TabCount(props: { count: number | undefined }) {
|
|
197
|
-
if (!props.count) return null;
|
|
198
|
-
return (
|
|
199
|
-
<span className="min-w-5 shrink-0 rounded-full bg-dashboard-fill-strong px-1.5 text-center text-xs font-semibold leading-5 tabular-nums">
|
|
200
|
-
{props.count.toLocaleString("en-US")}
|
|
201
|
-
</span>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/** Draw the Sentry glyph in the current text color, sized like a header icon. */
|
|
206
|
-
function SentryGlyph() {
|
|
207
|
-
return (
|
|
208
|
-
<svg
|
|
209
|
-
aria-hidden="true"
|
|
210
|
-
className="size-4"
|
|
211
|
-
fill="currentColor"
|
|
212
|
-
viewBox="0 0 24 24"
|
|
213
|
-
>
|
|
214
|
-
<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" />
|
|
215
|
-
</svg>
|
|
216
|
-
);
|
|
217
|
-
}
|