@sentry/junior-dashboard 0.266.0 → 0.268.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 +85 -12
- package/dist/client.js +94 -64
- package/dist/index.js +85 -12
- package/dist/tailwind.css +1 -1
- package/package.json +11 -5
- package/src/app.ts +0 -2
- package/src/auth.ts +4 -7
- package/src/client/App.tsx +55 -53
- package/src/client/components/DetailsPanel.tsx +12 -13
- package/src/client/components/Drawer.tsx +46 -23
- package/src/client/components/ImageAttachment.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +6 -2
- package/src/client/components/README.md +15 -2
- package/src/client/components/UserInputForm.tsx +283 -0
- package/src/client/components/layout/DashboardHeader.tsx +13 -0
- package/src/client/components/ui/alert.tsx +1 -1
- package/src/client/components/ui/button-group.tsx +59 -0
- package/src/client/components/ui/button.tsx +11 -1
- package/src/client/components/ui/dialog.tsx +29 -35
- package/src/client/components/ui/drawer.tsx +140 -0
- package/src/client/components/ui/empty.tsx +30 -1
- package/src/client/components/ui/separator.tsx +22 -0
- package/src/client/components/ui/useSheet.ts +5 -1
- package/src/client/conversations/ComposerImages.tsx +66 -14
- package/src/client/conversations/ConversationComposer.tsx +84 -12
- package/src/client/conversations/ConversationDetails.tsx +3 -27
- package/src/client/conversations/ConversationEventDetails.tsx +26 -10
- package/src/client/conversations/ConversationHomeList.tsx +6 -0
- package/src/client/conversations/ConversationListStatusIcon.tsx +15 -1
- package/src/client/conversations/ConversationPage.tsx +160 -2
- package/src/client/conversations/ConversationSidebar.tsx +6 -0
- package/src/client/conversations/ConversationTranscript.tsx +48 -16
- package/src/client/conversations/ConversationWorkspace.tsx +4 -0
- package/src/client/conversations/DetailsList.tsx +33 -0
- package/src/client/conversations/EventDetails.tsx +46 -19
- package/src/client/conversations/ForkConversationButton.tsx +79 -73
- package/src/client/conversations/PendingMailboxStack.tsx +310 -112
- package/src/client/conversations/README.md +30 -13
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +19 -22
- package/src/client/conversations/TranscriptActivityGroup.tsx +27 -19
- package/src/client/conversations/TranscriptAttachment.tsx +39 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +14 -19
- package/src/client/conversations/TranscriptFailureView.tsx +10 -13
- package/src/client/conversations/TranscriptMarkdown.tsx +232 -436
- package/src/client/conversations/TranscriptMessageContextView.tsx +12 -1
- package/src/client/conversations/TranscriptMessageView.tsx +10 -1
- package/src/client/conversations/TranscriptSummary.tsx +5 -31
- package/src/client/conversations/TranscriptToolView.tsx +13 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +174 -161
- package/src/client/conversations/TranscriptView.tsx +7 -3
- package/src/client/conversations/UserInputRequestCard.tsx +39 -0
- package/src/client/conversations/conversationOutbox.ts +46 -0
- package/src/client/conversations/eventTime.ts +13 -1
- package/src/client/conversations/eventTranscript.ts +12 -5
- package/src/client/conversations/queries.ts +127 -26
- package/src/client/conversations/transcriptMarkdownLinks.ts +5 -367
- package/src/client/conversations/turnContext.ts +15 -0
- package/src/client/conversations/userInputRequest.ts +56 -0
- package/src/client/format.ts +31 -8
- package/src/client/pages/SettingsPage.tsx +103 -3
- package/src/client/pages/automations/{AutomationLifecycle.tsx → AutomationActions.tsx} +60 -69
- package/src/client/pages/automations/AutomationDraftChanges.tsx +171 -0
- package/src/client/pages/automations/AutomationEditor.tsx +182 -156
- package/src/client/pages/automations/AutomationEventFields.tsx +119 -45
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +1 -1
- package/src/client/pages/automations/AutomationFormSection.tsx +20 -2
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +1 -1
- package/src/client/pages/automations/AutomationOverview.tsx +238 -0
- package/src/client/pages/automations/AutomationPage.tsx +343 -0
- package/src/client/pages/automations/AutomationRecentRuns.tsx +3 -2
- package/src/client/pages/automations/AutomationRow.tsx +11 -13
- package/src/client/pages/automations/AutomationRunDetails.tsx +4 -2
- package/src/client/pages/automations/{AutomationExecutionsPage.tsx → AutomationRunsTab.tsx} +94 -103
- package/src/client/pages/automations/AutomationStatus.tsx +164 -108
- package/src/client/pages/automations/AutomationVersionsTab.tsx +518 -0
- package/src/client/pages/automations/AutomationsPage.tsx +10 -98
- package/src/client/pages/automations/AutomationsPageLayout.tsx +5 -23
- package/src/client/pages/automations/README.md +56 -46
- package/src/client/pages/code/RepositorySections.tsx +1 -0
- package/src/client/pages/code/RepositoryTabs.tsx +3 -0
- package/src/client/primaryNav.ts +15 -2
- package/src/client/review-desk/ConversationReviewBar.tsx +62 -0
- package/src/client/review-desk/ReviewDeskPage.tsx +277 -0
- package/src/client/review-desk/ReviewItemActions.tsx +165 -0
- package/src/client/review-desk/ReviewItemCard.tsx +47 -0
- package/src/client/review-desk/ReviewItemDetail.tsx +115 -0
- package/src/client/review-desk/queries.ts +164 -0
- package/src/client/review-desk/reviewItemFormat.ts +61 -0
- package/src/client/types.ts +3 -0
- package/src/mock-reporting/fixtures.ts +54 -2
- package/src/mock-reporting/routes.ts +20 -0
- package/src/shell.ts +3 -0
- package/src/client/conversations/transcriptStyles.ts +0 -10
- package/src/client/pages/automations/AutomationDetails.tsx +0 -193
- package/src/client/pages/automations/AutomationEditPage.tsx +0 -176
- package/src/client/pages/automations/AutomationVersionsPage.tsx +0 -390
|
@@ -3,7 +3,11 @@ import { useState } from "react";
|
|
|
3
3
|
|
|
4
4
|
import { Collapsible, CollapsibleContent } from "../components/ui/collapsible";
|
|
5
5
|
import { dashboardDisclosureChevronClass } from "../styles";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
formatMessageTimestamp,
|
|
8
|
+
transcriptMessageActor,
|
|
9
|
+
transcriptMessageActorLabel,
|
|
10
|
+
} from "../format";
|
|
7
11
|
import type { ConversationTranscript, TranscriptViewMessage } from "../types";
|
|
8
12
|
import {
|
|
9
13
|
TranscriptHeadingMeta,
|
|
@@ -61,8 +65,13 @@ export function TranscriptMessageContextView(props: {
|
|
|
61
65
|
{!props.redacted && props.message.contexts?.length ? (
|
|
62
66
|
<div className="mt-2">
|
|
63
67
|
<TranscriptTurnContextView
|
|
68
|
+
actor={transcriptMessageActor(
|
|
69
|
+
props.conversation,
|
|
70
|
+
props.message,
|
|
71
|
+
)}
|
|
64
72
|
contexts={props.message.contexts}
|
|
65
73
|
messageId={props.message.messageId}
|
|
74
|
+
timestamp={props.message.timestamp}
|
|
66
75
|
/>
|
|
67
76
|
</div>
|
|
68
77
|
) : null}
|
|
@@ -93,8 +102,10 @@ export function TranscriptMessageContextView(props: {
|
|
|
93
102
|
{!props.redacted && props.message.contexts?.length ? (
|
|
94
103
|
<div className="mt-2">
|
|
95
104
|
<TranscriptTurnContextView
|
|
105
|
+
actor={transcriptMessageActor(props.conversation, props.message)}
|
|
96
106
|
contexts={props.message.contexts}
|
|
97
107
|
messageId={props.message.messageId}
|
|
108
|
+
timestamp={props.message.timestamp}
|
|
98
109
|
/>
|
|
99
110
|
</div>
|
|
100
111
|
) : null}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { ForkConversationButton } from "./ForkConversationButton";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
TranscriptAttachments,
|
|
4
|
+
TranscriptLocalImages,
|
|
5
|
+
} from "./TranscriptAttachment";
|
|
3
6
|
import { ObjectCard } from "./ObjectCard";
|
|
4
7
|
import { AutomationCard } from "../components/AutomationCard";
|
|
5
8
|
import { memo, type ReactNode } from "react";
|
|
6
9
|
|
|
7
10
|
import {
|
|
8
11
|
formatMessageTimestamp,
|
|
12
|
+
transcriptMessageActor,
|
|
9
13
|
transcriptMessageActorLabel,
|
|
10
14
|
transcriptRoleKind,
|
|
11
15
|
} from "../format";
|
|
@@ -69,6 +73,9 @@ export const TranscriptMessageView = memo(
|
|
|
69
73
|
conversationId={props.conversation.conversationId}
|
|
70
74
|
/>
|
|
71
75
|
) : null}
|
|
76
|
+
{props.message.images?.length ? (
|
|
77
|
+
<TranscriptLocalImages images={props.message.images} />
|
|
78
|
+
) : null}
|
|
72
79
|
{props.message.cards?.map((card) => {
|
|
73
80
|
switch (card.kind) {
|
|
74
81
|
case "object":
|
|
@@ -124,8 +131,10 @@ function messageAction(
|
|
|
124
131
|
if (message.role === "user" && message.contexts?.length) {
|
|
125
132
|
return (
|
|
126
133
|
<TranscriptTurnContextView
|
|
134
|
+
actor={transcriptMessageActor(conversation, message)}
|
|
127
135
|
contexts={message.contexts}
|
|
128
136
|
messageId={message.messageId}
|
|
137
|
+
timestamp={message.timestamp}
|
|
129
138
|
/>
|
|
130
139
|
);
|
|
131
140
|
}
|
|
@@ -1,37 +1,12 @@
|
|
|
1
|
-
import type { ComponentProps
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
2
|
|
|
3
3
|
import { CollapsibleTrigger } from "../components/ui/collapsible";
|
|
4
4
|
import { cn } from "../styles";
|
|
5
5
|
|
|
6
|
-
const summaryClass =
|
|
7
|
-
"cursor-pointer rounded-md px-2 py-1.5 transition-colors hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus";
|
|
8
|
-
|
|
9
|
-
/** Keep hover and keyboard focus around a padded transcript cell. */
|
|
10
|
-
export function TranscriptSummary({
|
|
11
|
-
className,
|
|
12
|
-
flush = false,
|
|
13
|
-
...props
|
|
14
|
-
}: ComponentPropsWithoutRef<"summary"> & {
|
|
15
|
-
/** Extend padding around text that already sits on the transcript edge. */
|
|
16
|
-
flush?: boolean;
|
|
17
|
-
}) {
|
|
18
|
-
return (
|
|
19
|
-
<summary
|
|
20
|
-
{...props}
|
|
21
|
-
className={cn(
|
|
22
|
-
summaryClass,
|
|
23
|
-
"list-none [&::-webkit-details-marker]:hidden",
|
|
24
|
-
flush && "-mx-2 -my-1.5",
|
|
25
|
-
className,
|
|
26
|
-
)}
|
|
27
|
-
/>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
6
|
/**
|
|
32
|
-
* Give a `Collapsible` trigger the
|
|
33
|
-
*
|
|
34
|
-
* such as `span`, inside the trigger.
|
|
7
|
+
* Give a `Collapsible` trigger the padding, hover, and keyboard focus of a
|
|
8
|
+
* transcript disclosure. The caller sets the display. Put only inline
|
|
9
|
+
* elements, such as `span`, inside the trigger.
|
|
35
10
|
*/
|
|
36
11
|
export function TranscriptSummaryTrigger({
|
|
37
12
|
className,
|
|
@@ -46,8 +21,7 @@ export function TranscriptSummaryTrigger({
|
|
|
46
21
|
<CollapsibleTrigger
|
|
47
22
|
{...props}
|
|
48
23
|
className={cn(
|
|
49
|
-
|
|
50
|
-
"text-left disabled:cursor-default disabled:hover:bg-transparent",
|
|
24
|
+
"cursor-pointer rounded-md px-2 py-1.5 text-left transition-colors hover:bg-dashboard-fill-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus disabled:cursor-default disabled:hover:bg-transparent",
|
|
51
25
|
flush ? "-mx-2 -my-1.5 w-[calc(100%+1rem)]" : "w-full",
|
|
52
26
|
className,
|
|
53
27
|
)}
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from "../format";
|
|
11
11
|
import type { TranscriptViewToolCallPart } from "../types";
|
|
12
12
|
import { ShimmerText } from "../components/ShimmerText";
|
|
13
|
+
import { TranscriptReviewItemCard } from "../review-desk/ReviewItemCard";
|
|
14
|
+
import { reviewItemIdFromToolCall } from "../review-desk/reviewItemFormat";
|
|
13
15
|
import { cn } from "../styles";
|
|
14
16
|
import { ToolFrame } from "./ToolFrame";
|
|
15
17
|
import { toolCallPreview } from "./toolCallPreview";
|
|
@@ -64,7 +66,17 @@ export const TranscriptToolView = memo(function TranscriptToolView(props: {
|
|
|
64
66
|
</ToolFrame>
|
|
65
67
|
);
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
const reviewItemId = reviewItemIdFromToolCall(props.part);
|
|
70
|
+
return (
|
|
71
|
+
<div className="min-w-0">
|
|
72
|
+
{frame}
|
|
73
|
+
{reviewItemId ? (
|
|
74
|
+
<div className="mt-2">
|
|
75
|
+
<TranscriptReviewItemCard itemId={reviewItemId} />
|
|
76
|
+
</div>
|
|
77
|
+
) : null}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
68
80
|
});
|
|
69
81
|
|
|
70
82
|
function ToolSignature(props: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ActorIdentity } from "@sentry/junior/api/schema";
|
|
2
|
+
import { Layers } from "lucide-react";
|
|
2
3
|
import {
|
|
3
4
|
createContext,
|
|
4
5
|
useContext,
|
|
@@ -7,23 +8,39 @@ import {
|
|
|
7
8
|
type ReactNode,
|
|
8
9
|
} from "react";
|
|
9
10
|
import { createPortal } from "react-dom";
|
|
11
|
+
import { Link } from "react-router";
|
|
10
12
|
|
|
13
|
+
import { ActorAvatar } from "../components/ActorAvatar";
|
|
11
14
|
import { DetailsPanel } from "../components/DetailsPanel";
|
|
12
|
-
import {
|
|
15
|
+
import { participantName } from "../components/ParticipantAvatarStack";
|
|
16
|
+
import { Button } from "../components/ui/button";
|
|
13
17
|
import { Drawer } from "../components/Drawer";
|
|
18
|
+
import {
|
|
19
|
+
Tooltip,
|
|
20
|
+
TooltipContent,
|
|
21
|
+
TooltipTrigger,
|
|
22
|
+
} from "../components/ui/tooltip";
|
|
14
23
|
import type { TranscriptViewTurnContext } from "../types";
|
|
15
|
-
import { formatMessageTimestamp,
|
|
16
|
-
import {
|
|
17
|
-
|
|
24
|
+
import { formatMessageTimestamp, memoryPath, peoplePath } from "../format";
|
|
25
|
+
import {
|
|
26
|
+
memoryRecallContent,
|
|
27
|
+
userCustomizationContent,
|
|
28
|
+
type MemoryRecallContent,
|
|
29
|
+
} from "./turnContext";
|
|
30
|
+
import { cn } from "../styles";
|
|
31
|
+
import { DetailsCard } from "./DetailsList";
|
|
18
32
|
import { HighlightText } from "./transcriptSearch";
|
|
19
|
-
import { TranscriptSummaryTrigger } from "./TranscriptSummary";
|
|
20
33
|
|
|
21
|
-
/**
|
|
22
|
-
export type
|
|
34
|
+
/** What one turn context view shows: who spoke, when, and the context. */
|
|
35
|
+
export type TurnContext = {
|
|
36
|
+
actor?: ActorIdentity;
|
|
23
37
|
contexts: TranscriptViewTurnContext[];
|
|
24
|
-
|
|
38
|
+
timestamp?: number;
|
|
25
39
|
};
|
|
26
40
|
|
|
41
|
+
/** The turn context that a page panel shows, keyed by its message. */
|
|
42
|
+
export type TurnContextSelection = TurnContext & { id: string };
|
|
43
|
+
|
|
27
44
|
/** Let the page that owns a panel show turn context there. */
|
|
28
45
|
export type TurnContextPanel = {
|
|
29
46
|
openId?: string;
|
|
@@ -55,10 +72,9 @@ export function TurnContextPanelProvider(props: {
|
|
|
55
72
|
* keys context by `messageId`, so every control for one message, including a
|
|
56
73
|
* remounted one, shows the same open state.
|
|
57
74
|
*/
|
|
58
|
-
export function TranscriptTurnContextView(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}) {
|
|
75
|
+
export function TranscriptTurnContextView(
|
|
76
|
+
props: TurnContext & { messageId?: string },
|
|
77
|
+
) {
|
|
62
78
|
const panel = useContext(TurnContextPanelContext);
|
|
63
79
|
const [open, setOpen] = useState(false);
|
|
64
80
|
const instanceId = useId();
|
|
@@ -67,24 +83,38 @@ export function TranscriptTurnContextView(props: {
|
|
|
67
83
|
|
|
68
84
|
return (
|
|
69
85
|
<>
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
<Tooltip>
|
|
87
|
+
<TooltipTrigger
|
|
88
|
+
// The open panel keeps focus here. Close the tooltip on click so
|
|
89
|
+
// Escape reaches the panel.
|
|
90
|
+
closeOnClick
|
|
91
|
+
render={
|
|
92
|
+
<Button
|
|
93
|
+
aria-haspopup={panel ? undefined : "dialog"}
|
|
94
|
+
aria-expanded={expanded}
|
|
95
|
+
aria-label="View turn context"
|
|
96
|
+
className={cn(
|
|
97
|
+
"size-6 shrink-0",
|
|
98
|
+
expanded && "bg-dashboard-fill-strong text-dashboard-text",
|
|
99
|
+
)}
|
|
100
|
+
data-turn-context-id={id}
|
|
101
|
+
onClick={() => {
|
|
102
|
+
if (!panel) return setOpen(true);
|
|
103
|
+
panel.onToggle({
|
|
104
|
+
actor: props.actor,
|
|
105
|
+
contexts: props.contexts,
|
|
106
|
+
id,
|
|
107
|
+
timestamp: props.timestamp,
|
|
108
|
+
});
|
|
109
|
+
}}
|
|
110
|
+
size="icon"
|
|
111
|
+
>
|
|
112
|
+
<Layers aria-hidden="true" size={14} strokeWidth={1.8} />
|
|
113
|
+
</Button>
|
|
114
|
+
}
|
|
115
|
+
/>
|
|
116
|
+
<TooltipContent size="compact">View turn context</TooltipContent>
|
|
117
|
+
</Tooltip>
|
|
88
118
|
|
|
89
119
|
{!panel && open
|
|
90
120
|
? createPortal(
|
|
@@ -102,7 +132,11 @@ export function TranscriptTurnContextView(props: {
|
|
|
102
132
|
openKey={instanceId}
|
|
103
133
|
titleId={instanceId}
|
|
104
134
|
>
|
|
105
|
-
<
|
|
135
|
+
<TurnContextBody
|
|
136
|
+
actor={props.actor}
|
|
137
|
+
contexts={props.contexts}
|
|
138
|
+
timestamp={props.timestamp}
|
|
139
|
+
/>
|
|
106
140
|
</Drawer>,
|
|
107
141
|
document.body,
|
|
108
142
|
)
|
|
@@ -112,10 +146,7 @@ export function TranscriptTurnContextView(props: {
|
|
|
112
146
|
}
|
|
113
147
|
|
|
114
148
|
/** Show turn context in the page panel: an aside, or a sheet on small screens. */
|
|
115
|
-
export function TurnContextDetails(props: {
|
|
116
|
-
contexts: TranscriptViewTurnContext[];
|
|
117
|
-
onClose(): void;
|
|
118
|
-
}) {
|
|
149
|
+
export function TurnContextDetails(props: TurnContext & { onClose(): void }) {
|
|
119
150
|
return (
|
|
120
151
|
<DetailsPanel
|
|
121
152
|
label="Turn context"
|
|
@@ -126,151 +157,133 @@ export function TurnContextDetails(props: {
|
|
|
126
157
|
</h2>
|
|
127
158
|
}
|
|
128
159
|
>
|
|
129
|
-
<div className="
|
|
130
|
-
<
|
|
160
|
+
<div className="p-4 pb-[max(1rem,env(safe-area-inset-bottom))]">
|
|
161
|
+
<TurnContextBody
|
|
162
|
+
actor={props.actor}
|
|
163
|
+
contexts={props.contexts}
|
|
164
|
+
timestamp={props.timestamp}
|
|
165
|
+
/>
|
|
131
166
|
</div>
|
|
132
167
|
</DetailsPanel>
|
|
133
168
|
);
|
|
134
169
|
}
|
|
135
170
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
)
|
|
171
|
+
/** Show who spoke, then their customization, then plugin context. */
|
|
172
|
+
function TurnContextBody(props: TurnContext) {
|
|
173
|
+
const contexts = [...props.contexts].sort(
|
|
174
|
+
(a, b) =>
|
|
175
|
+
Number(Boolean(userCustomizationContent(b))) -
|
|
176
|
+
Number(Boolean(userCustomizationContent(a))),
|
|
177
|
+
);
|
|
178
|
+
return (
|
|
179
|
+
<div className="grid min-w-0 gap-3">
|
|
180
|
+
{props.actor ? (
|
|
181
|
+
<TurnContextActor actor={props.actor} timestamp={props.timestamp} />
|
|
182
|
+
) : null}
|
|
183
|
+
{contexts.map((context, index) => (
|
|
184
|
+
<TurnContextCard
|
|
185
|
+
context={context}
|
|
186
|
+
key={`${context.pluginName}:${context.kind}:${context.version}:${index}`}
|
|
187
|
+
/>
|
|
188
|
+
))}
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
143
191
|
}
|
|
144
192
|
|
|
145
|
-
function
|
|
146
|
-
const
|
|
147
|
-
|
|
193
|
+
function TurnContextActor(props: { actor: ActorIdentity; timestamp?: number }) {
|
|
194
|
+
const name = participantName(props.actor);
|
|
195
|
+
const email = props.actor.email?.trim();
|
|
196
|
+
const handle = props.actor.slackUserName?.trim();
|
|
197
|
+
const meta = [
|
|
198
|
+
email && email !== name ? email : undefined,
|
|
199
|
+
handle && handle !== name ? `@${handle}` : undefined,
|
|
200
|
+
].filter(Boolean);
|
|
148
201
|
return (
|
|
149
|
-
<section
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
202
|
+
<section
|
|
203
|
+
aria-label="Speaker"
|
|
204
|
+
className="flex min-w-0 items-center gap-3 rounded-lg border border-dashboard-border-subtle bg-dashboard-surface-panel p-3"
|
|
205
|
+
>
|
|
206
|
+
<ActorAvatar name={name} size="detail" />
|
|
207
|
+
<div className="min-w-0 flex-1">
|
|
208
|
+
<div className="flex min-w-0 items-baseline justify-between gap-3">
|
|
209
|
+
{email ? (
|
|
210
|
+
<Link
|
|
211
|
+
className="min-w-0 truncate text-sm font-medium text-dashboard-text no-underline hover:underline"
|
|
212
|
+
to={peoplePath(email)}
|
|
213
|
+
>
|
|
214
|
+
{name}
|
|
215
|
+
</Link>
|
|
158
216
|
) : (
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
size={15}
|
|
163
|
-
/>
|
|
217
|
+
<span className="min-w-0 truncate text-sm font-medium text-dashboard-text">
|
|
218
|
+
{name}
|
|
219
|
+
</span>
|
|
164
220
|
)}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
221
|
+
{props.timestamp !== undefined ? (
|
|
222
|
+
<time
|
|
223
|
+
className="shrink-0 text-xs tabular-nums text-dashboard-text-muted"
|
|
224
|
+
dateTime={new Date(props.timestamp).toISOString()}
|
|
225
|
+
>
|
|
226
|
+
{formatMessageTimestamp(props.timestamp, true)}
|
|
227
|
+
</time>
|
|
228
|
+
) : null}
|
|
168
229
|
</div>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
230
|
+
{meta.length ? (
|
|
231
|
+
<p
|
|
232
|
+
className="m-0 mt-0.5 truncate text-xs text-dashboard-text-muted"
|
|
233
|
+
title={props.actor.slackUserId}
|
|
234
|
+
>
|
|
235
|
+
{meta.join(" · ")}
|
|
236
|
+
</p>
|
|
237
|
+
) : null}
|
|
172
238
|
</div>
|
|
173
|
-
|
|
174
|
-
{memory ? (
|
|
175
|
-
<MemoryRecall
|
|
176
|
-
loadedAt={props.context.loadedAt}
|
|
177
|
-
memories={memory.memories}
|
|
178
|
-
/>
|
|
179
|
-
) : (
|
|
180
|
-
<GenericContext context={props.context} />
|
|
181
|
-
)}
|
|
182
239
|
</section>
|
|
183
240
|
);
|
|
184
241
|
}
|
|
185
242
|
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
243
|
+
function TurnContextCard(props: { context: TranscriptViewTurnContext }) {
|
|
244
|
+
const memory = memoryRecallContent(props.context);
|
|
245
|
+
if (memory) {
|
|
246
|
+
return (
|
|
247
|
+
<DetailsCard count={memory.memories.length} title="Recalled memories">
|
|
248
|
+
<MemoryRecall memories={memory.memories} />
|
|
249
|
+
</DetailsCard>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
const customization = userCustomizationContent(props.context);
|
|
253
|
+
if (customization) {
|
|
254
|
+
return (
|
|
255
|
+
<DetailsCard title="User customization">
|
|
256
|
+
<p className="m-0 whitespace-pre-wrap text-sm leading-relaxed text-dashboard-text">
|
|
257
|
+
<HighlightText text={customization.text} />
|
|
258
|
+
</p>
|
|
259
|
+
</DetailsCard>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
190
262
|
return (
|
|
191
|
-
<
|
|
192
|
-
{props.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
<ChevronRight
|
|
199
|
-
aria-hidden="true"
|
|
200
|
-
className={cn(
|
|
201
|
-
dashboardDisclosureChevronClass,
|
|
202
|
-
"mt-0.5 text-dashboard-text-muted",
|
|
203
|
-
)}
|
|
204
|
-
/>
|
|
205
|
-
<span className="min-w-0 flex-1">
|
|
206
|
-
<span className="flex items-center justify-between gap-3">
|
|
207
|
-
<span className="text-xs font-medium text-dashboard-text-muted">
|
|
208
|
-
Memory {index + 1}
|
|
209
|
-
</span>
|
|
210
|
-
<span className="shrink-0 text-xs text-dashboard-text-muted">
|
|
211
|
-
{memory.kind} · {memory.scope}
|
|
212
|
-
</span>
|
|
213
|
-
</span>
|
|
214
|
-
<span className="mt-1 block truncate text-sm text-dashboard-text-muted">
|
|
215
|
-
<HighlightText text={memory.content} />
|
|
216
|
-
</span>
|
|
217
|
-
</span>
|
|
218
|
-
</TranscriptSummaryTrigger>
|
|
219
|
-
|
|
220
|
-
<CollapsibleContent hiddenUntilFound>
|
|
221
|
-
<div className="border-t border-dashboard-border bg-dashboard-fill-faint px-4 py-4">
|
|
222
|
-
<div className="whitespace-pre-wrap text-sm leading-6 text-dashboard-text">
|
|
223
|
-
<HighlightText text={memory.content} />
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
<dl className="mt-4 grid gap-2 text-xs">
|
|
227
|
-
<div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
|
|
228
|
-
<dt className="text-dashboard-text-muted">Memory ID</dt>
|
|
229
|
-
<dd className="m-0 break-all font-mono text-dashboard-text-muted">
|
|
230
|
-
<HighlightText text={memory.id} />
|
|
231
|
-
</dd>
|
|
232
|
-
</div>
|
|
233
|
-
<div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
|
|
234
|
-
<dt className="text-dashboard-text-muted">Observed</dt>
|
|
235
|
-
<dd className="m-0 text-dashboard-text-muted">
|
|
236
|
-
{formatTime(new Date(memory.observedAtMs).toISOString())}
|
|
237
|
-
</dd>
|
|
238
|
-
</div>
|
|
239
|
-
<div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
|
|
240
|
-
<dt className="text-dashboard-text-muted">Scope</dt>
|
|
241
|
-
<dd className="m-0 text-dashboard-text-muted">
|
|
242
|
-
{memory.scope}
|
|
243
|
-
</dd>
|
|
244
|
-
</div>
|
|
245
|
-
<div className="grid grid-cols-[5rem_minmax(0,1fr)] items-start gap-3">
|
|
246
|
-
<dt className="text-dashboard-text-muted">Kind</dt>
|
|
247
|
-
<dd className="m-0 text-dashboard-text-muted">
|
|
248
|
-
{memory.kind}
|
|
249
|
-
</dd>
|
|
250
|
-
</div>
|
|
251
|
-
</dl>
|
|
252
|
-
</div>
|
|
253
|
-
</CollapsibleContent>
|
|
254
|
-
</Collapsible>
|
|
255
|
-
))}
|
|
256
|
-
<p className="m-0 border-t border-dashboard-border-strong px-3 py-2 text-xs text-dashboard-text-muted">
|
|
257
|
-
{props.memories.length}{" "}
|
|
258
|
-
{props.memories.length === 1 ? "memory" : "memories"} · Loaded{" "}
|
|
259
|
-
{formatMessageTimestamp(Date.parse(props.loadedAt))}
|
|
260
|
-
</p>
|
|
261
|
-
</div>
|
|
263
|
+
<DetailsCard
|
|
264
|
+
title={`${props.context.pluginName} · ${props.context.kind} v${props.context.version}`}
|
|
265
|
+
>
|
|
266
|
+
<pre className="m-0 overflow-x-auto whitespace-pre-wrap break-words font-mono text-xs leading-relaxed">
|
|
267
|
+
<HighlightText text={JSON.stringify(props.context.content, null, 2)} />
|
|
268
|
+
</pre>
|
|
269
|
+
</DetailsCard>
|
|
262
270
|
);
|
|
263
271
|
}
|
|
264
272
|
|
|
265
|
-
|
|
273
|
+
// Each row links to the memory's page, which shows its details.
|
|
274
|
+
function MemoryRecall(props: { memories: MemoryRecallContent["memories"] }) {
|
|
266
275
|
return (
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
<ul className="-mx-1.5 -mb-1.5 -mt-1 grid list-none gap-0.5 p-0">
|
|
277
|
+
{props.memories.map((memory) => (
|
|
278
|
+
<li key={memory.id}>
|
|
279
|
+
<Link
|
|
280
|
+
className="line-clamp-2 rounded-md px-1.5 py-1.5 text-sm leading-5 text-dashboard-text no-underline transition-colors hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
281
|
+
to={memoryPath(memory.id)}
|
|
282
|
+
>
|
|
283
|
+
<HighlightText text={memory.content} />
|
|
284
|
+
</Link>
|
|
285
|
+
</li>
|
|
286
|
+
))}
|
|
287
|
+
</ul>
|
|
275
288
|
);
|
|
276
289
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
+
import type { ConversationMailboxMessage } from "./conversationOutbox";
|
|
2
3
|
import { ArrowDownToLine } from "lucide-react";
|
|
3
4
|
|
|
4
5
|
import type {
|
|
5
6
|
ConversationTranscript,
|
|
6
7
|
TranscriptViewSubagentPart,
|
|
7
8
|
} from "../types";
|
|
9
|
+
import { EmptyTelemetry } from "../components/EmptyTelemetry";
|
|
8
10
|
import { Button } from "../components/ui/button";
|
|
9
11
|
import {
|
|
10
12
|
Tooltip,
|
|
@@ -20,7 +22,6 @@ import {
|
|
|
20
22
|
usePinnedTranscriptBottom,
|
|
21
23
|
} from "./transcriptBottomPinning";
|
|
22
24
|
import type { TranscriptViewMode } from "./transcriptRenderModel";
|
|
23
|
-
import { transcriptEmptyClass } from "./transcriptStyles";
|
|
24
25
|
import { TranscriptSearchProvider } from "./transcriptSearch";
|
|
25
26
|
import {
|
|
26
27
|
TurnContextPanelProvider,
|
|
@@ -37,6 +38,8 @@ export function Transcript(props: {
|
|
|
37
38
|
pinRequestVersion?: number;
|
|
38
39
|
onLoadPreviousPage?: () => void;
|
|
39
40
|
onSelectedEventSeqChange?(seq: number | undefined): void;
|
|
41
|
+
/** The reader's own sends to show at the end until history has them. */
|
|
42
|
+
pendingMessages?: readonly ConversationMailboxMessage[];
|
|
40
43
|
responding?: boolean;
|
|
41
44
|
search?: string;
|
|
42
45
|
onSearchChange?(value: string): void;
|
|
@@ -86,9 +89,9 @@ export function Transcript(props: {
|
|
|
86
89
|
|
|
87
90
|
if (!props.transcript) {
|
|
88
91
|
return (
|
|
89
|
-
<
|
|
92
|
+
<EmptyTelemetry>
|
|
90
93
|
No transcript is available for this conversation.
|
|
91
|
-
</
|
|
94
|
+
</EmptyTelemetry>
|
|
92
95
|
);
|
|
93
96
|
}
|
|
94
97
|
|
|
@@ -139,6 +142,7 @@ export function Transcript(props: {
|
|
|
139
142
|
<ConversationTranscriptView
|
|
140
143
|
onOpenSubagentTranscript={props.onOpenSubagentTranscript}
|
|
141
144
|
conversation={props.transcript}
|
|
145
|
+
pendingMessages={props.pendingMessages}
|
|
142
146
|
responding={props.responding ?? props.live ?? false}
|
|
143
147
|
/>
|
|
144
148
|
</TurnContextPanelProvider>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { formatUserInputAnswer } from "@sentry/junior/api/schema";
|
|
2
|
+
|
|
3
|
+
import { UserInputForm } from "../components/UserInputForm";
|
|
4
|
+
import { TranscriptText } from "./TranscriptText";
|
|
5
|
+
import type { OpenUserInputRequest } from "./userInputRequest";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Show Junior's open request above the composer. The answer goes as the
|
|
9
|
+
* reader's next message. Typing a reply also answers it.
|
|
10
|
+
*/
|
|
11
|
+
export function UserInputRequestCard(props: {
|
|
12
|
+
onAnswer(answer: string): Promise<void>;
|
|
13
|
+
request: OpenUserInputRequest;
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<section
|
|
17
|
+
aria-label="Junior asks"
|
|
18
|
+
className="mb-2 rounded-lg border border-cyan-300/20 bg-cyan-300/[0.07] px-3 py-2.5 font-sans text-sm text-dashboard-text"
|
|
19
|
+
>
|
|
20
|
+
<div className="max-h-48 overflow-y-auto">
|
|
21
|
+
<TranscriptText
|
|
22
|
+
mentions={[]}
|
|
23
|
+
role="assistant"
|
|
24
|
+
text={props.request.question}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
{/* Above the composer, a long form scrolls so the transcript stays visible. */}
|
|
28
|
+
<div className="max-h-96 overflow-y-auto">
|
|
29
|
+
<UserInputForm
|
|
30
|
+
onSubmit={(answer) =>
|
|
31
|
+
props.onAnswer(formatUserInputAnswer(props.request, answer))
|
|
32
|
+
}
|
|
33
|
+
request={props.request}
|
|
34
|
+
requestId={props.request.toolCallId}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</section>
|
|
38
|
+
);
|
|
39
|
+
}
|