@sentry/junior-dashboard 0.264.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.
Files changed (191) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +2 -5
  3. package/dist/app.js +119 -35
  4. package/dist/client.js +145 -136
  5. package/dist/index.d.ts +0 -1
  6. package/dist/index.js +119 -35
  7. package/dist/tailwind.css +1 -1
  8. package/package.json +5 -4
  9. package/src/api/schema.ts +0 -1
  10. package/src/app.ts +5 -20
  11. package/src/client/App.tsx +29 -31
  12. package/src/client/api.ts +7 -0
  13. package/src/client/components/BottomSheet.tsx +12 -2
  14. package/src/client/components/CopyButton.tsx +83 -0
  15. package/src/client/components/DetailsPanel.tsx +133 -0
  16. package/src/client/components/Drawer.tsx +15 -24
  17. package/src/client/components/FilterBar.tsx +16 -31
  18. package/src/client/components/Metric.tsx +33 -23
  19. package/src/client/components/PageContentSkeleton.tsx +1 -1
  20. package/src/client/components/Pagination.tsx +1 -1
  21. package/src/client/components/ParticipantAvatarStack.tsx +40 -36
  22. package/src/client/components/ProfileMenu.tsx +27 -93
  23. package/src/client/components/README.md +86 -77
  24. package/src/client/components/SegmentedNav.tsx +1 -1
  25. package/src/client/components/ShimmerText.tsx +2 -6
  26. package/src/client/components/ThemeSwitcher.tsx +19 -15
  27. package/src/client/components/charts/ActivityChart.tsx +173 -288
  28. package/src/client/components/charts/CacheWrites.tsx +63 -50
  29. package/src/client/components/charts/InputCacheChart.tsx +137 -131
  30. package/src/client/components/charts/README.md +4 -4
  31. package/src/client/components/charts/SystemMetricCharts.tsx +68 -95
  32. package/src/client/components/charts/WorkspaceUsageChart.tsx +42 -62
  33. package/src/client/components/controls/DirectorySortSelect.tsx +3 -3
  34. package/src/client/components/controls/TimeRangeSelector.tsx +10 -15
  35. package/src/client/components/layout/DashboardHeader.tsx +44 -90
  36. package/src/client/components/layout/SecondaryNavigation.tsx +2 -1
  37. package/src/client/components/layout/VisualViewportShell.tsx +2 -2
  38. package/src/client/components/ui/accordion.tsx +77 -0
  39. package/src/client/components/ui/alert-dialog.tsx +89 -0
  40. package/src/client/components/ui/button.tsx +85 -0
  41. package/src/client/components/ui/chart.tsx +74 -0
  42. package/src/client/components/ui/checkbox.tsx +26 -0
  43. package/src/client/components/ui/collapsible.tsx +22 -0
  44. package/src/client/components/ui/field.tsx +49 -0
  45. package/src/client/components/ui/input.tsx +35 -0
  46. package/src/client/components/ui/native-select.tsx +57 -0
  47. package/src/client/components/ui/popover.tsx +67 -0
  48. package/src/client/components/ui/radio-group.tsx +32 -0
  49. package/src/client/components/ui/sheet.tsx +13 -3
  50. package/src/client/components/ui/skeleton.tsx +19 -0
  51. package/src/client/components/ui/table.tsx +80 -0
  52. package/src/client/components/ui/tabs.tsx +75 -0
  53. package/src/client/components/ui/textarea.tsx +17 -0
  54. package/src/client/components/ui/toast.tsx +215 -0
  55. package/src/client/components/ui/toggle-group.tsx +36 -0
  56. package/src/client/components/ui/toggle.tsx +37 -0
  57. package/src/client/components/ui/tooltip.tsx +168 -0
  58. package/src/client/components/ui/trace.tsx +96 -0
  59. package/src/client/conversations/ChatLayout.tsx +7 -3
  60. package/src/client/conversations/ConversationBrief.tsx +17 -6
  61. package/src/client/conversations/ConversationComposer.tsx +26 -12
  62. package/src/client/conversations/ConversationDetails.tsx +147 -62
  63. package/src/client/conversations/ConversationEventDetails.tsx +141 -0
  64. package/src/client/conversations/ConversationEventLog.tsx +150 -129
  65. package/src/client/conversations/ConversationHeader.tsx +8 -3
  66. package/src/client/conversations/ConversationHeaderActions.tsx +90 -82
  67. package/src/client/conversations/ConversationHomeList.tsx +1 -1
  68. package/src/client/conversations/ConversationLinkedWork.tsx +118 -0
  69. package/src/client/conversations/ConversationMeta.tsx +55 -88
  70. package/src/client/conversations/ConversationPage.tsx +220 -101
  71. package/src/client/conversations/ConversationSidebar.tsx +48 -29
  72. package/src/client/conversations/ConversationWatches.tsx +165 -0
  73. package/src/client/conversations/ConversationWorkspace.tsx +12 -22
  74. package/src/client/conversations/ConversationWorkspaceLoading.tsx +1 -1
  75. package/src/client/conversations/DetailsList.tsx +124 -0
  76. package/src/client/conversations/EventDetails.tsx +42 -8
  77. package/src/client/conversations/ForkConversationButton.tsx +27 -16
  78. package/src/client/conversations/ObjectCard.tsx +20 -8
  79. package/src/client/conversations/PendingMailboxStack.tsx +52 -33
  80. package/src/client/conversations/README.md +86 -13
  81. package/src/client/conversations/ShareConversationButton.tsx +25 -13
  82. package/src/client/conversations/SubagentTranscriptDrawer.tsx +21 -15
  83. package/src/client/conversations/ToolFrame.tsx +36 -52
  84. package/src/client/conversations/TranscriptActivityGroup.tsx +13 -4
  85. package/src/client/conversations/TranscriptAutomationTriggerView.tsx +5 -4
  86. package/src/client/conversations/TranscriptContextEventView.tsx +25 -11
  87. package/src/client/conversations/TranscriptHeadingRow.tsx +7 -7
  88. package/src/client/conversations/TranscriptLoading.tsx +1 -1
  89. package/src/client/conversations/TranscriptMarkdown.tsx +25 -20
  90. package/src/client/conversations/TranscriptMessageContextView.tsx +8 -2
  91. package/src/client/conversations/TranscriptMessageView.tsx +6 -1
  92. package/src/client/conversations/TranscriptReasoningView.tsx +54 -68
  93. package/src/client/conversations/TranscriptTimestamp.tsx +9 -7
  94. package/src/client/conversations/TranscriptToolView.tsx +1 -1
  95. package/src/client/conversations/TranscriptTurnContextView.tsx +99 -27
  96. package/src/client/conversations/TranscriptView.tsx +63 -27
  97. package/src/client/conversations/eventLog.ts +57 -0
  98. package/src/client/conversations/eventTime.ts +60 -0
  99. package/src/client/conversations/queries.ts +29 -0
  100. package/src/client/conversations/transcriptBottomPinning.ts +34 -0
  101. package/src/client/format.ts +5 -0
  102. package/src/client/pages/ConnectionsPage.tsx +2 -2
  103. package/src/client/pages/PersonalTokensPage.tsx +4 -4
  104. package/src/client/pages/SettingsPage.tsx +9 -10
  105. package/src/client/pages/admin/ServiceAccountsSection.tsx +2 -2
  106. package/src/client/pages/automations/AutomationCostChart.tsx +50 -71
  107. package/src/client/pages/automations/AutomationDeleteDialog.tsx +87 -0
  108. package/src/client/pages/automations/AutomationDetails.tsx +193 -0
  109. package/src/client/pages/automations/AutomationEditPage.tsx +7 -8
  110. package/src/client/pages/automations/AutomationEditor.tsx +41 -29
  111. package/src/client/pages/automations/AutomationEditorLoading.tsx +1 -1
  112. package/src/client/pages/automations/AutomationEventFields.tsx +202 -119
  113. package/src/client/pages/automations/AutomationExecutionChart.tsx +51 -64
  114. package/src/client/pages/automations/AutomationExecutionStatusChart.tsx +47 -70
  115. package/src/client/pages/automations/AutomationExecutionsPage.tsx +150 -126
  116. package/src/client/pages/automations/AutomationFilters.tsx +189 -74
  117. package/src/client/pages/automations/AutomationLifecycle.tsx +109 -78
  118. package/src/client/pages/automations/AutomationOutcomeFields.tsx +26 -24
  119. package/src/client/pages/automations/AutomationRecentRuns.tsx +143 -0
  120. package/src/client/pages/automations/AutomationRow.tsx +12 -138
  121. package/src/client/pages/automations/AutomationRunDetails.tsx +307 -0
  122. package/src/client/pages/automations/AutomationRunsPage.tsx +119 -94
  123. package/src/client/pages/automations/AutomationScheduleFields.tsx +51 -51
  124. package/src/client/pages/automations/AutomationStatus.tsx +201 -0
  125. package/src/client/pages/automations/AutomationVersionsPage.tsx +65 -28
  126. package/src/client/pages/automations/AutomationsPage.tsx +80 -68
  127. package/src/client/pages/automations/AutomationsPageLayout.tsx +46 -14
  128. package/src/client/pages/automations/README.md +57 -0
  129. package/src/client/pages/automations/TriggerFilters.tsx +22 -0
  130. package/src/client/pages/automations/automationFormat.ts +37 -23
  131. package/src/client/pages/code/CodeActivityChart.tsx +59 -101
  132. package/src/client/pages/code/CodePage.tsx +52 -55
  133. package/src/client/pages/code/RepositoryPage.tsx +6 -8
  134. package/src/client/pages/locations/LocationActivityChart.tsx +53 -70
  135. package/src/client/pages/locations/LocationDirectoryActivityChart.tsx +60 -80
  136. package/src/client/pages/memory/MemoryCostChart.tsx +79 -103
  137. package/src/client/pages/memory/MemoryDetails.tsx +218 -0
  138. package/src/client/pages/memory/MemoryPage.tsx +168 -112
  139. package/src/client/pages/memory/MemoryPageLayout.tsx +33 -9
  140. package/src/client/pages/memory/MemoryTimeline.tsx +46 -69
  141. package/src/client/pages/memory/memoryRecord.ts +1 -1
  142. package/src/client/pages/people/ContributionGrid.tsx +23 -16
  143. package/src/client/pages/people/PeopleActivityChart.tsx +68 -107
  144. package/src/client/pages/people/ProfileCodeChanges.tsx +5 -5
  145. package/src/client/pages/system/BaseSandboxCard.tsx +1 -1
  146. package/src/client/pages/system/ConversationActivityChart.tsx +49 -67
  147. package/src/client/pages/system/GuardianActivity.tsx +1 -2
  148. package/src/client/pages/system/PluginBarChart.tsx +106 -87
  149. package/src/client/pages/system/PluginReportRecordSet.tsx +25 -24
  150. package/src/client/pages/system/README.md +23 -0
  151. package/src/client/pages/system/SystemNavigation.tsx +5 -1
  152. package/src/client/pages/system/WorkspaceDeleteDialog.tsx +111 -0
  153. package/src/client/pages/system/WorkspaceDetails.tsx +200 -43
  154. package/src/client/pages/system/WorkspaceEditor.tsx +59 -61
  155. package/src/client/pages/system/WorkspaceFormPage.tsx +23 -47
  156. package/src/client/pages/system/WorkspaceList.tsx +20 -30
  157. package/src/client/pages/system/WorkspacesPage.tsx +117 -119
  158. package/src/client/pages/system/sandboxSize.ts +8 -0
  159. package/src/client/pages/system/workspaceQueries.ts +53 -0
  160. package/src/client/pages/user/PluginUserPage.tsx +51 -2
  161. package/src/client/pages/user/pluginUserPageData.ts +1 -6
  162. package/src/client/styles.ts +4 -0
  163. package/src/client.tsx +7 -1
  164. package/src/mock-reporting/automation-collection.ts +4 -2
  165. package/src/mock-reporting/fixtures.ts +23 -7
  166. package/src/mock-reporting/routes.ts +12 -0
  167. package/src/mock-reporting/watches.ts +94 -0
  168. package/src/shell.ts +4 -9
  169. package/src/tailwind.css +0 -10
  170. package/src/client/components/Button.tsx +0 -118
  171. package/src/client/components/Field.tsx +0 -53
  172. package/src/client/components/SegmentedTabs.tsx +0 -119
  173. package/src/client/components/Select.tsx +0 -52
  174. package/src/client/components/Skeleton.tsx +0 -17
  175. package/src/client/components/TextInput.tsx +0 -58
  176. package/src/client/components/Tooltip.tsx +0 -216
  177. package/src/client/components/charts/useChartLayout.ts +0 -27
  178. package/src/client/components/ui/sheet.LICENSE +0 -21
  179. package/src/client/conversations/ConversationDetailsFrame.tsx +0 -119
  180. package/src/client/conversations/CopyMarkdownButton.tsx +0 -65
  181. package/src/client/pages/automations/AutomationDetailsDrawer.tsx +0 -219
  182. package/src/client/pages/dev/ChartFixtures.tsx +0 -325
  183. package/src/client/pages/dev/ComponentsPage.tsx +0 -899
  184. package/src/client/pages/dev/ConversationFixture.tsx +0 -231
  185. package/src/client/pages/dev/ObjectFixtures.tsx +0 -138
  186. package/src/client/pages/dev/SegmentedFixtures.tsx +0 -59
  187. package/src/client/pages/dev/SelectFixtures.tsx +0 -62
  188. package/src/client/pages/dev/TranscriptActivityFixtures.tsx +0 -206
  189. package/src/client/pages/memory/MemoryDetailsDrawer.tsx +0 -167
  190. package/src/client/pages/system/SandboxSizeSummary.tsx +0 -47
  191. package/src/client/pages/system/SnapshotSummary.tsx +0 -78
