@neuronection/assistant-ui 0.52.0 → 0.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +4 -1
  3. package/dist/chat-branch-tree.d.ts +15 -2
  4. package/dist/chat-composer.d.ts +43 -3
  5. package/dist/chat-composer.js +1 -1
  6. package/dist/chat-core.d.ts +54 -4
  7. package/dist/chat-launcher.js +1 -1
  8. package/dist/chat-markdown.d.ts +8 -1
  9. package/dist/chat-markdown.js +1 -1
  10. package/dist/chat-message.d.ts +16 -1
  11. package/dist/chat-message.js +1 -1
  12. package/dist/chat-panel.js +1 -1
  13. package/dist/chat-pending.d.ts +25 -0
  14. package/dist/chat-pending.js +3 -0
  15. package/dist/chat-pending.js.map +1 -0
  16. package/dist/chat-reasoning.d.ts +10 -2
  17. package/dist/chat-reasoning.js +1 -1
  18. package/dist/chat-session-list.d.ts +7 -0
  19. package/dist/chat-session-list.js +1 -1
  20. package/dist/chat-suggestion-card.d.ts +19 -0
  21. package/dist/chat-suggestion-card.js +3 -0
  22. package/dist/chat-suggestion-card.js.map +1 -0
  23. package/dist/chat-tool-card.js +1 -1
  24. package/dist/chat-transcript.d.ts +81 -8
  25. package/dist/chat-transcript.js +1 -1
  26. package/dist/chat-turn-status.js +1 -1
  27. package/dist/chat-welcome.d.ts +23 -0
  28. package/dist/chat-welcome.js +3 -0
  29. package/dist/chat-welcome.js.map +1 -0
  30. package/dist/{chunk-PC3CE4W2.js → chunk-22MIIWC7.js} +45 -7
  31. package/dist/chunk-22MIIWC7.js.map +1 -0
  32. package/dist/chunk-56QSIK4Y.js +264 -0
  33. package/dist/chunk-56QSIK4Y.js.map +1 -0
  34. package/dist/{chunk-GDGKARRB.js → chunk-5C2OLUDR.js} +11 -2
  35. package/dist/chunk-5C2OLUDR.js.map +1 -0
  36. package/dist/chunk-E2NNTHFB.js +479 -0
  37. package/dist/chunk-E2NNTHFB.js.map +1 -0
  38. package/dist/{chunk-5BJ4VULP.js → chunk-EQPXM2R4.js} +23 -10
  39. package/dist/chunk-EQPXM2R4.js.map +1 -0
  40. package/dist/chunk-F4BTJQLK.js +43 -0
  41. package/dist/chunk-F4BTJQLK.js.map +1 -0
  42. package/dist/{chunk-TQDTFPM4.js → chunk-GIQZNEG5.js} +3 -3
  43. package/dist/chunk-GIQZNEG5.js.map +1 -0
  44. package/dist/{chunk-E3NZM4U7.js → chunk-GO4ILLS6.js} +17 -4
  45. package/dist/chunk-GO4ILLS6.js.map +1 -0
  46. package/dist/{chunk-5U45TQJE.js → chunk-I5H4KYEZ.js} +3 -3
  47. package/dist/{chunk-5U45TQJE.js.map → chunk-I5H4KYEZ.js.map} +1 -1
  48. package/dist/{chunk-S63SYV6O.js → chunk-INXQJM2K.js} +27 -6
  49. package/dist/chunk-INXQJM2K.js.map +1 -0
  50. package/dist/{chunk-HQ3X5SVR.js → chunk-KLCULTOQ.js} +105 -16
  51. package/dist/chunk-KLCULTOQ.js.map +1 -0
  52. package/dist/chunk-M4SYVPC4.js +3 -0
  53. package/dist/chunk-M4SYVPC4.js.map +1 -0
  54. package/dist/chunk-OOQO6VGO.js +33 -0
  55. package/dist/chunk-OOQO6VGO.js.map +1 -0
  56. package/dist/chunk-P3QUNCBR.js +366 -0
  57. package/dist/chunk-P3QUNCBR.js.map +1 -0
  58. package/dist/chunk-PABMR4RA.js +30 -0
  59. package/dist/chunk-PABMR4RA.js.map +1 -0
  60. package/dist/{chunk-SEZZVAGP.js → chunk-SR4IN564.js} +10 -3
  61. package/dist/chunk-SR4IN564.js.map +1 -0
  62. package/dist/{chunk-MFM4W6ZO.js → chunk-TLFEVL42.js} +3 -3
  63. package/dist/{chunk-MFM4W6ZO.js.map → chunk-TLFEVL42.js.map} +1 -1
  64. package/dist/index.d.ts +243 -36
  65. package/dist/index.js +40 -37
  66. package/dist/markdown-diff-view.js +1 -1
  67. package/dist/model-registry.js +1 -1
  68. package/dist/styles.css +1 -1
  69. package/dist/theme-scope.d.ts +1 -1
  70. package/dist/tokens.d.ts +1 -1
  71. package/dist/tokens.js +1 -1
  72. package/dist/{types-LhE1KFBf.d.ts → types-CxieoN2u.d.ts} +1 -1
  73. package/dist/types-pivJdPC8.d.ts +14 -0
  74. package/package.json +13 -1
  75. package/dist/chunk-5BJ4VULP.js.map +0 -1
  76. package/dist/chunk-C5JZCCU7.js +0 -220
  77. package/dist/chunk-C5JZCCU7.js.map +0 -1
  78. package/dist/chunk-E3NZM4U7.js.map +0 -1
  79. package/dist/chunk-GDGKARRB.js.map +0 -1
  80. package/dist/chunk-HQ3X5SVR.js.map +0 -1
  81. package/dist/chunk-PC3CE4W2.js.map +0 -1
  82. package/dist/chunk-S63SYV6O.js.map +0 -1
  83. package/dist/chunk-SEZZVAGP.js.map +0 -1
  84. package/dist/chunk-TQDTFPM4.js.map +0 -1
  85. package/dist/chunk-XOODPYJ3.js +0 -173
  86. package/dist/chunk-XOODPYJ3.js.map +0 -1
  87. package/dist/chunk-YQQZMWHL.js +0 -201
  88. package/dist/chunk-YQQZMWHL.js.map +0 -1
  89. package/dist/tree-4Hv9stjF.d.ts +0 -54
