@sentry/junior-dashboard 0.274.0 → 0.276.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +28 -9
- package/dist/client.js +115 -92
- package/dist/index.js +28 -9
- package/dist/tailwind.css +1 -1
- package/package.json +5 -4
- package/src/app.ts +8 -3
- package/src/client/App.tsx +11 -0
- package/src/client/components/Metric.tsx +0 -32
- package/src/client/components/ProfileMenu.tsx +56 -4
- package/src/client/components/layout/DashboardChrome.tsx +3 -3
- package/src/client/components/layout/DashboardHeader.tsx +41 -4
- package/src/client/components/layout/NavRail.tsx +43 -40
- package/src/client/conversations/ConversationComposer.tsx +42 -26
- package/src/client/conversations/ConversationDetails.tsx +149 -166
- package/src/client/conversations/ConversationHeader.tsx +126 -132
- package/src/client/conversations/ConversationHeaderActions.tsx +92 -120
- package/src/client/conversations/ConversationListStatusIcon.tsx +16 -11
- package/src/client/conversations/ConversationMemories.tsx +118 -78
- package/src/client/conversations/ConversationMeta.tsx +94 -84
- package/src/client/conversations/ConversationOverflowMenu.tsx +59 -0
- package/src/client/conversations/ConversationPage.tsx +197 -422
- package/src/client/conversations/ConversationReplyFooter.tsx +342 -0
- package/src/client/conversations/ConversationSidebar.tsx +5 -7
- package/src/client/conversations/ConversationTranscript.tsx +37 -8
- package/src/client/conversations/ConversationWatches.tsx +37 -15
- package/src/client/conversations/PendingMailboxStack.tsx +11 -1
- package/src/client/conversations/QuickQuestion.tsx +476 -0
- package/src/client/conversations/TelemetryMetrics.tsx +2 -15
- package/src/client/conversations/TranscriptActivityGroup.tsx +5 -0
- package/src/client/conversations/TranscriptEventView.tsx +25 -2
- package/src/client/conversations/TranscriptMemoryPopover.tsx +179 -0
- package/src/client/conversations/TranscriptMessageView.tsx +46 -16
- package/src/client/conversations/TranscriptRows.tsx +9 -2
- package/src/client/conversations/TranscriptView.tsx +15 -32
- package/src/client/conversations/UsageCard.tsx +4 -18
- package/src/client/conversations/conversationMemory.ts +138 -0
- package/src/client/conversations/conversationSections.ts +18 -14
- package/src/client/conversations/eventTranscript.ts +8 -0
- package/src/client/review-desk/ConversationReviewItems.tsx +99 -0
- package/src/client/review-desk/ReviewItemActions.tsx +1 -1
- package/src/client/review-desk/ReviewItemChangeRow.tsx +85 -0
- package/src/client/review-desk/ReviewItemContext.tsx +41 -23
- package/src/client/review-desk/reviewItemFormat.ts +16 -0
- package/src/client/sentry.ts +77 -0
- package/src/client/types.ts +6 -0
- package/src/client.tsx +3 -0
- package/src/mock-reporting/watches.ts +2 -0
- package/src/shell.ts +27 -2
- package/src/tailwind.css +12 -19
- package/src/client/components/ActiveIndicator.tsx +0 -14
- package/src/client/conversations/ConversationHeaderMeta.tsx +0 -20
- package/src/client/review-desk/ConversationReviewBar.tsx +0 -85
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.276.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@base-ui/react": "^1.9.0",
|
|
26
|
+
"@sentry/browser": "11.10.0",
|
|
26
27
|
"@tanstack/react-query": "^5.100.14",
|
|
27
28
|
"@uidotdev/usehooks": "2.4.1",
|
|
28
29
|
"class-variance-authority": "^0.7.1",
|
|
@@ -39,10 +40,10 @@
|
|
|
39
40
|
"remark-gfm": "^4.0.1",
|
|
40
41
|
"shiki": "4.1.0",
|
|
41
42
|
"zod": "^4.5.4",
|
|
42
|
-
"@sentry/junior-plugin-api": "0.
|
|
43
|
+
"@sentry/junior-plugin-api": "0.276.0"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
|
-
"@sentry/junior": "0.
|
|
46
|
+
"@sentry/junior": "0.276.0"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@oxlint/plugins": "1.79.0",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"tw-animate-css": "^1.4.0",
|
|
64
65
|
"typescript": "^7.0.2",
|
|
65
66
|
"vitest": "^4.1.12",
|
|
66
|
-
"@sentry/junior": "0.
|
|
67
|
+
"@sentry/junior": "0.276.0"
|
|
67
68
|
},
|
|
68
69
|
"scripts": {
|
|
69
70
|
"build": "pnpm --filter @sentry/junior-plugin-api build && pnpm --filter @sentry/junior build && pnpm run build:client && pnpm run build:css && pnpm run build:server",
|
package/src/app.ts
CHANGED
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
type JuniorApiVariables,
|
|
12
12
|
} from "@sentry/junior/api";
|
|
13
13
|
import { apiErrorSchema } from "@sentry/junior/api/schema";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
getBrowserSentryConfig,
|
|
16
|
+
initSentry,
|
|
17
|
+
} from "@sentry/junior/instrumentation";
|
|
15
18
|
import { JUNIOR_VERSION } from "@sentry/junior/version";
|
|
16
19
|
import { DASHBOARD_VERSION_HEADER } from "./dashboard-version";
|
|
17
20
|
import { revalidateConversation } from "./conversation-cache";
|
|
@@ -527,10 +530,12 @@ export function createDashboardApp(
|
|
|
527
530
|
}
|
|
528
531
|
}
|
|
529
532
|
|
|
533
|
+
const renderPage = () =>
|
|
534
|
+
renderDashboard(basePath, agentName, getBrowserSentryConfig());
|
|
530
535
|
for (const { nested, path } of dashboardPagePaths(basePath)) {
|
|
531
|
-
app.get(path,
|
|
536
|
+
app.get(path, renderPage);
|
|
532
537
|
if (nested) {
|
|
533
|
-
app.get(`${path}/*`,
|
|
538
|
+
app.get(`${path}/*`, renderPage);
|
|
534
539
|
}
|
|
535
540
|
}
|
|
536
541
|
for (const route of options.pluginRoutes ?? []) {
|
package/src/client/App.tsx
CHANGED
|
@@ -240,6 +240,17 @@ export function DashboardShell() {
|
|
|
240
240
|
mobileNavigationOpen={mobileNavigationOpen}
|
|
241
241
|
navItems={primaryNavItems}
|
|
242
242
|
onMobileNavigationOpenChange={setMobileNavigationOpen}
|
|
243
|
+
profile={
|
|
244
|
+
loggedIn ? (
|
|
245
|
+
<ProfileMenu
|
|
246
|
+
identity={data!.me}
|
|
247
|
+
onSignOut={signOut}
|
|
248
|
+
spend={personalSpendQuery.data}
|
|
249
|
+
userPages={userPages}
|
|
250
|
+
variant="top-bar"
|
|
251
|
+
/>
|
|
252
|
+
) : undefined
|
|
253
|
+
}
|
|
243
254
|
mobileIdentity={
|
|
244
255
|
loggedIn ? (
|
|
245
256
|
<ProfileMenu
|
|
@@ -12,11 +12,6 @@ export type MetricTooltipLine = {
|
|
|
12
12
|
valueStyle?: "heading";
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export type MetricListItem = {
|
|
16
|
-
content: ReactNode;
|
|
17
|
-
key: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
15
|
function TooltipLines(props: { lines: MetricTooltipLine[] }) {
|
|
21
16
|
return (
|
|
22
17
|
<span className="grid min-w-0 self-start grid-cols-[minmax(0,1fr)_auto] content-start gap-x-3 gap-y-1.5">
|
|
@@ -126,30 +121,3 @@ export function MetricValue(props: {
|
|
|
126
121
|
</Tooltip>
|
|
127
122
|
);
|
|
128
123
|
}
|
|
129
|
-
|
|
130
|
-
/** Render inline metadata with consistent dot spacing across dashboard headers. */
|
|
131
|
-
export function MetricList(props: {
|
|
132
|
-
className?: string;
|
|
133
|
-
items: MetricListItem[];
|
|
134
|
-
}) {
|
|
135
|
-
return (
|
|
136
|
-
<div
|
|
137
|
-
className={cn(
|
|
138
|
-
"flex flex-wrap items-center gap-x-1.5 gap-y-1",
|
|
139
|
-
props.className,
|
|
140
|
-
)}
|
|
141
|
-
>
|
|
142
|
-
{props.items.map((item, index) => (
|
|
143
|
-
<span
|
|
144
|
-
className="inline-flex min-w-0 items-center gap-x-1.5"
|
|
145
|
-
key={item.key}
|
|
146
|
-
>
|
|
147
|
-
{index > 0 ? (
|
|
148
|
-
<span className="text-dashboard-text-muted">·</span>
|
|
149
|
-
) : null}
|
|
150
|
-
<span className="min-w-0">{item.content}</span>
|
|
151
|
-
</span>
|
|
152
|
-
))}
|
|
153
|
-
</div>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Boxes,
|
|
3
|
+
ChevronDown,
|
|
4
|
+
LogOut,
|
|
5
|
+
Settings,
|
|
6
|
+
ShieldCheck,
|
|
7
|
+
UserRound,
|
|
8
|
+
} from "lucide-react";
|
|
2
9
|
import { useEffect, useRef, useState } from "react";
|
|
3
10
|
import { Link } from "react-router";
|
|
4
11
|
import type { PersonalSpendReport } from "@sentry/junior/api/schema";
|
|
@@ -19,11 +26,17 @@ type ProfileMenuProps = {
|
|
|
19
26
|
userPages: PluginUserPageLink[];
|
|
20
27
|
/**
|
|
21
28
|
* `popover` is the avatar control at the bottom of the desktop nav rail.
|
|
29
|
+
* `top-bar` is the spend and avatar control in the tall desktop top bar.
|
|
22
30
|
* `sheet-spend` is the vertical spend callout at the top of the mobile sheet.
|
|
23
31
|
* `sheet-links` is plain account destinations for the mobile nav sheet.
|
|
24
32
|
* `sheet-identity` is the quiet signed-in strip at the bottom of that sheet.
|
|
25
33
|
*/
|
|
26
|
-
variant?:
|
|
34
|
+
variant?:
|
|
35
|
+
| "popover"
|
|
36
|
+
| "top-bar"
|
|
37
|
+
| "sheet-spend"
|
|
38
|
+
| "sheet-links"
|
|
39
|
+
| "sheet-identity";
|
|
27
40
|
};
|
|
28
41
|
|
|
29
42
|
const HOVER_OPEN_DELAY_MS = 80;
|
|
@@ -307,6 +320,7 @@ export function ProfileMenu({
|
|
|
307
320
|
);
|
|
308
321
|
}
|
|
309
322
|
|
|
323
|
+
const topBar = variant === "top-bar";
|
|
310
324
|
return (
|
|
311
325
|
<Popover
|
|
312
326
|
onOpenChange={(nextOpen, details) => {
|
|
@@ -324,18 +338,56 @@ export function ProfileMenu({
|
|
|
324
338
|
<PopoverTrigger
|
|
325
339
|
aria-label={`${open ? "Close" : "Open"} profile menu for ${name}. Personal model spend: 7 days ${sevenDaySpend}, 30 days ${thirtyDaySpend}.`}
|
|
326
340
|
className={cn(
|
|
327
|
-
"
|
|
341
|
+
"cursor-pointer rounded-lg border-0 bg-transparent text-dashboard-text transition-colors hover:bg-dashboard-fill-hover hover:text-dashboard-text focus-visible:outline focus-visible:outline-2 focus-visible:outline-dashboard-focus/70",
|
|
342
|
+
topBar
|
|
343
|
+
? "group flex h-10 items-center gap-2 px-1.5"
|
|
344
|
+
: "grid size-10 place-items-center",
|
|
328
345
|
open && "bg-dashboard-fill-hover text-dashboard-text",
|
|
329
346
|
)}
|
|
330
347
|
closeDelay={HOVER_CLOSE_DELAY_MS}
|
|
331
348
|
delay={HOVER_OPEN_DELAY_MS}
|
|
332
349
|
openOnHover
|
|
333
350
|
>
|
|
351
|
+
{topBar ? (
|
|
352
|
+
<span className="flex items-center gap-2">
|
|
353
|
+
<span className="flex items-baseline gap-1 whitespace-nowrap tabular-nums">
|
|
354
|
+
<span className="text-xs font-medium tracking-[0.08em] text-dashboard-text-muted">
|
|
355
|
+
7d
|
|
356
|
+
</span>
|
|
357
|
+
<span className="text-xs font-semibold text-dashboard-text">
|
|
358
|
+
<SpendAmount value={spend?.sevenDaysUsd} />
|
|
359
|
+
</span>
|
|
360
|
+
</span>
|
|
361
|
+
<span
|
|
362
|
+
aria-hidden="true"
|
|
363
|
+
className="h-3 w-px bg-dashboard-fill-strong"
|
|
364
|
+
/>
|
|
365
|
+
<span className="flex items-baseline gap-1 whitespace-nowrap tabular-nums">
|
|
366
|
+
<span className="text-xs font-medium tracking-[0.08em] text-dashboard-text-muted">
|
|
367
|
+
30d
|
|
368
|
+
</span>
|
|
369
|
+
<span className="text-xs font-semibold text-dashboard-text">
|
|
370
|
+
<SpendAmount value={spend?.thirtyDaysUsd} />
|
|
371
|
+
</span>
|
|
372
|
+
</span>
|
|
373
|
+
</span>
|
|
374
|
+
) : null}
|
|
334
375
|
<ProfileAvatar user={identity.user} />
|
|
376
|
+
{topBar ? (
|
|
377
|
+
<ChevronDown
|
|
378
|
+
aria-hidden="true"
|
|
379
|
+
className={cn(
|
|
380
|
+
"text-dashboard-text-muted transition-[color,transform] group-hover:text-dashboard-text",
|
|
381
|
+
open && "rotate-180 text-dashboard-text",
|
|
382
|
+
)}
|
|
383
|
+
size={14}
|
|
384
|
+
strokeWidth={2}
|
|
385
|
+
/>
|
|
386
|
+
) : null}
|
|
335
387
|
</PopoverTrigger>
|
|
336
388
|
<PopoverContent
|
|
337
389
|
align="end"
|
|
338
|
-
side="right"
|
|
390
|
+
side={topBar ? "bottom" : "right"}
|
|
339
391
|
className="w-64 rounded-xl border-0 bg-dashboard-surface-raised/95 p-1.5 shadow-2xl shadow-black/75 backdrop-blur-xl"
|
|
340
392
|
id="profile-popover"
|
|
341
393
|
>
|
|
@@ -104,8 +104,8 @@ export function DashboardChromeProvider(props: { children: ReactNode }) {
|
|
|
104
104
|
/**
|
|
105
105
|
* Sticky shell chrome: mobile header, secondary navigation slot, and banner.
|
|
106
106
|
* One bottom border closes the header and page links, so page link
|
|
107
|
-
* underlines sit on it. Banners render below that border.
|
|
108
|
-
*
|
|
107
|
+
* underlines sit on it. Banners render below that border. Short desktop
|
|
108
|
+
* windows hide the header, so the border shows only under page links.
|
|
109
109
|
*/
|
|
110
110
|
export function DashboardChrome(props: {
|
|
111
111
|
banner?: ReactNode;
|
|
@@ -118,7 +118,7 @@ export function DashboardChrome(props: {
|
|
|
118
118
|
|
|
119
119
|
return (
|
|
120
120
|
<div className="sticky top-0 z-30 bg-dashboard-bg/95 pt-[env(safe-area-inset-top)]">
|
|
121
|
-
<div className="border-b border-dashboard-border-strong md:border-b-0 md:has-[[data-secondary-slot]>*]:border-b">
|
|
121
|
+
<div className="border-b border-dashboard-border-strong md:border-b-0 md:has-[[data-secondary-slot]>*]:border-b md:tall:border-b">
|
|
122
122
|
{props.header}
|
|
123
123
|
<div
|
|
124
124
|
className="hidden md:block"
|
|
@@ -3,6 +3,7 @@ import { useEffect, useRef, type ReactNode, type RefObject } from "react";
|
|
|
3
3
|
import { Link, NavLink } from "react-router";
|
|
4
4
|
|
|
5
5
|
import { getDashboardAgentName } from "../../agentName";
|
|
6
|
+
import { JuniorLogo } from "../JuniorLogo";
|
|
6
7
|
import { Sheet, SheetClose, SheetContent } from "../ui/sheet";
|
|
7
8
|
import { useSheet } from "../ui/useSheet";
|
|
8
9
|
import {
|
|
@@ -31,8 +32,8 @@ function NavCount(props: { count?: number }) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
|
-
* Render the
|
|
35
|
-
*
|
|
35
|
+
* Render the shell header: the mobile bar and navigation sheet, and the
|
|
36
|
+
* desktop top bar on tall windows. Short desktop windows use `NavRail`.
|
|
36
37
|
*/
|
|
37
38
|
export function DashboardHeader(props: {
|
|
38
39
|
/** Back target used on mobile conversation detail. */
|
|
@@ -47,6 +48,8 @@ export function DashboardHeader(props: {
|
|
|
47
48
|
mobileSpend?: ReactNode;
|
|
48
49
|
navItems: DashboardHeaderNavItem[];
|
|
49
50
|
onMobileNavigationOpenChange(open: boolean): void;
|
|
51
|
+
/** Spend and account control at the right of the tall desktop top bar. */
|
|
52
|
+
profile?: ReactNode;
|
|
50
53
|
version?: string;
|
|
51
54
|
workspaceActive: boolean;
|
|
52
55
|
}) {
|
|
@@ -63,6 +66,13 @@ export function DashboardHeader(props: {
|
|
|
63
66
|
onOpenChangeRef.current(true);
|
|
64
67
|
});
|
|
65
68
|
|
|
69
|
+
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
|
|
70
|
+
cn(
|
|
71
|
+
"shrink-0 whitespace-nowrap rounded-lg px-3 py-2 font-sans text-sm font-medium tracking-normal no-underline transition-colors",
|
|
72
|
+
isActive
|
|
73
|
+
? "bg-dashboard-accent/[0.1] text-dashboard-accent-text"
|
|
74
|
+
: cn("hover:bg-dashboard-fill-soft", dashboardInteractiveTextClass),
|
|
75
|
+
);
|
|
66
76
|
const sheetLinkClass = ({ isActive }: { isActive: boolean }) =>
|
|
67
77
|
cn(
|
|
68
78
|
"rounded-lg px-3 py-3 font-sans text-sm font-medium no-underline transition-colors",
|
|
@@ -85,8 +95,35 @@ export function DashboardHeader(props: {
|
|
|
85
95
|
}, [props.mobileNavigationOpen]);
|
|
86
96
|
|
|
87
97
|
return (
|
|
88
|
-
<header className="relative bg-dashboard-bg md:hidden">
|
|
89
|
-
<div className="
|
|
98
|
+
<header className="relative bg-dashboard-bg md:hidden md:tall:block">
|
|
99
|
+
<div className="hidden w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-5 px-7 py-3 md:tall:grid">
|
|
100
|
+
<Link
|
|
101
|
+
aria-label={`${getDashboardAgentName()} home`}
|
|
102
|
+
className="flex items-center text-inherit no-underline"
|
|
103
|
+
to="/"
|
|
104
|
+
>
|
|
105
|
+
<JuniorLogo />
|
|
106
|
+
</Link>
|
|
107
|
+
<nav aria-label="Primary" className="flex min-w-0 items-center gap-1">
|
|
108
|
+
<Link
|
|
109
|
+
aria-current={props.workspaceActive ? "page" : undefined}
|
|
110
|
+
className={navLinkClass({ isActive: props.workspaceActive })}
|
|
111
|
+
to="/"
|
|
112
|
+
>
|
|
113
|
+
Conversations
|
|
114
|
+
</Link>
|
|
115
|
+
{props.navItems.map((item) => (
|
|
116
|
+
<NavLink className={navLinkClass} key={item.key} to={item.to}>
|
|
117
|
+
{item.label}
|
|
118
|
+
<NavCount count={item.count} />
|
|
119
|
+
</NavLink>
|
|
120
|
+
))}
|
|
121
|
+
</nav>
|
|
122
|
+
{props.profile ? (
|
|
123
|
+
<div className="justify-self-end">{props.profile}</div>
|
|
124
|
+
) : null}
|
|
125
|
+
</div>
|
|
126
|
+
<div className="grid w-full min-w-0 grid-cols-[2.5rem_minmax(0,1fr)_2.5rem] items-center gap-x-2 px-3 py-2 md:hidden">
|
|
90
127
|
{conversationMode && props.mobileBackTo ? (
|
|
91
128
|
<Link
|
|
92
129
|
aria-label="Back to conversations"
|
|
@@ -82,7 +82,7 @@ function RailItem(props: {
|
|
|
82
82
|
{props.count ? (
|
|
83
83
|
<SidebarMenuBadge
|
|
84
84
|
aria-hidden="true"
|
|
85
|
-
className="
|
|
85
|
+
className="top-0 right-0 h-4 min-w-4 rounded-full bg-dashboard-accent px-1 text-2xs font-semibold leading-none normal-nums text-dashboard-text-inverse ring-2 ring-sidebar group-data-[collapsible=icon]:flex peer-data-[size=default]/menu-button:top-0 peer-data-active/menu-button:text-dashboard-text-inverse peer-hover/menu-button:text-dashboard-text-inverse"
|
|
86
86
|
>
|
|
87
87
|
{props.count > 99 ? "99+" : props.count.toLocaleString("en-US")}
|
|
88
88
|
</SidebarMenuBadge>
|
|
@@ -105,8 +105,8 @@ function RailNavItem(props: { item: DashboardHeaderNavItem }) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
|
-
* Desktop primary navigation as a shadcn icon sidebar
|
|
109
|
-
*
|
|
108
|
+
* Desktop primary navigation as a shadcn icon sidebar on short windows. Tall
|
|
109
|
+
* desktop windows and mobile use `DashboardHeader`.
|
|
110
110
|
*/
|
|
111
111
|
export function NavRail(props: {
|
|
112
112
|
navItems: DashboardHeaderNavItem[];
|
|
@@ -114,43 +114,46 @@ export function NavRail(props: {
|
|
|
114
114
|
workspaceActive: boolean;
|
|
115
115
|
}) {
|
|
116
116
|
return (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
117
|
+
// Tall windows use the top bar in `DashboardHeader` instead.
|
|
118
|
+
<div className="contents md:tall:hidden">
|
|
119
|
+
<Sidebar
|
|
120
|
+
className="border-dashboard-border-strong pt-[env(safe-area-inset-top)]"
|
|
121
|
+
collapsible="icon"
|
|
122
|
+
>
|
|
123
|
+
<SidebarHeader className="items-center pt-3 pb-4">
|
|
124
|
+
<Link
|
|
125
|
+
aria-label={`${getDashboardAgentName()} home`}
|
|
126
|
+
className="grid place-items-center text-inherit no-underline"
|
|
127
|
+
to="/"
|
|
128
|
+
>
|
|
129
|
+
<JuniorLogo />
|
|
130
|
+
</Link>
|
|
131
|
+
</SidebarHeader>
|
|
132
|
+
{/* Stock icon mode hides overflow; the rail stays collapsed, so let
|
|
131
133
|
plugin pages scroll on short windows. */}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
134
|
+
<SidebarContent className="group-data-[collapsible=icon]:overflow-auto">
|
|
135
|
+
<SidebarGroup className="items-center py-0">
|
|
136
|
+
<nav aria-label="Primary" className="w-full">
|
|
137
|
+
<SidebarMenu className="items-center gap-1.5">
|
|
138
|
+
<RailItem
|
|
139
|
+
active={props.workspaceActive}
|
|
140
|
+
icon={MessagesSquare}
|
|
141
|
+
label="Conversations"
|
|
142
|
+
to="/"
|
|
143
|
+
/>
|
|
144
|
+
{props.navItems.map((item) => (
|
|
145
|
+
<RailNavItem item={item} key={item.key} />
|
|
146
|
+
))}
|
|
147
|
+
</SidebarMenu>
|
|
148
|
+
</nav>
|
|
149
|
+
</SidebarGroup>
|
|
150
|
+
</SidebarContent>
|
|
151
|
+
{props.profile ? (
|
|
152
|
+
<SidebarFooter className="items-center pb-3">
|
|
153
|
+
{props.profile}
|
|
154
|
+
</SidebarFooter>
|
|
155
|
+
) : null}
|
|
156
|
+
</Sidebar>
|
|
157
|
+
</div>
|
|
155
158
|
);
|
|
156
159
|
}
|
|
@@ -81,7 +81,10 @@ type ConversationComposerProps = {
|
|
|
81
81
|
* crushed beside the mobile send button.
|
|
82
82
|
*/
|
|
83
83
|
footerStart?: ReactNode;
|
|
84
|
+
/** Text only, for a narrow panel: no images, no key hint, icon-only send. */
|
|
85
|
+
compact?: boolean;
|
|
84
86
|
label: string;
|
|
87
|
+
placeholder?: string;
|
|
85
88
|
/**
|
|
86
89
|
* Restore the submitted text after a failed accept when there is no mailbox
|
|
87
90
|
* outbox (new conversation create). Existing conversations keep failures in
|
|
@@ -433,7 +436,12 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
433
436
|
)}
|
|
434
437
|
onDragOver={(event) => {
|
|
435
438
|
if (!event.dataTransfer.types.includes("Files")) return;
|
|
439
|
+
// Always cancel a file drop, or the browser opens the file.
|
|
436
440
|
event.preventDefault();
|
|
441
|
+
if (props.compact) {
|
|
442
|
+
event.dataTransfer.dropEffect = "none";
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
437
445
|
event.dataTransfer.dropEffect = "copy";
|
|
438
446
|
setDragging(true);
|
|
439
447
|
}}
|
|
@@ -444,12 +452,13 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
444
452
|
onDrop={(event) => {
|
|
445
453
|
if (!event.dataTransfer.types.includes("Files")) return;
|
|
446
454
|
event.preventDefault();
|
|
455
|
+
if (props.compact) return;
|
|
447
456
|
setDragging(false);
|
|
448
457
|
void addImages(Array.from(event.dataTransfer.files));
|
|
449
458
|
}}
|
|
450
459
|
onPaste={(event) => {
|
|
451
460
|
const files = Array.from(event.clipboardData.files);
|
|
452
|
-
if (!files.length) return;
|
|
461
|
+
if (props.compact || !files.length) return;
|
|
453
462
|
event.preventDefault();
|
|
454
463
|
void addImages(files);
|
|
455
464
|
}}
|
|
@@ -510,7 +519,7 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
510
519
|
}}
|
|
511
520
|
onFocus={props.onFocus}
|
|
512
521
|
onKeyDown={handleKeyDown}
|
|
513
|
-
placeholder="Message Junior…"
|
|
522
|
+
placeholder={props.placeholder ?? "Message Junior…"}
|
|
514
523
|
ref={textareaRef}
|
|
515
524
|
rows={1}
|
|
516
525
|
spellCheck={autocorrect}
|
|
@@ -524,27 +533,29 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
524
533
|
)}
|
|
525
534
|
>
|
|
526
535
|
<div className="flex min-w-0 items-center gap-3">
|
|
527
|
-
|
|
528
|
-
<
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
536
|
+
{props.compact ? null : (
|
|
537
|
+
<Tooltip>
|
|
538
|
+
<TooltipTrigger
|
|
539
|
+
render={
|
|
540
|
+
<Button
|
|
541
|
+
focusableWhenDisabled
|
|
542
|
+
aria-label="Attach images"
|
|
543
|
+
disabled={sendLocked || readingImages}
|
|
544
|
+
onClick={() => fileInputRef.current?.click()}
|
|
545
|
+
size="icon"
|
|
546
|
+
type="button"
|
|
547
|
+
>
|
|
548
|
+
<ImagePlus aria-hidden="true" size={16} />
|
|
549
|
+
</Button>
|
|
550
|
+
}
|
|
551
|
+
/>
|
|
552
|
+
<TooltipContent size="compact">
|
|
553
|
+
Attach images (up to 3 MB total)
|
|
554
|
+
</TooltipContent>
|
|
555
|
+
</Tooltip>
|
|
556
|
+
)}
|
|
546
557
|
{props.footerStart}
|
|
547
|
-
{props.footerStart ? null : (
|
|
558
|
+
{props.footerStart || props.compact ? null : (
|
|
548
559
|
<div className="hidden min-w-0 font-sans text-xs leading-relaxed text-dashboard-text-muted md:block">
|
|
549
560
|
Enter to send · Shift+Enter for a new line
|
|
550
561
|
</div>
|
|
@@ -565,7 +576,10 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
565
576
|
) : null}
|
|
566
577
|
<Button
|
|
567
578
|
aria-label={sendLocked ? "Sending message" : props.submitLabel}
|
|
568
|
-
className=
|
|
579
|
+
className={cn(
|
|
580
|
+
"disabled:!border-transparent disabled:!bg-dashboard-fill-hover disabled:!text-dashboard-text-muted disabled:!opacity-100",
|
|
581
|
+
props.compact && "w-9 px-0",
|
|
582
|
+
)}
|
|
569
583
|
disabled={
|
|
570
584
|
(!canSend && !images.length) ||
|
|
571
585
|
!online ||
|
|
@@ -576,9 +590,11 @@ export const ConversationComposer = memo(function ConversationComposer(
|
|
|
576
590
|
type="submit"
|
|
577
591
|
>
|
|
578
592
|
<Send aria-hidden="true" size={14} />
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
593
|
+
{props.compact ? null : (
|
|
594
|
+
<span className="hidden md:inline">
|
|
595
|
+
{sendLocked ? "Sending…" : props.submitLabel}
|
|
596
|
+
</span>
|
|
597
|
+
)}
|
|
582
598
|
</Button>
|
|
583
599
|
</div>
|
|
584
600
|
</div>
|