@sentry/junior-dashboard 0.279.0 → 0.280.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 +100 -31
- package/dist/client-manifest.json +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +100 -31
- package/dist/public/chunks/AdminPage-YEKZOKH6.js +1 -0
- package/dist/public/chunks/AutomationPage-5GS3IUAK.js +3 -0
- package/dist/public/chunks/AutomationRunsPage-IPYEZU4L.js +1 -0
- package/dist/public/chunks/AutomationsPage-HSAYPC3C.js +1 -0
- package/dist/public/chunks/CodePage-IPFKXWFB.js +1 -0
- package/dist/public/chunks/ConnectionsPage-5XUEK6FS.js +1 -0
- package/dist/public/chunks/DrawerContent-RYYS57ZH.js +1 -0
- package/dist/public/chunks/LocationDetailPage-27ETUGR6.js +1 -0
- package/dist/public/chunks/LocationsPage-WOKMQAGM.js +1 -0
- package/dist/public/chunks/McpSettingsPage-3RT7JPEK.js +1 -0
- package/dist/public/chunks/PeoplePage-6YQLDOUL.js +1 -0
- package/dist/public/chunks/PersonProfilePage-GYMAZFIK.js +1 -0
- package/dist/public/chunks/PersonalTokensPage-4BCR744B.js +1 -0
- package/dist/public/chunks/PluginUserPage-IP6WL7MD.js +1 -0
- package/dist/public/chunks/RepositoryPage-TRD44LQ3.js +1 -0
- package/dist/public/chunks/SettingsPage-4E3SLQ36.js +1 -0
- package/dist/public/chunks/SystemPage-C3XBKXKW.js +1 -0
- package/dist/public/chunks/WorkspaceFormPage-2LS2X67Q.js +2 -0
- package/dist/public/chunks/WorkspacesPage-5WB3CUTX.js +1 -0
- package/dist/public/chunks/chunk-2BCS5AQL.js +1 -0
- package/dist/public/chunks/chunk-3Y3ICT6K.js +1 -0
- package/dist/public/chunks/chunk-4AKUZW6R.js +1 -0
- package/dist/public/chunks/chunk-4UGZNVJM.js +1 -0
- package/dist/public/chunks/chunk-564V7YHG.js +1 -0
- package/dist/public/chunks/chunk-5GZDBGAR.js +1 -0
- package/dist/public/chunks/chunk-5ISD3QSC.js +1 -0
- package/dist/public/chunks/chunk-5LNGNJ3P.js +1 -0
- package/dist/public/chunks/chunk-5ZA674OU.js +1 -0
- package/dist/public/chunks/chunk-6FSJ3W2W.js +1 -0
- package/dist/public/chunks/chunk-6GQL3EVJ.js +1 -0
- package/dist/public/chunks/chunk-6PHJZPWD.js +19 -0
- package/dist/public/chunks/chunk-A2QWWA7O.js +41 -0
- package/dist/public/chunks/chunk-AYXSBMWA.js +1 -0
- package/dist/public/chunks/chunk-B5ZN3SWQ.js +14 -0
- package/dist/public/chunks/chunk-B6HGRBBX.js +1 -0
- package/dist/public/chunks/chunk-BKOACADP.js +1 -0
- package/dist/public/chunks/chunk-CQY6BYUB.js +1 -0
- package/dist/public/chunks/chunk-CR2ICLSH.js +1 -0
- package/dist/public/chunks/chunk-DZB4LPI6.js +1 -0
- package/dist/public/chunks/chunk-EFEREKX4.js +1 -0
- package/dist/public/chunks/chunk-FE522UEA.js +1 -0
- package/dist/public/chunks/chunk-FVX42XAD.js +1 -0
- package/dist/public/chunks/chunk-G5NWYJTV.js +1 -0
- package/dist/public/chunks/chunk-GMZIELFU.js +1 -0
- package/dist/public/chunks/chunk-GO45RQDL.js +1 -0
- package/dist/public/chunks/chunk-GOTQ4D6W.js +1 -0
- package/dist/public/chunks/chunk-GRMX2HX6.js +1 -0
- package/dist/public/chunks/chunk-GYRM5ZIV.js +1 -0
- package/dist/public/chunks/chunk-H2ZU34CP.js +1 -0
- package/dist/public/chunks/chunk-H76B43OQ.js +1 -0
- package/dist/public/chunks/chunk-HCLRG2EC.js +1 -0
- package/dist/public/chunks/chunk-IA6BJDKN.js +1 -0
- package/dist/public/chunks/chunk-IZWTFLCC.js +1 -0
- package/dist/public/chunks/chunk-J37MXUED.js +1 -0
- package/dist/public/chunks/chunk-JAZABOBA.js +0 -0
- package/dist/public/chunks/chunk-L2H3ZWZN.js +1 -0
- package/dist/public/chunks/chunk-LBELRO5P.js +1 -0
- package/dist/public/chunks/chunk-LKW7MULF.js +1 -0
- package/dist/public/chunks/chunk-MEHLQZDG.js +3 -0
- package/dist/public/chunks/chunk-MTBUV2GJ.js +1 -0
- package/dist/public/chunks/chunk-N4AONKD3.js +1 -0
- package/dist/public/chunks/chunk-NQMDED2R.js +2 -0
- package/dist/public/chunks/chunk-NQOJAZFQ.js +1 -0
- package/dist/public/chunks/chunk-PEQNT5MO.js +1 -0
- package/dist/public/chunks/chunk-PQPAPPEI.js +1 -0
- package/dist/public/chunks/chunk-PXHLJIOT.js +1 -0
- package/dist/public/chunks/chunk-QNA7F7QB.js +1 -0
- package/dist/public/chunks/chunk-QOX5RMLM.js +1 -0
- package/dist/public/chunks/chunk-QPBLZSNX.js +1 -0
- package/dist/public/chunks/chunk-R4ORRE77.js +1 -0
- package/dist/public/chunks/chunk-RHIXHBS5.js +1 -0
- package/dist/public/chunks/chunk-RHMZNAJX.js +1 -0
- package/dist/public/chunks/chunk-SDKV3OID.js +1 -0
- package/dist/public/chunks/chunk-SIOW7OOW.js +1 -0
- package/dist/public/chunks/chunk-SUX4QBVF.js +1 -0
- package/dist/public/chunks/chunk-T4M53QQT.js +1 -0
- package/dist/public/chunks/chunk-TFJOEF3Q.js +1 -0
- package/dist/public/chunks/chunk-UAH6DWNS.js +1 -0
- package/dist/public/chunks/chunk-UJD3MBMB.js +1 -0
- package/dist/public/chunks/chunk-UK2V4UBT.js +1 -0
- package/dist/public/chunks/chunk-UT6HZPGL.js +1 -0
- package/dist/public/chunks/chunk-UZ44J2LH.js +1 -0
- package/dist/public/chunks/chunk-V5UDSIRA.js +63 -0
- package/dist/public/chunks/chunk-VC6IS564.js +1 -0
- package/dist/public/chunks/chunk-VFEBNOPX.js +1 -0
- package/dist/public/chunks/chunk-VXT3FIVL.js +55 -0
- package/dist/public/chunks/chunk-WJ3OXMVX.js +1 -0
- package/dist/public/chunks/chunk-WRXMQB7X.js +25 -0
- package/dist/public/chunks/chunk-WZVO3HG4.js +1 -0
- package/dist/public/chunks/chunk-XNR4RZEC.js +1 -0
- package/dist/public/chunks/chunk-YPMUHXYT.js +147 -0
- package/dist/public/chunks/chunk-YYNMZ4WC.js +1 -0
- package/dist/public/chunks/chunk-Z2JPRGFT.js +1 -0
- package/dist/public/chunks/chunk-ZIJ6SJDN.js +1 -0
- package/dist/public/chunks/code-highlighter-Q7WC7AEF.js +12 -0
- package/dist/public/client-TYC7JKRE.js +91 -0
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/api/schema.ts +1 -1
- package/src/assets.ts +4 -1
- package/src/client/App.tsx +100 -23
- package/src/client/TracedRoutes.tsx +8 -4
- package/src/client/code-highlighter.ts +26 -0
- package/src/client/code.tsx +2 -22
- package/src/client/components/Drawer.tsx +12 -71
- package/src/client/components/DrawerContent.tsx +76 -0
- package/src/client/components/ProfileMenu.tsx +12 -3
- package/src/client/components/UserInputForm.tsx +28 -5
- package/src/client/conversations/ConversationCatchUp.tsx +107 -0
- package/src/client/conversations/ConversationContextTags.tsx +110 -0
- package/src/client/conversations/ConversationEventLog.tsx +213 -71
- package/src/client/conversations/ConversationHeader.tsx +35 -41
- package/src/client/conversations/ConversationHeaderActions.tsx +34 -38
- package/src/client/conversations/ConversationHomeList.tsx +41 -3
- package/src/client/conversations/ConversationMeta.tsx +6 -12
- package/src/client/conversations/ConversationOverflowMenu.tsx +15 -4
- package/src/client/conversations/ConversationPage.tsx +188 -150
- package/src/client/conversations/ShareConversationButton.tsx +5 -50
- package/src/client/conversations/SlackMark.tsx +16 -4
- package/src/client/conversations/TranscriptRows.tsx +24 -11
- package/src/client/conversations/TranscriptView.tsx +21 -13
- package/src/client/conversations/conversationArchive.ts +22 -0
- package/src/client/conversations/conversationCatchUpState.ts +96 -0
- package/src/client/conversations/conversationMemory.ts +8 -1
- package/src/client/conversations/queries.ts +11 -3
- package/src/client/conversations/{transcriptUnreadHistory.ts → transcriptAroundHistory.ts} +48 -42
- package/src/client/conversations/transcriptBottomPinning.ts +27 -3
- package/src/client/conversations/transcriptReadPosition.ts +24 -2
- package/src/client/conversations/turnContext.ts +1 -1
- package/src/client/pages/admin/AdminPage.tsx +1 -1
- package/src/client/pages/admin/ServiceAccountsSection.tsx +1 -1
- package/src/client/pages/memory/memoryDashboard.ts +1 -1
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/settings/ConnectionsPage.tsx +1 -1
- package/src/client/pages/user/PluginUserPage.tsx +0 -6
- package/src/client/pages/user/pluginUserPageRoutes.ts +5 -0
- package/src/client/primaryNav.ts +1 -1
- package/src/client/review-desk/reviewItemFormat.ts +1 -1
- package/src/mock-reporting/fixtures.ts +43 -0
- package/src/mock-reporting/routes.ts +40 -3
- package/src/public-assets.ts +28 -20
- package/src/shell.ts +12 -2
- package/dist/public/client-ZCZXZBTL.js +0 -460
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useCallback,
|
|
3
3
|
useEffect,
|
|
4
|
+
useEffectEvent,
|
|
4
5
|
useMemo,
|
|
5
6
|
useRef,
|
|
6
7
|
useState,
|
|
7
8
|
useSyncExternalStore,
|
|
8
9
|
type ReactNode,
|
|
9
10
|
} from "react";
|
|
10
|
-
import { useSearchParams } from "react-router";
|
|
11
|
+
import { useNavigate, useSearchParams } from "react-router";
|
|
11
12
|
import type {
|
|
12
13
|
ConversationDetailReport,
|
|
13
14
|
ConversationFeed,
|
|
15
|
+
ConversationReportEvent,
|
|
14
16
|
} from "@sentry/junior/api/schema";
|
|
15
17
|
|
|
16
18
|
import type { ConversationData } from "./queries";
|
|
17
19
|
import {
|
|
18
20
|
useArchiveConversation,
|
|
21
|
+
type ArchivedFromConversationState,
|
|
19
22
|
type PendingArchiveConversationUpdate,
|
|
20
23
|
} from "./conversationArchive";
|
|
21
24
|
import { usePinConversation } from "./usePinConversation";
|
|
22
25
|
import { conversationIsResponding } from "./transcript";
|
|
26
|
+
import {
|
|
27
|
+
ConversationCatchUpContext,
|
|
28
|
+
useConversationCatchUp,
|
|
29
|
+
} from "./conversationCatchUpState";
|
|
23
30
|
import { buildConversationMarkdown } from "../markdownExport";
|
|
24
31
|
import { CopyButton } from "../components/CopyButton";
|
|
25
32
|
import {
|
|
@@ -39,6 +46,7 @@ import {
|
|
|
39
46
|
memoryAnchorKey,
|
|
40
47
|
useConversationMemories,
|
|
41
48
|
type ConversationMemoryState,
|
|
49
|
+
type MemoryJump,
|
|
42
50
|
type OpenMemoryPopover,
|
|
43
51
|
} from "./conversationMemory";
|
|
44
52
|
import { ConversationEventDetails } from "./ConversationEventDetails";
|
|
@@ -57,14 +65,13 @@ import {
|
|
|
57
65
|
import { cn } from "../styles";
|
|
58
66
|
import { ConversationForkLinks } from "./ConversationForkLinks";
|
|
59
67
|
import { ConversationAgentTree } from "./ConversationAgentTree";
|
|
68
|
+
import { conversationContextTags } from "./ConversationContextTags";
|
|
60
69
|
import { conversationLinks } from "./ConversationLinkedWork";
|
|
61
70
|
import {
|
|
62
71
|
ConversationAnnotations,
|
|
63
72
|
conversationCost,
|
|
64
|
-
conversationLocation,
|
|
65
73
|
ConversationPrivacyChip,
|
|
66
74
|
ConversationStats,
|
|
67
|
-
hasConversationAnnotations,
|
|
68
75
|
hasConversationStats,
|
|
69
76
|
} from "./ConversationMeta";
|
|
70
77
|
import {
|
|
@@ -100,9 +107,10 @@ import {
|
|
|
100
107
|
import { TranscriptLoading } from "./TranscriptLoading";
|
|
101
108
|
import { recordTranscriptShown } from "../sentry";
|
|
102
109
|
import {
|
|
103
|
-
|
|
110
|
+
useHistoryAround,
|
|
111
|
+
type HistoryStart,
|
|
104
112
|
type TranscriptHistory,
|
|
105
|
-
} from "./
|
|
113
|
+
} from "./transcriptAroundHistory";
|
|
106
114
|
import type { TranscriptViewMode } from "./transcriptRenderModel";
|
|
107
115
|
import {
|
|
108
116
|
SubagentTranscriptDrawer,
|
|
@@ -184,7 +192,7 @@ export function ConversationPage(props: {
|
|
|
184
192
|
const [turnContext, setTurnContext] = useState<TurnContextSelection>();
|
|
185
193
|
const [memoryPopover, setMemoryPopover] = useState<OpenMemoryPopover>();
|
|
186
194
|
// A memory chosen in details waits here until its transcript row renders.
|
|
187
|
-
const [memoryJump, setMemoryJump] = useState<
|
|
195
|
+
const [memoryJump, setMemoryJump] = useState<MemoryJump>();
|
|
188
196
|
// The page stays mounted across Conversations. Memory state belongs to one.
|
|
189
197
|
const [memoryConversationId, setMemoryConversationId] = useState(
|
|
190
198
|
props.conversationId,
|
|
@@ -214,11 +222,33 @@ export function ConversationPage(props: {
|
|
|
214
222
|
const [startAtUnread] = useState(
|
|
215
223
|
() => readConversationStartPosition() === "unread",
|
|
216
224
|
);
|
|
225
|
+
// A link at open shows its row, so the transcript does not open at the
|
|
226
|
+
// latest message or the read position, unless the link fails first.
|
|
227
|
+
const [startAtLink, setStartAtLink] = useState(
|
|
228
|
+
() => selectedEventSeq !== undefined || linkedTurnId !== undefined,
|
|
229
|
+
);
|
|
217
230
|
const detail = props.detail;
|
|
218
231
|
// Jump to latest, a send, or the mobile keyboard skips the newer pages for
|
|
219
232
|
// the rest of the visit. It can come before the unread page loads.
|
|
220
233
|
const [showLatest, setShowLatest] = useState(false);
|
|
221
|
-
|
|
234
|
+
// A link at open wins over the read position. When the loaded rows hold
|
|
235
|
+
// the link, the transcript keeps them and does not page to the read
|
|
236
|
+
// position.
|
|
237
|
+
const opensAtUnread = startAtUnread && !showLatest && !startAtLink;
|
|
238
|
+
// A linked row that the transcript does not hold. The transcript opens at
|
|
239
|
+
// the page around it until the reader jumps to the latest. A link at open
|
|
240
|
+
// starts there, so the first render reads no other page.
|
|
241
|
+
const [linkStart, setLinkStart] = useState(() =>
|
|
242
|
+
linkedTurnId !== undefined
|
|
243
|
+
? unheldLinkStart(detail.data?.events, { turn: linkedTurnId })
|
|
244
|
+
: selectedEventSeq === undefined
|
|
245
|
+
? undefined
|
|
246
|
+
: unheldLinkStart(detail.data?.events, { event: selectedEventSeq }),
|
|
247
|
+
);
|
|
248
|
+
const showLatestPage = useCallback(() => {
|
|
249
|
+
setShowLatest(true);
|
|
250
|
+
setLinkStart(undefined);
|
|
251
|
+
}, []);
|
|
222
252
|
const memoryList = useConversationMemories(conversationId).data;
|
|
223
253
|
const memoryState = useMemo<ConversationMemoryState>(
|
|
224
254
|
() => ({
|
|
@@ -229,41 +259,78 @@ export function ConversationPage(props: {
|
|
|
229
259
|
[memoryList, memoryPopover],
|
|
230
260
|
);
|
|
231
261
|
const archive = useArchiveConversation(conversationId);
|
|
262
|
+
const navigate = useNavigate();
|
|
232
263
|
const pin = usePinConversation(conversationId);
|
|
233
264
|
const markRead = useMarkConversationRead(conversationId);
|
|
234
265
|
const readAtOpen = useReadPositionAtOpen(detail.data, detail.fetchedDetail);
|
|
235
|
-
const
|
|
266
|
+
const dropLink = useCallback(() => {
|
|
267
|
+
setLinkStart(undefined);
|
|
268
|
+
setStartAtLink(false);
|
|
269
|
+
setMemoryJump(undefined);
|
|
270
|
+
setSearchParams(
|
|
271
|
+
(current) => {
|
|
272
|
+
const next = new URLSearchParams(current);
|
|
273
|
+
next.delete(CONVERSATION_EVENT_PARAM);
|
|
274
|
+
next.delete(CONVERSATION_TURN_PARAM);
|
|
275
|
+
return next;
|
|
276
|
+
},
|
|
277
|
+
{ replace: true },
|
|
278
|
+
);
|
|
279
|
+
}, [setSearchParams]);
|
|
280
|
+
const unreadCursor = opensAtUnread ? readAtOpen.unreadCursor : undefined;
|
|
281
|
+
const aroundHistory = useHistoryAround({
|
|
236
282
|
conversationId,
|
|
237
283
|
// Cached earlier pages from a past visit would look like newer events.
|
|
238
284
|
detail: detail.latest,
|
|
239
|
-
onShowLatest:
|
|
240
|
-
|
|
241
|
-
|
|
285
|
+
onShowLatest: showLatestPage,
|
|
286
|
+
// A link that fails to load is dropped. The transcript goes back to
|
|
287
|
+
// where it was.
|
|
288
|
+
...(linkStart
|
|
289
|
+
? { onStartFailed: dropLink, start: linkStart }
|
|
290
|
+
: unreadCursor
|
|
291
|
+
? {
|
|
292
|
+
start: { around: unreadCursor },
|
|
293
|
+
...(readAtOpen.unreadPage
|
|
294
|
+
? { startPage: readAtOpen.unreadPage }
|
|
295
|
+
: undefined),
|
|
296
|
+
}
|
|
297
|
+
: undefined),
|
|
242
298
|
});
|
|
243
|
-
// The
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
299
|
+
// The transcript on screen, so it can stay while the next one loads. The
|
|
300
|
+
// page is keyed by Conversation, so at open this is the cached copy.
|
|
301
|
+
const shownRef = useRef(detail.data);
|
|
302
|
+
// A reader far behind sees the events from their read position, and a
|
|
303
|
+
// link shows the events around its row. Others see the latest page and
|
|
304
|
+
// the pages before it. The transcript on screen stays until this visit's
|
|
305
|
+
// fetch tells where to open and the page around that row loads. It loads
|
|
306
|
+
// no earlier pages and shows no marker: at open, both would come from the
|
|
307
|
+
// cached read position. It reports no reads, because it can sit at rows
|
|
308
|
+
// the reader has not read yet. Then the transcript opens at the row, if
|
|
309
|
+
// the reader has not moved.
|
|
310
|
+
const holdShown = Boolean(
|
|
311
|
+
shownRef.current &&
|
|
312
|
+
(aroundHistory?.isPending ||
|
|
313
|
+
(opensAtUnread && !linkStart && !detail.fetchedAtOpen)),
|
|
255
314
|
);
|
|
256
|
-
const
|
|
315
|
+
const transcriptPending =
|
|
316
|
+
detail.isPending || Boolean(aroundHistory?.isPending && !shownRef.current);
|
|
317
|
+
const history: TranscriptHistory = holdShown
|
|
257
318
|
? {
|
|
258
319
|
...detail,
|
|
259
|
-
data:
|
|
320
|
+
data: shownRef.current,
|
|
260
321
|
hasPreviousPage: false,
|
|
261
322
|
isLoadingPreviousPage: false,
|
|
262
323
|
loadPreviousPage: () => {},
|
|
263
324
|
}
|
|
264
|
-
: (
|
|
265
|
-
|
|
266
|
-
|
|
325
|
+
: (aroundHistory ?? detail);
|
|
326
|
+
if (history.data) shownRef.current = history.data;
|
|
327
|
+
const catchUp = useConversationCatchUp({
|
|
328
|
+
conversationId,
|
|
329
|
+
events: history.data?.events,
|
|
330
|
+
hasNewerPage: Boolean(history.newer),
|
|
331
|
+
hasPreviousPage: history.hasPreviousPage,
|
|
332
|
+
unread: holdShown ? undefined : readAtOpen.unread,
|
|
333
|
+
});
|
|
267
334
|
const feedConversation = conversations.find(
|
|
268
335
|
(item) => item.id === conversationId,
|
|
269
336
|
);
|
|
@@ -277,6 +344,7 @@ export function ConversationPage(props: {
|
|
|
277
344
|
<ParticipantAvatarStack participants={participants} size="detail" />
|
|
278
345
|
) : null;
|
|
279
346
|
const conversationDetail = detail.data;
|
|
347
|
+
const links = conversationLinks(conversationDetail);
|
|
280
348
|
const title =
|
|
281
349
|
conversationDetailTitle(detail.data) ??
|
|
282
350
|
conversationDisplayTitle(conversation);
|
|
@@ -342,53 +410,29 @@ export function ConversationPage(props: {
|
|
|
342
410
|
},
|
|
343
411
|
[eventOpen, setSelectedEventSeq],
|
|
344
412
|
);
|
|
345
|
-
// A linked event, Turn, or memory can be outside the loaded
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
? undefined
|
|
362
|
-
: selectedEventSeq < oldestLoadedSeq
|
|
363
|
-
? "earlier"
|
|
364
|
-
: selectedEventSeq > newestLoadedSeq &&
|
|
365
|
-
(history.newer || holdCachedTranscript)
|
|
366
|
-
? "newer"
|
|
367
|
-
: undefined;
|
|
368
|
-
// Replace a found Turn link with a link to its first event, or drop it
|
|
369
|
-
// when history has no such event.
|
|
413
|
+
// A linked event, Turn, or memory can be outside the loaded rows. Then the
|
|
414
|
+
// transcript opens at the page around it, which the server finds by the
|
|
415
|
+
// event seq or Turn id. Only a new link does this, so a reader who jumps to
|
|
416
|
+
// the latest stays there. Returns false when the loaded rows hold the link.
|
|
417
|
+
const openLink = useEffectEvent((link: ConversationLink) => {
|
|
418
|
+
const start = unheldLinkStart(history.data?.events, link);
|
|
419
|
+
if (start) setLinkStart(start);
|
|
420
|
+
return Boolean(start);
|
|
421
|
+
});
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (linkedTurnId !== undefined) openLink({ turn: linkedTurnId });
|
|
424
|
+
else if (selectedEventSeq !== undefined) {
|
|
425
|
+
openLink({ event: selectedEventSeq });
|
|
426
|
+
}
|
|
427
|
+
}, [linkedTurnId, selectedEventSeq]);
|
|
428
|
+
// Replace a found Turn link with a link to its first event.
|
|
370
429
|
const linkedTurnSeq = linkedTurnId
|
|
371
430
|
? turnStartedEventSeq(transcript?.events, linkedTurnId)
|
|
372
431
|
: undefined;
|
|
373
|
-
const linkedTurnMissing =
|
|
374
|
-
linkedTurnId !== undefined &&
|
|
375
|
-
linkedTurnSeq === undefined &&
|
|
376
|
-
transcript !== undefined;
|
|
377
|
-
const linkedSearch = linkedEventDirection
|
|
378
|
-
? searchHistory(linkedEventDirection)
|
|
379
|
-
: linkedTurnMissing
|
|
380
|
-
? searchHistory("any")
|
|
381
|
-
: undefined;
|
|
382
|
-
useEffect(() => loadToward(linkedSearch), [linkedSearch, loadToward]);
|
|
383
|
-
const linkedTurnNotFound = linkedTurnMissing && linkedSearch === "end";
|
|
384
432
|
useEffect(() => {
|
|
385
433
|
if (linkedTurnSeq !== undefined) setSelectedEventSeq(linkedTurnSeq);
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Show a chosen memory at its transcript row. Wait for the first page, then
|
|
389
|
-
// load pages toward the row until it renders, or stop when history ends.
|
|
390
|
-
const memorySearch =
|
|
391
|
-
memoryJump && transcript ? searchHistory("any") : undefined;
|
|
434
|
+
}, [linkedTurnSeq, setSelectedEventSeq]);
|
|
435
|
+
// Show a chosen memory at its transcript row, once the row renders.
|
|
392
436
|
useEffect(() => {
|
|
393
437
|
if (!memoryJump || !transcript) return;
|
|
394
438
|
const anchor = pageRef.current?.querySelector(
|
|
@@ -398,22 +442,17 @@ export function ConversationPage(props: {
|
|
|
398
442
|
anchor.scrollIntoView({ block: "center" });
|
|
399
443
|
setMemoryPopover(memoryJump);
|
|
400
444
|
setMemoryJump(undefined);
|
|
401
|
-
} else if (
|
|
445
|
+
} else if (!holdShown && !openLink({ event: memoryJump.seq })) {
|
|
446
|
+
// The loaded rows hold the event, but no row shows the memory.
|
|
402
447
|
setMemoryJump(undefined);
|
|
403
|
-
} else {
|
|
404
|
-
loadToward(memorySearch);
|
|
405
448
|
}
|
|
406
|
-
}, [
|
|
449
|
+
}, [holdShown, memoryJump, transcript, view]);
|
|
407
450
|
// Keep the panel while the transcript or a linked event loads, and for the
|
|
408
451
|
// render before a found Turn link becomes an event link.
|
|
409
452
|
const eventLoading =
|
|
410
453
|
eventOpen &&
|
|
411
454
|
!selectedEvent &&
|
|
412
|
-
(transcriptPending ||
|
|
413
|
-
linkedTurnSeq !== undefined ||
|
|
414
|
-
linkedSearch === "load-earlier" ||
|
|
415
|
-
linkedSearch === "load-newer" ||
|
|
416
|
-
linkedSearch === "wait");
|
|
455
|
+
(transcriptPending || holdShown || linkedTurnId !== undefined);
|
|
417
456
|
const showEvent = Boolean(selectedEvent) || eventLoading;
|
|
418
457
|
// Step through the same filtered rows that the event log shows.
|
|
419
458
|
const events = transcript?.events;
|
|
@@ -436,9 +475,9 @@ export function ConversationPage(props: {
|
|
|
436
475
|
[turnContext?.id],
|
|
437
476
|
);
|
|
438
477
|
const requestPin = useCallback(() => {
|
|
439
|
-
|
|
478
|
+
showLatestPage();
|
|
440
479
|
setPinRequestVersion((version) => version + 1);
|
|
441
|
-
}, []);
|
|
480
|
+
}, [showLatestPage]);
|
|
442
481
|
const onOpenSubagentTranscript = useCallback(
|
|
443
482
|
({
|
|
444
483
|
part,
|
|
@@ -489,8 +528,9 @@ export function ConversationPage(props: {
|
|
|
489
528
|
agentTree={
|
|
490
529
|
agentTree ? <ConversationAgentTree nodes={agentTree} /> : null
|
|
491
530
|
}
|
|
531
|
+
contextTags={conversationContextTags(conversation)}
|
|
492
532
|
conversationId={conversationId}
|
|
493
|
-
cost={conversationCost(conversation,
|
|
533
|
+
cost={conversationCost(conversation, conversationDetail, "below")}
|
|
494
534
|
detailsOpen={showDetails}
|
|
495
535
|
onDetailsClick={() => {
|
|
496
536
|
if (eventOpen) setSelectedEventSeq(undefined);
|
|
@@ -529,9 +569,7 @@ export function ConversationPage(props: {
|
|
|
529
569
|
/>
|
|
530
570
|
}
|
|
531
571
|
linkedWork={
|
|
532
|
-
|
|
533
|
-
<ConversationAnnotations detail={detail.data} />
|
|
534
|
-
) : null
|
|
572
|
+
links.length ? <ConversationAnnotations links={links} /> : null
|
|
535
573
|
}
|
|
536
574
|
pin={{
|
|
537
575
|
// The API lets only participants pin, the same as archive.
|
|
@@ -545,15 +583,25 @@ export function ConversationPage(props: {
|
|
|
545
583
|
archived: Boolean(conversation?.archivedAt),
|
|
546
584
|
disabled: !conversation || archive.isPending,
|
|
547
585
|
error: Boolean(archive.error),
|
|
548
|
-
onClick: () =>
|
|
586
|
+
onClick: () => {
|
|
587
|
+
const archived = !conversation!.archivedAt;
|
|
549
588
|
archive.mutate({
|
|
550
|
-
archived
|
|
589
|
+
archived,
|
|
551
590
|
lastSeenAt: conversation!.lastSeenAt,
|
|
552
|
-
})
|
|
591
|
+
});
|
|
592
|
+
// Archiving hides this conversation, so go home at once.
|
|
593
|
+
// The mutation keeps running after this page unmounts.
|
|
594
|
+
if (archived) {
|
|
595
|
+
navigate("/", {
|
|
596
|
+
state: {
|
|
597
|
+
archivedConversation: conversation!,
|
|
598
|
+
} satisfies ArchivedFromConversationState,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
},
|
|
553
602
|
pending: archive.isPending,
|
|
554
603
|
}}
|
|
555
604
|
live={live}
|
|
556
|
-
location={conversationLocation(conversation, detail.data)}
|
|
557
605
|
onSearchChange={setSearch}
|
|
558
606
|
onViewChange={onViewChange}
|
|
559
607
|
privacy={
|
|
@@ -600,39 +648,40 @@ export function ConversationPage(props: {
|
|
|
600
648
|
</div>
|
|
601
649
|
) : null}
|
|
602
650
|
<ConversationMemoryContext value={memoryState}>
|
|
603
|
-
<
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
651
|
+
<ConversationCatchUpContext value={catchUp}>
|
|
652
|
+
<Transcript
|
|
653
|
+
hasPreviousPage={history.hasPreviousPage}
|
|
654
|
+
historyError={history.historyError}
|
|
655
|
+
historyVersion={history.historyVersion}
|
|
656
|
+
lastReadSeq={readAtOpen.lastReadSeq}
|
|
657
|
+
live={live}
|
|
658
|
+
loadingPreviousPage={history.isLoadingPreviousPage}
|
|
659
|
+
newerHistory={history.newer}
|
|
660
|
+
onLoadPreviousPage={history.loadPreviousPage}
|
|
661
|
+
onRead={holdShown ? undefined : markRead}
|
|
662
|
+
onSelectedEventSeqChange={setSelectedEventSeq}
|
|
663
|
+
// Sends and the thinking row belong after the latest event.
|
|
664
|
+
pendingMessages={
|
|
665
|
+
history.newer ? undefined : detail.inlineMessages
|
|
666
|
+
}
|
|
667
|
+
pinRequestVersion={pinRequestVersion}
|
|
668
|
+
responding={
|
|
669
|
+
!detail.error &&
|
|
670
|
+
!history.newer &&
|
|
671
|
+
conversationIsResponding(detail.data)
|
|
672
|
+
}
|
|
673
|
+
onOpenSubagentTranscript={onOpenSubagentTranscript}
|
|
674
|
+
search={search}
|
|
675
|
+
onSearchChange={setSearch}
|
|
676
|
+
selectedEventSeq={selectedEventSeq}
|
|
677
|
+
startAtLink={startAtLink}
|
|
678
|
+
startAtUnread={startAtUnread}
|
|
679
|
+
transcript={transcript}
|
|
680
|
+
turnContextPanel={turnContextPanel}
|
|
681
|
+
unread={holdShown ? undefined : readAtOpen.unread}
|
|
682
|
+
view={view}
|
|
683
|
+
/>
|
|
684
|
+
</ConversationCatchUpContext>
|
|
636
685
|
</ConversationMemoryContext>
|
|
637
686
|
</>
|
|
638
687
|
)}
|
|
@@ -697,11 +746,12 @@ export function ConversationPage(props: {
|
|
|
697
746
|
setSearch("");
|
|
698
747
|
setMemoryJump({
|
|
699
748
|
memoryId: entry.memory.id,
|
|
749
|
+
seq: entry.seq,
|
|
700
750
|
source: entry.source,
|
|
701
751
|
turnId: entry.turnId,
|
|
702
752
|
});
|
|
703
753
|
}}
|
|
704
|
-
links={
|
|
754
|
+
links={links}
|
|
705
755
|
overview={
|
|
706
756
|
hasConversationStats({
|
|
707
757
|
conversation,
|
|
@@ -812,32 +862,20 @@ function committedMessageIds(
|
|
|
812
862
|
|
|
813
863
|
const EMPTY_MESSAGE_IDS: readonly string[] = [];
|
|
814
864
|
|
|
815
|
-
type
|
|
816
|
-
type HistorySearch = "end" | "failed" | "load-earlier" | "load-newer" | "wait";
|
|
865
|
+
type ConversationLink = { event: number } | { turn: string };
|
|
817
866
|
|
|
818
|
-
/**
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
)
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
if (direction !== "earlier" && history.newer && !history.newer.error) {
|
|
833
|
-
return "load-newer";
|
|
834
|
-
}
|
|
835
|
-
if (
|
|
836
|
-
direction !== "newer" &&
|
|
837
|
-
history.hasPreviousPage &&
|
|
838
|
-
!history.historyError
|
|
839
|
-
) {
|
|
840
|
-
return "load-earlier";
|
|
841
|
-
}
|
|
842
|
-
return history.historyError || history.newer?.error ? "failed" : "end";
|
|
867
|
+
/** The page to open for a link, or nothing when `events` hold its row. */
|
|
868
|
+
function unheldLinkStart(
|
|
869
|
+
events: readonly ConversationReportEvent[] | undefined,
|
|
870
|
+
link: ConversationLink,
|
|
871
|
+
): HistoryStart | undefined {
|
|
872
|
+
const seq =
|
|
873
|
+
"event" in link ? link.event : turnStartedEventSeq(events, link.turn);
|
|
874
|
+
const held =
|
|
875
|
+
seq !== undefined &&
|
|
876
|
+
events !== undefined &&
|
|
877
|
+
seq >= (events[0]?.seq ?? Infinity) &&
|
|
878
|
+
seq <= (events.at(-1)?.seq ?? -Infinity);
|
|
879
|
+
if (held) return undefined;
|
|
880
|
+
return "event" in link ? { event: String(link.event) } : { turn: link.turn };
|
|
843
881
|
}
|
|
@@ -3,18 +3,10 @@ import { useHref } from "react-router";
|
|
|
3
3
|
import { Check, Share2 } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
import { Button } from "../components/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
Tooltip,
|
|
8
|
-
TooltipContent,
|
|
9
|
-
TooltipTrigger,
|
|
10
|
-
} from "../components/ui/tooltip";
|
|
11
6
|
import { conversationPath } from "./conversationRoutes";
|
|
12
7
|
|
|
13
8
|
/** Share a conversation link without changing who can access it. */
|
|
14
|
-
export function ShareConversationButton(props: {
|
|
15
|
-
conversationId: string;
|
|
16
|
-
layout?: "button" | "icon" | "menu";
|
|
17
|
-
}) {
|
|
9
|
+
export function ShareConversationButton(props: { conversationId: string }) {
|
|
18
10
|
const href = useHref(conversationPath(props.conversationId));
|
|
19
11
|
const [status, setStatus] = useState<
|
|
20
12
|
"idle" | "sharing" | "copied" | "copy-failed" | "share-failed"
|
|
@@ -68,55 +60,18 @@ export function ShareConversationButton(props: {
|
|
|
68
60
|
}
|
|
69
61
|
}
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
return (
|
|
72
64
|
<>
|
|
73
|
-
<Icon aria-hidden="true" size={16} strokeWidth={2} />
|
|
74
|
-
<span>{label}</span>
|
|
75
|
-
</>
|
|
76
|
-
);
|
|
77
|
-
const button =
|
|
78
|
-
props.layout === "icon" ? (
|
|
79
|
-
<Tooltip>
|
|
80
|
-
<TooltipTrigger
|
|
81
|
-
render={
|
|
82
|
-
<Button
|
|
83
|
-
focusableWhenDisabled
|
|
84
|
-
aria-label={label}
|
|
85
|
-
className="text-dashboard-text-muted"
|
|
86
|
-
disabled={status === "sharing"}
|
|
87
|
-
onClick={() => void shareLink()}
|
|
88
|
-
size="icon"
|
|
89
|
-
>
|
|
90
|
-
<Icon aria-hidden="true" size={15} />
|
|
91
|
-
</Button>
|
|
92
|
-
}
|
|
93
|
-
/>
|
|
94
|
-
<TooltipContent size="compact" side="bottom">
|
|
95
|
-
{label}
|
|
96
|
-
</TooltipContent>
|
|
97
|
-
</Tooltip>
|
|
98
|
-
) : props.layout === "menu" ? (
|
|
99
65
|
<Button
|
|
100
66
|
aria-label={label}
|
|
101
67
|
disabled={status === "sharing"}
|
|
68
|
+
focusableWhenDisabled
|
|
102
69
|
onClick={() => void shareLink()}
|
|
103
70
|
size="menu"
|
|
104
71
|
>
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
) : (
|
|
108
|
-
<Button
|
|
109
|
-
aria-label={label}
|
|
110
|
-
disabled={status === "sharing"}
|
|
111
|
-
onClick={() => void shareLink()}
|
|
112
|
-
>
|
|
113
|
-
{content}
|
|
72
|
+
<Icon aria-hidden="true" size={16} strokeWidth={2} />
|
|
73
|
+
<span>{label}</span>
|
|
114
74
|
</Button>
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<>
|
|
119
|
-
{button}
|
|
120
75
|
<span className="sr-only" role="status">
|
|
121
76
|
{feedback}
|
|
122
77
|
</span>
|
|
@@ -9,10 +9,22 @@ export function SlackMark(props: { className?: string }) {
|
|
|
9
9
|
viewBox="0 0 127 127"
|
|
10
10
|
>
|
|
11
11
|
{/* Keep the geometry from packages/docs/public/integrations/slack.svg. */}
|
|
12
|
-
<path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<path
|
|
13
|
+
className="[--slack-mark-color:#E01E5A]"
|
|
14
|
+
d="M27.2 80c0 7.3-5.9 13.2-13.2 13.2C6.7 93.2.8 87.3.8 80c0-7.3 5.9-13.2 13.2-13.2h13.2V80zm6.6 0c0-7.3 5.9-13.2 13.2-13.2 7.3 0 13.2 5.9 13.2 13.2v33c0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V80z"
|
|
15
|
+
/>
|
|
16
|
+
<path
|
|
17
|
+
className="[--slack-mark-color:#36C5F0]"
|
|
18
|
+
d="M47 27c-7.3 0-13.2-5.9-13.2-13.2C33.8 6.5 39.7.6 47 .6c7.3 0 13.2 5.9 13.2 13.2V27H47zm0 6.7c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H13.9C6.6 60.1.7 54.2.7 46.9c0-7.3 5.9-13.2 13.2-13.2H47z"
|
|
19
|
+
/>
|
|
20
|
+
<path
|
|
21
|
+
className="[--slack-mark-color:#2EB67D]"
|
|
22
|
+
d="M99.9 46.9c0-7.3 5.9-13.2 13.2-13.2 7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H99.9V46.9zm-6.6 0c0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V13.8C66.9 6.5 72.8.6 80.1.6c7.3 0 13.2 5.9 13.2 13.2v33.1z"
|
|
23
|
+
/>
|
|
24
|
+
<path
|
|
25
|
+
className="[--slack-mark-color:#ECB22E]"
|
|
26
|
+
d="M80.1 99.8c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2-7.3 0-13.2-5.9-13.2-13.2V99.8h13.2zm0-6.6c-7.3 0-13.2-5.9-13.2-13.2 0-7.3 5.9-13.2 13.2-13.2h33.1c7.3 0 13.2 5.9 13.2 13.2 0 7.3-5.9 13.2-13.2 13.2H80.1z"
|
|
27
|
+
/>
|
|
16
28
|
</svg>
|
|
17
29
|
);
|
|
18
30
|
}
|