@@ -1,216 +0,0 @@
1
- import * as HoverCard from "@radix-ui/react-hover-card";
2
- import {
3
- type ReactElement,
4
- type ReactNode,
5
- type RefObject,
6
- useEffect,
7
- useId,
8
- useRef,
9
- useState,
10
- } from "react";
11
-
12
- import { cn } from "../styles";
13
-
14
- type TooltipProps = {
15
- align?: "center" | "left" | "right";
16
- children: ReactElement;
17
- className?: string;
18
- content: ReactNode;
19
- focusable?: boolean;
20
- label?: ReactNode;
21
- placement?: "above" | "below";
22
- triggerClassName?: string;
23
- };
24
-
25
- const VIEWPORT_GAP = 8;
26
- const ANCHOR_GAP = 10;
27
- const CLOSE_DELAY_MS = 150;
28
- const TOOLTIP_MEDIA_QUERY =
29
- "(min-width: 768px) and (hover: hover) and (pointer: fine)";
30
-
31
- /** SVG hosts cannot wrap triggers in an HTML span without collapsing geometry. */
32
- const SVG_TRIGGER_TAGS = new Set([
33
- "circle",
34
- "ellipse",
35
- "g",
36
- "line",
37
- "path",
38
- "polygon",
39
- "polyline",
40
- "rect",
41
- "svg",
42
- "text",
43
- ]);
44
-
45
- function isSvgTrigger(element: ReactElement): boolean {
46
- return typeof element.type === "string" && SVG_TRIGGER_TAGS.has(element.type);
47
- }
48
-
49
- /** Show selectable dashboard details beside an element. */
50
- export function Tooltip({
51
- align = "center",
52
- children,
53
- className,
54
- content,
55
- focusable = false,
56
- label,
57
- placement = "above",
58
- triggerClassName,
59
- }: TooltipProps) {
60
- const tooltipId = useId();
61
- const rootRef = useRef<Element | null>(null);
62
- const touchStartedOpenRef = useRef<boolean | null>(null);
63
- const suppressOpenUntilRef = useRef(0);
64
- const [open, setOpen] = useState(false);
65
- const [available, setAvailable] = useState(() =>
66
- typeof window === "undefined"
67
- ? false
68
- : window.matchMedia(TOOLTIP_MEDIA_QUERY).matches,
69
- );
70
- const svgTrigger = isSvgTrigger(children);
71
-
72
- useEffect(() => {
73
- const media = window.matchMedia(TOOLTIP_MEDIA_QUERY);
74
- const syncAvailable = () => {
75
- setAvailable(media.matches);
76
- if (!media.matches) setOpen(false);
77
- };
78
- syncAvailable();
79
- media.addEventListener("change", syncAvailable);
80
- return () => media.removeEventListener("change", syncAvailable);
81
- }, []);
82
-
83
- useEffect(() => {
84
- if (!open) return;
85
- const onPointerDown = (event: PointerEvent) => {
86
- const root = rootRef.current;
87
- const target = event.target;
88
- if (!(target instanceof Node)) return;
89
- // Keep interactions inside the trigger or content from auto-closing.
90
- if (root?.contains(target)) return;
91
- const contentNode = document.getElementById(tooltipId);
92
- if (contentNode?.contains(target)) return;
93
- suppressOpenUntilRef.current = performance.now() + CLOSE_DELAY_MS + 50;
94
- setOpen(false);
95
- };
96
- document.addEventListener("pointerdown", onPointerDown, true);
97
- return () => {
98
- document.removeEventListener("pointerdown", onPointerDown, true);
99
- };
100
- }, [open, tooltipId]);
101
-
102
- // Keep chart/layout wrappers even when tooltips are off.
103
- if (!available) {
104
- if (svgTrigger) return children;
105
- return (
106
- <span className={cn("inline-flex", triggerClassName)}>{children}</span>
107
- );
108
- }
109
-
110
- const card = (
111
- <HoverCard.Root
112
- closeDelay={CLOSE_DELAY_MS}
113
- onOpenChange={(nextOpen) => {
114
- // Touch toggle/outside close can race HoverCard's hover reopen.
115
- if (nextOpen && performance.now() < suppressOpenUntilRef.current) {
116
- return;
117
- }
118
- setOpen(nextOpen);
119
- }}
120
- open={open}
121
- openDelay={0}
122
- >
123
- <HoverCard.Trigger
124
- aria-describedby={open ? tooltipId : undefined}
125
- asChild
126
- onBlur={focusable ? () => setOpen(false) : undefined}
127
- onFocus={focusable ? () => setOpen(true) : undefined}
128
- onPointerCancel={() => {
129
- touchStartedOpenRef.current = null;
130
- }}
131
- onPointerDown={(event) => {
132
- if (event.pointerType === "touch") {
133
- touchStartedOpenRef.current = open;
134
- }
135
- }}
136
- onPointerUp={(event) => {
137
- if (
138
- event.pointerType !== "touch" ||
139
- touchStartedOpenRef.current === null
140
- ) {
141
- return;
142
- }
143
- const nextOpen = !touchStartedOpenRef.current;
144
- if (!nextOpen) {
145
- suppressOpenUntilRef.current =
146
- performance.now() + CLOSE_DELAY_MS + 50;
147
- }
148
- setOpen(nextOpen);
149
- touchStartedOpenRef.current = null;
150
- }}
151
- ref={
152
- svgTrigger
153
- ? (node) => {
154
- rootRef.current = node;
155
- }
156
- : undefined
157
- }
158
- >
159
- {children}
160
- </HoverCard.Trigger>
161
- <HoverCard.Portal>
162
- <HoverCard.Content
163
- align={
164
- align === "left" ? "start" : align === "right" ? "end" : "center"
165
- }
166
- className={cn(
167
- "z-50 select-text outline-none",
168
- className ??
169
- "min-w-36 max-w-lg rounded-md border border-dashboard-border-emphasis bg-dashboard-surface-raised px-3 py-2 font-sans text-xs leading-relaxed text-dashboard-text-muted shadow-2xl shadow-black/70",
170
- )}
171
- collisionPadding={VIEWPORT_GAP}
172
- hideWhenDetached
173
- id={tooltipId}
174
- role="tooltip"
175
- side={placement === "above" ? "top" : "bottom"}
176
- sideOffset={ANCHOR_GAP}
177
- >
178
- {label ? (
179
- <div className="mb-1 font-semibold text-dashboard-text-muted">
180
- {label}
181
- </div>
182
- ) : null}
183
- {content}
184
- </HoverCard.Content>
185
- </HoverCard.Portal>
186
- </HoverCard.Root>
187
- );
188
-
189
- if (svgTrigger) return card;
190
-
191
- return (
192
- <span
193
- className={cn("inline-flex", triggerClassName)}
194
- ref={rootRef as RefObject<HTMLSpanElement | null>}
195
- >
196
- {card}
197
- </span>
198
- );
199
- }
200
-
201
- /** Compact label tooltip for icon-only controls such as header actions. */
202
- export function IconButtonTooltip(props: {
203
- children: ReactElement;
204
- label: string;
205
- placement?: "above" | "below";
206
- }) {
207
- return (
208
- <Tooltip
209
- className="min-w-0 rounded-md border border-dashboard-border-emphasis bg-dashboard-surface-raised px-2.5 py-1.5 font-sans text-xs leading-none text-dashboard-text shadow-2xl shadow-black/70"
210
- content={props.label}
211
- placement={props.placement ?? "above"}
212
- >
213
- <span className="inline-flex">{props.children}</span>
214
- </Tooltip>
215
- );
216
- }
@@ -1,27 +0,0 @@
1
- import { useLayoutEffect, useRef, useState } from "react";
2
- import { createActivityChartLayout } from "./ActivityChart";
3
-
4
- /** Keep chart height and label spacing fixed as its container width changes. */
5
- export function useChartLayout(height: number, left = 64) {
6
- const ref = useRef<HTMLDivElement>(null);
7
- const [width, setWidth] = useState(400);
8
- useLayoutEffect(() => {
9
- const container = ref.current;
10
- if (!container) return;
11
- const observer = new ResizeObserver(([entry]) => {
12
- if (entry && entry.contentRect.width > 0)
13
- setWidth(entry.contentRect.width);
14
- });
15
- observer.observe(container);
16
- return () => observer.disconnect();
17
- }, []);
18
- return {
19
- ref,
20
- layout: createActivityChartLayout(height, {
21
- width,
22
- left,
23
- top: 18,
24
- bottom: 28,
25
- }),
26
- };
27
- }
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 shadcn
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,119 +0,0 @@
1
- import { X } from "lucide-react";
2
- import {
3
- useEffect,
4
- useId,
5
- useRef,
6
- useSyncExternalStore,
7
- type ReactNode,
8
- } from "react";
9
-
10
- import { Button } from "../components/Button";
11
- import { BottomSheet } from "../components/BottomSheet";
12
-
13
- const panelMediaQuery = "(min-width: 1280px)";
14
- // Open details by default only when the chat column stays wide beside them.
15
- const defaultOpenMediaQuery = "(min-width: 1440px)";
16
-
17
- function useMediaQuery(query: string): boolean {
18
- return useSyncExternalStore(
19
- (onChange) => {
20
- const media = window.matchMedia(query);
21
- media.addEventListener("change", onChange);
22
- return () => media.removeEventListener("change", onChange);
23
- },
24
- () => window.matchMedia(query).matches,
25
- () => false,
26
- );
27
- }
28
-
29
- /**
30
- * Report whether the viewport has room to show details as a panel beside the
31
- * chat. Narrower viewports show details in a bottom sheet.
32
- */
33
- export function useConversationDetailsPanel(): boolean {
34
- return useMediaQuery(panelMediaQuery);
35
- }
36
-
37
- /** Report whether details should open beside the chat without a request. */
38
- export function useConversationDetailsDefaultOpen(): boolean {
39
- return useMediaQuery(defaultOpenMediaQuery);
40
- }
41
-
42
- /** Keep details beside the chat when space permits, or in a modal bottom sheet. */
43
- export function ConversationDetailsFrame(props: {
44
- actions?: ReactNode;
45
- children: ReactNode;
46
- onClose(): void;
47
- }) {
48
- const titleId = useId();
49
- const panel = useConversationDetailsPanel();
50
- const closeRef = useRef<HTMLButtonElement>(null);
51
- const wasPanel = useRef(panel);
52
- useEffect(() => {
53
- if (
54
- panel &&
55
- !wasPanel.current &&
56
- !document.querySelector("[data-dashboard-modal]")
57
- ) {
58
- closeRef.current?.focus({ preventScroll: true });
59
- }
60
- wasPanel.current = panel;
61
- }, [panel]);
62
- if (!panel) {
63
- return (
64
- <BottomSheet label="Conversation details" onClose={props.onClose}>
65
- {props.children}
66
- {/* After the tabs, so the sheet still opens with the first tab focused. */}
67
- {props.actions ? (
68
- <div className="flex justify-end gap-1 border-t border-dashboard-border px-4 py-2">
69
- {props.actions}
70
- </div>
71
- ) : null}
72
- </BottomSheet>
73
- );
74
- }
75
-
76
- return (
77
- <aside
78
- aria-labelledby={titleId}
79
- className="grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] border-l border-dashboard-border bg-dashboard-bg-elevated"
80
- onKeyDown={(event) => {
81
- if (
82
- event.key === "Escape" &&
83
- !event.defaultPrevented &&
84
- !document.querySelector("[data-dashboard-modal]")
85
- ) {
86
- event.preventDefault();
87
- props.onClose();
88
- }
89
- }}
90
- >
91
- <header className="flex items-center justify-between gap-3 border-b border-dashboard-border py-2 pl-5 pr-3">
92
- {/* Keep actions beside the title. The panel opens under the pointer
93
- that clicked the chat's Details button, so that corner holds only
94
- Close. */}
95
- <div className="flex min-w-0 items-center gap-3">
96
- <h2
97
- aria-label="Conversation details"
98
- className="m-0 text-base font-semibold text-dashboard-text"
99
- id={titleId}
100
- >
101
- Details
102
- </h2>
103
- {props.actions ? (
104
- <div className="flex items-center gap-0.5">{props.actions}</div>
105
- ) : null}
106
- </div>
107
- <Button
108
- ref={closeRef}
109
- aria-label="Close conversation details"
110
- onClick={props.onClose}
111
- size="icon"
112
- >
113
- <X aria-hidden="true" size={15} />
114
- </Button>
115
- </header>
116
- <div className="min-h-0">{props.children}</div>
117
- </aside>
118
- );
119
- }
@@ -1,65 +0,0 @@
1
- import { useState } from "react";
2
- import { Check, Copy } from "lucide-react";
3
-
4
- import { Button } from "../components/Button";
5
- import { IconButtonTooltip } from "../components/Tooltip";
6
-
7
- /** Copy an available Markdown document while exposing clipboard result state. */
8
- export function CopyMarkdownButton(props: {
9
- getMarkdown?: () => Promise<string> | string;
10
- /** `menu` shows icon + label for mobile overflow lists. */
11
- layout?: "icon" | "menu";
12
- }) {
13
- const [status, setStatus] = useState<
14
- "copied" | "copying" | "failed" | "idle"
15
- >("idle");
16
- const label =
17
- status === "copied"
18
- ? "Copied"
19
- : status === "copying"
20
- ? "Preparing Markdown"
21
- : status === "failed"
22
- ? "Copy failed"
23
- : "Copy as Markdown";
24
- const Icon = status === "copied" ? Check : Copy;
25
-
26
- async function copyMarkdown() {
27
- if (!props.getMarkdown) return;
28
-
29
- try {
30
- setStatus("copying");
31
- await navigator.clipboard.writeText(await props.getMarkdown());
32
- setStatus("copied");
33
- } catch {
34
- setStatus("failed");
35
- }
36
- }
37
-
38
- if (props.layout === "menu") {
39
- return (
40
- <button
41
- aria-label={label}
42
- className="flex w-full cursor-pointer items-center gap-2.5 rounded-md border-0 bg-transparent px-2.5 py-2 text-left text-sm font-semibold text-dashboard-text transition-colors hover:bg-dashboard-fill-strong focus-visible:bg-dashboard-fill-strong focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
43
- disabled={!props.getMarkdown || status === "copying"}
44
- onClick={() => void copyMarkdown()}
45
- type="button"
46
- >
47
- <Icon aria-hidden="true" size={16} strokeWidth={2} />
48
- <span>{label}</span>
49
- </button>
50
- );
51
- }
52
-
53
- return (
54
- <IconButtonTooltip label={label}>
55
- <Button
56
- aria-label={label}
57
- disabled={!props.getMarkdown || status === "copying"}
58
- onClick={() => void copyMarkdown()}
59
- size="icon"
60
- >
61
- <Icon aria-hidden="true" size={15} strokeWidth={2} />
62
- </Button>
63
- </IconButtonTooltip>
64
- );
65
- }
@@ -1,219 +0,0 @@
1
- import { AutomationLifecycle } from "./AutomationLifecycle";
2
- import { automationOutcomeLabel } from "./automationOutcomes";
3
- import {
4
- automationEventConditions,
5
- automationEventNames,
6
- automationScheduleLabel,
7
- formatAutomationRunTime,
8
- } from "./automationFormat";
9
- import type { AutomationSummary } from "@sentry/junior/api/schema";
10
- import { Link, useLocation } from "react-router";
11
- import { Detail, DetailList } from "../../components/DetailList";
12
- import { Drawer } from "../../components/Drawer";
13
- import {
14
- timeRangeLabel,
15
- type TimeRangeDays,
16
- } from "../../components/controls/TimeRangeSelector";
17
- import { conversationPath } from "../../conversations/conversationRoutes";
18
- import { formatTime, peoplePath } from "../../format";
19
- import { TranscriptText } from "../../conversations/TranscriptText";
20
- import { cn, dashboardInlineLinkClass } from "../../styles";
21
-
22
- /** Show one automation's instruction and metadata in a right-side slide-out. */
23
- export function AutomationDetailsDrawer(props: {
24
- onClose(): void;
25
- range: TimeRangeDays;
26
- automation: AutomationSummary | undefined;
27
- }) {
28
- const location = useLocation();
29
- if (!props.automation) return null;
30
-
31
- const { automation } = props;
32
- const createdBy = automation.createdByEmail ? (
33
- <Link
34
- className={cn("font-semibold", dashboardInlineLinkClass)}
35
- to={peoplePath(automation.createdByEmail)}
36
- >
37
- {automation.ownedByViewer ? "you" : automation.createdBy}
38
- </Link>
39
- ) : automation.ownedByViewer ? (
40
- "you"
41
- ) : (
42
- automation.createdBy
43
- );
44
- const conditions =
45
- automation.kind === "event" ? automationEventConditions(automation) : "";
46
- const details =
47
- automation.kind === "scheduled"
48
- ? [
49
- { label: "Schedule", value: automationScheduleLabel(automation) },
50
- {
51
- label: "Next run",
52
- value:
53
- automation.status === "active" && automation.nextRunAt
54
- ? formatAutomationRunTime(automation.nextRunAt)
55
- : "None",
56
- },
57
- ]
58
- : [
59
- { label: "Watching", value: automation.resource },
60
- { label: "Events", value: automationEventNames(automation) },
61
- ...(conditions ? [{ label: "Conditions", value: conditions }] : []),
62
- ];
63
- const titleId = "automation-details-drawer-title";
64
-
65
- return (
66
- <Drawer
67
- closeLabel="Close automation details"
68
- header={
69
- <>
70
- <h2
71
- className="m-0 font-display text-lg font-medium tracking-normal text-dashboard-text"
72
- id={titleId}
73
- >
74
- {automation.title}
75
- </h2>
76
- <div className="mt-1 break-words text-sm text-dashboard-text-muted">
77
- Created by {createdBy} · {automation.destination.label}
78
- </div>
79
- </>
80
- }
81
- onClose={props.onClose}
82
- openKey={`${automation.kind}:${automation.id}`}
83
- titleId={titleId}
84
- >
85
- <section className="grid gap-5">
86
- <div>
87
- <h3 className="mt-0 mb-2 text-sm font-medium text-dashboard-text-muted">
88
- What to do
89
- </h3>
90
- <TranscriptText
91
- mentions={automation.mentions ?? []}
92
- text={automation.instruction}
93
- />
94
- </div>
95
- <AutomationLifecycle
96
- key={`${automation.kind}:${automation.id}`}
97
- automation={automation}
98
- editPath={`/automations/${encodeURIComponent(automation.id)}/edit${location.search}`}
99
- />
100
- <DetailList>
101
- {details.map((detail) => (
102
- <Detail key={detail.label} label={detail.label}>
103
- {detail.value}
104
- </Detail>
105
- ))}
106
- <Detail label="On success">
107
- {automation.outcomes.length === 0 ? (
108
- "No message on success"
109
- ) : (
110
- <ol className="m-0 pl-4">
111
- {automation.outcomes.map((outcome, index) => (
112
- <li key={index}>
113
- {automationOutcomeLabel(outcome, automation.destination)}
114
- </li>
115
- ))}
116
- </ol>
117
- )}
118
- </Detail>
119
- </DetailList>
120
- <div className="flex flex-wrap gap-x-5">
121
- {automation.totalRuns > 0 &&
122
- automation.lastRunStatus !== "failed" &&
123
- automation.lastRunStatus !== "blocked" ? (
124
- <Link
125
- className="w-fit py-2 text-sm text-dashboard-text underline underline-offset-2"
126
- to={`/automations/${encodeURIComponent(automation.id)}/executions`}
127
- >
128
- View all runs
129
- </Link>
130
- ) : null}
131
- <Link
132
- className="w-fit py-2 text-sm text-dashboard-text underline underline-offset-2"
133
- to={`/automations/${encodeURIComponent(automation.id)}/versions${location.search}`}
134
- >
135
- Version history
136
- </Link>
137
- </div>
138
- <details>
139
- <summary className="cursor-pointer py-3 text-sm text-dashboard-text-muted hover:text-dashboard-text focus-visible:outline focus-visible:outline-dashboard-focus">
140
- More details
141
- </summary>
142
- <DetailList>
143
- <Detail label="Connected accounts">
144
- {automation.credentialMode === "creator" ? (
145
- <>
146
- Uses accounts connected by{" "}
147
- {automation.ownedByViewer ? "you" : automation.createdBy}.
148
- </>
149
- ) : (
150
- "Junior’s accounts only"
151
- )}
152
- </Detail>
153
- <Detail label="Created">{formatDate(automation.createdAt)}</Detail>
154
- <Detail label="Run history">
155
- <AutomationExecutionSummary
156
- range={props.range}
157
- automation={automation}
158
- />
159
- </Detail>
160
- </DetailList>
161
- </details>
162
- </section>
163
- </Drawer>
164
- );
165
- }
166
-
167
- function AutomationExecutionSummary(props: {
168
- range: TimeRangeDays;
169
- automation: Pick<
170
- AutomationSummary,
171
- "id" | "kind" | "lastConversationId" | "lastRunAt" | "runs" | "totalRuns"
172
- >;
173
- }) {
174
- const { range, automation } = props;
175
- const runCount = automation.runs[range];
176
- const rangeLabel = timeRangeLabel(range);
177
- const executionsPath = `/automations/${encodeURIComponent(automation.id)}/executions`;
178
- return (
179
- <div className="text-sm text-dashboard-text-muted">
180
- {automation.totalRuns > 0 ? (
181
- <Link className={dashboardInlineLinkClass} to={executionsPath}>
182
- {runCount} runs / {rangeLabel}
183
- </Link>
184
- ) : (
185
- <span className="text-dashboard-text">
186
- {runCount} runs / {rangeLabel}
187
- </span>
188
- )}
189
- <span className="mx-2 opacity-45">·</span>
190
- <span>
191
- {automation.totalRuns > 0 ? (
192
- <Link className={dashboardInlineLinkClass} to={executionsPath}>
193
- {automation.totalRuns} total
194
- </Link>
195
- ) : (
196
- <>{automation.totalRuns} total</>
197
- )}
198
- {automation.lastRunAt ? " · Last run " : " · Never run"}
199
- {automation.lastRunAt && automation.lastConversationId ? (
200
- <Link
201
- className={dashboardInlineLinkClass}
202
- to={conversationPath(automation.lastConversationId)}
203
- >
204
- {formatAutomationRunTime(automation.lastRunAt)}
205
- </Link>
206
- ) : automation.lastRunAt ? (
207
- formatAutomationRunTime(automation.lastRunAt)
208
- ) : null}
209
- </span>
210
- </div>
211
- );
212
- }
213
-
214
- function formatDate(value: string): string {
215
- return formatTime(value, {
216
- dateStyle: "medium",
217
- timeStyle: "short",
218
- });
219
- }