@neuronection/assistant-ui 0.46.0 → 0.48.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.
@@ -0,0 +1,415 @@
1
+ import { Input } from './chunk-GQDW6BBO.js';
2
+ import { Popover, PopoverTrigger, PopoverContent } from './chunk-VX3EQRSM.js';
3
+ import { Button } from './chunk-4WBBF5LH.js';
4
+ import { cn } from './chunk-RVVXVTH3.js';
5
+ import * as React from 'react';
6
+ import { UserRound, ChevronDown, Pencil, Star, Trash2, Plus, Check } from 'lucide-react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ var defaultLabels = {
10
+ trigger: "Switch profile",
11
+ panelTitle: "Profiles",
12
+ currentBadge: "Current",
13
+ defaultBadge: "Default",
14
+ newProfile: "New profile",
15
+ newProfilePlaceholder: "Profile name",
16
+ create: "Create",
17
+ rename: (name) => `Rename ${name}`,
18
+ renamePlaceholder: "Profile name",
19
+ renameSubmit: "Save",
20
+ setDefault: (name) => `Make ${name} the default profile`,
21
+ delete: (name) => `Delete ${name}`,
22
+ cancel: "Cancel",
23
+ loading: "Loading profiles\u2026",
24
+ empty: "No profiles yet"
25
+ };
26
+ function ProfileAvatar({
27
+ profile,
28
+ size = "sm"
29
+ }) {
30
+ const letter = (profile.name.trim()[0] ?? "?").toUpperCase();
31
+ return /* @__PURE__ */ jsx(
32
+ "span",
33
+ {
34
+ "aria-hidden": true,
35
+ className: cn(
36
+ "flex shrink-0 items-center justify-center rounded-full font-semibold ring-1 ring-[var(--as-border)]",
37
+ size === "md" ? "size-8 text-xs" : "size-6 text-[10px]"
38
+ ),
39
+ style: profile.color ? { backgroundColor: `${profile.color}22`, color: profile.color } : {
40
+ backgroundColor: "var(--as-secondary)",
41
+ color: "var(--as-muted-fg)"
42
+ },
43
+ children: profile.color ? letter : /* @__PURE__ */ jsx(UserRound, { className: size === "md" ? "size-4" : "size-3.5" })
44
+ }
45
+ );
46
+ }
47
+ function ProfileRowBadges({
48
+ isCurrent,
49
+ isDefault,
50
+ currentBadge,
51
+ defaultBadge
52
+ }) {
53
+ if (!isCurrent && !isDefault) return null;
54
+ return /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
55
+ isCurrent ? /* @__PURE__ */ jsxs(
56
+ "span",
57
+ {
58
+ className: "inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium",
59
+ style: {
60
+ backgroundColor: "color-mix(in srgb, var(--as-accent) 12%, transparent)",
61
+ color: "var(--as-accent)"
62
+ },
63
+ children: [
64
+ /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: "size-3" }),
65
+ currentBadge
66
+ ]
67
+ }
68
+ ) : null,
69
+ isDefault ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-[var(--as-secondary)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--as-muted-fg)]", children: defaultBadge }) : null
70
+ ] });
71
+ }
72
+ var ProfileSwitcher = React.forwardRef(
73
+ function ProfileSwitcher2({
74
+ profiles,
75
+ currentId,
76
+ onSelect,
77
+ onCreate,
78
+ onRename,
79
+ onDelete,
80
+ onSetDefault,
81
+ loading = false,
82
+ error = null,
83
+ align = "start",
84
+ labels,
85
+ icons,
86
+ className
87
+ }, ref) {
88
+ const merged = { ...defaultLabels, ...labels };
89
+ const TriggerIcon = icons?.trigger ?? UserRound;
90
+ const CreateIcon = icons?.create ?? Plus;
91
+ const RenameIcon = icons?.rename ?? Pencil;
92
+ const SetDefaultIcon = icons?.setDefault ?? Star;
93
+ const DeleteIcon = icons?.delete ?? Trash2;
94
+ const EmptyIcon = icons?.empty ?? UserRound;
95
+ const [open, setOpen] = React.useState(false);
96
+ const [draftName, setDraftName] = React.useState("");
97
+ const [renamingId, setRenamingId] = React.useState(null);
98
+ const [renameDraft, setRenameDraft] = React.useState("");
99
+ const renameInputRef = React.useRef(null);
100
+ const current = profiles.find((profile) => profile.id === currentId) ?? profiles[0] ?? null;
101
+ const close = React.useCallback(() => {
102
+ setOpen(false);
103
+ setRenamingId(null);
104
+ setDraftName("");
105
+ }, []);
106
+ React.useEffect(() => {
107
+ if (renamingId !== null) {
108
+ renameInputRef.current?.focus();
109
+ renameInputRef.current?.select();
110
+ }
111
+ }, [renamingId]);
112
+ const startRename = (profile) => {
113
+ setRenamingId(profile.id);
114
+ setRenameDraft(profile.name);
115
+ };
116
+ const submitRename = async (profile) => {
117
+ const name = renameDraft.trim();
118
+ if (!name || name === profile.name) {
119
+ setRenamingId(null);
120
+ return;
121
+ }
122
+ await onRename(profile, name);
123
+ setRenamingId(null);
124
+ };
125
+ const submitCreate = async (event) => {
126
+ event.preventDefault();
127
+ const name = draftName.trim();
128
+ if (!name) return;
129
+ await onCreate(name);
130
+ setDraftName("");
131
+ };
132
+ const onListKeyDown = (event) => {
133
+ const rows = Array.from(
134
+ event.currentTarget.querySelectorAll("[data-profile-row]")
135
+ );
136
+ if (rows.length === 0) return;
137
+ const index = rows.indexOf(document.activeElement);
138
+ let next = -1;
139
+ if (event.key === "ArrowDown") next = index < 0 ? 0 : Math.min(index + 1, rows.length - 1);
140
+ else if (event.key === "ArrowUp")
141
+ next = index < 0 ? rows.length - 1 : Math.max(index - 1, 0);
142
+ else if (event.key === "Home") next = 0;
143
+ else if (event.key === "End") next = rows.length - 1;
144
+ if (next >= 0) {
145
+ event.preventDefault();
146
+ rows[next]?.focus();
147
+ }
148
+ };
149
+ return /* @__PURE__ */ jsx("div", { ref, "data-as": "profile-switcher", className: cn("inline-block", className), children: /* @__PURE__ */ jsxs(
150
+ Popover,
151
+ {
152
+ open,
153
+ onOpenChange: (next) => {
154
+ setOpen(next);
155
+ if (!next) {
156
+ setRenamingId(null);
157
+ setDraftName("");
158
+ }
159
+ },
160
+ children: [
161
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
162
+ "button",
163
+ {
164
+ type: "button",
165
+ "aria-haspopup": "dialog",
166
+ "aria-label": merged.trigger,
167
+ "data-testid": "profile-trigger",
168
+ className: "flex cursor-pointer items-center gap-2 rounded-full p-0.5 pr-2 transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]",
169
+ children: [
170
+ current ? /* @__PURE__ */ jsx(ProfileAvatar, { profile: current, size: "md" }) : /* @__PURE__ */ jsx(
171
+ "span",
172
+ {
173
+ "aria-hidden": true,
174
+ className: "flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-[var(--as-border)]",
175
+ children: /* @__PURE__ */ jsx(TriggerIcon, { className: "size-4 text-[var(--as-muted-fg)]" })
176
+ }
177
+ ),
178
+ /* @__PURE__ */ jsx("span", { className: "max-w-32 truncate text-sm font-medium", children: current ? current.name : merged.empty }),
179
+ /* @__PURE__ */ jsx(
180
+ ChevronDown,
181
+ {
182
+ "aria-hidden": true,
183
+ className: "size-3.5 text-[var(--as-muted-fg)] transition-transform duration-150 [[data-state=open]>&]:rotate-180"
184
+ }
185
+ )
186
+ ]
187
+ }
188
+ ) }),
189
+ /* @__PURE__ */ jsxs(
190
+ PopoverContent,
191
+ {
192
+ align,
193
+ role: "dialog",
194
+ "aria-label": merged.panelTitle,
195
+ className: "w-80 p-1.5 shadow-[var(--as-shadow-pop)] backdrop-blur-xl",
196
+ onEscapeKeyDown: (event) => {
197
+ if (renamingId !== null) event.preventDefault();
198
+ },
199
+ children: [
200
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 pb-1 pt-1", children: [
201
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]", children: merged.panelTitle }),
202
+ profiles.length > 0 ? /* @__PURE__ */ jsx("span", { className: "text-[11px] tabular-nums text-[var(--as-muted-fg)]", children: profiles.length }) : null
203
+ ] }),
204
+ loading ? /* @__PURE__ */ jsx(
205
+ "p",
206
+ {
207
+ className: "as-anim-fade px-2 py-3 text-sm text-[var(--as-muted-fg)]",
208
+ role: "status",
209
+ children: merged.loading
210
+ }
211
+ ) : profiles.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 px-2 py-3 text-sm text-[var(--as-muted-fg)]", children: [
212
+ /* @__PURE__ */ jsx(EmptyIcon, { "aria-hidden": true, className: "size-4" }),
213
+ merged.empty
214
+ ] }) : /* @__PURE__ */ jsx(
215
+ "ul",
216
+ {
217
+ className: "max-h-72 space-y-0.5 overflow-y-auto",
218
+ onKeyDown: onListKeyDown,
219
+ children: profiles.map((profile) => {
220
+ const isCurrent = current !== null && profile.id === current.id;
221
+ return /* @__PURE__ */ jsx(
222
+ "li",
223
+ {
224
+ className: "flex items-center gap-1 rounded-[var(--as-radius-sm)] px-0.5 py-0.5 hover:bg-[var(--as-secondary)]",
225
+ children: renamingId === profile.id ? /* @__PURE__ */ jsxs(
226
+ "form",
227
+ {
228
+ className: "flex w-full items-center gap-1 py-0.5",
229
+ onSubmit: (event) => {
230
+ event.preventDefault();
231
+ void submitRename(profile);
232
+ },
233
+ children: [
234
+ /* @__PURE__ */ jsx(
235
+ Input,
236
+ {
237
+ ref: renameInputRef,
238
+ "aria-label": merged.rename(profile.name),
239
+ placeholder: merged.renamePlaceholder,
240
+ value: renameDraft,
241
+ onChange: (event) => setRenameDraft(event.target.value),
242
+ onKeyDown: (event) => {
243
+ if (event.key === "Escape") {
244
+ event.preventDefault();
245
+ setRenamingId(null);
246
+ }
247
+ },
248
+ className: "h-8 flex-1"
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx(
252
+ Button,
253
+ {
254
+ type: "submit",
255
+ size: "sm",
256
+ className: "h-8",
257
+ disabled: !renameDraft.trim(),
258
+ "aria-label": merged.renameSubmit,
259
+ children: merged.renameSubmit
260
+ }
261
+ ),
262
+ /* @__PURE__ */ jsx(
263
+ Button,
264
+ {
265
+ type: "button",
266
+ size: "sm",
267
+ variant: "ghost",
268
+ className: "h-8",
269
+ "aria-label": merged.cancel,
270
+ onClick: () => setRenamingId(null),
271
+ children: merged.cancel
272
+ }
273
+ )
274
+ ]
275
+ }
276
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
277
+ /* @__PURE__ */ jsxs(
278
+ "button",
279
+ {
280
+ type: "button",
281
+ "aria-current": isCurrent || void 0,
282
+ "data-profile-row": true,
283
+ "data-testid": `profile-option-${profile.id}`,
284
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-[var(--as-radius-sm)] px-1.5 py-1.5 text-left text-sm outline-none transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] aria-current:bg-[color-mix(in_srgb,var(--as-accent)_8%,transparent)]",
285
+ onClick: () => {
286
+ onSelect(profile);
287
+ close();
288
+ },
289
+ children: [
290
+ /* @__PURE__ */ jsx(ProfileAvatar, { profile, size: "md" }),
291
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: profile.name }),
292
+ /* @__PURE__ */ jsx(
293
+ ProfileRowBadges,
294
+ {
295
+ isCurrent,
296
+ isDefault: Boolean(profile.is_default),
297
+ currentBadge: merged.currentBadge,
298
+ defaultBadge: merged.defaultBadge
299
+ }
300
+ )
301
+ ]
302
+ }
303
+ ),
304
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-0.5 pr-0.5", children: [
305
+ /* @__PURE__ */ jsx(
306
+ Button,
307
+ {
308
+ type: "button",
309
+ size: "icon",
310
+ variant: "ghost",
311
+ className: "size-7 rounded-[var(--as-radius-sm)]",
312
+ "aria-label": merged.rename(profile.name),
313
+ title: merged.rename(profile.name),
314
+ onClick: () => startRename(profile),
315
+ children: /* @__PURE__ */ jsx(RenameIcon, { "aria-hidden": true })
316
+ }
317
+ ),
318
+ onSetDefault && !profile.is_default ? /* @__PURE__ */ jsx(
319
+ Button,
320
+ {
321
+ type: "button",
322
+ size: "icon",
323
+ variant: "ghost",
324
+ className: "size-7 rounded-[var(--as-radius-sm)]",
325
+ "aria-label": merged.setDefault(profile.name),
326
+ title: merged.setDefault(profile.name),
327
+ onClick: () => void onSetDefault(profile),
328
+ children: /* @__PURE__ */ jsx(SetDefaultIcon, { "aria-hidden": true })
329
+ }
330
+ ) : null,
331
+ /* @__PURE__ */ jsx(
332
+ Button,
333
+ {
334
+ type: "button",
335
+ size: "icon",
336
+ variant: "ghost",
337
+ className: "size-7 rounded-[var(--as-radius-sm)] hover:text-[var(--as-danger)]",
338
+ "aria-label": merged.delete(profile.name),
339
+ title: merged.delete(profile.name),
340
+ onClick: () => void onDelete(profile),
341
+ children: /* @__PURE__ */ jsx(DeleteIcon, { "aria-hidden": true })
342
+ }
343
+ )
344
+ ] })
345
+ ] })
346
+ },
347
+ profile.id
348
+ );
349
+ })
350
+ }
351
+ ),
352
+ error ? /* @__PURE__ */ jsx(
353
+ "p",
354
+ {
355
+ role: "alert",
356
+ className: "mx-1 mt-1 rounded-[var(--as-radius-sm)] bg-[color-mix(in_srgb,var(--as-danger)_10%,transparent)] px-2 py-1 text-xs text-[var(--as-danger)]",
357
+ children: error
358
+ }
359
+ ) : null,
360
+ /* @__PURE__ */ jsxs(
361
+ "form",
362
+ {
363
+ className: "mt-1 flex items-center gap-1.5 border-t border-[var(--as-border)] p-1.5 pt-2",
364
+ onSubmit: (event) => {
365
+ void submitCreate(event);
366
+ },
367
+ children: [
368
+ /* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
369
+ /* @__PURE__ */ jsx(
370
+ CreateIcon,
371
+ {
372
+ "aria-hidden": true,
373
+ className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-[var(--as-muted-fg)]"
374
+ }
375
+ ),
376
+ /* @__PURE__ */ jsx(
377
+ Input,
378
+ {
379
+ "aria-label": merged.newProfile,
380
+ placeholder: merged.newProfilePlaceholder,
381
+ value: draftName,
382
+ onChange: (event) => setDraftName(event.target.value),
383
+ className: "h-8 pl-8"
384
+ }
385
+ )
386
+ ] }),
387
+ /* @__PURE__ */ jsxs(
388
+ Button,
389
+ {
390
+ type: "submit",
391
+ size: "sm",
392
+ className: "h-8 gap-1 px-2.5",
393
+ disabled: !draftName.trim(),
394
+ "aria-label": merged.create,
395
+ children: [
396
+ /* @__PURE__ */ jsx(CreateIcon, { "aria-hidden": true }),
397
+ merged.create
398
+ ]
399
+ }
400
+ )
401
+ ]
402
+ }
403
+ )
404
+ ]
405
+ }
406
+ )
407
+ ]
408
+ }
409
+ ) });
410
+ }
411
+ );
412
+
413
+ export { ProfileSwitcher };
414
+ //# sourceMappingURL=chunk-WY5TO6R4.js.map
415
+ //# sourceMappingURL=chunk-WY5TO6R4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/profile-switcher/ProfileSwitcher.tsx"],"names":["ProfileSwitcher"],"mappings":"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","file":"chunk-WY5TO6R4.js","sourcesContent":["import * as React from 'react'\nimport {\n Check,\n ChevronDown,\n Pencil,\n Plus,\n Star,\n Trash2,\n UserRound,\n type LucideIcon,\n} from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\nimport { Button } from '../button/Button'\nimport { Input } from '../input/Input'\nimport { Popover, PopoverContent, PopoverTrigger } from '../popover/Popover'\n\nexport interface ProfileItem {\n id: string\n name: string\n is_default?: boolean\n color?: string | null\n}\n\nexport interface ProfileSwitcherLabels {\n trigger: string\n panelTitle: string\n currentBadge: string\n defaultBadge: string\n newProfile: string\n newProfilePlaceholder: string\n create: string\n rename: (name: string) => string\n renamePlaceholder: string\n renameSubmit: string\n setDefault: (name: string) => string\n delete: (name: string) => string\n cancel: string\n loading: string\n empty: string\n}\n\nexport interface ProfileSwitcherIcons {\n trigger?: LucideIcon\n create?: LucideIcon\n rename?: LucideIcon\n setDefault?: LucideIcon\n delete?: LucideIcon\n empty?: LucideIcon\n}\n\nexport interface ProfileSwitcherProps {\n profiles: ProfileItem[]\n currentId: string | null\n onSelect: (profile: ProfileItem) => void\n onCreate: (name: string) => void | Promise<unknown>\n onRename: (profile: ProfileItem, name: string) => void | Promise<unknown>\n onDelete: (profile: ProfileItem) => void | Promise<unknown>\n /** When given, rows expose a \"make Default\" action (identity-auth §12). */\n onSetDefault?: (profile: ProfileItem) => void | Promise<unknown>\n loading?: boolean\n error?: string | null\n align?: 'start' | 'end'\n labels?: Partial<ProfileSwitcherLabels>\n icons?: ProfileSwitcherIcons\n className?: string\n}\n\nconst defaultLabels: ProfileSwitcherLabels = {\n trigger: 'Switch profile',\n panelTitle: 'Profiles',\n currentBadge: 'Current',\n defaultBadge: 'Default',\n newProfile: 'New profile',\n newProfilePlaceholder: 'Profile name',\n create: 'Create',\n rename: (name) => `Rename ${name}`,\n renamePlaceholder: 'Profile name',\n renameSubmit: 'Save',\n setDefault: (name) => `Make ${name} the default profile`,\n delete: (name) => `Delete ${name}`,\n cancel: 'Cancel',\n loading: 'Loading profiles…',\n empty: 'No profiles yet',\n}\n\nfunction ProfileAvatar({\n profile,\n size = 'sm',\n}: {\n profile: ProfileItem\n size?: 'sm' | 'md'\n}) {\n const letter = (profile.name.trim()[0] ?? '?').toUpperCase()\n return (\n <span\n aria-hidden\n className={cn(\n 'flex shrink-0 items-center justify-center rounded-full font-semibold ring-1 ring-[var(--as-border)]',\n size === 'md' ? 'size-8 text-xs' : 'size-6 text-[10px]',\n )}\n style={\n profile.color\n ? { backgroundColor: `${profile.color}22`, color: profile.color }\n : {\n backgroundColor: 'var(--as-secondary)',\n color: 'var(--as-muted-fg)',\n }\n }\n >\n {profile.color ? (\n letter\n ) : (\n <UserRound className={size === 'md' ? 'size-4' : 'size-3.5'} />\n )}\n </span>\n )\n}\n\n/** Status pills on a row: accent \"Current\" when active, muted \"Default\". */\nfunction ProfileRowBadges({\n isCurrent,\n isDefault,\n currentBadge,\n defaultBadge,\n}: {\n isCurrent: boolean\n isDefault: boolean\n currentBadge: string\n defaultBadge: string\n}) {\n if (!isCurrent && !isDefault) return null\n return (\n <span className=\"flex shrink-0 items-center gap-1\">\n {isCurrent ? (\n <span\n className=\"inline-flex items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium\"\n style={{\n backgroundColor: 'color-mix(in srgb, var(--as-accent) 12%, transparent)',\n color: 'var(--as-accent)',\n }}\n >\n <Check aria-hidden className=\"size-3\" />\n {currentBadge}\n </span>\n ) : null}\n {isDefault ? (\n <span className=\"inline-flex items-center rounded-full bg-[var(--as-secondary)] px-1.5 py-0.5 text-[10px] font-medium text-[var(--as-muted-fg)]\">\n {defaultBadge}\n </span>\n ) : null}\n </span>\n )\n}\n\nexport const ProfileSwitcher = React.forwardRef<HTMLDivElement, ProfileSwitcherProps>(\n function ProfileSwitcher(\n {\n profiles,\n currentId,\n onSelect,\n onCreate,\n onRename,\n onDelete,\n onSetDefault,\n loading = false,\n error = null,\n align = 'start',\n labels,\n icons,\n className,\n },\n ref,\n ) {\n const merged = { ...defaultLabels, ...labels }\n const TriggerIcon = icons?.trigger ?? UserRound\n const CreateIcon = icons?.create ?? Plus\n const RenameIcon = icons?.rename ?? Pencil\n const SetDefaultIcon = icons?.setDefault ?? Star\n const DeleteIcon = icons?.delete ?? Trash2\n const EmptyIcon = icons?.empty ?? UserRound\n\n const [open, setOpen] = React.useState(false)\n const [draftName, setDraftName] = React.useState('')\n const [renamingId, setRenamingId] = React.useState<string | null>(null)\n const [renameDraft, setRenameDraft] = React.useState('')\n const renameInputRef = React.useRef<HTMLInputElement>(null)\n\n const current = profiles.find((profile) => profile.id === currentId) ?? profiles[0] ?? null\n\n const close = React.useCallback(() => {\n setOpen(false)\n setRenamingId(null)\n setDraftName('')\n }, [])\n\n React.useEffect(() => {\n if (renamingId !== null) {\n renameInputRef.current?.focus()\n renameInputRef.current?.select()\n }\n }, [renamingId])\n\n const startRename = (profile: ProfileItem) => {\n setRenamingId(profile.id)\n setRenameDraft(profile.name)\n }\n\n const submitRename = async (profile: ProfileItem) => {\n const name = renameDraft.trim()\n if (!name || name === profile.name) {\n setRenamingId(null)\n return\n }\n await onRename(profile, name)\n setRenamingId(null)\n }\n\n const submitCreate = async (event: React.FormEvent) => {\n event.preventDefault()\n const name = draftName.trim()\n if (!name) return\n await onCreate(name)\n setDraftName('')\n }\n\n // Roving arrows: ↓/↑ (and Home/End) walk the profile rows without\n // leaving the panel; Tab keeps the DOM order (select → actions).\n const onListKeyDown = (event: React.KeyboardEvent<HTMLUListElement>) => {\n const rows = Array.from(\n event.currentTarget.querySelectorAll<HTMLButtonElement>('[data-profile-row]'),\n )\n if (rows.length === 0) return\n const index = rows.indexOf(document.activeElement as HTMLButtonElement)\n let next = -1\n if (event.key === 'ArrowDown') next = index < 0 ? 0 : Math.min(index + 1, rows.length - 1)\n else if (event.key === 'ArrowUp')\n next = index < 0 ? rows.length - 1 : Math.max(index - 1, 0)\n else if (event.key === 'Home') next = 0\n else if (event.key === 'End') next = rows.length - 1\n if (next >= 0) {\n event.preventDefault()\n rows[next]?.focus()\n }\n }\n\n return (\n <div ref={ref} data-as=\"profile-switcher\" className={cn('inline-block', className)}>\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next)\n if (!next) {\n setRenamingId(null)\n setDraftName('')\n }\n }}\n >\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-haspopup=\"dialog\"\n aria-label={merged.trigger}\n data-testid=\"profile-trigger\"\n className=\"flex cursor-pointer items-center gap-2 rounded-full p-0.5 pr-2 transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n {current ? (\n <ProfileAvatar profile={current} size=\"md\" />\n ) : (\n <span\n aria-hidden\n className=\"flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-[var(--as-border)]\"\n >\n <TriggerIcon className=\"size-4 text-[var(--as-muted-fg)]\" />\n </span>\n )}\n <span className=\"max-w-32 truncate text-sm font-medium\">\n {current ? current.name : merged.empty}\n </span>\n <ChevronDown\n aria-hidden\n className=\"size-3.5 text-[var(--as-muted-fg)] transition-transform duration-150 [[data-state=open]>&]:rotate-180\"\n />\n </button>\n </PopoverTrigger>\n <PopoverContent\n align={align}\n role=\"dialog\"\n aria-label={merged.panelTitle}\n className=\"w-80 p-1.5 shadow-[var(--as-shadow-pop)] backdrop-blur-xl\"\n onEscapeKeyDown={(event) => {\n // Escape cancels an inline rename first, closes the panel\n // only on the second press.\n if (renamingId !== null) event.preventDefault()\n }}\n >\n <div className=\"flex items-center justify-between px-2 pb-1 pt-1\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wide text-[var(--as-muted-fg)]\">\n {merged.panelTitle}\n </span>\n {profiles.length > 0 ? (\n <span className=\"text-[11px] tabular-nums text-[var(--as-muted-fg)]\">\n {profiles.length}\n </span>\n ) : null}\n </div>\n {loading ? (\n <p\n className=\"as-anim-fade px-2 py-3 text-sm text-[var(--as-muted-fg)]\"\n role=\"status\"\n >\n {merged.loading}\n </p>\n ) : profiles.length === 0 ? (\n <p className=\"flex items-center gap-2 px-2 py-3 text-sm text-[var(--as-muted-fg)]\">\n <EmptyIcon aria-hidden className=\"size-4\" />\n {merged.empty}\n </p>\n ) : (\n <ul\n className=\"max-h-72 space-y-0.5 overflow-y-auto\"\n onKeyDown={onListKeyDown}\n >\n {profiles.map((profile) => {\n const isCurrent = current !== null && profile.id === current.id\n return (\n <li\n key={profile.id}\n className=\"flex items-center gap-1 rounded-[var(--as-radius-sm)] px-0.5 py-0.5 hover:bg-[var(--as-secondary)]\"\n >\n {renamingId === profile.id ? (\n <form\n className=\"flex w-full items-center gap-1 py-0.5\"\n onSubmit={(event) => {\n event.preventDefault()\n void submitRename(profile)\n }}\n >\n <Input\n ref={renameInputRef}\n aria-label={merged.rename(profile.name)}\n placeholder={merged.renamePlaceholder}\n value={renameDraft}\n onChange={(event) => setRenameDraft(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n setRenamingId(null)\n }\n }}\n className=\"h-8 flex-1\"\n />\n <Button\n type=\"submit\"\n size=\"sm\"\n className=\"h-8\"\n disabled={!renameDraft.trim()}\n aria-label={merged.renameSubmit}\n >\n {merged.renameSubmit}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"h-8\"\n aria-label={merged.cancel}\n onClick={() => setRenamingId(null)}\n >\n {merged.cancel}\n </Button>\n </form>\n ) : (\n <>\n <button\n type=\"button\"\n aria-current={isCurrent || undefined}\n data-profile-row\n data-testid={`profile-option-${profile.id}`}\n className=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2.5 rounded-[var(--as-radius-sm)] px-1.5 py-1.5 text-left text-sm outline-none transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] aria-current:bg-[color-mix(in_srgb,var(--as-accent)_8%,transparent)]\"\n onClick={() => {\n onSelect(profile)\n close()\n }}\n >\n <ProfileAvatar profile={profile} size=\"md\" />\n <span className=\"min-w-0 flex-1 truncate font-medium\">\n {profile.name}\n </span>\n <ProfileRowBadges\n isCurrent={isCurrent}\n isDefault={Boolean(profile.is_default)}\n currentBadge={merged.currentBadge}\n defaultBadge={merged.defaultBadge}\n />\n </button>\n <span className=\"flex shrink-0 items-center gap-0.5 pr-0.5\">\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-7 rounded-[var(--as-radius-sm)]\"\n aria-label={merged.rename(profile.name)}\n title={merged.rename(profile.name)}\n onClick={() => startRename(profile)}\n >\n <RenameIcon aria-hidden />\n </Button>\n {onSetDefault && !profile.is_default ? (\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-7 rounded-[var(--as-radius-sm)]\"\n aria-label={merged.setDefault(profile.name)}\n title={merged.setDefault(profile.name)}\n onClick={() => void onSetDefault(profile)}\n >\n <SetDefaultIcon aria-hidden />\n </Button>\n ) : null}\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"ghost\"\n className=\"size-7 rounded-[var(--as-radius-sm)] hover:text-[var(--as-danger)]\"\n aria-label={merged.delete(profile.name)}\n title={merged.delete(profile.name)}\n onClick={() => void onDelete(profile)}\n >\n <DeleteIcon aria-hidden />\n </Button>\n </span>\n </>\n )}\n </li>\n )\n })}\n </ul>\n )}\n {error ? (\n <p\n role=\"alert\"\n className=\"mx-1 mt-1 rounded-[var(--as-radius-sm)] bg-[color-mix(in_srgb,var(--as-danger)_10%,transparent)] px-2 py-1 text-xs text-[var(--as-danger)]\"\n >\n {error}\n </p>\n ) : null}\n <form\n className=\"mt-1 flex items-center gap-1.5 border-t border-[var(--as-border)] p-1.5 pt-2\"\n onSubmit={(event) => {\n void submitCreate(event)\n }}\n >\n <div className=\"relative min-w-0 flex-1\">\n <CreateIcon\n aria-hidden\n className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-[var(--as-muted-fg)]\"\n />\n <Input\n aria-label={merged.newProfile}\n placeholder={merged.newProfilePlaceholder}\n value={draftName}\n onChange={(event) => setDraftName(event.target.value)}\n className=\"h-8 pl-8\"\n />\n </div>\n <Button\n type=\"submit\"\n size=\"sm\"\n className=\"h-8 gap-1 px-2.5\"\n disabled={!draftName.trim()}\n aria-label={merged.create}\n >\n <CreateIcon aria-hidden />\n {merged.create}\n </Button>\n </form>\n </PopoverContent>\n </Popover>\n </div>\n )\n },\n)\n"]}
@@ -1,5 +1,5 @@
1
1
  import { MarkdownSurface } from './chunk-HQ3X5SVR.js';
