@sentry/junior-dashboard 0.263.0 → 0.265.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/LICENSE +201 -0
- package/README.md +2 -5
- package/dist/app.js +119 -35
- package/dist/client.js +148 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +119 -35
- package/dist/tailwind.css +1 -1
- package/package.json +9 -4
- package/src/api/schema.ts +0 -1
- package/src/app.ts +5 -20
- package/src/client/App.tsx +29 -31
- package/src/client/api.ts +7 -0
- package/src/client/components/BottomSheet.tsx +26 -18
- package/src/client/components/CopyButton.tsx +83 -0
- package/src/client/components/DetailsPanel.tsx +133 -0
- package/src/client/components/Drawer.tsx +34 -33
- package/src/client/components/FilterBar.tsx +16 -31
- package/src/client/components/Metric.tsx +33 -23
- package/src/client/components/PageContentSkeleton.tsx +1 -1
- package/src/client/components/Pagination.tsx +1 -1
- package/src/client/components/ParticipantAvatarStack.tsx +40 -36
- package/src/client/components/ProfileMenu.tsx +27 -93
- package/src/client/components/README.md +101 -64
- package/src/client/components/SegmentedNav.tsx +1 -1
- package/src/client/components/ShimmerText.tsx +2 -6
- package/src/client/components/ThemeSwitcher.tsx +19 -15
- package/src/client/components/charts/ActivityChart.tsx +173 -288
- package/src/client/components/charts/CacheWrites.tsx +63 -50
- package/src/client/components/charts/InputCacheChart.tsx +137 -131
- package/src/client/components/charts/README.md +4 -4
- package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
- package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
- package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
- package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
- package/src/client/components/layout/DashboardHeader.tsx +44 -90
- package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
- package/src/client/components/layout/VisualViewportShell.tsx +2 -2
- package/src/client/components/ui/accordion.tsx +77 -0
- package/src/client/components/ui/alert-dialog.tsx +89 -0
- package/src/client/components/ui/button.tsx +85 -0
- package/src/client/components/ui/chart.tsx +74 -0
- package/src/client/components/ui/checkbox.tsx +26 -0
- package/src/client/components/ui/collapsible.tsx +22 -0
- package/src/client/components/ui/field.tsx +49 -0
- package/src/client/components/ui/input.tsx +35 -0
- package/src/client/components/ui/native-select.tsx +57 -0
- package/src/client/components/ui/popover.tsx +67 -0
- package/src/client/components/ui/radio-group.tsx +32 -0
- package/src/client/components/ui/sheet.tsx +151 -0
- package/src/client/components/ui/skeleton.tsx +19 -0
- package/src/client/components/ui/table.tsx +80 -0
- package/src/client/components/ui/tabs.tsx +75 -0
- package/src/client/components/ui/textarea.tsx +17 -0
- package/src/client/components/ui/toast.tsx +215 -0
- package/src/client/components/ui/toggle-group.tsx +36 -0
- package/src/client/components/ui/toggle.tsx +37 -0
- package/src/client/components/ui/tooltip.tsx +168 -0
- package/src/client/components/ui/trace.tsx +96 -0
- package/src/client/components/ui/useSheet.ts +74 -0
- package/src/client/conversations/ChatLayout.tsx +7 -3
- package/src/client/conversations/ConversationBrief.tsx +17 -6
- package/src/client/conversations/ConversationChildAgents.tsx +87 -58
- package/src/client/conversations/ConversationComposer.tsx +26 -12
- package/src/client/conversations/ConversationDetails.tsx +198 -95
- package/src/client/conversations/ConversationEventDetails.tsx +141 -0
- package/src/client/conversations/ConversationEventLog.tsx +150 -129
- package/src/client/conversations/ConversationHeader.tsx +8 -3
- package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
- package/src/client/conversations/ConversationHomeList.tsx +1 -1
- package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
- package/src/client/conversations/ConversationMeta.tsx +111 -92
- package/src/client/conversations/ConversationPage.tsx +241 -110
- package/src/client/conversations/ConversationSidebar.tsx +48 -29
- package/src/client/conversations/ConversationUsageBreakdown.tsx +16 -3
- package/src/client/conversations/ConversationWatches.tsx +165 -0
- package/src/client/conversations/ConversationWorkspace.tsx +12 -22
- package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
- package/src/client/conversations/DetailsList.tsx +124 -0
- package/src/client/conversations/EventDetails.tsx +42 -8
- package/src/client/conversations/ForkConversationButton.tsx +27 -16
- package/src/client/conversations/ObjectCard.tsx +20 -8
- package/src/client/conversations/PendingMailboxStack.tsx +52 -33
- package/src/client/conversations/README.md +93 -19
- package/src/client/conversations/ShareConversationButton.tsx +28 -3
- package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
- package/src/client/conversations/ToolFrame.tsx +36 -52
- package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
- package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
- package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
- package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
- package/src/client/conversations/TranscriptLoading.tsx +1 -1
- package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
- package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
- package/src/client/conversations/TranscriptMessageView.tsx +6 -1
- package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
- package/src/client/conversations/TranscriptSubagentView.tsx +3 -3
- package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
- package/src/client/conversations/TranscriptToolView.tsx +1 -1
- package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
- package/src/client/conversations/TranscriptView.tsx +63 -27
- package/src/client/conversations/eventLog.ts +57 -0
- package/src/client/conversations/eventTime.ts +60 -0
- package/src/client/conversations/queries.ts +29 -0
- package/src/client/conversations/transcriptBottomPinning.ts +34 -0
- package/src/client/format.ts +5 -0
- package/src/client/lib/utils.ts +1 -0
- package/src/client/pages/ConnectionsPage.tsx +2 -2
- package/src/client/pages/PersonalTokensPage.tsx +4 -4
- package/src/client/pages/SettingsPage.tsx +9 -10
- package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
- package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
- package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
- package/src/client/pages/automations/AutomationDetails.tsx +193 -0
- package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
- package/src/client/pages/automations/AutomationEditor.tsx +41 -29
- package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
- package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
- package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
- package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
- package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
- package/src/client/pages/automations/AutomationFilters.tsx +189 -74
- package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
- package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
- package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
- package/src/client/pages/automations/AutomationRow.tsx +12 -138
- package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
- package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
- package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
- package/src/client/pages/automations/AutomationStatus.tsx +201 -0
- package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
- package/src/client/pages/automations/AutomationsPage.tsx +80 -68
- package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
- package/src/client/pages/automations/README.md +57 -0
- package/src/client/pages/automations/TriggerFilters.tsx +22 -0
- package/src/client/pages/automations/automationFormat.ts +37 -23
- package/src/client/pages/code/CodeActivityChart.tsx +59 -101
- package/src/client/pages/code/CodePage.tsx +52 -55
- package/src/client/pages/code/RepositoryPage.tsx +6 -8
- package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
- package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
- package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
- package/src/client/pages/memory/MemoryDetails.tsx +218 -0
- package/src/client/pages/memory/MemoryPage.tsx +168 -112
- package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
- package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
- package/src/client/pages/memory/memoryRecord.ts +1 -1
- package/src/client/pages/people/ContributionGrid.tsx +23 -16
- package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
- package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
- package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
- package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
- package/src/client/pages/system/GuardianActivity.tsx +1 -2
- package/src/client/pages/system/PluginBarChart.tsx +106 -87
- package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
- package/src/client/pages/system/README.md +23 -0
- package/src/client/pages/system/SystemNavigation.tsx +5 -1
- package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
- package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
- package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
- package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
- package/src/client/pages/system/WorkspaceList.tsx +20 -30
- package/src/client/pages/system/WorkspacesPage.tsx +117 -119
- package/src/client/pages/system/sandboxSize.ts +8 -0
- package/src/client/pages/system/workspaceQueries.ts +53 -0
- package/src/client/pages/user/PluginUserPage.tsx +51 -2
- package/src/client/pages/user/pluginUserPageData.ts +1 -6
- package/src/client/styles.ts +4 -0
- package/src/client.tsx +7 -1
- package/src/mock-reporting/automation-collection.ts +4 -2
- package/src/mock-reporting/fixtures.ts +23 -7
- package/src/mock-reporting/routes.ts +12 -0
- package/src/mock-reporting/watches.ts +94 -0
- package/src/shell.ts +4 -9
- package/src/tailwind.css +77 -10
- package/src/client/components/Button.tsx +0 -118
- package/src/client/components/Field.tsx +0 -53
- package/src/client/components/SegmentedTabs.tsx +0 -119
- package/src/client/components/Select.tsx +0 -52
- package/src/client/components/Sheet.LICENSE +0 -21
- package/src/client/components/Sheet.tsx +0 -141
- package/src/client/components/Skeleton.tsx +0 -17
- package/src/client/components/TextInput.tsx +0 -58
- package/src/client/components/Tooltip.tsx +0 -216
- package/src/client/components/charts/useChartLayout.ts +0 -27
- package/src/client/conversations/ConversationDetailsFrame.tsx +0 -91
- package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
- package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
- package/src/client/pages/dev/ChartFixtures.tsx +0 -325
- package/src/client/pages/dev/ComponentsPage.tsx +0 -899
- package/src/client/pages/dev/ConversationFixture.tsx +0 -231
- package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
- package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
- package/src/client/pages/dev/SelectFixtures.tsx +0 -62
- package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
- package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
- package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
- package/src/client/pages/system/SnapshotSummary.tsx +0 -78
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useId,
|
|
7
|
+
useState,
|
|
8
|
+
useSyncExternalStore,
|
|
9
|
+
type ComponentProps,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { cn } from "@dashboard/lib/utils";
|
|
12
|
+
|
|
13
|
+
/** Open dashboard tooltips at once and keep them open briefly for the pointer to reach them. */
|
|
14
|
+
function TooltipProvider({
|
|
15
|
+
delay = 0,
|
|
16
|
+
closeDelay = 150,
|
|
17
|
+
...props
|
|
18
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
19
|
+
return (
|
|
20
|
+
<TooltipPrimitive.Provider
|
|
21
|
+
data-slot="tooltip-provider"
|
|
22
|
+
delay={delay}
|
|
23
|
+
closeDelay={closeDelay}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Link the open popup to its trigger. Base UI leaves this to the app. */
|
|
30
|
+
const TooltipDescriptionContext = createContext<string | undefined>(undefined);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Wide screens with a fine hover pointer show tooltips. On smaller or touch
|
|
34
|
+
* screens, a tooltip can cover the menu that its trigger opens.
|
|
35
|
+
*/
|
|
36
|
+
const TOOLTIP_MEDIA_QUERY =
|
|
37
|
+
"(min-width: 768px) and (hover: hover) and (pointer: fine)";
|
|
38
|
+
|
|
39
|
+
function subscribeToTooltipMedia(onChange: () => void) {
|
|
40
|
+
const media = window.matchMedia(TOOLTIP_MEDIA_QUERY);
|
|
41
|
+
media.addEventListener("change", onChange);
|
|
42
|
+
return () => media.removeEventListener("change", onChange);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function tooltipMediaMatches() {
|
|
46
|
+
return window.matchMedia(TOOLTIP_MEDIA_QUERY).matches;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Own one tooltip's open state. It stays off where tooltips are not shown. */
|
|
50
|
+
function Tooltip({
|
|
51
|
+
disabled,
|
|
52
|
+
onOpenChange,
|
|
53
|
+
open: openProp,
|
|
54
|
+
...props
|
|
55
|
+
}: TooltipPrimitive.Root.Props) {
|
|
56
|
+
const available = useSyncExternalStore(
|
|
57
|
+
subscribeToTooltipMedia,
|
|
58
|
+
tooltipMediaMatches,
|
|
59
|
+
() => false,
|
|
60
|
+
);
|
|
61
|
+
const id = useId();
|
|
62
|
+
const [openState, setOpenState] = useState(props.defaultOpen ?? false);
|
|
63
|
+
const open = available && (openProp ?? openState);
|
|
64
|
+
return (
|
|
65
|
+
<TooltipDescriptionContext.Provider value={open ? id : undefined}>
|
|
66
|
+
<TooltipPrimitive.Root
|
|
67
|
+
data-slot="tooltip"
|
|
68
|
+
disabled={disabled || !available}
|
|
69
|
+
onOpenChange={(next, details) => {
|
|
70
|
+
setOpenState(next);
|
|
71
|
+
onOpenChange?.(next, details);
|
|
72
|
+
}}
|
|
73
|
+
open={openProp}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
</TooltipDescriptionContext.Provider>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Show the tooltip on mouse hover or keyboard focus. Touch does not open it.
|
|
82
|
+
* A click keeps it open, so copy and share feedback stays visible.
|
|
83
|
+
*/
|
|
84
|
+
function TooltipTrigger({
|
|
85
|
+
closeOnClick = false,
|
|
86
|
+
...props
|
|
87
|
+
}: TooltipPrimitive.Trigger.Props) {
|
|
88
|
+
const describedBy = useContext(TooltipDescriptionContext);
|
|
89
|
+
return (
|
|
90
|
+
<TooltipPrimitive.Trigger
|
|
91
|
+
aria-describedby={describedBy}
|
|
92
|
+
closeOnClick={closeOnClick}
|
|
93
|
+
data-slot="tooltip-trigger"
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tooltipContentVariants = cva(
|
|
100
|
+
"z-50 select-text rounded-md border border-dashboard-border-emphasis bg-dashboard-surface-raised font-sans text-xs shadow-2xl shadow-black/70 outline-none",
|
|
101
|
+
{
|
|
102
|
+
variants: {
|
|
103
|
+
size: {
|
|
104
|
+
/** Selectable detail cards, such as chart buckets and metric breakdowns. */
|
|
105
|
+
default:
|
|
106
|
+
"min-w-36 max-w-lg px-3 py-2 leading-relaxed text-dashboard-text-muted",
|
|
107
|
+
/** One short label for an icon-only control. */
|
|
108
|
+
compact: "px-2.5 py-1.5 leading-none text-dashboard-text",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
defaultVariants: { size: "default" },
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
/** Show tooltip text beside its trigger and keep it inside the viewport. */
|
|
116
|
+
function TooltipContent({
|
|
117
|
+
className,
|
|
118
|
+
side = "top",
|
|
119
|
+
sideOffset = 10,
|
|
120
|
+
align = "center",
|
|
121
|
+
size,
|
|
122
|
+
children,
|
|
123
|
+
...props
|
|
124
|
+
}: TooltipPrimitive.Popup.Props &
|
|
125
|
+
Pick<TooltipPrimitive.Positioner.Props, "align" | "side" | "sideOffset"> &
|
|
126
|
+
VariantProps<typeof tooltipContentVariants>) {
|
|
127
|
+
const id = useContext(TooltipDescriptionContext);
|
|
128
|
+
return (
|
|
129
|
+
<TooltipPrimitive.Portal>
|
|
130
|
+
<TooltipPrimitive.Positioner
|
|
131
|
+
align={align}
|
|
132
|
+
collisionPadding={8}
|
|
133
|
+
side={side}
|
|
134
|
+
sideOffset={sideOffset}
|
|
135
|
+
className="isolate z-50 data-anchor-hidden:hidden"
|
|
136
|
+
>
|
|
137
|
+
<TooltipPrimitive.Popup
|
|
138
|
+
data-slot="tooltip-content"
|
|
139
|
+
id={id}
|
|
140
|
+
role="tooltip"
|
|
141
|
+
className={cn(tooltipContentVariants({ size }), className)}
|
|
142
|
+
{...props}
|
|
143
|
+
>
|
|
144
|
+
{children}
|
|
145
|
+
</TooltipPrimitive.Popup>
|
|
146
|
+
</TooltipPrimitive.Positioner>
|
|
147
|
+
</TooltipPrimitive.Portal>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Name what a detail tooltip describes, such as a chart bucket date. */
|
|
152
|
+
function TooltipTitle({ className, ...props }: ComponentProps<"div">) {
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
className={cn("mb-1 font-semibold text-dashboard-text-muted", className)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
Tooltip,
|
|
163
|
+
TooltipTrigger,
|
|
164
|
+
TooltipContent,
|
|
165
|
+
TooltipTitle,
|
|
166
|
+
TooltipProvider,
|
|
167
|
+
tooltipContentVariants,
|
|
168
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@dashboard/lib/utils";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
Collapsible,
|
|
6
|
+
CollapsibleContent,
|
|
7
|
+
CollapsibleTrigger,
|
|
8
|
+
} from "@dashboard/components/ui/collapsible";
|
|
9
|
+
import { ChevronRightIcon } from "lucide-react";
|
|
10
|
+
|
|
11
|
+
function TraceIcon({ className, ...props }: React.ComponentProps<"span">) {
|
|
12
|
+
return (
|
|
13
|
+
<span
|
|
14
|
+
data-slot="trace-icon"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
className={cn(
|
|
17
|
+
"flex size-4 shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Keep one trace row and its details in one open state. */
|
|
26
|
+
function TraceCollapsible({
|
|
27
|
+
className,
|
|
28
|
+
...props
|
|
29
|
+
}: Omit<React.ComponentProps<typeof Collapsible>, "open"> &
|
|
30
|
+
(
|
|
31
|
+
| { open?: undefined }
|
|
32
|
+
| {
|
|
33
|
+
open: boolean;
|
|
34
|
+
onOpenChange: NonNullable<
|
|
35
|
+
React.ComponentProps<typeof Collapsible>["onOpenChange"]
|
|
36
|
+
>;
|
|
37
|
+
}
|
|
38
|
+
)) {
|
|
39
|
+
return (
|
|
40
|
+
<Collapsible
|
|
41
|
+
data-slot="trace-collapsible"
|
|
42
|
+
className={cn("flex w-full min-w-0 flex-col", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Render the row button with a chevron that turns while open. */
|
|
49
|
+
function TraceCollapsibleTrigger({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<typeof CollapsibleTrigger>) {
|
|
54
|
+
return (
|
|
55
|
+
<CollapsibleTrigger
|
|
56
|
+
data-slot="trace-collapsible-trigger"
|
|
57
|
+
className={cn(
|
|
58
|
+
"group/trace-collapsible-trigger flex w-fit max-w-full min-w-0 cursor-pointer items-center gap-1 rounded-md px-2 py-1.5 text-left text-dashboard-text-muted transition-colors outline-none 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",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
<TraceIcon className="transition-transform group-aria-expanded/trace-collapsible-trigger:rotate-90 motion-reduce:transition-none">
|
|
65
|
+
<ChevronRightIcon className="size-3 text-dashboard-text-faint" />
|
|
66
|
+
</TraceIcon>
|
|
67
|
+
</CollapsibleTrigger>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Reveal row details in a card under the row title. */
|
|
72
|
+
function TraceCollapsibleContent({
|
|
73
|
+
className,
|
|
74
|
+
children,
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<typeof CollapsibleContent>) {
|
|
77
|
+
return (
|
|
78
|
+
<CollapsibleContent
|
|
79
|
+
data-slot="trace-collapsible-content"
|
|
80
|
+
className="h-(--collapsible-panel-height) min-w-0 overflow-hidden transition-[height] duration-200 data-ending-style:h-0 data-starting-style:h-0 motion-reduce:transition-none"
|
|
81
|
+
{...props}
|
|
82
|
+
>
|
|
83
|
+
<div
|
|
84
|
+
data-slot="trace-collapsible-content-inner"
|
|
85
|
+
className={cn(
|
|
86
|
+
"mt-1.5 ml-6 min-w-0 overflow-hidden rounded-md border border-dashboard-border-subtle bg-dashboard-surface-panel",
|
|
87
|
+
className,
|
|
88
|
+
)}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</div>
|
|
92
|
+
</CollapsibleContent>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { TraceCollapsible, TraceCollapsibleTrigger, TraceCollapsibleContent };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Dialog } from "@base-ui/react/dialog";
|
|
2
|
+
import {
|
|
3
|
+
useEffect,
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
useSyncExternalStore,
|
|
7
|
+
type RefObject,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
acquireBodyScrollLock,
|
|
12
|
+
releaseBodyScrollLock,
|
|
13
|
+
} from "../../bodyScrollLock";
|
|
14
|
+
|
|
15
|
+
// A resize can mount conversation details below an already open side drawer.
|
|
16
|
+
// Document order keeps the front modal in charge across these separate roots.
|
|
17
|
+
function subscribeToSheets(onChange: () => void) {
|
|
18
|
+
const observer = new MutationObserver(onChange);
|
|
19
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
20
|
+
return () => observer.disconnect();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Keep an app sheet mounted through dismissal and share the document scroll lock. */
|
|
24
|
+
export function useSheet(
|
|
25
|
+
onClose: () => void,
|
|
26
|
+
initialFocusRef?: RefObject<HTMLElement | null>,
|
|
27
|
+
) {
|
|
28
|
+
const [open, setOpen] = useState(false);
|
|
29
|
+
const closingRef = useRef(false);
|
|
30
|
+
const popupRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const topmost = useSyncExternalStore(
|
|
32
|
+
subscribeToSheets,
|
|
33
|
+
() => {
|
|
34
|
+
const modals = document.querySelectorAll("[data-dashboard-modal]");
|
|
35
|
+
const last = modals[modals.length - 1];
|
|
36
|
+
return popupRef.current ? last === popupRef.current : !last;
|
|
37
|
+
},
|
|
38
|
+
() => true,
|
|
39
|
+
);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
acquireBodyScrollLock();
|
|
42
|
+
setOpen(true);
|
|
43
|
+
return releaseBodyScrollLock;
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
rootProps: {
|
|
48
|
+
open,
|
|
49
|
+
modal: topmost ? "trap-focus" : false,
|
|
50
|
+
disablePointerDismissal: !topmost,
|
|
51
|
+
onOpenChange(nextOpen, details) {
|
|
52
|
+
if (!nextOpen && !topmost) {
|
|
53
|
+
details.cancel();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
closingRef.current = !nextOpen;
|
|
57
|
+
setOpen(nextOpen);
|
|
58
|
+
},
|
|
59
|
+
onOpenChangeComplete(nextOpen) {
|
|
60
|
+
if (!nextOpen && closingRef.current) onClose();
|
|
61
|
+
},
|
|
62
|
+
} satisfies Dialog.Root.Props,
|
|
63
|
+
contentProps: {
|
|
64
|
+
ref: popupRef,
|
|
65
|
+
"aria-modal": topmost ? true : undefined,
|
|
66
|
+
finalFocus: () => Boolean(closingRef.current && topmost),
|
|
67
|
+
initialFocus: () => {
|
|
68
|
+
const modals = document.querySelectorAll("[data-dashboard-modal]");
|
|
69
|
+
if (modals[modals.length - 1] !== popupRef.current) return false;
|
|
70
|
+
return initialFocusRef?.current ?? true;
|
|
71
|
+
},
|
|
72
|
+
} satisfies Dialog.Popup.Props,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -5,12 +5,14 @@ import { cn } from "../styles";
|
|
|
5
5
|
/**
|
|
6
6
|
* Own the conversation chat frame: one scroll region above one dock.
|
|
7
7
|
*
|
|
8
|
-
* Pages pass `scroll
|
|
8
|
+
* Pages pass `header`, `scroll`, and `dock`. Do not invent footer padding, safe-area,
|
|
9
9
|
* or shell height math outside this component and `ComposerDock`.
|
|
10
10
|
*/
|
|
11
11
|
export function ChatLayout(props: {
|
|
12
12
|
className?: string;
|
|
13
13
|
dock?: ReactNode;
|
|
14
|
+
/** Keep conversation chrome outside the scrolling content. */
|
|
15
|
+
header?: ReactNode;
|
|
14
16
|
/** When true, keep a taller minimum scroll region for long transcripts. */
|
|
15
17
|
scrollMinTall?: boolean;
|
|
16
18
|
scroll: ReactNode;
|
|
@@ -20,11 +22,13 @@ export function ChatLayout(props: {
|
|
|
20
22
|
return (
|
|
21
23
|
<div
|
|
22
24
|
className={cn(
|
|
23
|
-
"grid h-full min-h-0 min-w-0 grid-rows-[
|
|
24
|
-
props.scrollMinTall &&
|
|
25
|
+
"grid h-full min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)_minmax(0,auto)]",
|
|
26
|
+
props.scrollMinTall &&
|
|
27
|
+
"grid-rows-[auto_minmax(7rem,1fr)_minmax(0,auto)]",
|
|
25
28
|
props.className,
|
|
26
29
|
)}
|
|
27
30
|
>
|
|
31
|
+
<div className="min-w-0">{props.header}</div>
|
|
28
32
|
<div
|
|
29
33
|
aria-label={props.scrollAriaLabel}
|
|
30
34
|
className={cn(
|
|
@@ -4,7 +4,13 @@ import type { ConversationDetailReport } from "@sentry/junior/api/schema";
|
|
|
4
4
|
|
|
5
5
|
import { Detail, DetailList } from "../components/DetailList";
|
|
6
6
|
import { StatusChip, type StatusChipTone } from "../components/StatusChip";
|
|
7
|
+
import {
|
|
8
|
+
Collapsible,
|
|
9
|
+
CollapsibleContent,
|
|
10
|
+
CollapsibleTrigger,
|
|
11
|
+
} from "../components/ui/collapsible";
|
|
7
12
|
import { formatTime } from "../format";
|
|
13
|
+
import { cn, dashboardDisclosureChevronClass } from "../styles";
|
|
8
14
|
import { MentionPeopleProvider, renderMentionText } from "./transcriptMentions";
|
|
9
15
|
|
|
10
16
|
type ConversationBriefReport = NonNullable<ConversationDetailReport["brief"]>;
|
|
@@ -160,16 +166,21 @@ function BriefContentView(props: {
|
|
|
160
166
|
|
|
161
167
|
function BriefSection(props: { children: ReactNode; title: string }) {
|
|
162
168
|
return (
|
|
163
|
-
<
|
|
164
|
-
<
|
|
169
|
+
<Collapsible className="min-w-0 border-t border-dashboard-border pt-3">
|
|
170
|
+
<CollapsibleTrigger className="group flex w-full cursor-pointer items-center gap-2 text-left text-sm font-medium text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus">
|
|
165
171
|
<ChevronRight
|
|
166
172
|
aria-hidden="true"
|
|
167
|
-
className=
|
|
173
|
+
className={cn(
|
|
174
|
+
dashboardDisclosureChevronClass,
|
|
175
|
+
"text-dashboard-text-muted",
|
|
176
|
+
)}
|
|
168
177
|
/>
|
|
169
178
|
{props.title}
|
|
170
|
-
</
|
|
171
|
-
<
|
|
172
|
-
|
|
179
|
+
</CollapsibleTrigger>
|
|
180
|
+
<CollapsibleContent hiddenUntilFound>
|
|
181
|
+
<div className="mt-3">{props.children}</div>
|
|
182
|
+
</CollapsibleContent>
|
|
183
|
+
</Collapsible>
|
|
173
184
|
);
|
|
174
185
|
}
|
|
175
186
|
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Bot,
|
|
4
|
+
Circle,
|
|
5
|
+
CircleAlert,
|
|
6
|
+
CircleCheck,
|
|
7
|
+
CircleDot,
|
|
8
|
+
CirclePause,
|
|
9
|
+
CircleX,
|
|
10
|
+
LoaderCircle,
|
|
11
|
+
LockKeyhole,
|
|
12
|
+
type LucideIcon,
|
|
13
|
+
} from "lucide-react";
|
|
3
14
|
import {
|
|
4
15
|
conversationChildAgentsReportSchema,
|
|
5
16
|
type ConversationChildAgent,
|
|
6
17
|
} from "@sentry/junior/api/schema";
|
|
7
18
|
|
|
8
|
-
import {
|
|
19
|
+
import type { StatusChipTone } from "../components/StatusChip";
|
|
9
20
|
import {
|
|
10
21
|
formatConversationCostTotal,
|
|
11
|
-
formatRelativeTime,
|
|
12
22
|
formatRuntime,
|
|
13
23
|
formatTime,
|
|
14
24
|
formatUsageTotal,
|
|
@@ -85,9 +95,12 @@ export function ConversationChildAgents(props: {
|
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
return (
|
|
88
|
-
<ol
|
|
98
|
+
<ol
|
|
99
|
+
aria-label="Subagents"
|
|
100
|
+
className="m-0 grid list-none gap-px overflow-hidden rounded-lg border border-dashboard-border-subtle bg-dashboard-border-subtle p-0"
|
|
101
|
+
>
|
|
89
102
|
{query.data.agents.map((agent) => (
|
|
90
|
-
<li key={agent.conversationId}>
|
|
103
|
+
<li className="bg-dashboard-surface-panel" key={agent.conversationId}>
|
|
91
104
|
<ChildAgentRow agent={agent} onOpen={props.onOpenAgent} />
|
|
92
105
|
</li>
|
|
93
106
|
))}
|
|
@@ -95,9 +108,48 @@ export function ConversationChildAgents(props: {
|
|
|
95
108
|
);
|
|
96
109
|
}
|
|
97
110
|
|
|
111
|
+
const statusIcons: Partial<Record<string, LucideIcon>> = {
|
|
112
|
+
active: CircleDot,
|
|
113
|
+
blocked: CircleAlert,
|
|
114
|
+
completed: CircleCheck,
|
|
115
|
+
error: CircleX,
|
|
116
|
+
failed: CircleX,
|
|
117
|
+
paused: CirclePause,
|
|
118
|
+
running: LoaderCircle,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const statusIconToneClass: Record<StatusChipTone, string> = {
|
|
122
|
+
accent: "text-violet-300",
|
|
123
|
+
danger: "text-rose-300",
|
|
124
|
+
info: "text-cyan-300",
|
|
125
|
+
neutral: "text-dashboard-text-faint",
|
|
126
|
+
success: "text-emerald-300",
|
|
127
|
+
warning: "text-amber-300",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/** Mark a child agent status with one icon and an audible label. */
|
|
131
|
+
function ChildAgentStatusIcon(props: { status: string }) {
|
|
132
|
+
const Icon = statusIcons[props.status] ?? Circle;
|
|
133
|
+
return (
|
|
134
|
+
<span className="mt-px grid size-4 shrink-0 place-items-center">
|
|
135
|
+
<Icon
|
|
136
|
+
aria-hidden="true"
|
|
137
|
+
className={cn(
|
|
138
|
+
"size-3.5",
|
|
139
|
+
statusIconToneClass[subagentStatusTone(props.status)],
|
|
140
|
+
props.status === "running" &&
|
|
141
|
+
"animate-spin motion-reduce:animate-none",
|
|
142
|
+
)}
|
|
143
|
+
/>
|
|
144
|
+
<span className="sr-only">{props.status}</span>
|
|
145
|
+
</span>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
98
149
|
/**
|
|
99
|
-
* Show
|
|
100
|
-
*
|
|
150
|
+
* Show one child agent as a dense row: status, task title, and run time first,
|
|
151
|
+
* then agent name and cost. The row text is the accessible name, so status,
|
|
152
|
+
* title, and error stay audible.
|
|
101
153
|
*/
|
|
102
154
|
function ChildAgentRow(props: {
|
|
103
155
|
agent: ConversationChildAgent;
|
|
@@ -107,24 +159,20 @@ function ChildAgentRow(props: {
|
|
|
107
159
|
const tone = subagentStatusTone(agent.status);
|
|
108
160
|
const task = agent.task?.trim();
|
|
109
161
|
const headline = agent.title ?? task;
|
|
110
|
-
// A title is often a short form of the task. Skip the task when it adds nothing.
|
|
111
|
-
const detail = agent.title && task && task !== agent.title ? task : undefined;
|
|
112
162
|
const runtime = formatRuntime(agent.cumulativeDurationMs);
|
|
163
|
+
const name = agent.redacted ? "Private subagent" : (agent.name ?? "subagent");
|
|
113
164
|
// Totals include this subagent's own subagents. Say so, so the row does not
|
|
114
165
|
// read as the work of this subagent alone.
|
|
115
166
|
const nested = agent.childAgentTotals?.count;
|
|
116
|
-
const
|
|
167
|
+
const meta = [
|
|
117
168
|
formatUsageTotal(agent.cumulativeUsage),
|
|
118
169
|
formatConversationCostTotal(agent.cumulativeUsage, agent.auxiliaryCosts),
|
|
119
|
-
|
|
120
|
-
nested
|
|
121
|
-
? `incl. ${nested} nested ${nested === 1 ? "subagent" : "subagents"}`
|
|
122
|
-
: "",
|
|
170
|
+
nested ? `+${nested} nested` : "",
|
|
123
171
|
].filter(Boolean);
|
|
124
172
|
|
|
125
173
|
return (
|
|
126
174
|
<button
|
|
127
|
-
className="grid w-full min-w-0 cursor-pointer
|
|
175
|
+
className="grid w-full min-w-0 cursor-pointer grid-cols-[auto_minmax(0,1fr)_auto] items-start gap-x-2.5 gap-y-0.5 px-3 py-2.5 text-left text-inherit transition-colors hover:bg-dashboard-surface-hover focus-visible:outline focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-dashboard-focus"
|
|
128
176
|
onClick={() =>
|
|
129
177
|
props.onOpen({
|
|
130
178
|
conversationId: agent.conversationId,
|
|
@@ -137,59 +185,40 @@ function ChildAgentRow(props: {
|
|
|
137
185
|
status: agent.status,
|
|
138
186
|
})
|
|
139
187
|
}
|
|
188
|
+
title={[
|
|
189
|
+
task && task !== headline ? task : "",
|
|
190
|
+
`Updated ${formatTime(agent.updatedAt, {
|
|
191
|
+
dateStyle: "medium",
|
|
192
|
+
timeStyle: "short",
|
|
193
|
+
})}`,
|
|
194
|
+
]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join("\n")}
|
|
140
197
|
type="button"
|
|
141
198
|
>
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<LockKeyhole aria-hidden="true" className="size-3 shrink-0" />
|
|
146
|
-
Private subagent
|
|
147
|
-
</span>
|
|
148
|
-
) : (
|
|
149
|
-
<span className="min-w-0 truncate font-medium text-dashboard-text-muted">
|
|
150
|
-
{agent.name ?? "subagent"}
|
|
151
|
-
</span>
|
|
152
|
-
)}
|
|
153
|
-
<StatusChip className="ml-auto shrink-0" size="compact" tone={tone}>
|
|
154
|
-
{agent.status}
|
|
155
|
-
</StatusChip>
|
|
156
|
-
<time
|
|
157
|
-
className="shrink-0 text-dashboard-text-faint"
|
|
158
|
-
dateTime={agent.updatedAt}
|
|
159
|
-
title={`Updated ${formatTime(agent.updatedAt, {
|
|
160
|
-
dateStyle: "medium",
|
|
161
|
-
timeStyle: "short",
|
|
162
|
-
})}`}
|
|
163
|
-
>
|
|
164
|
-
{formatRelativeTime(agent.updatedAt)}
|
|
165
|
-
</time>
|
|
199
|
+
<ChildAgentStatusIcon status={agent.status} />
|
|
200
|
+
<span className="min-w-0 truncate text-sm font-medium leading-5 text-dashboard-text">
|
|
201
|
+
{headline ?? name}
|
|
166
202
|
</span>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
203
|
+
<span className="shrink-0 text-xs leading-5 tabular-nums text-dashboard-text-muted">
|
|
204
|
+
{runtime}
|
|
205
|
+
</span>
|
|
206
|
+
<span className="col-start-2 col-end-4 flex min-w-0 items-center gap-1.5 text-xs leading-4 text-dashboard-text-muted">
|
|
207
|
+
{agent.redacted ? (
|
|
208
|
+
<LockKeyhole aria-hidden="true" className="size-3 shrink-0" />
|
|
209
|
+
) : null}
|
|
210
|
+
<span className="min-w-0 truncate">
|
|
211
|
+
{[headline ? name : "", ...meta].filter(Boolean).join(" · ")}
|
|
175
212
|
</span>
|
|
176
|
-
|
|
213
|
+
</span>
|
|
177
214
|
{agent.error ? (
|
|
178
215
|
<span
|
|
179
216
|
className={cn(
|
|
180
|
-
"
|
|
217
|
+
"col-start-2 col-end-4 line-clamp-2 min-w-0 whitespace-pre-wrap break-words pt-0.5 text-xs leading-4",
|
|
181
218
|
errorToneClass[tone] ?? "text-dashboard-text-muted",
|
|
182
219
|
)}
|
|
183
220
|
>
|
|
184
|
-
|
|
185
|
-
<span className="line-clamp-3 min-w-0 whitespace-pre-wrap break-words">
|
|
186
|
-
{agent.error}
|
|
187
|
-
</span>
|
|
188
|
-
</span>
|
|
189
|
-
) : null}
|
|
190
|
-
{stats.length > 0 ? (
|
|
191
|
-
<span className="block text-xs tabular-nums text-dashboard-text-muted">
|
|
192
|
-
{stats.join(" · ")}
|
|
221
|
+
{agent.error}
|
|
193
222
|
</span>
|
|
194
223
|
) : null}
|
|
195
224
|
</button>
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
} from "react";
|
|
13
13
|
import { ImagePlus, Send, Square } from "lucide-react";
|
|
14
14
|
|
|
15
|
-
import { Button } from "../components/
|
|
15
|
+
import { Button } from "../components/ui/button";
|
|
16
|
+
import {
|
|
17
|
+
Tooltip,
|
|
18
|
+
TooltipContent,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
} from "../components/ui/tooltip";
|
|
16
21
|
import { useDashboardOnline } from "../connection";
|
|
17
22
|
import { cn, dashboardComposerSurfaceClass } from "../styles";
|
|
18
23
|
|
|
@@ -447,16 +452,25 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
447
452
|
)}
|
|
448
453
|
>
|
|
449
454
|
<div className="flex min-w-0 items-center gap-3">
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
455
|
+
<Tooltip>
|
|
456
|
+
<TooltipTrigger
|
|
457
|
+
render={
|
|
458
|
+
<Button
|
|
459
|
+
focusableWhenDisabled
|
|
460
|
+
aria-label="Attach images"
|
|
461
|
+
disabled={sendLocked || readingImages}
|
|
462
|
+
onClick={() => fileInputRef.current?.click()}
|
|
463
|
+
size="icon"
|
|
464
|
+
type="button"
|
|
465
|
+
>
|
|
466
|
+
<ImagePlus aria-hidden="true" size={16} />
|
|
467
|
+
</Button>
|
|
468
|
+
}
|
|
469
|
+
/>
|
|
470
|
+
<TooltipContent size="compact">
|
|
471
|
+
Attach images (up to 3 MB total)
|
|
472
|
+
</TooltipContent>
|
|
473
|
+
</Tooltip>
|
|
460
474
|
{props.footerStart}
|
|
461
475
|
{props.footerStart ? null : (
|
|
462
476
|
<div className="hidden min-w-0 font-sans text-xs leading-relaxed text-dashboard-text-muted md:block">
|
|
@@ -488,7 +502,7 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
488
502
|
readingImages
|
|
489
503
|
}
|
|
490
504
|
title={sendLocked ? "Sending message" : props.submitLabel}
|
|
491
|
-
|
|
505
|
+
variant="primary"
|
|
492
506
|
type="submit"
|
|
493
507
|
>
|
|
494
508
|
<Send aria-hidden="true" size={14} />
|