@@ -37,13 +37,22 @@ var semanticTokens = [
37
37
  "--as-radius-sm",
38
38
  "--as-radius",
39
39
  "--as-radius-lg",
40
+ "--as-duration-fast",
41
+ "--as-duration-base",
42
+ "--as-duration-slow",
43
+ "--as-duration-spring",
44
+ "--as-ease-out",
45
+ "--as-ease-standard",
46
+ "--as-ease-spring",
40
47
  "--as-font-sans",
41
48
  "--as-font-mono",
49
+ "--as-scrollbar-thumb",
50
+ "--as-scrollbar-track",
42
51
  "--as-shadow-1",
43
52
  "--as-shadow-2",
44
53
  "--as-shadow-3"
45
54
  ];
46
55
 
47
56
  export { rawTokens, semanticTokens };
48
- //# sourceMappingURL=chunk-GDGKARRB.js.map
49
- //# sourceMappingURL=chunk-GDGKARRB.js.map
57
+ //# sourceMappingURL=chunk-5C2OLUDR.js.map
58
+ //# sourceMappingURL=chunk-5C2OLUDR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/tokens/tokens.ts"],"names":[],"mappings":";AAAO,IAAM,SAAA,GAAY;AAAA,EACvB,aAAA;AAAA,EACA,cAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA,aAAA;AAAA,EACA,oBAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF;AAEO,IAAM,cAAA,GAAiB;AAAA,EAC5B,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,mBAAA;AAAA,EACA,cAAA;AAAA,EACA,qBAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,gBAAA;AAAA,EACA,YAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,aAAA;AAAA,EACA,gBAAA;AAAA,EACA,oBAAA;AAAA,EACA,oBAAA;AAAA,EACA,oBAAA;AAAA,EACA,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,oBAAA;AAAA,EACA,kBAAA;AAAA,EACA,gBAAA;AAAA,EACA,gBAAA;AAAA,EACA,sBAAA;AAAA,EACA,sBAAA;AAAA,EACA,eAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF","file":"chunk-5C2OLUDR.js","sourcesContent":["export const rawTokens = [\n '--as-tone-0',\n '--as-tone-50',\n '--as-tone-100',\n '--as-tone-200',\n '--as-tone-300',\n '--as-tone-500',\n '--as-tone-700',\n '--as-tone-900',\n '--as-accent',\n '--as-accent-strong',\n '--as-success',\n '--as-warning',\n '--as-danger',\n '--as-ai',\n] as const\n\nexport const semanticTokens = [\n '--as-primary',\n '--as-primary-fg',\n '--as-secondary',\n '--as-secondary-fg',\n '--as-surface',\n '--as-surface-raised',\n '--as-border',\n '--as-fg',\n '--as-muted',\n '--as-muted-fg',\n '--as-success-fg',\n '--as-warning-fg',\n '--as-danger-fg',\n '--as-ai-fg',\n '--as-focus-ring',\n '--as-overlay',\n '--as-z-modal',\n '--as-z-popover',\n '--as-radius-sm',\n '--as-radius',\n '--as-radius-lg',\n '--as-duration-fast',\n '--as-duration-base',\n '--as-duration-slow',\n '--as-duration-spring',\n '--as-ease-out',\n '--as-ease-standard',\n '--as-ease-spring',\n '--as-font-sans',\n '--as-font-mono',\n '--as-scrollbar-thumb',\n '--as-scrollbar-track',\n '--as-shadow-1',\n '--as-shadow-2',\n '--as-shadow-3',\n] as const\n\nexport type RawTokenName = (typeof rawTokens)[number]\nexport type SemanticTokenName = (typeof semanticTokens)[number]\nexport type ThemeTokenName = SemanticTokenName | RawTokenName\nexport type ThemeTokens = Partial<Record<ThemeTokenName, string>>\n"]}
@@ -0,0 +1,479 @@
1
+ import { MarkdownSurface } from './chunk-KLCULTOQ.js';
2
+ import { ChatMessage } from './chunk-56QSIK4Y.js';
3
+ import { cn } from './chunk-RVVXVTH3.js';
4
+ import * as React2 from 'react';
5
+ import { Search, X, ChevronUp, ChevronDown, Square, ArrowDown } from 'lucide-react';
6
+ import { useVirtualizer } from '@tanstack/react-virtual';
7
+ import { format } from 'date-fns';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+
10
+ var ChatSearchField = React2.forwardRef(
11
+ function ChatSearchField2({ query, onQueryChange, matchCount, activeIndex, onPrevious, onNext, labels, className }, ref) {
12
+ const inputId = React2.useId();
13
+ const onKeyDown = (event) => {
14
+ if (event.key === "Escape") {
15
+ event.preventDefault();
16
+ onQueryChange("");
17
+ return;
18
+ }
19
+ if (event.key === "Enter") {
20
+ event.preventDefault();
21
+ if (event.shiftKey) {
22
+ onPrevious?.();
23
+ } else {
24
+ onNext?.();
25
+ }
26
+ }
27
+ };
28
+ const hasMatches = matchCount !== void 0 && matchCount > 0;
29
+ const readout = matchCount === void 0 ? null : hasMatches ? `${(activeIndex ?? 0) + 1}/${matchCount}` : "0";
30
+ return /* @__PURE__ */ jsxs(
31
+ "div",
32
+ {
33
+ ref,
34
+ "data-as": "chat-search-field",
35
+ className: cn(
36
+ "flex items-center gap-1 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1",
37
+ className
38
+ ),
39
+ children: [
40
+ /* @__PURE__ */ jsx(Search, { className: "size-3.5 shrink-0 text-[var(--as-muted-fg)]", "aria-hidden": true }),
41
+ /* @__PURE__ */ jsx(
42
+ "input",
43
+ {
44
+ id: inputId,
45
+ type: "text",
46
+ value: query,
47
+ placeholder: labels?.placeholder ?? "Search conversation",
48
+ "aria-label": labels?.search ?? "Search conversation",
49
+ onChange: (event) => onQueryChange(event.target.value),
50
+ onKeyDown,
51
+ className: "min-w-0 flex-1 bg-transparent text-xs text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-none"
52
+ }
53
+ ),
54
+ readout !== null ? /* @__PURE__ */ jsx(
55
+ "span",
56
+ {
57
+ "aria-label": labels?.matches ?? "matches",
58
+ className: "shrink-0 font-mono text-[10px] tabular-nums text-[var(--as-muted-fg)]",
59
+ children: readout
60
+ }
61
+ ) : null,
62
+ query !== "" ? /* @__PURE__ */ jsx(
63
+ "button",
64
+ {
65
+ type: "button",
66
+ "aria-label": labels?.clear ?? "Clear search",
67
+ onClick: () => onQueryChange(""),
68
+ className: "rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
69
+ children: /* @__PURE__ */ jsx(X, { className: "size-3.5", "aria-hidden": true })
70
+ }
71
+ ) : null,
72
+ /* @__PURE__ */ jsx(
73
+ "button",
74
+ {
75
+ type: "button",
76
+ "aria-label": labels?.previous ?? "Previous match",
77
+ disabled: !hasMatches,
78
+ onClick: onPrevious,
79
+ className: "rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
80
+ children: /* @__PURE__ */ jsx(ChevronUp, { className: "size-3.5", "aria-hidden": true })
81
+ }
82
+ ),
83
+ /* @__PURE__ */ jsx(
84
+ "button",
85
+ {
86
+ type: "button",
87
+ "aria-label": labels?.next ?? "Next match",
88
+ disabled: !hasMatches,
89
+ onClick: onNext,
90
+ className: "rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
91
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5", "aria-hidden": true })
92
+ }
93
+ )
94
+ ]
95
+ }
96
+ );
97
+ }
98
+ );
99
+ function toDate(value) {
100
+ if (value === void 0) {
101
+ return void 0;
102
+ }
103
+ if (value instanceof Date) {
104
+ return value;
105
+ }
106
+ if (typeof value === "number") {
107
+ return new Date(value);
108
+ }
109
+ const parsed = new Date(value);
110
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
111
+ }
112
+ function defaultRenderItem(message, design, query) {
113
+ return /* @__PURE__ */ jsx(
114
+ ChatMessage,
115
+ {
116
+ role: message.role,
117
+ status: message.status,
118
+ design,
119
+ content: /* @__PURE__ */ jsx(MarkdownSurface, { value: message.content, streaming: message.status === "streaming", query }),
120
+ variants: message.variants
121
+ }
122
+ );
123
+ }
124
+ var ChatTranscript = React2.forwardRef(
125
+ function ChatTranscript2({
126
+ items,
127
+ renderItem,
128
+ live,
129
+ emptyState,
130
+ autoScroll = true,
131
+ scrollThresholdPx = 120,
132
+ virtualized = false,
133
+ groupConsecutive = false,
134
+ design = "bubbles",
135
+ search,
136
+ showDateSeparators = false,
137
+ formatDate,
138
+ focusMessageId,
139
+ onStop,
140
+ labels,
141
+ className
142
+ }, ref) {
143
+ const scrollRef = React2.useRef(null);
144
+ const atBottomRef = React2.useRef(true);
145
+ const lastScrollTopRef = React2.useRef(0);
146
+ const lastTouchYRef = React2.useRef(null);
147
+ const [showJump, setShowJump] = React2.useState(false);
148
+ const [fadeTop, setFadeTop] = React2.useState(false);
149
+ const [unread, setUnread] = React2.useState(0);
150
+ const [announcement, setAnnouncement] = React2.useState("");
151
+ const previousCountRef = React2.useRef(-1);
152
+ const [searchActive, setSearchActive] = React2.useState(0);
153
+ const [flashId, setFlashId] = React2.useState(null);
154
+ const searchNeedle = search?.query.trim().toLowerCase() ?? "";
155
+ const searchMatches = React2.useMemo(() => {
156
+ if (searchNeedle === "") {
157
+ return [];
158
+ }
159
+ return items.map(
160
+ (message, index) => message.content.toLowerCase().includes(searchNeedle) ? index : -1
161
+ ).filter((index) => index >= 0);
162
+ }, [items, searchNeedle]);
163
+ React2.useEffect(() => {
164
+ setSearchActive(0);
165
+ if (searchMatches.length > 0) {
166
+ jumpToMatch(0);
167
+ }
168
+ }, [searchNeedle]);
169
+ React2.useEffect(() => {
170
+ if (flashId === null) {
171
+ return;
172
+ }
173
+ const id = window.setTimeout(() => setFlashId(null), 1200);
174
+ return () => window.clearTimeout(id);
175
+ }, [flashId]);
176
+ const jumpToMatch = (index) => {
177
+ if (searchMatches.length === 0) {
178
+ return;
179
+ }
180
+ const bounded = (index % searchMatches.length + searchMatches.length) % searchMatches.length;
181
+ setSearchActive(bounded);
182
+ const messageIndex = searchMatches[bounded];
183
+ const messageId = messageIndex !== void 0 ? items[messageIndex]?.id : void 0;
184
+ if (messageId === void 0) {
185
+ return;
186
+ }
187
+ setFlashId(messageId);
188
+ if (!virtualized) {
189
+ const row = scrollRef.current?.querySelector(`[data-message-id="${messageId}"]`);
190
+ row?.scrollIntoView({ block: "center", behavior: "smooth" });
191
+ }
192
+ };
193
+ React2.useEffect(() => {
194
+ if (focusMessageId === null || focusMessageId === void 0) {
195
+ return;
196
+ }
197
+ setFlashId(focusMessageId);
198
+ if (!virtualized) {
199
+ const row = scrollRef.current?.querySelector(`[data-message-id="${focusMessageId}"]`);
200
+ row?.scrollIntoView({ block: "center", behavior: "smooth" });
201
+ }
202
+ }, [focusMessageId, virtualized]);
203
+ const dayKey = (message) => {
204
+ if (message.createdAt === void 0) {
205
+ return null;
206
+ }
207
+ const date = toDate(message.createdAt);
208
+ return date === void 0 ? null : `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`;
209
+ };
210
+ const dayLabel = (message) => {
211
+ if (message.createdAt === void 0) {
212
+ return null;
213
+ }
214
+ const date = toDate(message.createdAt);
215
+ if (date === void 0) {
216
+ return null;
217
+ }
218
+ const now = /* @__PURE__ */ new Date();
219
+ const startOf = (value) => new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime();
220
+ const dayDiff = Math.round((startOf(now) - startOf(date)) / 864e5);
221
+ if (dayDiff === 0) {
222
+ return labels?.today ?? "Today";
223
+ }
224
+ if (dayDiff === 1) {
225
+ return labels?.yesterday ?? "Yesterday";
226
+ }
227
+ return formatDate !== void 0 ? formatDate(date) : format(date, "MMM d, yyyy");
228
+ };
229
+ const onScroll = () => {
230
+ const element = scrollRef.current;
231
+ if (element === null) {
232
+ return;
233
+ }
234
+ const distance = element.scrollHeight - element.scrollTop - element.clientHeight;
235
+ const goingUp = element.scrollTop < lastScrollTopRef.current;
236
+ lastScrollTopRef.current = element.scrollTop;
237
+ const atBottom = distance <= scrollThresholdPx && !(goingUp && distance > 0);
238
+ atBottomRef.current = atBottom;
239
+ setShowJump(element.scrollHeight > element.clientHeight && !atBottom);
240
+ setFadeTop(element.scrollTop > 8);
241
+ if (atBottom) {
242
+ setUnread(0);
243
+ }
244
+ };
245
+ const cancelStick = () => {
246
+ const element = scrollRef.current;
247
+ if (element === null) {
248
+ return;
249
+ }
250
+ atBottomRef.current = false;
251
+ setShowJump(element.scrollHeight > element.clientHeight);
252
+ };
253
+ const onWheel = (event) => {
254
+ if (event.deltaY < 0) {
255
+ cancelStick();
256
+ }
257
+ };
258
+ const onTouchStart = (event) => {
259
+ lastTouchYRef.current = event.touches[0]?.clientY ?? null;
260
+ };
261
+ const onTouchMove = (event) => {
262
+ const y = event.touches[0]?.clientY;
263
+ if (y === void 0) {
264
+ return;
265
+ }
266
+ if (lastTouchYRef.current !== null && y > lastTouchYRef.current) {
267
+ cancelStick();
268
+ }
269
+ lastTouchYRef.current = y;
270
+ };
271
+ const onKeyDown = (event) => {
272
+ if (event.key === "ArrowUp" || event.key === "PageUp" || event.key === "Home" || event.key === " " && event.shiftKey) {
273
+ cancelStick();
274
+ }
275
+ };
276
+ React2.useEffect(() => {
277
+ const element = scrollRef.current;
278
+ if (element === null || !autoScroll || !atBottomRef.current) {
279
+ return;
280
+ }
281
+ const frame = requestAnimationFrame(() => {
282
+ element.scrollTop = element.scrollHeight;
283
+ });
284
+ return () => cancelAnimationFrame(frame);
285
+ });
286
+ React2.useEffect(() => {
287
+ if (previousCountRef.current === -1) {
288
+ previousCountRef.current = items.length;
289
+ return;
290
+ }
291
+ if (items.length > previousCountRef.current) {
292
+ const added = items.slice(previousCountRef.current);
293
+ previousCountRef.current = items.length;
294
+ const assistantAdded = added.filter((message) => message.role !== "user").length;
295
+ if (assistantAdded > 0 && !atBottomRef.current) {
296
+ setUnread((count) => count + assistantAdded);
297
+ }
298
+ if (added[added.length - 1]?.role !== "user") {
299
+ setAnnouncement(labels?.replied ?? "Assistant replied");
300
+ }
301
+ return;
302
+ }
303
+ previousCountRef.current = items.length;
304
+ }, [items, labels]);
305
+ const scrollToBottom = () => {
306
+ const element = scrollRef.current;
307
+ if (element !== null) {
308
+ setUnread(0);
309
+ element.scrollTo({ top: element.scrollHeight, behavior: "smooth" });
310
+ }
311
+ };
312
+ const groupPos = (index) => {
313
+ const role = items[index]?.role;
314
+ if (role === void 0 || role === "system") {
315
+ return "single";
316
+ }
317
+ const prevSame = index > 0 && items[index - 1]?.role === role && items[index - 1]?.role !== "system";
318
+ const nextSame = index < items.length - 1 && items[index + 1]?.role === role && items[index + 1]?.role !== "system";
319
+ if (prevSame && nextSame) {
320
+ return "mid";
321
+ }
322
+ if (prevSame) {
323
+ return "end";
324
+ }
325
+ if (nextSame) {
326
+ return "start";
327
+ }
328
+ return "single";
329
+ };
330
+ const renderRow = (message, index) => {
331
+ const pos = groupConsecutive ? groupPos(index) : void 0;
332
+ const node = renderItem ? renderItem(message, index, pos, searchNeedle === "" ? void 0 : search?.query.trim()) : defaultRenderItem(message, design, searchNeedle === "" ? void 0 : search?.query.trim());
333
+ const grouped = pos === "mid" || pos === "end";
334
+ return /* @__PURE__ */ jsx(
335
+ "div",
336
+ {
337
+ "data-message-id": message.id,
338
+ "data-group-pos": pos,
339
+ "data-search-flash": message.id === flashId || void 0,
340
+ className: cn(groupConsecutive && grouped && "-mt-2"),
341
+ children: node
342
+ },
343
+ message.id
344
+ );
345
+ };
346
+ const rowCount = items.length + (live !== void 0 ? 1 : 0);
347
+ const virtualizer = useVirtualizer({
348
+ count: virtualized ? rowCount : 0,
349
+ getScrollElement: () => scrollRef.current,
350
+ estimateSize: () => 120,
351
+ overscan: 8,
352
+ initialRect: { width: 480, height: 600 }
353
+ });
354
+ const jumpLabel = labels?.scrollToBottom ?? "Scroll to latest";
355
+ const jumpFullLabel = unread > 0 ? `${jumpLabel} \u2014 ${unread} ${labels?.newMessages ?? "new messages"}` : jumpLabel;
356
+ return /* @__PURE__ */ jsxs(
357
+ "div",
358
+ {
359
+ ref,
360
+ "data-as": "chat-transcript",
361
+ "data-grouped": groupConsecutive || void 0,
362
+ "data-fade-top": fadeTop || void 0,
363
+ className: cn("relative flex min-h-0 flex-1 flex-col", className),
364
+ children: [
365
+ search ? /* @__PURE__ */ jsx(
366
+ ChatSearchField,
367
+ {
368
+ query: search.query,
369
+ onQueryChange: search.onQueryChange,
370
+ matchCount: searchMatches.length,
371
+ activeIndex: searchActive,
372
+ onPrevious: () => jumpToMatch(searchActive - 1),
373
+ onNext: () => jumpToMatch(searchActive + 1),
374
+ labels: labels ? {
375
+ search: labels.search,
376
+ placeholder: labels.searchPlaceholder,
377
+ previous: labels.previousMatch,
378
+ next: labels.nextMatch,
379
+ clear: labels.clearSearch,
380
+ matches: labels.matches
381
+ } : void 0,
382
+ className: "mx-1 mb-1 shrink-0"
383
+ }
384
+ ) : null,
385
+ /* @__PURE__ */ jsx(
386
+ "div",
387
+ {
388
+ ref: scrollRef,
389
+ onScroll,
390
+ onWheel,
391
+ onTouchStart,
392
+ onTouchMove,
393
+ onKeyDown,
394
+ role: "log",
395
+ "aria-label": labels?.log ?? "Conversation",
396
+ "aria-busy": live !== void 0,
397
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain px-1 py-2",
398
+ children: items.length === 0 && live === void 0 ? /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-40 items-center justify-center p-4 text-sm text-[var(--as-muted-fg)]", children: emptyState ?? (labels?.empty ?? "Start the conversation") }) : virtualized ? /* @__PURE__ */ jsx("div", { style: { height: virtualizer.getTotalSize(), position: "relative" }, children: virtualizer.getVirtualItems().map((row) => {
399
+ const isLive = row.index >= items.length;
400
+ const node = isLive ? live : renderRow(items[row.index], row.index);
401
+ return /* @__PURE__ */ jsx(
402
+ "div",
403
+ {
404
+ "data-index": row.index,
405
+ ref: virtualizer.measureElement,
406
+ style: { position: "absolute", top: 0, left: 0, width: "100%", transform: `translateY(${row.start}px)` },
407
+ className: "px-2 py-1.5",
408
+ children: node
409
+ },
410
+ row.key
411
+ );
412
+ }) }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 px-2", children: [
413
+ items.map((message, index) => {
414
+ const rows = [];
415
+ if (showDateSeparators && index > 0) {
416
+ const key = dayKey(message);
417
+ const previousKey = dayKey(items[index - 1]);
418
+ if (key !== null && key !== previousKey) {
419
+ const label = dayLabel(message);
420
+ if (label !== null) {
421
+ rows.push(
422
+ /* @__PURE__ */ jsxs("div", { "data-as": "chat-date-separator", className: "flex items-center gap-2 py-1", role: "separator", "aria-label": label, children: [
423
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-[var(--as-border)]", "aria-hidden": true }),
424
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium uppercase tracking-wide text-[var(--as-muted-fg)]", children: label }),
425
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-[var(--as-border)]", "aria-hidden": true })
426
+ ] }, `day-${key}-${message.id}`)
427
+ );
428
+ }
429
+ }
430
+ }
431
+ rows.push(renderRow(message, index));
432
+ return /* @__PURE__ */ jsx(React2.Fragment, { children: rows }, message.id);
433
+ }),
434
+ live !== void 0 ? /* @__PURE__ */ jsx("div", { "data-as": "chat-transcript-live", children: live }) : null
435
+ ] })
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "sr-only", children: announcement }),
439
+ showJump && live !== void 0 && onStop !== void 0 ? /* @__PURE__ */ jsx(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ onClick: onStop,
444
+ "aria-label": labels?.stop ?? "Stop generating",
445
+ title: labels?.stop ?? "Stop generating",
446
+ className: "as-anim-slide-up absolute bottom-3 right-3 flex size-8 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)]",
447
+ children: /* @__PURE__ */ jsx(Square, { className: "size-3.5", "aria-hidden": true })
448
+ }
449
+ ) : null,
450
+ showJump ? /* @__PURE__ */ jsxs(
451
+ "button",
452
+ {
453
+ type: "button",
454
+ onClick: scrollToBottom,
455
+ "aria-label": jumpFullLabel,
456
+ title: jumpFullLabel,
457
+ className: "as-anim-slide-up absolute bottom-3 left-1/2 -ml-4 flex size-8 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)]",
458
+ children: [
459
+ /* @__PURE__ */ jsx(ArrowDown, { className: "size-4", "aria-hidden": true }),
460
+ unread > 0 ? /* @__PURE__ */ jsx(
461
+ "span",
462
+ {
463
+ "aria-hidden": true,
464
+ className: "absolute -right-1.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--as-primary)] px-1 text-[10px] font-semibold leading-none text-[var(--as-primary-fg)]",
465
+ children: unread > 9 ? "9+" : unread
466
+ }
467
+ ) : null
468
+ ]
469
+ }
470
+ ) : null
471
+ ]
472
+ }
473
+ );
474
+ }
475
+ );
476
+
477
+ export { ChatSearchField, ChatTranscript };
478
+ //# sourceMappingURL=chunk-E2NNTHFB.js.map
479
+ //# sourceMappingURL=chunk-E2NNTHFB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/chat-transcript/ChatSearchField.tsx","../src/components/chat-transcript/ChatTranscript.tsx"],"names":["React","ChatSearchField","jsx","ChatTranscript","jsxs"],"mappings":"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","file":"chunk-E2NNTHFB.js","sourcesContent":["import * as React from 'react'\nimport { ChevronDown, ChevronUp, Search, X } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\n\nexport interface ChatSearchFieldLabels {\n search: string\n placeholder: string\n previous: string\n next: string\n clear: string\n matches: string\n}\n\nexport interface ChatSearchFieldProps {\n query: string\n onQueryChange: (query: string) => void\n /** Total matches in the transcript (drives the `n/N` readout). */\n matchCount?: number\n /** 0-based active match index. */\n activeIndex?: number\n onPrevious?: () => void\n onNext?: () => void\n labels?: Partial<ChatSearchFieldLabels>\n className?: string\n}\n\n/**\n * The in-conversation search field (plan 101 P2): controlled query input\n * with an `n/N` match readout and prev/next controls. Enter jumps to the\n * next match, Shift+Enter to the previous, Escape clears. Composed by\n * `ChatTranscript` when its `search` prop is given, or standalone.\n */\nexport const ChatSearchField = React.forwardRef<HTMLDivElement, ChatSearchFieldProps>(\n function ChatSearchField(\n { query, onQueryChange, matchCount, activeIndex, onPrevious, onNext, labels, className },\n ref,\n ) {\n const inputId = React.useId()\n\n const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {\n if (event.key === 'Escape') {\n event.preventDefault()\n onQueryChange('')\n return\n }\n if (event.key === 'Enter') {\n event.preventDefault()\n if (event.shiftKey) {\n onPrevious?.()\n } else {\n onNext?.()\n }\n }\n }\n\n const hasMatches = matchCount !== undefined && matchCount > 0\n const readout =\n matchCount === undefined\n ? null\n : hasMatches\n ? `${(activeIndex ?? 0) + 1}/${matchCount}`\n : '0'\n\n return (\n <div\n ref={ref}\n data-as=\"chat-search-field\"\n className={cn(\n 'flex items-center gap-1 rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1',\n className,\n )}\n >\n <Search className=\"size-3.5 shrink-0 text-[var(--as-muted-fg)]\" aria-hidden />\n <input\n id={inputId}\n type=\"text\"\n value={query}\n placeholder={labels?.placeholder ?? 'Search conversation'}\n aria-label={labels?.search ?? 'Search conversation'}\n onChange={(event) => onQueryChange(event.target.value)}\n onKeyDown={onKeyDown}\n className=\"min-w-0 flex-1 bg-transparent text-xs text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-none\"\n />\n {readout !== null ? (\n <span\n aria-label={labels?.matches ?? 'matches'}\n className=\"shrink-0 font-mono text-[10px] tabular-nums text-[var(--as-muted-fg)]\"\n >\n {readout}\n </span>\n ) : null}\n {query !== '' ? (\n <button\n type=\"button\"\n aria-label={labels?.clear ?? 'Clear search'}\n onClick={() => onQueryChange('')}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <X className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n <button\n type=\"button\"\n aria-label={labels?.previous ?? 'Previous match'}\n disabled={!hasMatches}\n onClick={onPrevious}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronUp className=\"size-3.5\" aria-hidden />\n </button>\n <button\n type=\"button\"\n aria-label={labels?.next ?? 'Next match'}\n disabled={!hasMatches}\n onClick={onNext}\n className=\"rounded-[var(--as-radius-sm)] p-0.5 text-[var(--as-muted-fg)] transition-colors hover:text-[var(--as-fg)] disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <ChevronDown className=\"size-3.5\" aria-hidden />\n </button>\n </div>\n )\n },\n)\n","import * as React from 'react'\nimport { ArrowDown } from 'lucide-react'\nimport { useVirtualizer } from '@tanstack/react-virtual'\n\nimport { cn } from '../../lib/utils'\nimport { format } from 'date-fns'\nimport { Square } from 'lucide-react'\nimport { ChatMessage } from '../chat-message/ChatMessage'\nimport { MarkdownSurface } from '../chat-markdown/MarkdownSurface'\nimport type { ChatMessageView } from '../chat-core/types'\nimport { ChatSearchField, type ChatSearchFieldLabels } from './ChatSearchField'\n\nfunction toDate(value: string | number | Date | undefined): Date | undefined {\n if (value === undefined) {\n return undefined\n }\n if (value instanceof Date) {\n return value\n }\n if (typeof value === 'number') {\n return new Date(value)\n }\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? undefined : parsed\n}\n\nexport interface ChatTranscriptLabels {\n log: string\n scrollToBottom: string\n newMessages: string\n empty: string\n replied: string\n search: string\n searchPlaceholder: string\n previousMatch: string\n nextMatch: string\n clearSearch: string\n matches: string\n today: string\n yesterday: string\n stop: string\n}\n\nexport type ChatTranscriptGroupPos = 'single' | 'start' | 'mid' | 'end'\n\nexport interface ChatTranscriptSearch {\n query: string\n onQueryChange: (query: string) => void\n}\n\nexport interface ChatTranscriptProps {\n items: ChatMessageView[]\n /** Override the default `ChatMessage` + `MarkdownSurface` rendering (third arg: group position when `groupConsecutive`; fourth arg: active search query). */\n renderItem?: (message: ChatMessageView, index: number, groupPos?: ChatTranscriptGroupPos, query?: string) => React.ReactNode\n /** Streaming tail — the live turn bubble (or `ChatPending`) 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 /**\n * Tighten the spacing between consecutive same-role messages and tag\n * each row with `data-group-pos` (`single | start | mid | end`).\n * Default false.\n */\n groupConsecutive?: boolean\n /** Visual design for the default renderer: paired bubbles (default) or flat assistant prose over the panel surface. */\n design?: 'bubbles' | 'flat'\n /**\n * In-conversation search (plan 101 P2): renders `ChatSearchField`\n * above the log, highlights matches in the default renderer, and\n * jumps between them (Enter/Shift+Enter in the field, or the\n * prev/next buttons). Jump-scrolling requires a non-virtualized\n * transcript; virtualized transcripts highlight visible rows only.\n */\n search?: ChatTranscriptSearch\n /**\n * Today/Yesterday/date chips between day groups (reads\n * `ChatMessageView.createdAt`; rows without one join the previous\n * group). Default false.\n */\n showDateSeparators?: boolean\n /** Custom formatter for older-day chips (default `MMM d, yyyy`). */\n formatDate?: (date: Date) => string\n /** Scroll a message into view and flash it when this changes (branch-tree clicks, citations, deep links). */\n focusMessageId?: string | null\n /** Show a floating stop pill beside the jump pill while a live turn streams and the user scrolled away. */\n onStop?: () => void\n labels?: Partial<ChatTranscriptLabels & ChatSearchFieldLabels>\n className?: string\n}\n\nfunction defaultRenderItem(message: ChatMessageView, design: 'bubbles' | 'flat', query?: string) {\n return (\n <ChatMessage\n role={message.role}\n status={message.status}\n design={design}\n content={<MarkdownSurface value={message.content} streaming={message.status === 'streaming'} query={query} />}\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 an unread-counting\n * jump-to-latest pill, optional consecutive-message grouping, a top\n * fade mask while scrolled into the thread, thin themed scrollbars,\n * and optional virtualization for thousand-message threads.\n */\nexport const ChatTranscript = React.forwardRef<HTMLDivElement, ChatTranscriptProps>(\n function ChatTranscript(\n {\n items,\n renderItem,\n live,\n emptyState,\n autoScroll = true,\n scrollThresholdPx = 120,\n virtualized = false,\n groupConsecutive = false,\n design = 'bubbles',\n search,\n showDateSeparators = false,\n formatDate,\n focusMessageId,\n onStop,\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 [fadeTop, setFadeTop] = React.useState(false)\n const [unread, setUnread] = React.useState(0)\n const [announcement, setAnnouncement] = React.useState('')\n const previousCountRef = React.useRef(-1)\n const [searchActive, setSearchActive] = React.useState(0)\n const [flashId, setFlashId] = React.useState<string | null>(null)\n\n const searchNeedle = search?.query.trim().toLowerCase() ?? ''\n const searchMatches = React.useMemo(() => {\n if (searchNeedle === '') {\n return []\n }\n return items\n .map((message, index) =>\n message.content.toLowerCase().includes(searchNeedle) ? index : -1,\n )\n .filter((index) => index >= 0)\n }, [items, searchNeedle])\n\n React.useEffect(() => {\n setSearchActive(0)\n if (searchMatches.length > 0) {\n jumpToMatch(0)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [searchNeedle])\n\n React.useEffect(() => {\n if (flashId === null) {\n return\n }\n const id = window.setTimeout(() => setFlashId(null), 1200)\n return () => window.clearTimeout(id)\n }, [flashId])\n\n const jumpToMatch = (index: number) => {\n if (searchMatches.length === 0) {\n return\n }\n const bounded = ((index % searchMatches.length) + searchMatches.length) % searchMatches.length\n setSearchActive(bounded)\n const messageIndex = searchMatches[bounded]\n const messageId = messageIndex !== undefined ? items[messageIndex]?.id : undefined\n if (messageId === undefined) {\n return\n }\n setFlashId(messageId)\n if (!virtualized) {\n const row = scrollRef.current?.querySelector(`[data-message-id=\"${messageId}\"]`)\n row?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n }\n\n React.useEffect(() => {\n if (focusMessageId === null || focusMessageId === undefined) {\n return\n }\n setFlashId(focusMessageId)\n if (!virtualized) {\n const row = scrollRef.current?.querySelector(`[data-message-id=\"${focusMessageId}\"]`)\n row?.scrollIntoView({ block: 'center', behavior: 'smooth' })\n }\n }, [focusMessageId, virtualized])\n\n const dayKey = (message: ChatMessageView): string | null => {\n if (message.createdAt === undefined) {\n return null\n }\n const date = toDate(message.createdAt)\n return date === undefined\n ? null\n : `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}`\n }\n\n const dayLabel = (message: ChatMessageView): string | null => {\n if (message.createdAt === undefined) {\n return null\n }\n const date = toDate(message.createdAt)\n if (date === undefined) {\n return null\n }\n const now = new Date()\n const startOf = (value: Date) => new Date(value.getFullYear(), value.getMonth(), value.getDate()).getTime()\n const dayDiff = Math.round((startOf(now) - startOf(date)) / 86_400_000)\n if (dayDiff === 0) {\n return labels?.today ?? 'Today'\n }\n if (dayDiff === 1) {\n return labels?.yesterday ?? 'Yesterday'\n }\n return formatDate !== undefined ? formatDate(date) : format(date, 'MMM d, yyyy')\n }\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 setFadeTop(element.scrollTop > 8)\n if (atBottom) {\n setUnread(0)\n }\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.slice(previousCountRef.current)\n previousCountRef.current = items.length\n const assistantAdded = added.filter((message) => message.role !== 'user').length\n if (assistantAdded > 0 && !atBottomRef.current) {\n setUnread((count) => count + assistantAdded)\n }\n if (added[added.length - 1]?.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 setUnread(0)\n element.scrollTo({ top: element.scrollHeight, behavior: 'smooth' })\n }\n }\n\n const groupPos = (index: number): ChatTranscriptGroupPos => {\n const role = items[index]?.role\n if (role === undefined || role === 'system') {\n return 'single'\n }\n const prevSame = index > 0 && items[index - 1]?.role === role && items[index - 1]?.role !== 'system'\n const nextSame = index < items.length - 1 && items[index + 1]?.role === role && items[index + 1]?.role !== 'system'\n if (prevSame && nextSame) {\n return 'mid'\n }\n if (prevSame) {\n return 'end'\n }\n if (nextSame) {\n return 'start'\n }\n return 'single'\n }\n\n const renderRow = (message: ChatMessageView, index: number) => {\n const pos = groupConsecutive ? groupPos(index) : undefined\n const node = renderItem\n ? renderItem(message, index, pos, searchNeedle === '' ? undefined : search?.query.trim())\n : defaultRenderItem(message, design, searchNeedle === '' ? undefined : search?.query.trim())\n const grouped = pos === 'mid' || pos === 'end'\n return (\n <div\n key={message.id}\n data-message-id={message.id}\n data-group-pos={pos}\n data-search-flash={message.id === flashId || undefined}\n className={cn(groupConsecutive && grouped && '-mt-2')}\n >\n {node}\n </div>\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 const jumpLabel = labels?.scrollToBottom ?? 'Scroll to latest'\n const jumpFullLabel =\n unread > 0 ? `${jumpLabel} — ${unread} ${labels?.newMessages ?? 'new messages'}` : jumpLabel\n\n return (\n <div\n ref={ref}\n data-as=\"chat-transcript\"\n data-grouped={groupConsecutive || undefined}\n data-fade-top={fadeTop || undefined}\n className={cn('relative flex min-h-0 flex-1 flex-col', className)}\n >\n {search ? (\n <ChatSearchField\n query={search.query}\n onQueryChange={search.onQueryChange}\n matchCount={searchMatches.length}\n activeIndex={searchActive}\n onPrevious={() => jumpToMatch(searchActive - 1)}\n onNext={() => jumpToMatch(searchActive + 1)}\n labels={\n labels\n ? ({\n search: labels.search,\n placeholder: labels.searchPlaceholder,\n previous: labels.previousMatch,\n next: labels.nextMatch,\n clear: labels.clearSearch,\n matches: labels.matches,\n } satisfies Partial<ChatSearchFieldLabels>)\n : undefined\n }\n className=\"mx-1 mb-1 shrink-0\"\n />\n ) : null}\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=\"min-h-0 flex-1 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 : renderRow(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 const rows: React.ReactNode[] = []\n if (showDateSeparators && index > 0) {\n const key = dayKey(message)\n const previousKey = dayKey(items[index - 1]!)\n if (key !== null && key !== previousKey) {\n const label = dayLabel(message)\n if (label !== null) {\n rows.push(\n <div key={`day-${key}-${message.id}`} data-as=\"chat-date-separator\" className=\"flex items-center gap-2 py-1\" role=\"separator\" aria-label={label}>\n <span className=\"h-px flex-1 bg-[var(--as-border)]\" aria-hidden />\n <span className=\"text-[10px] font-medium uppercase tracking-wide text-[var(--as-muted-fg)]\">{label}</span>\n <span className=\"h-px flex-1 bg-[var(--as-border)]\" aria-hidden />\n </div>,\n )\n }\n }\n }\n rows.push(renderRow(message, index))\n return <React.Fragment key={message.id}>{rows}</React.Fragment>\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 && live !== undefined && onStop !== undefined ? (\n <button\n type=\"button\"\n onClick={onStop}\n aria-label={labels?.stop ?? 'Stop generating'}\n title={labels?.stop ?? 'Stop generating'}\n className=\"as-anim-slide-up absolute bottom-3 right-3 flex size-8 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 <Square className=\"size-3.5\" aria-hidden />\n </button>\n ) : null}\n {showJump ? (\n <button\n type=\"button\"\n onClick={scrollToBottom}\n aria-label={jumpFullLabel}\n title={jumpFullLabel}\n className=\"as-anim-slide-up absolute bottom-3 left-1/2 -ml-4 flex size-8 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 {unread > 0 ? (\n <span\n aria-hidden\n className=\"absolute -right-1.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-[var(--as-primary)] px-1 text-[10px] font-semibold leading-none text-[var(--as-primary-fg)]\"\n >\n {unread > 9 ? '9+' : unread}\n </span>\n ) : null}\n </button>\n ) : null}\n </div>\n )\n },\n)\n"]}