2
- import { ChatMessage } from './chunk-EAJU4LFG.js';
2
+ import { ChatMessage } from './chunk-C5JZCCU7.js';
3
3
  import { cn } from './chunk-RVVXVTH3.js';
4
4
  import * as React from 'react';
5
5
  import { ArrowDown } from 'lucide-react';
@@ -169,5 +169,5 @@ var ChatTranscript = React.forwardRef(
169
169
  );
170
170
 
171
171
  export { ChatTranscript };
172
- //# sourceMappingURL=chunk-MTFHU6QY.js.map
173
- //# sourceMappingURL=chunk-MTFHU6QY.js.map
172
+ //# sourceMappingURL=chunk-XOODPYJ3.js.map
173
+ //# sourceMappingURL=chunk-XOODPYJ3.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/chat-transcript/ChatTranscript.tsx"],"names":["ChatTranscript"],"mappings":";;;;;;;;AAiCA,SAAS,kBAAkB,OAAA,EAA0B;AACnD,EAAA,uBACE,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,MAAM,OAAA,CAAQ,IAAA;AAAA,MACd,QAAQ,OAAA,CAAQ,MAAA;AAAA,MAChB,OAAA,sBAAU,eAAA,EAAA,EAAgB,KAAA,EAAO,QAAQ,OAAA,EAAS,SAAA,EAAW,OAAA,CAAQ,MAAA,KAAW,WAAA,EAAa,CAAA;AAAA,MAC7F,UAAU,OAAA,CAAQ;AAAA;AAAA,GACpB;AAEJ;AAOO,IAAM,cAAA,GAAuB,KAAA,CAAA,UAAA;AAAA,EAClC,SAASA,eAAAA,CACP;AAAA,IACE,KAAA;AAAA,IACA,UAAA,GAAa,iBAAA;AAAA,IACb,IAAA;AAAA,IACA,UAAA;AAAA,IACA,UAAA,GAAa,IAAA;AAAA,IACb,iBAAA,GAAoB,GAAA;AAAA,IACpB,WAAA,GAAc,KAAA;AAAA,IACd,MAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,SAAA,GAAkB,aAA8B,IAAI,CAAA;AAC1D,IAAA,MAAM,WAAA,GAAoB,aAAO,IAAI,CAAA;AACrC,IAAA,MAAM,gBAAA,GAAyB,aAAO,CAAC,CAAA;AACvC,IAAA,MAAM,aAAA,GAAsB,aAAsB,IAAI,CAAA;AACtD,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAU,eAAS,KAAK,CAAA;AACpD,IAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAU,eAAS,EAAE,CAAA;AACzD,IAAA,MAAM,gBAAA,GAAyB,aAAO,EAAE,CAAA;AAExC,IAAA,MAAM,WAAW,MAAM;AACrB,MAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,YAAA,GAAe,OAAA,CAAQ,YAAY,OAAA,CAAQ,YAAA;AACpE,MAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,SAAA,GAAY,gBAAA,CAAiB,OAAA;AACrD,MAAA,gBAAA,CAAiB,UAAU,OAAA,CAAQ,SAAA;AACnC,MAAA,MAAM,QAAA,GAAW,QAAA,IAAY,iBAAA,IAAqB,EAAE,WAAW,QAAA,GAAW,CAAA,CAAA;AAC1E,MAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AACtB,MAAA,WAAA,CAAY,OAAA,CAAQ,YAAA,GAAe,OAAA,CAAQ,YAAA,IAAgB,CAAC,QAAQ,CAAA;AAAA,IACtE,CAAA;AAUA,IAAA,MAAM,cAAc,MAAM;AACxB,MAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA;AAAA,MACF;AACA,MAAA,WAAA,CAAY,OAAA,GAAU,KAAA;AACtB,MAAA,WAAA,CAAY,OAAA,CAAQ,YAAA,GAAe,OAAA,CAAQ,YAAY,CAAA;AAAA,IACzD,CAAA;AAEA,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAA4C;AAC3D,MAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,QAAA,WAAA,EAAY;AAAA,MACd;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAA4C;AAChE,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA,CAAM,OAAA,CAAQ,CAAC,GAAG,OAAA,IAAW,IAAA;AAAA,IACvD,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAA4C;AAC/D,MAAA,MAAM,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,OAAA;AAC5B,MAAA,IAAI,MAAM,MAAA,EAAW;AACnB,QAAA;AAAA,MACF;AACA,MAAA,IAAI,aAAA,CAAc,OAAA,KAAY,IAAA,IAAQ,CAAA,GAAI,cAAc,OAAA,EAAS;AAC/D,QAAA,WAAA,EAAY;AAAA,MACd;AACA,MAAA,aAAA,CAAc,OAAA,GAAU,CAAA;AAAA,IAC1B,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAA+C;AAChE,MAAA,IACE,KAAA,CAAM,GAAA,KAAQ,SAAA,IACd,KAAA,CAAM,GAAA,KAAQ,QAAA,IACd,KAAA,CAAM,GAAA,KAAQ,MAAA,IACb,KAAA,CAAM,GAAA,KAAQ,GAAA,IAAO,MAAM,QAAA,EAC5B;AACA,QAAA,WAAA,EAAY;AAAA,MACd;AAAA,IACF,CAAA;AAEA,IAAM,gBAAU,MAAM;AACpB,MAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,MAAA,IAAI,YAAY,IAAA,IAAQ,CAAC,UAAA,IAAc,CAAC,YAAY,OAAA,EAAS;AAC3D,QAAA;AAAA,MACF;AACA,MAAA,MAAM,KAAA,GAAQ,sBAAsB,MAAM;AACxC,QAAA,OAAA,CAAQ,YAAY,OAAA,CAAQ,YAAA;AAAA,MAC9B,CAAC,CAAA;AACD,MAAA,OAAO,MAAM,qBAAqB,KAAK,CAAA;AAAA,IACzC,CAAC,CAAA;AAED,IAAM,gBAAU,MAAM;AACpB,MAAA,IAAI,gBAAA,CAAiB,YAAY,EAAA,EAAI;AACnC,QAAA,gBAAA,CAAiB,UAAU,KAAA,CAAM,MAAA;AACjC,QAAA;AAAA,MACF;AACA,MAAA,IAAI,KAAA,CAAM,MAAA,GAAS,gBAAA,CAAiB,OAAA,EAAS;AAC3C,QAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAC,CAAA;AACpC,QAAA,gBAAA,CAAiB,UAAU,KAAA,CAAM,MAAA;AACjC,QAAA,IAAI,KAAA,IAAS,KAAA,CAAM,IAAA,KAAS,MAAA,EAAQ;AAClC,UAAA,eAAA,CAAgB,MAAA,EAAQ,WAAW,mBAAmB,CAAA;AAAA,QACxD;AACA,QAAA;AAAA,MACF;AACA,MAAA,gBAAA,CAAiB,UAAU,KAAA,CAAM,MAAA;AAAA,IACnC,CAAA,EAAG,CAAC,KAAA,EAAO,MAAM,CAAC,CAAA;AAElB,IAAA,MAAM,iBAAiB,MAAM;AAC3B,MAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,OAAA,CAAQ,SAAS,EAAE,GAAA,EAAK,QAAQ,YAAA,EAAc,QAAA,EAAU,UAAU,CAAA;AAAA,MACpE;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,QAAA,GAAW,KAAA,CAAM,MAAA,IAAU,IAAA,KAAS,SAAY,CAAA,GAAI,CAAA,CAAA;AAC1D,IAAA,MAAM,cAAc,cAAA,CAAe;AAAA,MACjC,KAAA,EAAO,cAAc,QAAA,GAAW,CAAA;AAAA,MAChC,gBAAA,EAAkB,MAAM,SAAA,CAAU,OAAA;AAAA,MAClC,cAAc,MAAM,GAAA;AAAA,MACpB,QAAA,EAAU,CAAA;AAAA,MACV,WAAA,EAAa,EAAE,KAAA,EAAO,GAAA,EAAK,QAAQ,GAAA;AAAI,KACxC,CAAA;AAED,IAAA,uBACE,IAAA,CAAC,SAAI,GAAA,EAAU,SAAA,EAAQ,mBAAkB,SAAA,EAAW,EAAA,CAAG,yBAAA,EAA2B,SAAS,CAAA,EACzF,QAAA,EAAA;AAAA,sBAAA,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,SAAA;AAAA,UACL,QAAA;AAAA,UACA,OAAA;AAAA,UACA,YAAA;AAAA,UACA,WAAA;AAAA,UACA,SAAA;AAAA,UACA,IAAA,EAAK,KAAA;AAAA,UACL,YAAA,EAAY,QAAQ,GAAA,IAAO,cAAA;AAAA,UAC3B,aAAW,IAAA,KAAS,MAAA;AAAA,UACpB,SAAA,EAAU,qDAAA;AAAA,UAET,QAAA,EAAA,KAAA,CAAM,MAAA,KAAW,CAAA,IAAK,IAAA,KAAS,MAAA,mBAC9B,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wFAAA,EACZ,QAAA,EAAA,UAAA,KAAe,MAAA,EAAQ,KAAA,IAAS,2BACnC,CAAA,GACE,WAAA,mBACF,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,MAAA,EAAQ,WAAA,CAAY,cAAa,EAAG,QAAA,EAAU,UAAA,EAAW,EACpE,QAAA,EAAA,WAAA,CAAY,eAAA,EAAgB,CAAE,GAAA,CAAI,CAAC,GAAA,KAAQ;AAC1C,YAAA,MAAM,MAAA,GAAS,GAAA,CAAI,KAAA,IAAS,KAAA,CAAM,MAAA;AAClC,YAAA,MAAM,IAAA,GAAO,SAAS,IAAA,GAAO,UAAA,CAAW,MAAM,GAAA,CAAI,KAAK,CAAA,EAAI,GAAA,CAAI,KAAK,CAAA;AACpE,YAAA,uBACE,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,cAAY,GAAA,CAAI,KAAA;AAAA,gBAChB,KAAK,WAAA,CAAY,cAAA;AAAA,gBACjB,KAAA,EAAO,EAAE,QAAA,EAAU,UAAA,EAAY,KAAK,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,KAAA,EAAO,MAAA,EAAQ,SAAA,EAAW,CAAA,WAAA,EAAc,GAAA,CAAI,KAAK,CAAA,GAAA,CAAA,EAAM;AAAA,gBACvG,SAAA,EAAU,aAAA;AAAA,gBAET,QAAA,EAAA;AAAA,eAAA;AAAA,cANI,GAAA,CAAI;AAAA,aAOX;AAAA,UAEJ,CAAC,CAAA,EACH,CAAA,mBAEA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,0BAAA,EACZ,QAAA,EAAA;AAAA,YAAA,KAAA,CAAM,GAAA,CAAI,CAAC,OAAA,EAAS,KAAA,qBACnB,GAAA,CAAC,KAAA,EAAA,EAAsB,QAAA,EAAA,UAAA,CAAW,OAAA,EAAS,KAAK,CAAA,EAAA,EAAtC,OAAA,CAAQ,EAAgC,CACnD,CAAA;AAAA,YACA,SAAS,MAAA,mBAAY,GAAA,CAAC,SAAI,SAAA,EAAQ,sBAAA,EAAwB,gBAAK,CAAA,GAAS;AAAA,WAAA,EAC3E;AAAA;AAAA,OAEJ;AAAA,0BACC,KAAA,EAAA,EAAI,WAAA,EAAU,QAAA,EAAS,SAAA,EAAU,WAC/B,QAAA,EAAA,YAAA,EACH,CAAA;AAAA,MACC,QAAA,mBACC,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACC,IAAA,EAAK,QAAA;AAAA,UACL,OAAA,EAAS,cAAA;AAAA,UACT,YAAA,EAAY,QAAQ,cAAA,IAAkB,kBAAA;AAAA,UACtC,KAAA,EAAO,QAAQ,cAAA,IAAkB,kBAAA;AAAA,UACjC,SAAA,EAAU,sWAAA;AAAA,UAEV,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA,OAC5C,GACE;AAAA,KAAA,EACN,CAAA;AAAA,EAEJ;AACF","file":"chunk-MTFHU6QY.js","sourcesContent":["import * as React from 'react'\nimport { ArrowDown } from 'lucide-react'\nimport { useVirtualizer } from '@tanstack/react-virtual'\n\nimport { cn } from '../../lib/utils'\nimport { ChatMessage } from '../chat-message/ChatMessage'\nimport { MarkdownSurface } from '../chat-markdown/MarkdownSurface'\nimport type { ChatMessageView } from '../chat-core/types'\n\nexport interface ChatTranscriptLabels {\n log: string\n scrollToBottom: string\n empty: string\n replied: string\n}\n\nexport interface ChatTranscriptProps {\n items: ChatMessageView[]\n /** Override the default `ChatMessage` + `MarkdownSurface` rendering. */\n renderItem?: (message: ChatMessageView, index: number) => React.ReactNode\n /** Streaming tail — the live turn bubble (or any node). */\n live?: React.ReactNode\n emptyState?: React.ReactNode\n /** Stick to bottom while the user hasn't scrolled away. Default true. */\n autoScroll?: boolean\n /** Distance from the bottom that still counts as \"at the bottom\". Default 120 px. */\n scrollThresholdPx?: number\n /** Virtualize long transcripts (dynamic row measurement). Default false. */\n virtualized?: boolean\n labels?: Partial<ChatTranscriptLabels>\n className?: string\n}\n\nfunction defaultRenderItem(message: ChatMessageView) {\n return (\n <ChatMessage\n role={message.role}\n status={message.status}\n content={<MarkdownSurface value={message.content} streaming={message.status === 'streaming'} />}\n variants={message.variants}\n />\n )\n}\n\n/**\n * The conversation list: `role=\"log\"` with polite turn-completion\n * announcements, stick-to-bottom auto-scroll with a jump-to-latest pill,\n * optional virtualization for thousand-message threads.\n */\nexport const ChatTranscript = React.forwardRef<HTMLDivElement, ChatTranscriptProps>(\n function ChatTranscript(\n {\n items,\n renderItem = defaultRenderItem,\n live,\n emptyState,\n autoScroll = true,\n scrollThresholdPx = 120,\n virtualized = false,\n labels,\n className,\n },\n ref,\n ) {\n const scrollRef = React.useRef<HTMLDivElement | null>(null)\n const atBottomRef = React.useRef(true)\n const lastScrollTopRef = React.useRef(0)\n const lastTouchYRef = React.useRef<number | null>(null)\n const [showJump, setShowJump] = React.useState(false)\n const [announcement, setAnnouncement] = React.useState('')\n const previousCountRef = React.useRef(-1)\n\n const onScroll = () => {\n const element = scrollRef.current\n if (element === null) {\n return\n }\n const distance = element.scrollHeight - element.scrollTop - element.clientHeight\n const goingUp = element.scrollTop < lastScrollTopRef.current\n lastScrollTopRef.current = element.scrollTop\n const atBottom = distance <= scrollThresholdPx && !(goingUp && distance > 0)\n atBottomRef.current = atBottom\n setShowJump(element.scrollHeight > element.clientHeight && !atBottom)\n }\n\n /**\n * Fast streams re-pin the bottom on every render (rAF), which erases\n * a partial upward scroll before it can cross `scrollThresholdPx` —\n * so user scroll-up INTENT cancels stickiness synchronously here\n * (wheel up, touch drag up, scroll-up keys), and any upward scroll\n * event with distance left also breaks it. Scrolling back to the\n * bottom (or the jump pill) re-arms the follow.\n */\n const cancelStick = () => {\n const element = scrollRef.current\n if (element === null) {\n return\n }\n atBottomRef.current = false\n setShowJump(element.scrollHeight > element.clientHeight)\n }\n\n const onWheel = (event: React.WheelEvent<HTMLDivElement>) => {\n if (event.deltaY < 0) {\n cancelStick()\n }\n }\n\n const onTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\n lastTouchYRef.current = event.touches[0]?.clientY ?? null\n }\n\n const onTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\n const y = event.touches[0]?.clientY\n if (y === undefined) {\n return\n }\n if (lastTouchYRef.current !== null && y > lastTouchYRef.current) {\n cancelStick()\n }\n lastTouchYRef.current = y\n }\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (\n event.key === 'ArrowUp' ||\n event.key === 'PageUp' ||\n event.key === 'Home' ||\n (event.key === ' ' && event.shiftKey)\n ) {\n cancelStick()\n }\n }\n\n React.useEffect(() => {\n const element = scrollRef.current\n if (element === null || !autoScroll || !atBottomRef.current) {\n return\n }\n const frame = requestAnimationFrame(() => {\n element.scrollTop = element.scrollHeight\n })\n return () => cancelAnimationFrame(frame)\n })\n\n React.useEffect(() => {\n if (previousCountRef.current === -1) {\n previousCountRef.current = items.length\n return\n }\n if (items.length > previousCountRef.current) {\n const added = items[items.length - 1]\n previousCountRef.current = items.length\n if (added && added.role !== 'user') {\n setAnnouncement(labels?.replied ?? 'Assistant replied')\n }\n return\n }\n previousCountRef.current = items.length\n }, [items, labels])\n\n const scrollToBottom = () => {\n const element = scrollRef.current\n if (element !== null) {\n element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' })\n }\n }\n\n const rowCount = items.length + (live !== undefined ? 1 : 0)\n const virtualizer = useVirtualizer({\n count: virtualized ? rowCount : 0,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => 120,\n overscan: 8,\n initialRect: { width: 480, height: 600 },\n })\n\n return (\n <div ref={ref} data-as=\"chat-transcript\" className={cn('relative min-h-0 flex-1', className)}>\n <div\n ref={scrollRef}\n onScroll={onScroll}\n onWheel={onWheel}\n onTouchStart={onTouchStart}\n onTouchMove={onTouchMove}\n onKeyDown={onKeyDown}\n role=\"log\"\n aria-label={labels?.log ?? 'Conversation'}\n aria-busy={live !== undefined}\n className=\"h-full overflow-y-auto overscroll-contain px-1 py-2\"\n >\n {items.length === 0 && live === undefined ? (\n <div className=\"flex h-full min-h-40 items-center justify-center p-4 text-sm text-[var(--as-muted-fg)]\">\n {emptyState ?? (labels?.empty ?? 'Start the conversation')}\n </div>\n ) : virtualized ? (\n <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>\n {virtualizer.getVirtualItems().map((row) => {\n const isLive = row.index >= items.length\n const node = isLive ? live : renderItem(items[row.index]!, row.index)\n return (\n <div\n key={row.key}\n data-index={row.index}\n ref={virtualizer.measureElement}\n style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${row.start}px)` }}\n className=\"px-2 py-1.5\"\n >\n {node}\n </div>\n )\n })}\n </div>\n ) : (\n <div className=\"flex flex-col gap-3 px-2\">\n {items.map((message, index) => (\n <div key={message.id}>{renderItem(message, index)}</div>\n ))}\n {live !== undefined ? <div data-as=\"chat-transcript-live\">{live}</div> : null}\n </div>\n )}\n </div>\n <div aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </div>\n {showJump ? (\n <button\n type=\"button\"\n onClick={scrollToBottom}\n aria-label={labels?.scrollToBottom ?? 'Scroll to latest'}\n title={labels?.scrollToBottom ?? 'Scroll to latest'}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-[var(--as-border)] bg-[var(--as-surface-raised)] text-[var(--as-fg)] shadow-[var(--as-shadow-2)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ArrowDown className=\"size-4\" aria-hidden />\n </button>\n ) : null}\n </div>\n )\n },\n)\n"]}
1
+ {"version":3,"sources":["../src/components/chat-transcript/ChatTranscript.tsx"],"names":["ChatTranscript"],"mappings":"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","file":"chunk-XOODPYJ3.js","sourcesContent":["import * as React from 'react'\nimport { ArrowDown } from 'lucide-react'\nimport { useVirtualizer } from '@tanstack/react-virtual'\n\nimport { cn } from '../../lib/utils'\nimport { ChatMessage } from '../chat-message/ChatMessage'\nimport { MarkdownSurface } from '../chat-markdown/MarkdownSurface'\nimport type { ChatMessageView } from '../chat-core/types'\n\nexport interface ChatTranscriptLabels {\n log: string\n scrollToBottom: string\n empty: string\n replied: string\n}\n\nexport interface ChatTranscriptProps {\n items: ChatMessageView[]\n /** Override the default `ChatMessage` + `MarkdownSurface` rendering. */\n renderItem?: (message: ChatMessageView, index: number) => React.ReactNode\n /** Streaming tail — the live turn bubble (or any node). */\n live?: React.ReactNode\n emptyState?: React.ReactNode\n /** Stick to bottom while the user hasn't scrolled away. Default true. */\n autoScroll?: boolean\n /** Distance from the bottom that still counts as \"at the bottom\". Default 120 px. */\n scrollThresholdPx?: number\n /** Virtualize long transcripts (dynamic row measurement). Default false. */\n virtualized?: boolean\n labels?: Partial<ChatTranscriptLabels>\n className?: string\n}\n\nfunction defaultRenderItem(message: ChatMessageView) {\n return (\n <ChatMessage\n role={message.role}\n status={message.status}\n content={<MarkdownSurface value={message.content} streaming={message.status === 'streaming'} />}\n variants={message.variants}\n />\n )\n}\n\n/**\n * The conversation list: `role=\"log\"` with polite turn-completion\n * announcements, stick-to-bottom auto-scroll with a jump-to-latest pill,\n * optional virtualization for thousand-message threads.\n */\nexport const ChatTranscript = React.forwardRef<HTMLDivElement, ChatTranscriptProps>(\n function ChatTranscript(\n {\n items,\n renderItem = defaultRenderItem,\n live,\n emptyState,\n autoScroll = true,\n scrollThresholdPx = 120,\n virtualized = false,\n labels,\n className,\n },\n ref,\n ) {\n const scrollRef = React.useRef<HTMLDivElement | null>(null)\n const atBottomRef = React.useRef(true)\n const lastScrollTopRef = React.useRef(0)\n const lastTouchYRef = React.useRef<number | null>(null)\n const [showJump, setShowJump] = React.useState(false)\n const [announcement, setAnnouncement] = React.useState('')\n const previousCountRef = React.useRef(-1)\n\n const onScroll = () => {\n const element = scrollRef.current\n if (element === null) {\n return\n }\n const distance = element.scrollHeight - element.scrollTop - element.clientHeight\n const goingUp = element.scrollTop < lastScrollTopRef.current\n lastScrollTopRef.current = element.scrollTop\n const atBottom = distance <= scrollThresholdPx && !(goingUp && distance > 0)\n atBottomRef.current = atBottom\n setShowJump(element.scrollHeight > element.clientHeight && !atBottom)\n }\n\n /**\n * Fast streams re-pin the bottom on every render (rAF), which erases\n * a partial upward scroll before it can cross `scrollThresholdPx` —\n * so user scroll-up INTENT cancels stickiness synchronously here\n * (wheel up, touch drag up, scroll-up keys), and any upward scroll\n * event with distance left also breaks it. Scrolling back to the\n * bottom (or the jump pill) re-arms the follow.\n */\n const cancelStick = () => {\n const element = scrollRef.current\n if (element === null) {\n return\n }\n atBottomRef.current = false\n setShowJump(element.scrollHeight > element.clientHeight)\n }\n\n const onWheel = (event: React.WheelEvent<HTMLDivElement>) => {\n if (event.deltaY < 0) {\n cancelStick()\n }\n }\n\n const onTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {\n lastTouchYRef.current = event.touches[0]?.clientY ?? null\n }\n\n const onTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {\n const y = event.touches[0]?.clientY\n if (y === undefined) {\n return\n }\n if (lastTouchYRef.current !== null && y > lastTouchYRef.current) {\n cancelStick()\n }\n lastTouchYRef.current = y\n }\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (\n event.key === 'ArrowUp' ||\n event.key === 'PageUp' ||\n event.key === 'Home' ||\n (event.key === ' ' && event.shiftKey)\n ) {\n cancelStick()\n }\n }\n\n React.useEffect(() => {\n const element = scrollRef.current\n if (element === null || !autoScroll || !atBottomRef.current) {\n return\n }\n const frame = requestAnimationFrame(() => {\n element.scrollTop = element.scrollHeight\n })\n return () => cancelAnimationFrame(frame)\n })\n\n React.useEffect(() => {\n if (previousCountRef.current === -1) {\n previousCountRef.current = items.length\n return\n }\n if (items.length > previousCountRef.current) {\n const added = items[items.length - 1]\n previousCountRef.current = items.length\n if (added && added.role !== 'user') {\n setAnnouncement(labels?.replied ?? 'Assistant replied')\n }\n return\n }\n previousCountRef.current = items.length\n }, [items, labels])\n\n const scrollToBottom = () => {\n const element = scrollRef.current\n if (element !== null) {\n element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' })\n }\n }\n\n const rowCount = items.length + (live !== undefined ? 1 : 0)\n const virtualizer = useVirtualizer({\n count: virtualized ? rowCount : 0,\n getScrollElement: () => scrollRef.current,\n estimateSize: () => 120,\n overscan: 8,\n initialRect: { width: 480, height: 600 },\n })\n\n return (\n <div ref={ref} data-as=\"chat-transcript\" className={cn('relative min-h-0 flex-1', className)}>\n <div\n ref={scrollRef}\n onScroll={onScroll}\n onWheel={onWheel}\n onTouchStart={onTouchStart}\n onTouchMove={onTouchMove}\n onKeyDown={onKeyDown}\n role=\"log\"\n aria-label={labels?.log ?? 'Conversation'}\n aria-busy={live !== undefined}\n className=\"h-full overflow-y-auto overscroll-contain px-1 py-2\"\n >\n {items.length === 0 && live === undefined ? (\n <div className=\"flex h-full min-h-40 items-center justify-center p-4 text-sm text-[var(--as-muted-fg)]\">\n {emptyState ?? (labels?.empty ?? 'Start the conversation')}\n </div>\n ) : virtualized ? (\n <div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>\n {virtualizer.getVirtualItems().map((row) => {\n const isLive = row.index >= items.length\n const node = isLive ? live : renderItem(items[row.index]!, row.index)\n return (\n <div\n key={row.key}\n data-index={row.index}\n ref={virtualizer.measureElement}\n style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${row.start}px)` }}\n className=\"px-2 py-1.5\"\n >\n {node}\n </div>\n )\n })}\n </div>\n ) : (\n <div className=\"flex flex-col gap-3 px-2\">\n {items.map((message, index) => (\n <div key={message.id}>{renderItem(message, index)}</div>\n ))}\n {live !== undefined ? <div data-as=\"chat-transcript-live\">{live}</div> : null}\n </div>\n )}\n </div>\n <div aria-live=\"polite\" className=\"sr-only\">\n {announcement}\n </div>\n {showJump ? (\n <button\n type=\"button\"\n onClick={scrollToBottom}\n aria-label={labels?.scrollToBottom ?? 'Scroll to latest'}\n title={labels?.scrollToBottom ?? 'Scroll to latest'}\n className=\"absolute bottom-3 left-1/2 flex size-8 -translate-x-1/2 items-center justify-center rounded-full border border-[var(--as-border)] bg-[var(--as-surface-raised)] text-[var(--as-fg)] shadow-[var(--as-shadow-2)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ArrowDown className=\"size-4\" aria-hidden />\n </button>\n ) : null}\n </div>\n )\n },\n)\n"]}
package/dist/index.d.ts CHANGED
@@ -1067,11 +1067,7 @@ interface UserMenuIcons {
1067
1067
  interface UserMenuProps {
1068
1068
  /** Structured identity — rendered in the trigger and the panel header. */
1069
1069
  user?: UserMenuUser;
1070
- /** Legacy flat identity fields (still supported; `user` wins per field). */
1071
- name?: string;
1072
- email?: string;
1073
- avatarUrl?: string;
1074
- /** Fallback disc text when no avatarUrl; derived from `name` when omitted. */
1070
+ /** Fallback disc text when no avatarUrl; derived from `user.name` when omitted. */
1075
1071
  initials?: string;
1076
1072
  /** Badge text for the role (app-translated); falls back to `user.role`. */
1077
1073
  roleBadge?: string;
@@ -1082,20 +1078,27 @@ interface UserMenuProps {
1082
1078
  switcher?: React.ReactNode;
1083
1079
  /** Status pill rendered under the identity block (e.g. "Synced"). */
1084
1080
  status?: UserMenuStatus;
1085
- /** Controlled theme preference — checked state of the theme rows. */
1081
+ /** Controlled theme preference — checked state of the theme items. */
1086
1082
  theme?: UserMenuTheme;
1087
1083
  onThemeChange?: (theme: UserMenuTheme) => void;
1088
1084
  /**
1089
- * Labels for the theme rows — one checkable row renders per label given,
1090
- * so apps opt into exactly the options they support (provide `dark` only
1091
- * for a single "Dark Mode" toggle row; `light`/`dark`/`system` for the
1092
- * full appearance section).
1085
+ * Labels for the theme options — with all three (`light`/`dark`/`system`)
1086
+ * they render as a checkable "Theme" submenu whose trigger shows the
1087
+ * active theme (mirroring the Languages submenu); fewer labels render as
1088
+ * inline checkable rows (provide `dark` only for a single "Dark Mode"
1089
+ * toggle row).
1093
1090
  */
1094
1091
  themeLabels?: {
1095
1092
  light?: string;
1096
1093
  dark?: string;
1097
1094
  system?: string;
1098
1095
  };
1096
+ /**
1097
+ * Layout of the theme section. `'auto'` (default) renders the submenu
1098
+ * when all three labels are present and inline rows otherwise;
1099
+ * `'submenu'` / `'inline'` force the layout regardless of label count.
1100
+ */
1101
+ themeLayout?: 'auto' | 'submenu' | 'inline';
1099
1102
  /** Controlled language — checked state of the language rows. */
1100
1103
  language?: string;
1101
1104
  onLanguageChange?: (id: string) => void;
@@ -1112,6 +1115,7 @@ interface UserMenuProps {
1112
1115
  openMenu?: string;
1113
1116
  account?: string;
1114
1117
  language?: string;
1118
+ theme?: string;
1115
1119
  };
1116
1120
  icons?: UserMenuIcons;
1117
1121
  className?: string;
@@ -2377,6 +2381,12 @@ interface ChatMessageActions {
2377
2381
  onRetry?: () => void;
2378
2382
  /** Extra icon actions (app-specific). */
2379
2383
  extras?: ChatMessageAction[];
2384
+ /**
2385
+ * Extra node rendered inside the error card next to the Retry button —
2386
+ * e.g. an "Open AI settings" link when the error is a not-configured
2387
+ * failure. Rendered only while the error card is visible.
2388
+ */
2389
+ errorExtra?: React.ReactNode;
2380
2390
  }
2381
2391
  interface ChatMessageLabels {
2382
2392
  copy: string;