@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
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// Adapted from shadcn/ui's Base UI Sheet. Drawer adds native swipe support.
|
|
2
|
-
// https://ui.shadcn.com/docs/components/base/sheet
|
|
3
|
-
import { Drawer as SheetPrimitive } from "@base-ui/react/drawer";
|
|
4
|
-
import {
|
|
5
|
-
createContext,
|
|
6
|
-
useContext,
|
|
7
|
-
useEffect,
|
|
8
|
-
useRef,
|
|
9
|
-
useState,
|
|
10
|
-
useSyncExternalStore,
|
|
11
|
-
type ReactNode,
|
|
12
|
-
type RefObject,
|
|
13
|
-
} from "react";
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
acquireBodyScrollLock,
|
|
17
|
-
releaseBodyScrollLock,
|
|
18
|
-
} from "../bodyScrollLock";
|
|
19
|
-
import { cn } from "../styles";
|
|
20
|
-
|
|
21
|
-
const SheetContext = createContext<{
|
|
22
|
-
closingRef: RefObject<boolean>;
|
|
23
|
-
popupRef: RefObject<HTMLDivElement | null>;
|
|
24
|
-
topmost: boolean;
|
|
25
|
-
} | null>(null);
|
|
26
|
-
|
|
27
|
-
// Separate roots can overlap when a resize mounts a sheet below an open drawer.
|
|
28
|
-
// Only the last modal in document order may trap focus or handle dismissal.
|
|
29
|
-
function subscribeToSheets(onChange: () => void) {
|
|
30
|
-
const observer = new MutationObserver(onChange);
|
|
31
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
32
|
-
return () => observer.disconnect();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Keep modal state until Base UI finishes its close transition. */
|
|
36
|
-
export function Sheet(props: {
|
|
37
|
-
children: ReactNode;
|
|
38
|
-
onClose(): void;
|
|
39
|
-
side: "right" | "bottom";
|
|
40
|
-
}) {
|
|
41
|
-
const [open, setOpen] = useState(false);
|
|
42
|
-
const closingRef = useRef(false);
|
|
43
|
-
const popupRef = useRef<HTMLDivElement>(null);
|
|
44
|
-
const topmost = useSyncExternalStore(
|
|
45
|
-
subscribeToSheets,
|
|
46
|
-
() => {
|
|
47
|
-
const modals = document.querySelectorAll("[data-dashboard-modal]");
|
|
48
|
-
const last = modals[modals.length - 1];
|
|
49
|
-
return popupRef.current ? last === popupRef.current : !last;
|
|
50
|
-
},
|
|
51
|
-
() => true,
|
|
52
|
-
);
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
// The mobile conversation shell uses this same document scroll lock.
|
|
55
|
-
acquireBodyScrollLock();
|
|
56
|
-
setOpen(true);
|
|
57
|
-
return releaseBodyScrollLock;
|
|
58
|
-
}, []);
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<SheetContext value={{ closingRef, popupRef, topmost }}>
|
|
62
|
-
<SheetPrimitive.Root
|
|
63
|
-
open={open}
|
|
64
|
-
modal={topmost ? "trap-focus" : false}
|
|
65
|
-
disablePointerDismissal={!topmost}
|
|
66
|
-
swipeDirection={props.side === "bottom" ? "down" : "right"}
|
|
67
|
-
onOpenChange={(open, details) => {
|
|
68
|
-
if (!open && !topmost) {
|
|
69
|
-
details.cancel();
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
closingRef.current = !open;
|
|
73
|
-
setOpen(open);
|
|
74
|
-
}}
|
|
75
|
-
onOpenChangeComplete={(open) => {
|
|
76
|
-
if (!open && closingRef.current) props.onClose();
|
|
77
|
-
}}
|
|
78
|
-
>
|
|
79
|
-
{props.children}
|
|
80
|
-
</SheetPrimitive.Root>
|
|
81
|
-
</SheetContext>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Close the enclosing sheet through its transition and focus lifecycle. */
|
|
86
|
-
export const SheetClose = SheetPrimitive.Close;
|
|
87
|
-
|
|
88
|
-
/** Render sheet chrome, a fading backdrop, and Base UI's focus boundary. */
|
|
89
|
-
export function SheetContent(
|
|
90
|
-
props: {
|
|
91
|
-
children: ReactNode;
|
|
92
|
-
className?: string;
|
|
93
|
-
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
94
|
-
side: "right" | "bottom";
|
|
95
|
-
} & ({ label: string; titleId?: never } | { label?: never; titleId: string }),
|
|
96
|
-
) {
|
|
97
|
-
const sheet = useContext(SheetContext);
|
|
98
|
-
const popupRef = sheet?.popupRef;
|
|
99
|
-
const portalRef = useRef<HTMLDivElement>(null);
|
|
100
|
-
return (
|
|
101
|
-
<>
|
|
102
|
-
<div ref={portalRef} className="contents" />
|
|
103
|
-
<SheetPrimitive.Portal container={portalRef}>
|
|
104
|
-
<SheetPrimitive.Backdrop className="fixed inset-0 z-50 bg-dashboard-overlay opacity-[calc(1-var(--drawer-swipe-progress,0))] transition-opacity duration-180 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[swiping]:transition-none motion-reduce:transition-none" />
|
|
105
|
-
<SheetPrimitive.Viewport className="pointer-events-none fixed inset-0 z-50">
|
|
106
|
-
<SheetPrimitive.Popup
|
|
107
|
-
ref={popupRef}
|
|
108
|
-
aria-modal={sheet?.topmost ? true : undefined}
|
|
109
|
-
aria-label={props.label}
|
|
110
|
-
aria-labelledby={props.titleId}
|
|
111
|
-
data-dashboard-modal=""
|
|
112
|
-
data-base-ui-swipe-ignore={props.side === "right" ? "" : undefined}
|
|
113
|
-
finalFocus={() =>
|
|
114
|
-
Boolean(sheet?.closingRef.current && sheet.topmost)
|
|
115
|
-
}
|
|
116
|
-
initialFocus={() => {
|
|
117
|
-
// A resize can mount a sheet beneath an existing event dialog.
|
|
118
|
-
const modals = document.querySelectorAll(
|
|
119
|
-
"[data-dashboard-modal]",
|
|
120
|
-
);
|
|
121
|
-
if (modals[modals.length - 1] !== popupRef?.current) return false;
|
|
122
|
-
return props.initialFocusRef?.current ?? true;
|
|
123
|
-
}}
|
|
124
|
-
className={cn(
|
|
125
|
-
"pointer-events-auto grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] bg-dashboard-bg-elevated outline-none transition-[opacity,transform] duration-180 ease-[cubic-bezier(0.2,0.8,0.2,1)] data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 data-[swiping]:transition-none motion-reduce:transition-none",
|
|
126
|
-
props.side === "bottom"
|
|
127
|
-
? "absolute inset-x-0 bottom-0 mx-auto h-[85dvh] max-h-full w-full max-w-2xl [transform:translateY(var(--drawer-swipe-movement-y,0px))] rounded-t-2xl border border-b-0 border-dashboard-border-strong shadow-[0_-12px_40px_rgba(0,0,0,0.25)] data-[ending-style]:[transform:translateY(100%)] data-[starting-style]:[transform:translateY(2.5rem)]"
|
|
128
|
-
: "absolute right-0 top-0 h-full w-full shadow-[-12px_0_40px_rgba(0,0,0,0.25)] data-[ending-style]:[transform:translateX(2.5rem)] data-[starting-style]:[transform:translateX(2.5rem)] md:border-l md:border-dashboard-border-strong",
|
|
129
|
-
props.className,
|
|
130
|
-
)}
|
|
131
|
-
>
|
|
132
|
-
{props.children}
|
|
133
|
-
</SheetPrimitive.Popup>
|
|
134
|
-
</SheetPrimitive.Viewport>
|
|
135
|
-
</SheetPrimitive.Portal>
|
|
136
|
-
</>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** Keep text selection and scrolling separate from the swipe handle. */
|
|
141
|
-
export const SheetBody = SheetPrimitive.Content;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { ElementType } from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../styles";
|
|
4
|
-
|
|
5
|
-
/** Hide one content placeholder from assistive technology. */
|
|
6
|
-
export function Skeleton(props: { as?: ElementType; className?: string }) {
|
|
7
|
-
const Component = props.as ?? "div";
|
|
8
|
-
return (
|
|
9
|
-
<Component
|
|
10
|
-
aria-hidden="true"
|
|
11
|
-
className={cn(
|
|
12
|
-
"animate-pulse rounded-md bg-dashboard-fill-soft motion-reduce:animate-none",
|
|
13
|
-
props.className,
|
|
14
|
-
)}
|
|
15
|
-
/>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { InputHTMLAttributes, TextareaHTMLAttributes } from "react";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../styles";
|
|
4
|
-
|
|
5
|
-
const textControlClassName =
|
|
6
|
-
"block w-full rounded-md border border-dashboard-border-emphasis bg-dashboard-ink px-3 py-2 font-sans text-sm text-dashboard-text placeholder:text-dashboard-text-muted/70 focus:border-dashboard-focus focus:outline-none focus:ring-1 focus:ring-dashboard-focus/25 aria-invalid:border-rose-300 disabled:cursor-not-allowed disabled:opacity-50";
|
|
7
|
-
|
|
8
|
-
export type TextInputProps = Omit<
|
|
9
|
-
InputHTMLAttributes<HTMLInputElement>,
|
|
10
|
-
"size"
|
|
11
|
-
> & {
|
|
12
|
-
className?: string;
|
|
13
|
-
size?: "default" | "comfortable";
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/** Render the dashboard's standard single-line text control. */
|
|
17
|
-
export function TextInput({
|
|
18
|
-
className,
|
|
19
|
-
type = "text",
|
|
20
|
-
size = "default",
|
|
21
|
-
...props
|
|
22
|
-
}: TextInputProps) {
|
|
23
|
-
return (
|
|
24
|
-
<input
|
|
25
|
-
{...props}
|
|
26
|
-
className={cn(
|
|
27
|
-
textControlClassName,
|
|
28
|
-
size === "comfortable" && "min-h-11 text-base sm:min-h-10 sm:text-sm",
|
|
29
|
-
className,
|
|
30
|
-
)}
|
|
31
|
-
type={type}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type TextAreaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|
37
|
-
prose?: boolean;
|
|
38
|
-
className?: string;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/** Render the dashboard's standard multi-line text control. */
|
|
42
|
-
export function TextArea({
|
|
43
|
-
className,
|
|
44
|
-
prose = false,
|
|
45
|
-
...props
|
|
46
|
-
}: TextAreaProps) {
|
|
47
|
-
return (
|
|
48
|
-
<textarea
|
|
49
|
-
{...props}
|
|
50
|
-
className={cn(
|
|
51
|
-
textControlClassName,
|
|
52
|
-
"min-h-36",
|
|
53
|
-
prose ? "font-sans leading-relaxed" : "font-mono",
|
|
54
|
-
className,
|
|
55
|
-
)}
|
|
56
|
-
/>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
@@ -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,91 +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
|
-
|
|
15
|
-
function subscribeToPanelLayout(onChange: () => void) {
|
|
16
|
-
const media = window.matchMedia(panelMediaQuery);
|
|
17
|
-
media.addEventListener("change", onChange);
|
|
18
|
-
return () => media.removeEventListener("change", onChange);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function hasPanelSpace() {
|
|
22
|
-
return window.matchMedia(panelMediaQuery).matches;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Keep details beside the chat when space permits, or in a modal bottom sheet. */
|
|
26
|
-
export function ConversationDetailsFrame(props: {
|
|
27
|
-
children: ReactNode;
|
|
28
|
-
onClose(): void;
|
|
29
|
-
}) {
|
|
30
|
-
const titleId = useId();
|
|
31
|
-
const panel = useSyncExternalStore(
|
|
32
|
-
subscribeToPanelLayout,
|
|
33
|
-
hasPanelSpace,
|
|
34
|
-
() => false,
|
|
35
|
-
);
|
|
36
|
-
const closeRef = useRef<HTMLButtonElement>(null);
|
|
37
|
-
const wasPanel = useRef(panel);
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (
|
|
40
|
-
panel &&
|
|
41
|
-
!wasPanel.current &&
|
|
42
|
-
!document.querySelector("[data-dashboard-modal]")
|
|
43
|
-
) {
|
|
44
|
-
closeRef.current?.focus({ preventScroll: true });
|
|
45
|
-
}
|
|
46
|
-
wasPanel.current = panel;
|
|
47
|
-
}, [panel]);
|
|
48
|
-
if (!panel) {
|
|
49
|
-
return (
|
|
50
|
-
<BottomSheet label="Conversation details" onClose={props.onClose}>
|
|
51
|
-
{props.children}
|
|
52
|
-
</BottomSheet>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<aside
|
|
58
|
-
aria-labelledby={titleId}
|
|
59
|
-
className="grid min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] border-l border-dashboard-border bg-dashboard-bg-elevated"
|
|
60
|
-
onKeyDown={(event) => {
|
|
61
|
-
if (
|
|
62
|
-
event.key === "Escape" &&
|
|
63
|
-
!event.defaultPrevented &&
|
|
64
|
-
!document.querySelector("[data-dashboard-modal]")
|
|
65
|
-
) {
|
|
66
|
-
event.preventDefault();
|
|
67
|
-
props.onClose();
|
|
68
|
-
}
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
<header className="flex items-center justify-between gap-3 border-b border-dashboard-border px-5 py-3">
|
|
72
|
-
<h2
|
|
73
|
-
aria-label="Conversation details"
|
|
74
|
-
className="m-0 text-base font-semibold text-dashboard-text"
|
|
75
|
-
id={titleId}
|
|
76
|
-
>
|
|
77
|
-
Details
|
|
78
|
-
</h2>
|
|
79
|
-
<Button
|
|
80
|
-
ref={closeRef}
|
|
81
|
-
aria-label="Close conversation details"
|
|
82
|
-
onClick={props.onClose}
|
|
83
|
-
size="icon"
|
|
84
|
-
>
|
|
85
|
-
<X aria-hidden="true" size={15} />
|
|
86
|
-
</Button>
|
|
87
|
-
</header>
|
|
88
|
-
<div className="min-h-0">{props.children}</div>
|
|
89
|
-
</aside>
|
|
90
|
-
);
|
|
91
|
-
}
|
|
@@ -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
|
-
}
|