@sentry/junior-dashboard 0.275.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/conversations/ConversationComposer.tsx +42 -26
- package/src/client/conversations/ConversationDetails.tsx +63 -35
- package/src/client/conversations/ConversationHeader.tsx +10 -2
- package/src/client/conversations/ConversationListStatusIcon.tsx +16 -11
- package/src/client/conversations/ConversationMemories.tsx +118 -78
- package/src/client/conversations/ConversationMeta.tsx +47 -42
- package/src/client/conversations/ConversationPage.tsx +168 -58
- 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 -1
- 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/UsageCard.tsx +3 -3
- package/src/client/conversations/conversationMemory.ts +138 -0
- package/src/client/conversations/eventTranscript.ts +8 -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 +8 -19
- package/src/client/components/ActiveIndicator.tsx +0 -14
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
CircleX,
|
|
7
7
|
GitMerge,
|
|
8
8
|
GitPullRequest,
|
|
9
|
-
Globe2,
|
|
10
9
|
LockKeyhole,
|
|
11
10
|
TriangleAlert,
|
|
12
11
|
} from "lucide-react";
|
|
@@ -72,35 +71,24 @@ export function PendingAuthorization(props: {
|
|
|
72
71
|
);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Mark a private conversation with a lock icon. Public is the default, so it
|
|
76
|
+
* gets no marker, which keeps the title bar short.
|
|
77
|
+
*/
|
|
76
78
|
export function ConversationPrivacyChip(props: {
|
|
77
79
|
visibility: Conversation["visibility"];
|
|
78
80
|
}) {
|
|
79
|
-
if (
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const shortLabel = isPublic ? "Public" : "Private";
|
|
83
|
-
const fullLabel = isPublic ? "Public conversation" : "Private conversation";
|
|
84
|
-
const detail = isPublic
|
|
85
|
-
? "Anyone in this workspace can see this transcript."
|
|
86
|
-
: "Only members of this conversation can see this transcript.";
|
|
81
|
+
if (props.visibility !== "private") return null;
|
|
82
|
+
const label =
|
|
83
|
+
"Private conversation. Only members of this conversation can see this transcript.";
|
|
87
84
|
return (
|
|
88
85
|
<span
|
|
89
|
-
className=
|
|
90
|
-
isPublic
|
|
91
|
-
? "inline-flex max-w-full items-center gap-1 rounded-full border border-dashboard-border-emphasis bg-dashboard-fill-soft px-2 py-0.5 font-sans text-2xs font-medium text-dashboard-text-muted"
|
|
92
|
-
: "inline-flex max-w-full items-center gap-1 rounded-full border border-dashboard-border-strong bg-dashboard-fill-soft px-2 py-0.5 font-sans text-2xs font-medium text-dashboard-text-muted"
|
|
93
|
-
}
|
|
86
|
+
className="inline-flex items-center text-dashboard-text-muted"
|
|
94
87
|
role="note"
|
|
95
|
-
title={
|
|
88
|
+
title={label}
|
|
96
89
|
>
|
|
97
|
-
<
|
|
98
|
-
<span
|
|
99
|
-
{shortLabel}
|
|
100
|
-
</span>
|
|
101
|
-
<span className="sr-only">
|
|
102
|
-
{fullLabel}. {detail}
|
|
103
|
-
</span>
|
|
90
|
+
<LockKeyhole aria-hidden="true" className="size-3.5 shrink-0" />
|
|
91
|
+
<span className="sr-only">{label}</span>
|
|
104
92
|
</span>
|
|
105
93
|
);
|
|
106
94
|
}
|
|
@@ -568,6 +556,37 @@ export function conversationLocation(
|
|
|
568
556
|
);
|
|
569
557
|
}
|
|
570
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Show the estimated cost of a conversation with its breakdown tooltip.
|
|
561
|
+
* Returns null until there is a cost or a live run to show.
|
|
562
|
+
*/
|
|
563
|
+
export function conversationCost(
|
|
564
|
+
conversation: Conversation | undefined,
|
|
565
|
+
detail: ConversationDetailReport | undefined,
|
|
566
|
+
tooltipPlacement?: "above" | "below",
|
|
567
|
+
): ReactNode {
|
|
568
|
+
if (!conversation) return null;
|
|
569
|
+
const costSummary = summarizeCost(
|
|
570
|
+
detail?.cumulativeUsage ?? conversation.cumulativeUsage,
|
|
571
|
+
);
|
|
572
|
+
const auxiliaryCosts = detail?.auxiliaryCosts ?? conversation.auxiliaryCosts;
|
|
573
|
+
const live = (detail?.status ?? conversation.status) === "active";
|
|
574
|
+
if (!costSummary && !auxiliaryCosts && !live) return null;
|
|
575
|
+
return (
|
|
576
|
+
<CostMetric
|
|
577
|
+
auxiliaryCosts={auxiliaryCosts}
|
|
578
|
+
childAgentTotals={
|
|
579
|
+
detail?.childAgentTotals ?? conversation.childAgentTotals
|
|
580
|
+
}
|
|
581
|
+
live={live}
|
|
582
|
+
liveModelId={liveModelId(detail)}
|
|
583
|
+
modelUsage={detail?.modelUsage}
|
|
584
|
+
summary={costSummary}
|
|
585
|
+
tooltipPlacement={tooltipPlacement}
|
|
586
|
+
/>
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
571
590
|
function conversationStatItems(props: {
|
|
572
591
|
conversation: Conversation | undefined;
|
|
573
592
|
detail?: ConversationDetailReport;
|
|
@@ -580,7 +599,6 @@ function conversationStatItems(props: {
|
|
|
580
599
|
props.detail?.cumulativeUsage ?? props.conversation.cumulativeUsage;
|
|
581
600
|
const tokenSummary =
|
|
582
601
|
summarizeModelUsage(props.detail?.modelUsage) ?? summarizeUsage(usage);
|
|
583
|
-
const costSummary = summarizeCost(usage);
|
|
584
602
|
const durationLabel = formatConversationDuration(props.conversation);
|
|
585
603
|
const live = (props.detail?.status ?? props.conversation.status) === "active";
|
|
586
604
|
const activeModelId = liveModelId(props.detail);
|
|
@@ -589,8 +607,9 @@ function conversationStatItems(props: {
|
|
|
589
607
|
props.detail?.childAgentTotals ?? props.conversation.childAgentTotals;
|
|
590
608
|
const lastActivityAt = props.conversation.lastSeenAt;
|
|
591
609
|
const location = conversationLocation(props.conversation, props.detail);
|
|
610
|
+
const cost = conversationCost(props.conversation, props.detail);
|
|
592
611
|
const rawStats: Array<ConversationStatItem | undefined> = [
|
|
593
|
-
// The desktop title bar shows the location. Mobile hides that bar.
|
|
612
|
+
// The desktop title bar shows the location and cost. Mobile hides that bar.
|
|
594
613
|
location
|
|
595
614
|
? {
|
|
596
615
|
content: location,
|
|
@@ -660,26 +679,12 @@ function conversationStatItems(props: {
|
|
|
660
679
|
label: "Tokens",
|
|
661
680
|
}
|
|
662
681
|
: undefined,
|
|
663
|
-
|
|
664
|
-
props.detail?.auxiliaryCosts ||
|
|
665
|
-
props.conversation.auxiliaryCosts ||
|
|
666
|
-
live
|
|
682
|
+
cost
|
|
667
683
|
? {
|
|
668
|
-
content:
|
|
669
|
-
<CostMetric
|
|
670
|
-
auxiliaryCosts={
|
|
671
|
-
props.detail?.auxiliaryCosts ??
|
|
672
|
-
props.conversation.auxiliaryCosts
|
|
673
|
-
}
|
|
674
|
-
childAgentTotals={childAgentTotals}
|
|
675
|
-
live={live}
|
|
676
|
-
liveModelId={activeModelId}
|
|
677
|
-
modelUsage={props.detail?.modelUsage}
|
|
678
|
-
summary={costSummary}
|
|
679
|
-
/>
|
|
680
|
-
),
|
|
684
|
+
content: cost,
|
|
681
685
|
key: "cost",
|
|
682
686
|
label: "Cost",
|
|
687
|
+
mobileOnly: true,
|
|
683
688
|
}
|
|
684
689
|
: undefined,
|
|
685
690
|
childAgentTotals
|
|
@@ -31,6 +31,14 @@ import {
|
|
|
31
31
|
import { ConversationHeader } from "./ConversationHeader";
|
|
32
32
|
import { ConversationReplyFooter } from "./ConversationReplyFooter";
|
|
33
33
|
import { ConversationDetails } from "./ConversationDetails";
|
|
34
|
+
import { turnPrompts } from "./ConversationMemories";
|
|
35
|
+
import {
|
|
36
|
+
ConversationMemoryContext,
|
|
37
|
+
memoryAnchorKey,
|
|
38
|
+
useConversationMemories,
|
|
39
|
+
type ConversationMemoryState,
|
|
40
|
+
type OpenMemoryPopover,
|
|
41
|
+
} from "./conversationMemory";
|
|
34
42
|
import { ConversationEventDetails } from "./ConversationEventDetails";
|
|
35
43
|
import { ConversationEventLogLoading } from "./ConversationEventLog";
|
|
36
44
|
import {
|
|
@@ -50,6 +58,7 @@ import { ConversationAgentTree } from "./ConversationAgentTree";
|
|
|
50
58
|
import { conversationLinks } from "./ConversationLinkedWork";
|
|
51
59
|
import {
|
|
52
60
|
ConversationAnnotations,
|
|
61
|
+
conversationCost,
|
|
53
62
|
conversationLocation,
|
|
54
63
|
ConversationPrivacyChip,
|
|
55
64
|
ConversationStats,
|
|
@@ -74,6 +83,11 @@ import {
|
|
|
74
83
|
ParticipantAvatarStack,
|
|
75
84
|
} from "../components/ParticipantAvatarStack";
|
|
76
85
|
import { ChatLayout } from "./ChatLayout";
|
|
86
|
+
import {
|
|
87
|
+
QuickQuestionPanel,
|
|
88
|
+
QuickQuestionSelectionButton,
|
|
89
|
+
useQuickQuestionThread,
|
|
90
|
+
} from "./QuickQuestion";
|
|
77
91
|
import { latestReviewItemId } from "../review-desk/reviewItemFormat";
|
|
78
92
|
import { Transcript } from "./TranscriptView";
|
|
79
93
|
import {
|
|
@@ -155,7 +169,24 @@ export function ConversationPage(props: {
|
|
|
155
169
|
);
|
|
156
170
|
}, [setSearchParams]);
|
|
157
171
|
const [turnContext, setTurnContext] = useState<TurnContextSelection>();
|
|
158
|
-
const
|
|
172
|
+
const [memoryPopover, setMemoryPopover] = useState<OpenMemoryPopover>();
|
|
173
|
+
// A memory chosen in details waits here until its transcript row renders.
|
|
174
|
+
const [memoryJump, setMemoryJump] = useState<OpenMemoryPopover>();
|
|
175
|
+
// The page stays mounted across Conversations. Memory state belongs to one.
|
|
176
|
+
const [memoryConversationId, setMemoryConversationId] = useState(
|
|
177
|
+
props.conversationId,
|
|
178
|
+
);
|
|
179
|
+
if (memoryConversationId !== props.conversationId) {
|
|
180
|
+
setMemoryConversationId(props.conversationId);
|
|
181
|
+
setMemoryPopover(undefined);
|
|
182
|
+
setMemoryJump(undefined);
|
|
183
|
+
}
|
|
184
|
+
const quickQuestion = useQuickQuestionThread(props.conversationId);
|
|
185
|
+
// Event details and turn context also show over a Quick question.
|
|
186
|
+
const showQuickQuestion =
|
|
187
|
+
Boolean(quickQuestion.thread) && !eventOpen && !turnContext;
|
|
188
|
+
const showDetails =
|
|
189
|
+
detailsOpen && !eventOpen && !turnContext && !quickQuestion.thread;
|
|
159
190
|
const [viewState, setView] = useState<TranscriptViewMode>(() =>
|
|
160
191
|
eventOpen ? "raw" : "rich",
|
|
161
192
|
);
|
|
@@ -167,6 +198,15 @@ export function ConversationPage(props: {
|
|
|
167
198
|
const summaries = props.data?.conversations.conversations ?? [];
|
|
168
199
|
const conversations = buildConversations(summaries);
|
|
169
200
|
const detail = useConversationData(conversationId);
|
|
201
|
+
const memoryList = useConversationMemories(conversationId).data;
|
|
202
|
+
const memoryState = useMemo<ConversationMemoryState>(
|
|
203
|
+
() => ({
|
|
204
|
+
list: memoryList,
|
|
205
|
+
open: memoryPopover,
|
|
206
|
+
setOpen: setMemoryPopover,
|
|
207
|
+
}),
|
|
208
|
+
[memoryList, memoryPopover],
|
|
209
|
+
);
|
|
170
210
|
const archive = useArchiveConversation(conversationId);
|
|
171
211
|
const markRead = useMarkConversationRead(conversationId);
|
|
172
212
|
const readAtOpen = useReadPositionAtOpen(detail.data, detail.fetchedDetail);
|
|
@@ -288,6 +328,38 @@ export function ConversationPage(props: {
|
|
|
288
328
|
if (linkedTurnSeq !== undefined) setSelectedEventSeq(linkedTurnSeq);
|
|
289
329
|
else if (linkedTurnNotFound) setSelectedEventSeq(undefined);
|
|
290
330
|
}, [linkedTurnNotFound, linkedTurnSeq, setSelectedEventSeq]);
|
|
331
|
+
// Show a chosen memory at its transcript row. Wait for the first page, then
|
|
332
|
+
// load earlier pages until the row renders, or stop when history ends.
|
|
333
|
+
const memoryPrompts = useMemo(
|
|
334
|
+
() => turnPrompts(transcript?.events),
|
|
335
|
+
[transcript?.events],
|
|
336
|
+
);
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (!memoryJump || !transcript) return;
|
|
339
|
+
const anchor = pageRef.current?.querySelector(
|
|
340
|
+
`[data-memory-anchor="${CSS.escape(memoryAnchorKey(memoryJump))}"]`,
|
|
341
|
+
);
|
|
342
|
+
if (anchor) {
|
|
343
|
+
anchor.scrollIntoView({ block: "center" });
|
|
344
|
+
setMemoryPopover(memoryJump);
|
|
345
|
+
setMemoryJump(undefined);
|
|
346
|
+
} else if (
|
|
347
|
+
detail.hasPreviousPage &&
|
|
348
|
+
!detail.isLoadingPreviousPage &&
|
|
349
|
+
!detail.historyError
|
|
350
|
+
) {
|
|
351
|
+
loadPreviousPageRef.current();
|
|
352
|
+
} else if (!detail.isLoadingPreviousPage) {
|
|
353
|
+
setMemoryJump(undefined);
|
|
354
|
+
}
|
|
355
|
+
}, [
|
|
356
|
+
detail.hasPreviousPage,
|
|
357
|
+
detail.historyError,
|
|
358
|
+
detail.isLoadingPreviousPage,
|
|
359
|
+
memoryJump,
|
|
360
|
+
transcript,
|
|
361
|
+
view,
|
|
362
|
+
]);
|
|
291
363
|
// Keep the panel while the transcript or a linked event loads, and for the
|
|
292
364
|
// render before a found Turn link becomes an event link.
|
|
293
365
|
const eventLoading =
|
|
@@ -362,7 +434,7 @@ export function ConversationPage(props: {
|
|
|
362
434
|
ref={pageRef}
|
|
363
435
|
className={cn(
|
|
364
436
|
"grid h-full min-h-0 min-w-0",
|
|
365
|
-
(showDetails || showEvent || turnContext) &&
|
|
437
|
+
(showDetails || showEvent || turnContext || showQuickQuestion) &&
|
|
366
438
|
"xl:grid-cols-[minmax(0,1fr)_25rem]",
|
|
367
439
|
)}
|
|
368
440
|
>
|
|
@@ -373,10 +445,12 @@ export function ConversationPage(props: {
|
|
|
373
445
|
agentTree ? <ConversationAgentTree nodes={agentTree} /> : null
|
|
374
446
|
}
|
|
375
447
|
conversationId={conversationId}
|
|
448
|
+
cost={conversationCost(conversation, detail.data, "below")}
|
|
376
449
|
detailsOpen={showDetails}
|
|
377
450
|
onDetailsClick={() => {
|
|
378
451
|
if (eventOpen) setSelectedEventSeq(undefined);
|
|
379
452
|
setTurnContext(undefined);
|
|
453
|
+
quickQuestion.close();
|
|
380
454
|
if (showDetails) closeDetails();
|
|
381
455
|
else setDetailsChoice(true);
|
|
382
456
|
}}
|
|
@@ -470,31 +544,35 @@ export function ConversationPage(props: {
|
|
|
470
544
|
data.
|
|
471
545
|
</div>
|
|
472
546
|
) : null}
|
|
473
|
-
<
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
547
|
+
<ConversationMemoryContext value={memoryState}>
|
|
548
|
+
<Transcript
|
|
549
|
+
hasPreviousPage={detail.hasPreviousPage}
|
|
550
|
+
historyError={detail.historyError}
|
|
551
|
+
historyVersion={detail.historyVersion}
|
|
552
|
+
lastReadSeq={readAtOpen.lastReadSeq}
|
|
553
|
+
live={live}
|
|
554
|
+
loadingPreviousPage={detail.isLoadingPreviousPage}
|
|
555
|
+
onLoadPreviousPage={detail.loadPreviousPage}
|
|
556
|
+
onRead={markRead}
|
|
557
|
+
onSelectedEventSeqChange={setSelectedEventSeq}
|
|
558
|
+
pendingMessages={detail.inlineMessages}
|
|
559
|
+
pinRequestVersion={pinRequestVersion}
|
|
560
|
+
responding={
|
|
561
|
+
!detail.error && conversationIsResponding(transcript)
|
|
562
|
+
}
|
|
563
|
+
onOpenSubagentTranscript={onOpenSubagentTranscript}
|
|
564
|
+
search={search}
|
|
565
|
+
onSearchChange={setSearch}
|
|
566
|
+
selectedEventSeq={selectedEventSeq}
|
|
567
|
+
startAtUnread={
|
|
568
|
+
readConversationStartPosition() === "unread"
|
|
569
|
+
}
|
|
570
|
+
transcript={transcript}
|
|
571
|
+
turnContextPanel={turnContextPanel}
|
|
572
|
+
unread={readAtOpen.unread}
|
|
573
|
+
view={view}
|
|
574
|
+
/>
|
|
575
|
+
</ConversationMemoryContext>
|
|
498
576
|
</>
|
|
499
577
|
)}
|
|
500
578
|
</div>
|
|
@@ -532,37 +610,51 @@ export function ConversationPage(props: {
|
|
|
532
610
|
}
|
|
533
611
|
/>
|
|
534
612
|
{showDetails ? (
|
|
535
|
-
<
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
613
|
+
<ConversationMemoryContext value={memoryState}>
|
|
614
|
+
<ConversationDetails
|
|
615
|
+
brief={detail.data?.brief}
|
|
616
|
+
conversationId={conversationId}
|
|
617
|
+
highlightReview={reviewOpen}
|
|
618
|
+
identity={identity}
|
|
619
|
+
onClose={closeDetails}
|
|
620
|
+
onOpenSubagent={setSubagentTarget}
|
|
621
|
+
memoryPrompts={memoryPrompts}
|
|
622
|
+
onSelectMemory={(entry) => {
|
|
623
|
+
// Memory rows show only in the Conversation view, and search
|
|
624
|
+
// can hide them.
|
|
625
|
+
if (view !== "rich") onViewChange("rich");
|
|
626
|
+
setSearch("");
|
|
627
|
+
setMemoryJump({
|
|
628
|
+
memoryId: entry.memory.id,
|
|
629
|
+
source: entry.source,
|
|
630
|
+
turnId: entry.turnId,
|
|
631
|
+
});
|
|
632
|
+
}}
|
|
633
|
+
links={conversationLinks(detail.data)}
|
|
634
|
+
overview={
|
|
635
|
+
hasConversationStats({
|
|
636
|
+
conversation,
|
|
637
|
+
detail: detail.data,
|
|
638
|
+
}) ? (
|
|
639
|
+
<ConversationStats
|
|
640
|
+
conversation={conversation}
|
|
641
|
+
detail={detail.data}
|
|
642
|
+
/>
|
|
643
|
+
) : null
|
|
644
|
+
}
|
|
645
|
+
usage={
|
|
646
|
+
hasConversationUsageBreakdown({
|
|
647
|
+
conversation,
|
|
648
|
+
detail: detail.data,
|
|
649
|
+
}) ? (
|
|
650
|
+
<ConversationUsageBreakdown
|
|
651
|
+
conversation={conversation}
|
|
652
|
+
detail={detail.data}
|
|
653
|
+
/>
|
|
654
|
+
) : null
|
|
655
|
+
}
|
|
656
|
+
/>
|
|
657
|
+
</ConversationMemoryContext>
|
|
566
658
|
) : null}
|
|
567
659
|
{showEvent ? (
|
|
568
660
|
<ConversationEventDetails
|
|
@@ -584,6 +676,24 @@ export function ConversationPage(props: {
|
|
|
584
676
|
/>
|
|
585
677
|
</TranscriptSearchProvider>
|
|
586
678
|
) : null}
|
|
679
|
+
{showQuickQuestion && quickQuestion.thread ? (
|
|
680
|
+
<QuickQuestionPanel
|
|
681
|
+
conversationId={conversationId}
|
|
682
|
+
error={quickQuestion.error}
|
|
683
|
+
onClose={quickQuestion.close}
|
|
684
|
+
onSubmit={quickQuestion.submit}
|
|
685
|
+
pending={quickQuestion.pending}
|
|
686
|
+
thread={quickQuestion.thread}
|
|
687
|
+
/>
|
|
688
|
+
) : null}
|
|
689
|
+
<QuickQuestionSelectionButton
|
|
690
|
+
onAsk={(selection) => {
|
|
691
|
+
if (eventOpen) setSelectedEventSeq(undefined);
|
|
692
|
+
setTurnContext(undefined);
|
|
693
|
+
quickQuestion.open(selection);
|
|
694
|
+
}}
|
|
695
|
+
rootRef={pageRef}
|
|
696
|
+
/>
|
|
587
697
|
<SubagentTranscriptDrawer
|
|
588
698
|
onClose={() => setSubagentTarget(undefined)}
|
|
589
699
|
target={subagentTarget}
|
|
@@ -28,6 +28,11 @@ import { TranscriptToolView } from "./TranscriptToolView";
|
|
|
28
28
|
import { TranscriptReasoningView } from "./TranscriptReasoningView";
|
|
29
29
|
import { TranscriptAttachmentsDeliveredView } from "./TranscriptAttachmentsDeliveredView";
|
|
30
30
|
import { TranscriptStructuredEventView } from "./TranscriptStructuredEventView";
|
|
31
|
+
import {
|
|
32
|
+
TranscriptCapturedMemories,
|
|
33
|
+
TranscriptRecalledMemories,
|
|
34
|
+
} from "./TranscriptMemoryPopover";
|
|
35
|
+
import { isMemoryCaptureEvent } from "./conversationMemory";
|
|
31
36
|
import { TranscriptFailureView } from "./TranscriptFailureView";
|
|
32
37
|
import {
|
|
33
38
|
structuredEventIcon,
|
|
@@ -248,16 +253,23 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
|
|
|
248
253
|
renderMessage={(entry) =>
|
|
249
254
|
props.conversation.sourceTask &&
|
|
250
255
|
isAutomationTriggerMessage(props.conversation, entry.message) ? (
|
|
251
|
-
<
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
<div className="grid min-w-0 gap-2">
|
|
257
|
+
<TranscriptAutomationTriggerView
|
|
258
|
+
message={entry.message}
|
|
259
|
+
sourceTask={props.conversation.sourceTask}
|
|
260
|
+
/>
|
|
261
|
+
<TranscriptRecalledMemories message={entry.message} />
|
|
262
|
+
</div>
|
|
255
263
|
) : entry.message.eventType ? (
|
|
256
264
|
<TranscriptRailEvent
|
|
257
265
|
kind="event"
|
|
258
266
|
objectType={entry.message.eventObjectType}
|
|
259
267
|
>
|
|
260
268
|
<TranscriptEventView message={entry.message} />
|
|
269
|
+
<TranscriptRecalledMemories
|
|
270
|
+
className="mt-1"
|
|
271
|
+
message={entry.message}
|
|
272
|
+
/>
|
|
261
273
|
</TranscriptRailEvent>
|
|
262
274
|
) : entry.message.context ? (
|
|
263
275
|
<TranscriptRailEvent kind="message_context">
|
|
@@ -265,6 +277,10 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
|
|
|
265
277
|
message={entry.message}
|
|
266
278
|
conversation={props.conversation}
|
|
267
279
|
/>
|
|
280
|
+
<TranscriptRecalledMemories
|
|
281
|
+
className="mt-2"
|
|
282
|
+
message={entry.message}
|
|
283
|
+
/>
|
|
268
284
|
</TranscriptRailEvent>
|
|
269
285
|
) : (
|
|
270
286
|
<TranscriptMessageView
|
|
@@ -285,10 +301,23 @@ const VisibleTranscriptEntries = memo(function VisibleTranscriptEntries(props: {
|
|
|
285
301
|
icon={structuredEventIcon(entry.part.presentation.icon)}
|
|
286
302
|
kind="structured_event"
|
|
287
303
|
>
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
304
|
+
{isMemoryCaptureEvent(entry.part) ? (
|
|
305
|
+
<TranscriptCapturedMemories
|
|
306
|
+
fallback={
|
|
307
|
+
<TranscriptStructuredEventView
|
|
308
|
+
part={entry.part}
|
|
309
|
+
timestamp={entry.timestamp}
|
|
310
|
+
/>
|
|
311
|
+
}
|
|
312
|
+
timestamp={entry.timestamp}
|
|
313
|
+
turnId={entry.part.turnId}
|
|
314
|
+
/>
|
|
315
|
+
) : (
|
|
316
|
+
<TranscriptStructuredEventView
|
|
317
|
+
part={entry.part}
|
|
318
|
+
timestamp={entry.timestamp}
|
|
319
|
+
/>
|
|
320
|
+
)}
|
|
292
321
|
</TranscriptRailEvent>
|
|
293
322
|
)}
|
|
294
323
|
renderSubagent={(entry) => (
|
|
@@ -69,9 +69,10 @@ const resourceIcons: Partial<Record<string, LucideIcon>> = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Show one Watch as a single line with its resource.
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* Show one Watch as a single line with its resource. When the resource has a
|
|
73
|
+
* web page, the line opens it and a chevron shows the details. Otherwise the
|
|
74
|
+
* line itself shows why the Watch exists and which events wake the
|
|
75
|
+
* Conversation. The expiry is an internal limit, so the row does not show it.
|
|
75
76
|
*/
|
|
76
77
|
function WatchRow(props: { watch: ConversationWatch }) {
|
|
77
78
|
const { watch } = props;
|
|
@@ -83,20 +84,41 @@ function WatchRow(props: { watch: ConversationWatch }) {
|
|
|
83
84
|
// A timer has one event, and its fire time already says when it wakes.
|
|
84
85
|
const events = watch.firesAt ? [] : watch.events;
|
|
85
86
|
const filters = Object.entries(watch.match ?? {});
|
|
86
|
-
const
|
|
87
|
+
const icon = <Icon aria-hidden="true" className="size-3.5" />;
|
|
88
|
+
const title = watch.label ?? `Private ${resource}`;
|
|
89
|
+
const chevron = (
|
|
90
|
+
<span className="grid size-5 place-items-center">
|
|
91
|
+
<ChevronRight
|
|
92
|
+
aria-hidden="true"
|
|
93
|
+
className={dashboardDisclosureChevronClass}
|
|
94
|
+
/>
|
|
95
|
+
</span>
|
|
96
|
+
);
|
|
97
|
+
const toggle = () => setExpanded((value) => !value);
|
|
98
|
+
const row = watch.url ? (
|
|
99
|
+
// The row opens the watched resource, like a linked work row. The
|
|
100
|
+
// chevron beside it shows the details.
|
|
101
|
+
<div className="flex min-w-0 items-start gap-0.5">
|
|
102
|
+
<div className="min-w-0 flex-1">
|
|
103
|
+
<DetailsRow href={watch.url} icon={icon} title={title} />
|
|
104
|
+
</div>
|
|
105
|
+
<button
|
|
106
|
+
aria-expanded={expanded}
|
|
107
|
+
aria-label={`Details for ${title}`}
|
|
108
|
+
className="group grid size-8 shrink-0 place-items-center rounded-md 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"
|
|
109
|
+
onClick={toggle}
|
|
110
|
+
type="button"
|
|
111
|
+
>
|
|
112
|
+
{chevron}
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
) : (
|
|
87
116
|
<DetailsRow
|
|
88
117
|
expanded={expanded}
|
|
89
|
-
icon={
|
|
90
|
-
meta={
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
aria-hidden="true"
|
|
94
|
-
className={dashboardDisclosureChevronClass}
|
|
95
|
-
/>
|
|
96
|
-
</span>
|
|
97
|
-
}
|
|
98
|
-
onToggle={() => setExpanded((value) => !value)}
|
|
99
|
-
title={watch.label ?? `Private ${resource}`}
|
|
118
|
+
icon={icon}
|
|
119
|
+
meta={chevron}
|
|
120
|
+
onToggle={toggle}
|
|
121
|
+
title={title}
|
|
100
122
|
/>
|
|
101
123
|
);
|
|
102
124
|
if (!expanded) return row;
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
} from "./TranscriptHeadingRow";
|
|
43
43
|
import { SlackMark } from "./SlackMark";
|
|
44
44
|
import { DisplayText } from "../display-text/DisplayText";
|
|
45
|
+
import { ReviewItemChangeLabel } from "../review-desk/ReviewItemChangeRow";
|
|
45
46
|
import { displayPlainText } from "../display-text/plainText";
|
|
46
47
|
|
|
47
48
|
// Rows are one line each, so the stack can show five before it collapses.
|
|
@@ -349,7 +350,16 @@ function PendingRow(props: {
|
|
|
349
350
|
className="m-0 min-w-0 truncate font-sans text-sm leading-snug text-dashboard-text-subtle"
|
|
350
351
|
title={displayPlainText(text, mentions)}
|
|
351
352
|
>
|
|
352
|
-
|
|
353
|
+
{props.message.reviewItemChange ? (
|
|
354
|
+
<ReviewItemChangeLabel
|
|
355
|
+
fallback={
|
|
356
|
+
<DisplayText inline mentions={mentions} text={text} />
|
|
357
|
+
}
|
|
358
|
+
record={props.message.reviewItemChange}
|
|
359
|
+
/>
|
|
360
|
+
) : (
|
|
361
|
+
<DisplayText inline mentions={mentions} text={text} />
|
|
362
|
+
)}
|
|
353
363
|
</p>
|
|
354
364
|
) : null}
|
|
355
365
|
</>
|