@nextclaw/agent-chat-ui 0.1.1 → 0.2.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 (3) hide show
  1. package/dist/index.d.ts +50 -13
  2. package/dist/index.js +1387 -257
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,40 +1,5 @@
1
- // src/components/chat/ui/chat-input-bar/chat-input-bar-textarea.tsx
2
- import { jsx } from "react/jsx-runtime";
3
- function ChatInputBarTextarea(props) {
4
- return /* @__PURE__ */ jsx(
5
- "textarea",
6
- {
7
- value: props.value,
8
- onChange: (event) => props.onValueChange(event.target.value),
9
- disabled: props.disabled,
10
- onKeyDown: props.onKeyDown,
11
- placeholder: props.placeholder,
12
- className: "w-full min-h-[68px] max-h-[220px] resize-y bg-transparent px-4 py-3 text-sm text-gray-800 outline-none placeholder:text-gray-400"
13
- }
14
- );
15
- }
16
-
17
- // src/components/chat/ui/chat-input-bar/chat-input-bar-selected-items.tsx
18
- import { X } from "lucide-react";
19
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
20
- function ChatInputBarSelectedItems(props) {
21
- if (props.items.length === 0) {
22
- return null;
23
- }
24
- return /* @__PURE__ */ jsx2("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsx2("div", { className: "flex flex-wrap items-center gap-2", children: props.items.map((item) => /* @__PURE__ */ jsxs(
25
- "button",
26
- {
27
- type: "button",
28
- onClick: () => props.onRemove(item.key),
29
- className: "inline-flex max-w-[200px] items-center gap-1.5 rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary",
30
- children: [
31
- /* @__PURE__ */ jsx2("span", { className: "truncate", children: item.label }),
32
- /* @__PURE__ */ jsx2(X, { className: "h-3 w-3 shrink-0" })
33
- ]
34
- },
35
- item.key
36
- )) }) });
37
- }
1
+ // src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
2
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState4 } from "react";
38
3
 
39
4
  // src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
40
5
  import { useMemo, useRef as useRef2 } from "react";
@@ -92,8 +57,8 @@ function cn(...inputs) {
92
57
  }
93
58
 
94
59
  // src/components/chat/default-skin/input.tsx
95
- import { jsx as jsx3 } from "react/jsx-runtime";
96
- var ChatInput = React.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsx3(
60
+ import { jsx } from "react/jsx-runtime";
61
+ var ChatInput = React.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */ jsx(
97
62
  "input",
98
63
  {
99
64
  type,
@@ -110,11 +75,11 @@ ChatInput.displayName = "ChatInput";
110
75
  // src/components/chat/default-skin/popover.tsx
111
76
  import * as React2 from "react";
112
77
  import * as PopoverPrimitive from "@radix-ui/react-popover";
113
- import { jsx as jsx4 } from "react/jsx-runtime";
78
+ import { jsx as jsx2 } from "react/jsx-runtime";
114
79
  var ChatPopover = PopoverPrimitive.Root;
115
80
  var ChatPopoverTrigger = PopoverPrimitive.Trigger;
116
81
  var ChatPopoverAnchor = PopoverPrimitive.Anchor;
117
- var ChatPopoverContent = React2.forwardRef(({ className, sideOffset = 8, align = "start", ...props }, ref) => /* @__PURE__ */ jsx4(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
82
+ var ChatPopoverContent = React2.forwardRef(({ className, sideOffset = 8, align = "start", ...props }, ref) => /* @__PURE__ */ jsx2(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx2(
118
83
  PopoverPrimitive.Content,
119
84
  {
120
85
  ref,
@@ -133,10 +98,10 @@ ChatPopoverContent.displayName = PopoverPrimitive.Content.displayName;
133
98
  import * as React3 from "react";
134
99
  import * as SelectPrimitive from "@radix-ui/react-select";
135
100
  import { Check, ChevronDown, ChevronUp } from "lucide-react";
136
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
101
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
137
102
  var ChatSelect = SelectPrimitive.Root;
138
103
  var ChatSelectValue = SelectPrimitive.Value;
139
- var ChatSelectTrigger = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(
104
+ var ChatSelectTrigger = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
140
105
  SelectPrimitive.Trigger,
141
106
  {
142
107
  ref,
@@ -147,16 +112,16 @@ var ChatSelectTrigger = React3.forwardRef(({ className, children, ...props }, re
147
112
  ...props,
148
113
  children: [
149
114
  children,
150
- /* @__PURE__ */ jsx5(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx5(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
115
+ /* @__PURE__ */ jsx3(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx3(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
151
116
  ]
152
117
  }
153
118
  ));
154
119
  ChatSelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
155
- var ChatSelectScrollUpButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(SelectPrimitive.ScrollUpButton, { ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: /* @__PURE__ */ jsx5(ChevronUp, { className: "h-4 w-4" }) }));
120
+ var ChatSelectScrollUpButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx3(SelectPrimitive.ScrollUpButton, { ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: /* @__PURE__ */ jsx3(ChevronUp, { className: "h-4 w-4" }) }));
156
121
  ChatSelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
157
- var ChatSelectScrollDownButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(SelectPrimitive.ScrollDownButton, { ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: /* @__PURE__ */ jsx5(ChevronDown, { className: "h-4 w-4" }) }));
122
+ var ChatSelectScrollDownButton = React3.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx3(SelectPrimitive.ScrollDownButton, { ref, className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: /* @__PURE__ */ jsx3(ChevronDown, { className: "h-4 w-4" }) }));
158
123
  ChatSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
159
- var ChatSelectContent = React3.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx5(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs2(
124
+ var ChatSelectContent = React3.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx3(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
160
125
  SelectPrimitive.Content,
161
126
  {
162
127
  ref,
@@ -168,20 +133,20 @@ var ChatSelectContent = React3.forwardRef(({ className, children, position = "po
168
133
  position,
169
134
  ...props,
170
135
  children: [
171
- /* @__PURE__ */ jsx5(ChatSelectScrollUpButton, {}),
172
- /* @__PURE__ */ jsx5(
136
+ /* @__PURE__ */ jsx3(ChatSelectScrollUpButton, {}),
137
+ /* @__PURE__ */ jsx3(
173
138
  SelectPrimitive.Viewport,
174
139
  {
175
140
  className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
176
141
  children
177
142
  }
178
143
  ),
179
- /* @__PURE__ */ jsx5(ChatSelectScrollDownButton, {})
144
+ /* @__PURE__ */ jsx3(ChatSelectScrollDownButton, {})
180
145
  ]
181
146
  }
182
147
  ) }));
183
148
  ChatSelectContent.displayName = SelectPrimitive.Content.displayName;
184
- var ChatSelectItem = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(
149
+ var ChatSelectItem = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
185
150
  SelectPrimitive.Item,
186
151
  {
187
152
  ref,
@@ -191,8 +156,8 @@ var ChatSelectItem = React3.forwardRef(({ className, children, ...props }, ref)
191
156
  ),
192
157
  ...props,
193
158
  children: [
194
- /* @__PURE__ */ jsx5("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx5(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx5(Check, { className: "h-4 w-4" }) }) }),
195
- /* @__PURE__ */ jsx5(SelectPrimitive.ItemText, { children })
159
+ /* @__PURE__ */ jsx3("span", { className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx3(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx3(Check, { className: "h-4 w-4" }) }) }),
160
+ /* @__PURE__ */ jsx3(SelectPrimitive.ItemText, { children })
196
161
  ]
197
162
  }
198
163
  ));
@@ -201,11 +166,11 @@ ChatSelectItem.displayName = SelectPrimitive.Item.displayName;
201
166
  // src/components/chat/default-skin/tooltip.tsx
202
167
  import * as React4 from "react";
203
168
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
204
- import { jsx as jsx6 } from "react/jsx-runtime";
169
+ import { jsx as jsx4 } from "react/jsx-runtime";
205
170
  var ChatTooltipProvider = TooltipPrimitive.Provider;
206
171
  var ChatTooltip = TooltipPrimitive.Root;
207
172
  var ChatTooltipTrigger = TooltipPrimitive.Trigger;
208
- var ChatTooltipContent = React4.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx6(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx6(
173
+ var ChatTooltipContent = React4.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx4(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
209
174
  TooltipPrimitive.Content,
210
175
  {
211
176
  ref,
@@ -238,7 +203,7 @@ var ChatUiPrimitives = {
238
203
  };
239
204
 
240
205
  // src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
241
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
206
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
242
207
  var SLASH_PANEL_MAX_WIDTH = 680;
243
208
  var SLASH_PANEL_DESKTOP_SHRINK_RATIO = 0.82;
244
209
  var SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
@@ -273,9 +238,9 @@ function ChatSlashMenu(props) {
273
238
  isEnabled: isOpen && !isLoading,
274
239
  getItemSelector: (index) => `[data-slash-index="${index}"]`
275
240
  });
276
- return /* @__PURE__ */ jsxs3(Popover, { open: isOpen, onOpenChange, children: [
277
- /* @__PURE__ */ jsx7(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx7("div", { ref: anchorRef, className: "pointer-events-none absolute bottom-full left-3 right-3 h-0" }) }),
278
- /* @__PURE__ */ jsx7(
241
+ return /* @__PURE__ */ jsxs2(Popover, { open: isOpen, onOpenChange, children: [
242
+ /* @__PURE__ */ jsx5(PopoverAnchor, { asChild: true, children: /* @__PURE__ */ jsx5("div", { ref: anchorRef, className: "pointer-events-none absolute bottom-full left-3 right-3 h-0" }) }),
243
+ /* @__PURE__ */ jsx5(
279
244
  PopoverContent,
280
245
  {
281
246
  side: "top",
@@ -284,19 +249,19 @@ function ChatSlashMenu(props) {
284
249
  className: "z-[70] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-2xl border border-gray-200 bg-white/95 p-0 shadow-2xl backdrop-blur-md",
285
250
  onOpenAutoFocus: (event) => event.preventDefault(),
286
251
  style: resolvedWidth ? { width: `${resolvedWidth}px` } : void 0,
287
- children: /* @__PURE__ */ jsxs3("div", { className: "grid min-h-[240px] grid-cols-[minmax(220px,300px)_minmax(0,1fr)]", children: [
288
- /* @__PURE__ */ jsx7(
252
+ children: /* @__PURE__ */ jsxs2("div", { className: "grid min-h-[240px] grid-cols-[minmax(220px,300px)_minmax(0,1fr)]", children: [
253
+ /* @__PURE__ */ jsx5(
289
254
  "div",
290
255
  {
291
256
  ref: listRef,
292
257
  role: "listbox",
293
258
  "aria-label": texts.slashSectionLabel,
294
259
  className: "custom-scrollbar max-h-[320px] overflow-y-auto border-r border-gray-200 p-2.5",
295
- children: isLoading ? /* @__PURE__ */ jsx7("div", { className: "p-2 text-xs text-gray-500", children: texts.slashLoadingLabel }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
296
- /* @__PURE__ */ jsx7("div", { className: "mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500", children: texts.slashSectionLabel }),
297
- items.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "px-2 text-xs text-gray-400", children: texts.slashEmptyLabel }) : /* @__PURE__ */ jsx7("div", { className: "space-y-1", children: items.map((item, index) => {
260
+ children: isLoading ? /* @__PURE__ */ jsx5("div", { className: "p-2 text-xs text-gray-500", children: texts.slashLoadingLabel }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
261
+ /* @__PURE__ */ jsx5("div", { className: "mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500", children: texts.slashSectionLabel }),
262
+ items.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "px-2 text-xs text-gray-400", children: texts.slashEmptyLabel }) : /* @__PURE__ */ jsx5("div", { className: "space-y-1", children: items.map((item, index) => {
298
263
  const isActive = index === activeIndex;
299
- return /* @__PURE__ */ jsxs3(
264
+ return /* @__PURE__ */ jsxs2(
300
265
  "button",
301
266
  {
302
267
  type: "button",
@@ -307,8 +272,8 @@ function ChatSlashMenu(props) {
307
272
  onClick: () => onSelectItem(item),
308
273
  className: `flex w-full items-start gap-2 rounded-lg px-2 py-1.5 text-left transition ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-50"}`,
309
274
  children: [
310
- /* @__PURE__ */ jsx7("span", { className: "truncate text-xs font-semibold", children: item.title }),
311
- /* @__PURE__ */ jsx7("span", { className: "truncate text-xs text-gray-500", children: item.subtitle })
275
+ /* @__PURE__ */ jsx5("span", { className: "truncate text-xs font-semibold", children: item.title }),
276
+ /* @__PURE__ */ jsx5("span", { className: "truncate text-xs text-gray-500", children: item.subtitle })
312
277
  ]
313
278
  },
314
279
  item.key
@@ -317,15 +282,15 @@ function ChatSlashMenu(props) {
317
282
  ] })
318
283
  }
319
284
  ),
320
- /* @__PURE__ */ jsx7("div", { className: "max-w-[320px] p-3.5", children: activeItem ? /* @__PURE__ */ jsxs3("div", { className: "space-y-3", children: [
321
- /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
322
- /* @__PURE__ */ jsx7("span", { className: "inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary", children: activeItem.subtitle }),
323
- /* @__PURE__ */ jsx7("span", { className: "text-sm font-semibold text-gray-900", children: activeItem.title })
285
+ /* @__PURE__ */ jsx5("div", { className: "max-w-[320px] p-3.5", children: activeItem ? /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
286
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
287
+ /* @__PURE__ */ jsx5("span", { className: "inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary", children: activeItem.subtitle }),
288
+ /* @__PURE__ */ jsx5("span", { className: "text-sm font-semibold text-gray-900", children: activeItem.title })
324
289
  ] }),
325
- /* @__PURE__ */ jsx7("p", { className: "text-xs leading-5 text-gray-600", children: activeItem.description }),
326
- /* @__PURE__ */ jsx7("div", { className: "space-y-1", children: activeItem.detailLines.map((line) => /* @__PURE__ */ jsx7("div", { className: "rounded-md bg-gray-50 px-2 py-1 text-[11px] text-gray-600", children: line }, line)) }),
327
- /* @__PURE__ */ jsx7("div", { className: "pt-1 text-[11px] text-gray-500", children: texts.slashSkillHintLabel })
328
- ] }) : /* @__PURE__ */ jsx7("div", { className: "text-xs text-gray-500", children: texts.slashHintLabel }) })
290
+ /* @__PURE__ */ jsx5("p", { className: "text-xs leading-5 text-gray-600", children: activeItem.description }),
291
+ /* @__PURE__ */ jsx5("div", { className: "space-y-1", children: activeItem.detailLines.map((line) => /* @__PURE__ */ jsx5("div", { className: "rounded-md bg-gray-50 px-2 py-1 text-[11px] text-gray-600", children: line }, line)) }),
292
+ /* @__PURE__ */ jsx5("div", { className: "pt-1 text-[11px] text-gray-500", children: texts.slashSkillHintLabel })
293
+ ] }) : /* @__PURE__ */ jsx5("div", { className: "text-xs text-gray-500", children: texts.slashHintLabel }) })
329
294
  ] })
330
295
  }
331
296
  )
@@ -338,7 +303,7 @@ import { Brain, Paperclip, Sparkles } from "lucide-react";
338
303
  // src/components/chat/default-skin/button.tsx
339
304
  import * as React5 from "react";
340
305
  import { cva } from "class-variance-authority";
341
- import { jsx as jsx8 } from "react/jsx-runtime";
306
+ import { jsx as jsx6 } from "react/jsx-runtime";
342
307
  var buttonVariants = cva(
343
308
  "inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium ring-offset-background transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
344
309
  {
@@ -369,18 +334,28 @@ var buttonVariants = cva(
369
334
  }
370
335
  );
371
336
  var ChatButton = React5.forwardRef(
372
- ({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx8("button", { className: cn(buttonVariants({ variant, size, className })), ref, ...props })
337
+ ({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx6("button", { className: cn(buttonVariants({ variant, size, className })), ref, ...props })
373
338
  );
374
339
  ChatButton.displayName = "ChatButton";
375
340
 
376
341
  // src/components/chat/ui/chat-input-bar/chat-input-bar-actions.tsx
377
- import { ArrowUp, Square } from "lucide-react";
378
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
342
+ import { ArrowUp } from "lucide-react";
343
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
344
+ function StopIcon() {
345
+ return /* @__PURE__ */ jsx7(
346
+ "span",
347
+ {
348
+ "aria-hidden": "true",
349
+ "data-testid": "chat-stop-icon",
350
+ className: "block h-3 w-3 rounded-[2px] bg-gray-700 shadow-[inset_0_0_0_1px_rgba(17,24,39,0.06)]"
351
+ }
352
+ );
353
+ }
379
354
  function ChatInputBarActions(props) {
380
355
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
381
- return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-end gap-1", children: [
382
- props.sendError?.trim() ? /* @__PURE__ */ jsx9("div", { className: "max-w-[420px] text-right text-[11px] text-red-600", children: props.sendError }) : null,
383
- /* @__PURE__ */ jsx9("div", { className: "flex items-center gap-2", children: props.isSending ? props.canStopGeneration ? /* @__PURE__ */ jsx9(
356
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-end gap-1", children: [
357
+ props.sendError?.trim() ? /* @__PURE__ */ jsx7("div", { className: "max-w-[420px] text-right text-[11px] text-red-600", children: props.sendError }) : null,
358
+ /* @__PURE__ */ jsx7("div", { className: "flex items-center gap-2", children: props.isSending ? props.canStopGeneration ? /* @__PURE__ */ jsx7(
384
359
  ChatButton,
385
360
  {
386
361
  size: "icon",
@@ -389,10 +364,10 @@ function ChatInputBarActions(props) {
389
364
  "aria-label": props.stopButtonLabel,
390
365
  onClick: () => void props.onStop(),
391
366
  disabled: props.stopDisabled,
392
- children: /* @__PURE__ */ jsx9(Square, { className: "h-3 w-3 fill-current" })
367
+ children: /* @__PURE__ */ jsx7(StopIcon, {})
393
368
  }
394
- ) : /* @__PURE__ */ jsx9(TooltipProvider, { children: /* @__PURE__ */ jsxs4(Tooltip, { children: [
395
- /* @__PURE__ */ jsx9(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9(
369
+ ) : /* @__PURE__ */ jsx7(TooltipProvider, { children: /* @__PURE__ */ jsxs3(Tooltip, { children: [
370
+ /* @__PURE__ */ jsx7(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx7("span", { children: /* @__PURE__ */ jsx7(
396
371
  ChatButton,
397
372
  {
398
373
  size: "icon",
@@ -400,11 +375,11 @@ function ChatInputBarActions(props) {
400
375
  className: "h-8 w-8 rounded-full",
401
376
  "aria-label": props.stopButtonLabel,
402
377
  disabled: true,
403
- children: /* @__PURE__ */ jsx9(Square, { className: "h-3 w-3 fill-current" })
378
+ children: /* @__PURE__ */ jsx7(StopIcon, {})
404
379
  }
405
380
  ) }) }),
406
- /* @__PURE__ */ jsx9(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx9("p", { className: "text-xs", children: props.stopHint }) })
407
- ] }) }) : /* @__PURE__ */ jsx9(
381
+ /* @__PURE__ */ jsx7(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx7("p", { className: "text-xs", children: props.stopHint }) })
382
+ ] }) }) : /* @__PURE__ */ jsx7(
408
383
  ChatButton,
409
384
  {
410
385
  size: "icon",
@@ -412,7 +387,7 @@ function ChatInputBarActions(props) {
412
387
  "aria-label": props.sendButtonLabel,
413
388
  onClick: () => void props.onSend(),
414
389
  disabled: props.sendDisabled,
415
- children: /* @__PURE__ */ jsx9(ArrowUp, { className: "h-5 w-5" })
390
+ children: /* @__PURE__ */ jsx7(ArrowUp, { className: "h-5 w-5" })
416
391
  }
417
392
  ) })
418
393
  ] });
@@ -421,7 +396,7 @@ function ChatInputBarActions(props) {
421
396
  // src/components/chat/ui/chat-input-bar/chat-input-bar-skill-picker.tsx
422
397
  import { useEffect as useEffect3, useId, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
423
398
  import { BrainCircuit, Check as Check2, ExternalLink, Puzzle, Search } from "lucide-react";
424
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
399
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
425
400
  function filterOptions(options, query) {
426
401
  const keyword = query.trim().toLowerCase();
427
402
  if (!keyword) {
@@ -489,26 +464,26 @@ function ChatInputBarSkillPicker(props) {
489
464
  }
490
465
  }
491
466
  };
492
- return /* @__PURE__ */ jsxs5(Popover, { children: [
493
- /* @__PURE__ */ jsx10(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
467
+ return /* @__PURE__ */ jsxs4(Popover, { children: [
468
+ /* @__PURE__ */ jsx8(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(
494
469
  "button",
495
470
  {
496
471
  type: "button",
497
472
  "aria-haspopup": "listbox",
498
473
  className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900",
499
474
  children: [
500
- /* @__PURE__ */ jsx10(BrainCircuit, { className: "h-4 w-4" }),
501
- /* @__PURE__ */ jsx10("span", { children: picker.title }),
502
- selectedCount > 0 ? /* @__PURE__ */ jsx10("span", { className: "ml-0.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: selectedCount }) : null
475
+ /* @__PURE__ */ jsx8(BrainCircuit, { className: "h-4 w-4" }),
476
+ /* @__PURE__ */ jsx8("span", { children: picker.title }),
477
+ selectedCount > 0 ? /* @__PURE__ */ jsx8("span", { className: "ml-0.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: selectedCount }) : null
503
478
  ]
504
479
  }
505
480
  ) }),
506
- /* @__PURE__ */ jsxs5(PopoverContent, { side: "top", align: "start", className: "w-[360px] p-0", children: [
507
- /* @__PURE__ */ jsxs5("div", { className: "space-y-2 border-b border-gray-100 px-4 py-3", children: [
508
- /* @__PURE__ */ jsx10("div", { className: "text-sm font-semibold text-gray-900", children: picker.title }),
509
- /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
510
- /* @__PURE__ */ jsx10(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-gray-400" }),
511
- /* @__PURE__ */ jsx10(
481
+ /* @__PURE__ */ jsxs4(PopoverContent, { side: "top", align: "start", className: "w-[360px] p-0", children: [
482
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-2 border-b border-gray-100 px-4 py-3", children: [
483
+ /* @__PURE__ */ jsx8("div", { className: "text-sm font-semibold text-gray-900", children: picker.title }),
484
+ /* @__PURE__ */ jsxs4("div", { className: "relative", children: [
485
+ /* @__PURE__ */ jsx8(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-gray-400" }),
486
+ /* @__PURE__ */ jsx8(
512
487
  Input,
513
488
  {
514
489
  value: query,
@@ -525,7 +500,7 @@ function ChatInputBarSkillPicker(props) {
525
500
  )
526
501
  ] })
527
502
  ] }),
528
- /* @__PURE__ */ jsx10(
503
+ /* @__PURE__ */ jsx8(
529
504
  "div",
530
505
  {
531
506
  ref: listRef,
@@ -533,10 +508,10 @@ function ChatInputBarSkillPicker(props) {
533
508
  role: "listbox",
534
509
  "aria-multiselectable": "true",
535
510
  className: "custom-scrollbar max-h-[320px] overflow-y-auto",
536
- children: picker.isLoading ? /* @__PURE__ */ jsx10("div", { className: "p-4 text-xs text-gray-500", children: picker.loadingLabel }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsx10("div", { className: "p-4 text-center text-xs text-gray-500", children: picker.emptyLabel }) : /* @__PURE__ */ jsx10("div", { className: "py-1", children: filteredOptions.map((option, index) => {
511
+ children: picker.isLoading ? /* @__PURE__ */ jsx8("div", { className: "p-4 text-xs text-gray-500", children: picker.loadingLabel }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsx8("div", { className: "p-4 text-center text-xs text-gray-500", children: picker.emptyLabel }) : /* @__PURE__ */ jsx8("div", { className: "py-1", children: filteredOptions.map((option, index) => {
537
512
  const isSelected = selectedSet.has(option.key);
538
513
  const isActive = index === activeIndex;
539
- return /* @__PURE__ */ jsxs5(
514
+ return /* @__PURE__ */ jsxs4(
540
515
  "button",
541
516
  {
542
517
  type: "button",
@@ -548,19 +523,19 @@ function ChatInputBarSkillPicker(props) {
548
523
  onMouseEnter: () => setActiveIndex(index),
549
524
  className: `flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${isActive ? "bg-gray-50" : "hover:bg-gray-50"}`,
550
525
  children: [
551
- /* @__PURE__ */ jsx10("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-500", children: /* @__PURE__ */ jsx10(Puzzle, { className: "h-4 w-4" }) }),
552
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
553
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
554
- /* @__PURE__ */ jsx10("span", { className: "truncate text-sm text-gray-900", children: option.label }),
555
- option.badgeLabel ? /* @__PURE__ */ jsx10("span", { className: "shrink-0 rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary", children: option.badgeLabel }) : null
526
+ /* @__PURE__ */ jsx8("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-500", children: /* @__PURE__ */ jsx8(Puzzle, { className: "h-4 w-4" }) }),
527
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
528
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
529
+ /* @__PURE__ */ jsx8("span", { className: "truncate text-sm text-gray-900", children: option.label }),
530
+ option.badgeLabel ? /* @__PURE__ */ jsx8("span", { className: "shrink-0 rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary", children: option.badgeLabel }) : null
556
531
  ] }),
557
- /* @__PURE__ */ jsx10("div", { className: "mt-0.5 truncate text-xs text-gray-500", children: option.description || option.key })
532
+ /* @__PURE__ */ jsx8("div", { className: "mt-0.5 truncate text-xs text-gray-500", children: option.description || option.key })
558
533
  ] }),
559
- /* @__PURE__ */ jsx10("div", { className: "ml-3 shrink-0", children: /* @__PURE__ */ jsx10(
534
+ /* @__PURE__ */ jsx8("div", { className: "ml-3 shrink-0", children: /* @__PURE__ */ jsx8(
560
535
  "span",
561
536
  {
562
537
  className: isSelected ? "inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary text-white" : "inline-flex h-5 w-5 items-center justify-center rounded-full border border-gray-300 bg-white",
563
- children: isSelected ? /* @__PURE__ */ jsx10(Check2, { className: "h-3 w-3" }) : null
538
+ children: isSelected ? /* @__PURE__ */ jsx8(Check2, { className: "h-3 w-3" }) : null
564
539
  }
565
540
  ) })
566
541
  ]
@@ -570,14 +545,14 @@ function ChatInputBarSkillPicker(props) {
570
545
  }) })
571
546
  }
572
547
  ),
573
- picker.manageHref && picker.manageLabel ? /* @__PURE__ */ jsx10("div", { className: "border-t border-gray-100 px-4 py-2.5", children: /* @__PURE__ */ jsxs5(
548
+ picker.manageHref && picker.manageLabel ? /* @__PURE__ */ jsx8("div", { className: "border-t border-gray-100 px-4 py-2.5", children: /* @__PURE__ */ jsxs4(
574
549
  "a",
575
550
  {
576
551
  href: picker.manageHref,
577
552
  className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary transition-colors hover:text-primary/80",
578
553
  children: [
579
554
  picker.manageLabel,
580
- /* @__PURE__ */ jsx10(ExternalLink, { className: "h-3 w-3" })
555
+ /* @__PURE__ */ jsx8(ExternalLink, { className: "h-3 w-3" })
581
556
  ]
582
557
  }
583
558
  ) }) : null
@@ -586,21 +561,21 @@ function ChatInputBarSkillPicker(props) {
586
561
  }
587
562
 
588
563
  // src/components/chat/ui/chat-input-bar/chat-input-bar-toolbar.tsx
589
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
564
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
590
565
  function ToolbarIcon({ icon }) {
591
566
  if (icon === "sparkles") {
592
- return /* @__PURE__ */ jsx11(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" });
567
+ return /* @__PURE__ */ jsx9(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" });
593
568
  }
594
569
  if (icon === "brain") {
595
- return /* @__PURE__ */ jsx11(Brain, { className: "h-3.5 w-3.5 shrink-0 text-gray-500" });
570
+ return /* @__PURE__ */ jsx9(Brain, { className: "h-3.5 w-3.5 shrink-0 text-gray-500" });
596
571
  }
597
572
  return null;
598
573
  }
599
574
  function AccessoryIcon({ icon }) {
600
575
  if (icon === "paperclip") {
601
- return /* @__PURE__ */ jsx11(Paperclip, { className: "h-4 w-4" });
576
+ return /* @__PURE__ */ jsx9(Paperclip, { className: "h-4 w-4" });
602
577
  }
603
- return /* @__PURE__ */ jsx11(ToolbarIcon, { icon });
578
+ return /* @__PURE__ */ jsx9(ToolbarIcon, { icon });
604
579
  }
605
580
  function resolveTriggerWidth(key) {
606
581
  if (key === "model") {
@@ -628,80 +603,1111 @@ function resolveContentWidth(key) {
628
603
  }
629
604
  function ToolbarSelect({ item }) {
630
605
  const { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } = ChatUiPrimitives;
631
- return /* @__PURE__ */ jsxs6(Select, { value: item.value, onValueChange: item.onValueChange, disabled: item.disabled, children: [
632
- /* @__PURE__ */ jsx11(
606
+ return /* @__PURE__ */ jsxs5(Select, { value: item.value, onValueChange: item.onValueChange, disabled: item.disabled, children: [
607
+ /* @__PURE__ */ jsx9(
633
608
  SelectTrigger,
634
609
  {
635
610
  className: `h-8 w-auto rounded-lg border-0 bg-transparent px-3 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0 ${resolveTriggerWidth(item.key)}`,
636
- children: item.selectedLabel ? /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 items-center gap-2 text-left", children: [
637
- /* @__PURE__ */ jsx11(ToolbarIcon, { icon: item.icon }),
638
- /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-700", children: item.selectedLabel })
639
- ] }) : item.loading ? /* @__PURE__ */ jsx11("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" }) : /* @__PURE__ */ jsx11(SelectValue, { placeholder: item.placeholder })
611
+ children: item.selectedLabel ? /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2 text-left", children: [
612
+ /* @__PURE__ */ jsx9(ToolbarIcon, { icon: item.icon }),
613
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-xs font-semibold text-gray-700", children: item.selectedLabel })
614
+ ] }) : item.loading ? /* @__PURE__ */ jsx9("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" }) : /* @__PURE__ */ jsx9(SelectValue, { placeholder: item.placeholder })
640
615
  }
641
616
  ),
642
- /* @__PURE__ */ jsxs6(SelectContent, { className: resolveContentWidth(item.key), children: [
643
- item.options.length === 0 ? item.loading ? /* @__PURE__ */ jsxs6("div", { className: "space-y-2 px-3 py-2", children: [
644
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-36 animate-pulse rounded bg-gray-200" }),
645
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
646
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-32 animate-pulse rounded bg-gray-200" })
647
- ] }) : item.emptyLabel ? /* @__PURE__ */ jsx11("div", { className: "px-3 py-2 text-xs text-gray-500", children: item.emptyLabel }) : null : null,
648
- item.options.map((option) => /* @__PURE__ */ jsx11(SelectItem, { value: option.value, className: "py-2", children: option.description ? /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
649
- /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }),
650
- /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-gray-500", children: option.description })
651
- ] }) : /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }) }, option.value))
617
+ /* @__PURE__ */ jsxs5(SelectContent, { className: resolveContentWidth(item.key), children: [
618
+ item.options.length === 0 ? item.loading ? /* @__PURE__ */ jsxs5("div", { className: "space-y-2 px-3 py-2", children: [
619
+ /* @__PURE__ */ jsx9("div", { className: "h-3 w-36 animate-pulse rounded bg-gray-200" }),
620
+ /* @__PURE__ */ jsx9("div", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
621
+ /* @__PURE__ */ jsx9("div", { className: "h-3 w-32 animate-pulse rounded bg-gray-200" })
622
+ ] }) : item.emptyLabel ? /* @__PURE__ */ jsx9("div", { className: "px-3 py-2 text-xs text-gray-500", children: item.emptyLabel }) : null : null,
623
+ item.options.map((option) => /* @__PURE__ */ jsx9(SelectItem, { value: option.value, className: "py-2", children: option.description ? /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
624
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }),
625
+ /* @__PURE__ */ jsx9("span", { className: "truncate text-[11px] text-gray-500", children: option.description })
626
+ ] }) : /* @__PURE__ */ jsx9("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }) }, option.value))
652
627
  ] })
653
628
  ] });
654
629
  }
655
630
  function ChatInputBarToolbar(props) {
656
631
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
657
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-3 pb-3", children: [
658
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
659
- props.skillPicker ? /* @__PURE__ */ jsx11(ChatInputBarSkillPicker, { picker: props.skillPicker }) : null,
660
- props.selects.map((item) => /* @__PURE__ */ jsx11(ToolbarSelect, { item }, item.key)),
632
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between px-3 pb-3", children: [
633
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1", children: [
634
+ props.skillPicker ? /* @__PURE__ */ jsx9(ChatInputBarSkillPicker, { picker: props.skillPicker }) : null,
635
+ props.selects.map((item) => /* @__PURE__ */ jsx9(ToolbarSelect, { item }, item.key)),
661
636
  props.accessories?.map((item) => {
662
- const button = /* @__PURE__ */ jsxs6(
637
+ const button = /* @__PURE__ */ jsxs5(
663
638
  "button",
664
639
  {
665
640
  type: "button",
666
- className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 disabled:cursor-not-allowed disabled:text-gray-400",
641
+ className: `inline-flex items-center rounded-lg py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 disabled:cursor-not-allowed disabled:text-gray-400 ${item.iconOnly ? "h-8 w-8 justify-center px-0" : "gap-1.5 px-3"}`,
667
642
  onClick: item.onClick,
668
643
  disabled: item.disabled,
669
644
  "aria-label": item.label,
670
645
  children: [
671
- /* @__PURE__ */ jsx11(AccessoryIcon, { icon: item.icon }),
672
- /* @__PURE__ */ jsx11("span", { children: item.label })
646
+ /* @__PURE__ */ jsx9(AccessoryIcon, { icon: item.icon }),
647
+ item.iconOnly ? null : /* @__PURE__ */ jsx9("span", { children: item.label })
673
648
  ]
674
649
  }
675
650
  );
676
651
  if (!item.tooltip) {
677
- return /* @__PURE__ */ jsx11("div", { children: button }, item.key);
652
+ return /* @__PURE__ */ jsx9("div", { children: button }, item.key);
678
653
  }
679
- return /* @__PURE__ */ jsx11(TooltipProvider, { children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
680
- /* @__PURE__ */ jsx11(TooltipTrigger, { asChild: true, children: button }),
681
- /* @__PURE__ */ jsx11(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx11("p", { className: "text-xs", children: item.tooltip }) })
654
+ const trigger = item.disabled ? /* @__PURE__ */ jsx9("span", { className: "inline-flex", children: button }) : button;
655
+ return /* @__PURE__ */ jsx9(TooltipProvider, { children: /* @__PURE__ */ jsxs5(Tooltip, { children: [
656
+ /* @__PURE__ */ jsx9(TooltipTrigger, { asChild: true, children: trigger }),
657
+ /* @__PURE__ */ jsx9(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx9("p", { className: "text-xs", children: item.tooltip }) })
682
658
  ] }) }, item.key);
683
659
  })
684
660
  ] }),
685
- /* @__PURE__ */ jsx11(ChatInputBarActions, { ...props.actions })
661
+ /* @__PURE__ */ jsx9(ChatInputBarActions, { ...props.actions })
686
662
  ] });
687
663
  }
688
664
 
665
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-tokenized-composer.tsx
666
+ import {
667
+ forwardRef as forwardRef6,
668
+ useImperativeHandle,
669
+ useLayoutEffect,
670
+ useState as useState3
671
+ } from "react";
672
+
673
+ // src/components/chat/ui/chat-input-bar/chat-composer.utils.ts
674
+ var CHAT_COMPOSER_TOKEN_PLACEHOLDER = "\uFFFC";
675
+ function createComposerNodeId() {
676
+ return `composer-${Math.random().toString(36).slice(2, 10)}`;
677
+ }
678
+ function createChatComposerTextNode(text = "") {
679
+ return {
680
+ id: createComposerNodeId(),
681
+ type: "text",
682
+ text
683
+ };
684
+ }
685
+ function createChatComposerTokenNode(params) {
686
+ return {
687
+ id: createComposerNodeId(),
688
+ type: "token",
689
+ tokenKind: params.tokenKind,
690
+ tokenKey: params.tokenKey,
691
+ label: params.label
692
+ };
693
+ }
694
+ function getChatComposerNodeLength(node) {
695
+ return node.type === "text" ? node.text.length : 1;
696
+ }
697
+ function createEmptyChatComposerNodes() {
698
+ return [createChatComposerTextNode("")];
699
+ }
700
+ function createChatComposerNodesFromText(text) {
701
+ return [createChatComposerTextNode(text)];
702
+ }
703
+ function normalizeChatComposerNodes(nodes) {
704
+ const normalized = [];
705
+ for (const node of nodes) {
706
+ if (node.type === "text") {
707
+ if (node.text.length === 0) {
708
+ continue;
709
+ }
710
+ const previous = normalized[normalized.length - 1];
711
+ if (previous?.type === "text") {
712
+ normalized[normalized.length - 1] = {
713
+ ...previous,
714
+ text: previous.text + node.text
715
+ };
716
+ continue;
717
+ }
718
+ }
719
+ normalized.push(node);
720
+ }
721
+ if (normalized.length === 0) {
722
+ return createEmptyChatComposerNodes();
723
+ }
724
+ return normalized;
725
+ }
726
+ function serializeChatComposerDocument(nodes) {
727
+ return nodes.map((node) => node.type === "text" ? node.text : CHAT_COMPOSER_TOKEN_PLACEHOLDER).join("");
728
+ }
729
+ function serializeChatComposerPlainText(nodes) {
730
+ return nodes.filter((node) => node.type === "text").map((node) => node.text).join("");
731
+ }
732
+ function extractChatComposerTokenKeys(nodes, tokenKind) {
733
+ const keys = [];
734
+ const keySet = /* @__PURE__ */ new Set();
735
+ for (const node of nodes) {
736
+ if (node.type !== "token" || node.tokenKind !== tokenKind || keySet.has(node.tokenKey)) {
737
+ continue;
738
+ }
739
+ keySet.add(node.tokenKey);
740
+ keys.push(node.tokenKey);
741
+ }
742
+ return keys;
743
+ }
744
+ function buildTrimmedTextEdges(node, nodeStart, rangeStart, rangeEnd) {
745
+ const prefixLength = Math.max(0, rangeStart - nodeStart);
746
+ const suffixLength = Math.max(0, nodeStart + node.text.length - rangeEnd);
747
+ const edges = [];
748
+ if (prefixLength > 0) {
749
+ edges.push({
750
+ ...node,
751
+ text: node.text.slice(0, prefixLength)
752
+ });
753
+ }
754
+ if (suffixLength > 0) {
755
+ edges.push({
756
+ ...node,
757
+ text: node.text.slice(node.text.length - suffixLength)
758
+ });
759
+ }
760
+ return edges;
761
+ }
762
+ function isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd) {
763
+ return nodeEnd <= boundedStart || nodeStart >= boundedEnd;
764
+ }
765
+ function appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd) {
766
+ if (!inserted && nodeStart >= boundedEnd) {
767
+ nextNodes.push(...replacement);
768
+ return true;
769
+ }
770
+ return inserted;
771
+ }
772
+ function appendTextNodeWithReplacement(nextNodes, node, nodeStart, boundedStart, boundedEnd, replacement, inserted) {
773
+ const edges = buildTrimmedTextEdges(node, nodeStart, boundedStart, boundedEnd);
774
+ if (edges[0]) {
775
+ nextNodes.push(edges[0]);
776
+ }
777
+ let didInsert = inserted;
778
+ if (!didInsert) {
779
+ nextNodes.push(...replacement);
780
+ didInsert = true;
781
+ }
782
+ if (edges[1]) {
783
+ nextNodes.push(edges[1]);
784
+ }
785
+ return didInsert;
786
+ }
787
+ function appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart) {
788
+ if (!inserted && boundedStart <= nodeStart) {
789
+ nextNodes.push(...replacement);
790
+ return true;
791
+ }
792
+ nextNodes.push(node);
793
+ return inserted;
794
+ }
795
+ function replaceChatComposerRange(nodes, start, end, replacement) {
796
+ const boundedStart = Math.max(0, start);
797
+ const boundedEnd = Math.max(boundedStart, end);
798
+ const nextNodes = [];
799
+ let cursor = 0;
800
+ let inserted = false;
801
+ for (const node of nodes) {
802
+ const nodeLength = getChatComposerNodeLength(node);
803
+ const nodeStart = cursor;
804
+ const nodeEnd = cursor + nodeLength;
805
+ const isOutsideRange = isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd);
806
+ if (isOutsideRange) {
807
+ inserted = appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd);
808
+ nextNodes.push(node);
809
+ cursor = nodeEnd;
810
+ continue;
811
+ }
812
+ if (node.type === "text") {
813
+ inserted = appendTextNodeWithReplacement(
814
+ nextNodes,
815
+ node,
816
+ nodeStart,
817
+ boundedStart,
818
+ boundedEnd,
819
+ replacement,
820
+ inserted
821
+ );
822
+ } else {
823
+ inserted = appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart);
824
+ }
825
+ cursor = nodeEnd;
826
+ }
827
+ if (!inserted) {
828
+ nextNodes.push(...replacement);
829
+ }
830
+ return normalizeChatComposerNodes(nextNodes);
831
+ }
832
+ function removeChatComposerTokenNodes(nodes, predicate) {
833
+ return normalizeChatComposerNodes(
834
+ nodes.filter((node) => node.type !== "token" || !predicate(node))
835
+ );
836
+ }
837
+ function resolveChatComposerSlashTrigger(nodes, selection) {
838
+ if (!selection || selection.start !== selection.end) {
839
+ return null;
840
+ }
841
+ const documentText = serializeChatComposerDocument(nodes);
842
+ const caret = selection.end;
843
+ const prefix = documentText.slice(0, caret);
844
+ const match = /(?:^|\s)\/([^\s\uFFFC]*)$/.exec(prefix);
845
+ if (!match) {
846
+ return null;
847
+ }
848
+ const slashStart = caret - match[0].length + (match[0].startsWith("/") ? 0 : 1);
849
+ return {
850
+ query: (match[1] ?? "").trim().toLowerCase(),
851
+ start: slashStart,
852
+ end: caret
853
+ };
854
+ }
855
+ function isChatComposerSelectionInsideRange(selection, start, end) {
856
+ if (!selection) {
857
+ return false;
858
+ }
859
+ return selection.start < end && selection.end > start;
860
+ }
861
+
862
+ // src/components/chat/ui/chat-input-bar/chat-composer-dom.utils.ts
863
+ function buildNodeStartMap(nodes) {
864
+ const map = /* @__PURE__ */ new Map();
865
+ let cursor = 0;
866
+ for (const node of nodes) {
867
+ map.set(node.id, cursor);
868
+ cursor += getChatComposerNodeLength(node);
869
+ }
870
+ return map;
871
+ }
872
+ function resolveRootChildIndex(root, target) {
873
+ let current = target;
874
+ while (current && current.parentNode !== root) {
875
+ current = current.parentNode;
876
+ }
877
+ if (!current) {
878
+ return root.childNodes.length;
879
+ }
880
+ return Array.prototype.indexOf.call(root.childNodes, current);
881
+ }
882
+ function findNodeByDomChild(child, index, nodes) {
883
+ if (child instanceof HTMLElement) {
884
+ const nodeId = child.dataset.composerNodeId;
885
+ if (nodeId) {
886
+ return nodes.find((node) => node.id === nodeId);
887
+ }
888
+ }
889
+ return nodes[index];
890
+ }
891
+ function sumNodeLengthsBeforeChildIndex(root, nodes, childIndex) {
892
+ let value = 0;
893
+ for (let index = 0; index < childIndex; index += 1) {
894
+ const matched = findNodeByDomChild(root.childNodes[index], index, nodes);
895
+ if (matched) {
896
+ value += getChatComposerNodeLength(matched);
897
+ }
898
+ }
899
+ return value;
900
+ }
901
+ function resolveDirectRootTextNodeOffset(root, container, offset, nodes) {
902
+ const childIndex = resolveRootChildIndex(root, container);
903
+ const valueBeforeNode = sumNodeLengthsBeforeChildIndex(root, nodes, childIndex);
904
+ const currentNode = nodes[childIndex];
905
+ if (currentNode?.type === "text") {
906
+ return valueBeforeNode + Math.min(offset, currentNode.text.length);
907
+ }
908
+ return valueBeforeNode + Math.min(offset, container.textContent?.length ?? 0);
909
+ }
910
+ function resolveElementBackedOffset(container, offset, matched, nodeStart, element) {
911
+ if (matched.type === "text") {
912
+ if (container.nodeType === Node.TEXT_NODE) {
913
+ return nodeStart + Math.min(offset, matched.text.length);
914
+ }
915
+ if (container === element) {
916
+ return nodeStart + (offset > 0 ? matched.text.length : 0);
917
+ }
918
+ return nodeStart + matched.text.length;
919
+ }
920
+ if (container === element) {
921
+ return nodeStart + (offset > 0 ? 1 : 0);
922
+ }
923
+ return nodeStart + 1;
924
+ }
925
+ function resolveSelectionPointOffset(root, container, offset, nodes, nodeStartMap) {
926
+ if (container === root) {
927
+ return sumNodeLengthsBeforeChildIndex(root, nodes, offset);
928
+ }
929
+ if (container.nodeType === Node.TEXT_NODE && container.parentNode === root) {
930
+ return resolveDirectRootTextNodeOffset(root, container, offset, nodes);
931
+ }
932
+ const element = container instanceof HTMLElement ? container.closest("[data-composer-node-id]") : container.parentElement?.closest("[data-composer-node-id]") ?? null;
933
+ if (!(element instanceof HTMLElement)) {
934
+ const childIndex = resolveRootChildIndex(root, container);
935
+ return resolveSelectionPointOffset(root, root, childIndex, nodes, nodeStartMap);
936
+ }
937
+ const nodeId = element.dataset.composerNodeId;
938
+ if (!nodeId) {
939
+ return 0;
940
+ }
941
+ const matched = nodes.find((node) => node.id === nodeId);
942
+ const nodeStart = nodeStartMap.get(nodeId) ?? 0;
943
+ if (!matched) {
944
+ return nodeStart;
945
+ }
946
+ return resolveElementBackedOffset(container, offset, matched, nodeStart, element);
947
+ }
948
+ function readComposerSelection(root, nodes) {
949
+ const selection = window.getSelection();
950
+ if (!selection || selection.rangeCount === 0) {
951
+ return null;
952
+ }
953
+ const range = selection.getRangeAt(0);
954
+ if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) {
955
+ return null;
956
+ }
957
+ const nodeStartMap = buildNodeStartMap(nodes);
958
+ const start = resolveSelectionPointOffset(root, range.startContainer, range.startOffset, nodes, nodeStartMap);
959
+ const end = resolveSelectionPointOffset(root, range.endContainer, range.endOffset, nodes, nodeStartMap);
960
+ return {
961
+ start: Math.min(start, end),
962
+ end: Math.max(start, end)
963
+ };
964
+ }
965
+ function restoreComposerSelection(root, nodes, selection) {
966
+ if (!selection) {
967
+ return;
968
+ }
969
+ const browserSelection = window.getSelection();
970
+ if (!browserSelection) {
971
+ return;
972
+ }
973
+ const resolveBoundary = (docOffset) => {
974
+ let cursor = 0;
975
+ for (let index = 0; index < nodes.length; index += 1) {
976
+ const node = nodes[index];
977
+ const nodeLength = getChatComposerNodeLength(node);
978
+ const nodeStart = cursor;
979
+ const nodeEnd = cursor + nodeLength;
980
+ const element = root.childNodes[index];
981
+ if (!element) {
982
+ cursor = nodeEnd;
983
+ continue;
984
+ }
985
+ if (node.type === "text") {
986
+ if (docOffset <= nodeEnd) {
987
+ const textNode = element.firstChild ?? element;
988
+ return {
989
+ container: textNode,
990
+ offset: Math.max(0, Math.min(docOffset - nodeStart, node.text.length))
991
+ };
992
+ }
993
+ } else {
994
+ if (docOffset <= nodeStart) {
995
+ return { container: root, offset: index };
996
+ }
997
+ if (docOffset <= nodeEnd) {
998
+ return { container: root, offset: index + 1 };
999
+ }
1000
+ }
1001
+ cursor = nodeEnd;
1002
+ }
1003
+ return {
1004
+ container: root,
1005
+ offset: root.childNodes.length
1006
+ };
1007
+ };
1008
+ const startBoundary = resolveBoundary(selection.start);
1009
+ const endBoundary = resolveBoundary(selection.end);
1010
+ const range = document.createRange();
1011
+ range.setStart(startBoundary.container, startBoundary.offset);
1012
+ range.setEnd(endBoundary.container, endBoundary.offset);
1013
+ browserSelection.removeAllRanges();
1014
+ browserSelection.addRange(range);
1015
+ }
1016
+ function parseComposerNodesFromDom(root) {
1017
+ const parsedNodes = [];
1018
+ for (const child of Array.from(root.childNodes)) {
1019
+ if (child.nodeType === Node.TEXT_NODE) {
1020
+ parsedNodes.push(createChatComposerTextNode(child.textContent ?? ""));
1021
+ continue;
1022
+ }
1023
+ if (!(child instanceof HTMLElement)) {
1024
+ continue;
1025
+ }
1026
+ const nodeType = child.dataset.composerNodeType;
1027
+ if (nodeType === "token") {
1028
+ const tokenKind = child.dataset.composerTokenKind;
1029
+ const tokenKey = child.dataset.composerTokenKey;
1030
+ const label = child.dataset.composerLabel;
1031
+ if (tokenKind && tokenKey && label) {
1032
+ parsedNodes.push({
1033
+ id: child.dataset.composerNodeId ?? createChatComposerTokenNode({ tokenKind, tokenKey, label }).id,
1034
+ type: "token",
1035
+ tokenKind,
1036
+ tokenKey,
1037
+ label
1038
+ });
1039
+ }
1040
+ continue;
1041
+ }
1042
+ const text = child.textContent ?? "";
1043
+ parsedNodes.push({
1044
+ id: child.dataset.composerNodeId ?? createChatComposerTextNode(text).id,
1045
+ type: "text",
1046
+ text
1047
+ });
1048
+ }
1049
+ return normalizeChatComposerNodes(parsedNodes);
1050
+ }
1051
+ function readComposerDocumentStateFromDom(root) {
1052
+ const nodes = parseComposerNodesFromDom(root);
1053
+ return {
1054
+ nodes,
1055
+ selection: readComposerSelection(root, nodes)
1056
+ };
1057
+ }
1058
+
1059
+ // src/components/chat/ui/chat-input-bar/chat-composer-controller.ts
1060
+ var ChatComposerController = class {
1061
+ constructor() {
1062
+ this.nodes = createEmptyChatComposerNodes();
1063
+ this.selection = null;
1064
+ this.sync = (nodes, selection) => {
1065
+ this.nodes = normalizeChatComposerNodes(nodes);
1066
+ this.selection = selection;
1067
+ return this.getSnapshot();
1068
+ };
1069
+ this.setSelection = (selection) => {
1070
+ this.selection = selection;
1071
+ return this.getSnapshot();
1072
+ };
1073
+ this.replaceDocument = (nodes, selection) => {
1074
+ this.nodes = normalizeChatComposerNodes(nodes);
1075
+ this.selection = selection;
1076
+ return this.getSnapshot();
1077
+ };
1078
+ this.insertText = (text) => {
1079
+ const selection = this.selection ?? { start: 0, end: 0 };
1080
+ this.nodes = replaceChatComposerRange(this.nodes, selection.start, selection.end, [createChatComposerTextNode(text)]);
1081
+ const nextOffset = selection.start + text.length;
1082
+ this.selection = { start: nextOffset, end: nextOffset };
1083
+ return this.getSnapshot();
1084
+ };
1085
+ this.insertSkillToken = (tokenKey, label) => {
1086
+ if (this.getSelectedSkillKeys().includes(tokenKey)) {
1087
+ return this.getSnapshot();
1088
+ }
1089
+ const trigger = this.getSlashTrigger();
1090
+ const documentLength = this.getDocumentLength();
1091
+ const replaceStart = trigger?.start ?? this.selection?.start ?? documentLength;
1092
+ const replaceEnd = trigger?.end ?? this.selection?.end ?? replaceStart;
1093
+ this.nodes = replaceChatComposerRange(
1094
+ this.nodes,
1095
+ replaceStart,
1096
+ replaceEnd,
1097
+ [createChatComposerTokenNode({ tokenKind: "skill", tokenKey, label })]
1098
+ );
1099
+ this.selection = { start: replaceStart + 1, end: replaceStart + 1 };
1100
+ return this.getSnapshot();
1101
+ };
1102
+ this.syncSelectedSkills = (nextKeys, options) => {
1103
+ const selectedSkillKeys = this.getSelectedSkillKeys();
1104
+ const optionMap = new Map(options.map((option) => [option.key, option]));
1105
+ const addedKey = nextKeys.find((key) => !selectedSkillKeys.includes(key));
1106
+ if (addedKey) {
1107
+ const option = optionMap.get(addedKey);
1108
+ return this.insertSkillToken(addedKey, option?.label ?? addedKey);
1109
+ }
1110
+ const removedKey = selectedSkillKeys.find((key) => !nextKeys.includes(key));
1111
+ if (removedKey) {
1112
+ this.nodes = removeChatComposerTokenNodes(
1113
+ this.nodes,
1114
+ (node) => node.tokenKind === "skill" && node.tokenKey === removedKey
1115
+ );
1116
+ }
1117
+ return this.getSnapshot();
1118
+ };
1119
+ this.deleteContent = (direction) => {
1120
+ const documentLength = this.getDocumentLength();
1121
+ const selection = this.selection ?? { start: documentLength, end: documentLength };
1122
+ let rangeStart = selection.start;
1123
+ let rangeEnd = selection.end;
1124
+ if (selection.start === selection.end) {
1125
+ if (direction === "backward" && selection.start > 0) {
1126
+ rangeStart = selection.start - 1;
1127
+ } else if (direction === "forward") {
1128
+ rangeEnd = selection.end + 1;
1129
+ } else {
1130
+ return this.getSnapshot();
1131
+ }
1132
+ }
1133
+ this.nodes = replaceChatComposerRange(this.nodes, rangeStart, rangeEnd, []);
1134
+ this.selection = { start: rangeStart, end: rangeStart };
1135
+ return this.getSnapshot();
1136
+ };
1137
+ this.getSnapshot = () => {
1138
+ return {
1139
+ nodes: this.nodes,
1140
+ selection: this.selection,
1141
+ nodeStartMap: buildNodeStartMap(this.nodes),
1142
+ documentLength: this.getDocumentLength(),
1143
+ selectedSkillKeys: this.getSelectedSkillKeys(),
1144
+ slashTrigger: this.getSlashTrigger()
1145
+ };
1146
+ };
1147
+ this.getDocumentLength = () => {
1148
+ return this.nodes.reduce((sum, node) => sum + getChatComposerNodeLength(node), 0);
1149
+ };
1150
+ this.getSelectedSkillKeys = () => {
1151
+ return extractChatComposerTokenKeys(this.nodes, "skill");
1152
+ };
1153
+ this.getSlashTrigger = () => {
1154
+ return resolveChatComposerSlashTrigger(this.nodes, this.selection);
1155
+ };
1156
+ }
1157
+ };
1158
+
1159
+ // src/components/chat/ui/chat-input-bar/chat-composer-keyboard.utils.ts
1160
+ function resolveChatComposerKeyboardAction(params) {
1161
+ const {
1162
+ key,
1163
+ shiftKey,
1164
+ isComposing,
1165
+ isSlashMenuOpen,
1166
+ slashItemCount,
1167
+ activeSlashIndex,
1168
+ isSending,
1169
+ canStopGeneration
1170
+ } = params;
1171
+ if (isSlashMenuOpen && slashItemCount > 0) {
1172
+ if (key === "ArrowDown") {
1173
+ return {
1174
+ type: "move-slash-index",
1175
+ index: (activeSlashIndex + 1) % slashItemCount
1176
+ };
1177
+ }
1178
+ if (key === "ArrowUp") {
1179
+ return {
1180
+ type: "move-slash-index",
1181
+ index: (activeSlashIndex - 1 + slashItemCount) % slashItemCount
1182
+ };
1183
+ }
1184
+ if (key === "Enter" && !shiftKey || key === "Tab") {
1185
+ return { type: "insert-active-slash-item" };
1186
+ }
1187
+ }
1188
+ if (key === "Escape") {
1189
+ if (isSlashMenuOpen) {
1190
+ return { type: "close-slash" };
1191
+ }
1192
+ if (isSending && canStopGeneration) {
1193
+ return { type: "stop-generation" };
1194
+ }
1195
+ return { type: "noop" };
1196
+ }
1197
+ if (key === "Enter" && shiftKey) {
1198
+ return { type: "insert-line-break" };
1199
+ }
1200
+ if (key === "Enter") {
1201
+ return { type: "send-message" };
1202
+ }
1203
+ if (!isComposing && (key === "Backspace" || key === "Delete")) {
1204
+ return {
1205
+ type: "delete-content",
1206
+ direction: key === "Backspace" ? "backward" : "forward"
1207
+ };
1208
+ }
1209
+ return { type: "noop" };
1210
+ }
1211
+
1212
+ // src/components/chat/ui/chat-input-bar/chat-composer-surface-renderer.ts
1213
+ var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
1214
+ var ChatComposerSurfaceRenderer = class {
1215
+ constructor() {
1216
+ this.render = (root, params) => {
1217
+ if (!root) {
1218
+ return;
1219
+ }
1220
+ const fragment = document.createDocumentFragment();
1221
+ for (const node of params.nodes) {
1222
+ const element = node.type === "text" ? this.createTextNodeElement(node) : this.createTokenNodeElement(node, params.selectedRange, params.nodeStartMap);
1223
+ if (element) {
1224
+ fragment.appendChild(element);
1225
+ }
1226
+ }
1227
+ root.replaceChildren(fragment);
1228
+ };
1229
+ this.createTextNodeElement = (node) => {
1230
+ if (node.text.length === 0) {
1231
+ return null;
1232
+ }
1233
+ const element = document.createElement("span");
1234
+ element.dataset.composerNodeId = node.id;
1235
+ element.dataset.composerNodeType = "text";
1236
+ element.textContent = node.text;
1237
+ return element;
1238
+ };
1239
+ this.createTokenNodeElement = (node, selectedRange, nodeStartMap) => {
1240
+ const nodeStart = nodeStartMap.get(node.id) ?? 0;
1241
+ const isSelected = isChatComposerSelectionInsideRange(selectedRange, nodeStart, nodeStart + 1);
1242
+ const element = document.createElement("span");
1243
+ element.contentEditable = "false";
1244
+ element.dataset.composerNodeId = node.id;
1245
+ element.dataset.composerNodeType = "token";
1246
+ element.dataset.composerTokenKind = node.tokenKind;
1247
+ element.dataset.composerTokenKey = node.tokenKey;
1248
+ element.dataset.composerLabel = node.label;
1249
+ element.className = [
1250
+ "mx-[2px]",
1251
+ "inline-flex",
1252
+ "h-6",
1253
+ "max-w-full",
1254
+ "items-center",
1255
+ "gap-1",
1256
+ "rounded-md",
1257
+ "border",
1258
+ "px-1.5",
1259
+ "align-baseline",
1260
+ "text-[11px]",
1261
+ "font-medium",
1262
+ "transition",
1263
+ isSelected ? "border-primary/30 bg-primary/18 text-primary" : "border-primary/12 bg-primary/8 text-primary"
1264
+ ].join(" ");
1265
+ element.append(this.createTokenIcon(node.tokenKind));
1266
+ const label = document.createElement("span");
1267
+ label.className = "truncate";
1268
+ label.textContent = node.label;
1269
+ element.append(label);
1270
+ return element;
1271
+ };
1272
+ this.createTokenIcon = (tokenKind) => {
1273
+ const wrapper = document.createElement("span");
1274
+ wrapper.className = "inline-flex h-3 w-3 shrink-0 items-center justify-center text-primary/70";
1275
+ wrapper.append(tokenKind === "file" ? this.createFileIcon() : this.createSkillIcon());
1276
+ return wrapper;
1277
+ };
1278
+ this.createSkillIcon = () => {
1279
+ return this.createSvgIcon([
1280
+ { tag: "path", attrs: { d: "M8.5 2.75 2.75 6l5.75 3.25L14.25 6 8.5 2.75Z" } },
1281
+ { tag: "path", attrs: { d: "M2.75 10 8.5 13.25 14.25 10" } },
1282
+ { tag: "path", attrs: { d: "M2.75 6v4l5.75 3.25V9.25L2.75 6Z" } },
1283
+ { tag: "path", attrs: { d: "M14.25 6v4L8.5 13.25V9.25L14.25 6Z" } }
1284
+ ]);
1285
+ };
1286
+ this.createFileIcon = () => {
1287
+ return this.createSvgIcon([
1288
+ { tag: "path", attrs: { d: "M5.25 2.75h4.5L13 6v7.25A1.75 1.75 0 0 1 11.25 15h-6.5A1.75 1.75 0 0 1 3 13.25v-8.75A1.75 1.75 0 0 1 4.75 2.75Z" } },
1289
+ { tag: "path", attrs: { d: "M9.75 2.75V6H13" } },
1290
+ { tag: "path", attrs: { d: "M5.75 8.75h4.5" } },
1291
+ { tag: "path", attrs: { d: "M5.75 10.75h4.5" } }
1292
+ ]);
1293
+ };
1294
+ this.createSvgIcon = (children) => {
1295
+ const svg = document.createElementNS(SVG_NAMESPACE, "svg");
1296
+ svg.setAttribute("viewBox", "0 0 16 16");
1297
+ svg.setAttribute("fill", "none");
1298
+ svg.setAttribute("stroke", "currentColor");
1299
+ svg.setAttribute("stroke-width", "1.25");
1300
+ svg.setAttribute("stroke-linecap", "round");
1301
+ svg.setAttribute("stroke-linejoin", "round");
1302
+ svg.setAttribute("aria-hidden", "true");
1303
+ svg.setAttribute("class", "h-3 w-3");
1304
+ for (const child of children) {
1305
+ const element = document.createElementNS(SVG_NAMESPACE, child.tag);
1306
+ for (const [key, value] of Object.entries(child.attrs)) {
1307
+ element.setAttribute(key, value);
1308
+ }
1309
+ svg.append(element);
1310
+ }
1311
+ return svg;
1312
+ };
1313
+ }
1314
+ };
1315
+
1316
+ // src/components/chat/ui/chat-input-bar/chat-composer-view-controller.ts
1317
+ var CHAT_INPUT_MAX_HEIGHT = 188;
1318
+ var ChatComposerViewController = class {
1319
+ constructor(controller) {
1320
+ this.controller = controller;
1321
+ this.surfaceRenderer = new ChatComposerSurfaceRenderer();
1322
+ this.sync = (nodes, selection) => {
1323
+ return this.controller.sync(nodes, selection);
1324
+ };
1325
+ this.syncSelectionFromRoot = (root) => {
1326
+ return this.controller.setSelection(readComposerSelection(root, this.controller.getSnapshot().nodes));
1327
+ };
1328
+ this.restoreSelectionIfFocused = (root, selection) => {
1329
+ if (!root || document.activeElement !== root) {
1330
+ return;
1331
+ }
1332
+ restoreComposerSelection(root, this.controller.getSnapshot().nodes, selection);
1333
+ };
1334
+ this.syncViewport = (root) => {
1335
+ if (!root) {
1336
+ return;
1337
+ }
1338
+ root.style.maxHeight = `${CHAT_INPUT_MAX_HEIGHT}px`;
1339
+ root.style.overflowY = root.scrollHeight > CHAT_INPUT_MAX_HEIGHT ? "auto" : "hidden";
1340
+ };
1341
+ this.renderSurface = (params) => {
1342
+ this.surfaceRenderer.render(params.root, {
1343
+ nodes: params.snapshot.nodes,
1344
+ selectedRange: params.selectedRange,
1345
+ nodeStartMap: params.snapshot.nodeStartMap
1346
+ });
1347
+ };
1348
+ this.insertSlashItem = (item, commitSnapshot) => {
1349
+ if (!item.value) {
1350
+ return;
1351
+ }
1352
+ commitSnapshot(this.controller.insertSkillToken(item.value, item.title));
1353
+ };
1354
+ this.syncSelectedSkills = (nextKeys, options, commitSnapshot) => {
1355
+ commitSnapshot(this.controller.syncSelectedSkills(nextKeys, options));
1356
+ };
1357
+ this.handleBeforeInput = (params) => {
1358
+ const { event, disabled, isComposing, commitSnapshot } = params;
1359
+ const nativeEvent = event.nativeEvent;
1360
+ if (disabled || isComposing || nativeEvent.isComposing) {
1361
+ return;
1362
+ }
1363
+ const shouldInsertText = nativeEvent.inputType === "insertText" || nativeEvent.inputType === "insertReplacementText";
1364
+ if (!shouldInsertText || !nativeEvent.data) {
1365
+ return;
1366
+ }
1367
+ event.preventDefault();
1368
+ commitSnapshot(this.controller.insertText(nativeEvent.data));
1369
+ };
1370
+ this.handleInput = (params) => {
1371
+ const { event, isComposing, commitSnapshot } = params;
1372
+ const nativeEvent = event.nativeEvent;
1373
+ if (isComposing || nativeEvent.isComposing) {
1374
+ return;
1375
+ }
1376
+ const root = event.currentTarget;
1377
+ const nextDocumentState = readComposerDocumentStateFromDom(root);
1378
+ commitSnapshot(this.controller.replaceDocument(nextDocumentState.nodes, nextDocumentState.selection));
1379
+ };
1380
+ this.handleCompositionEnd = (params) => {
1381
+ const { event, commitSnapshot } = params;
1382
+ const root = event.currentTarget;
1383
+ const nextDocumentState = readComposerDocumentStateFromDom(root);
1384
+ commitSnapshot(this.controller.replaceDocument(nextDocumentState.nodes, nextDocumentState.selection));
1385
+ };
1386
+ this.handleKeyDown = (params) => {
1387
+ const {
1388
+ event,
1389
+ slashItems,
1390
+ activeSlashIndex,
1391
+ activeSlashItem,
1392
+ actions,
1393
+ commitSnapshot,
1394
+ insertSkillToken,
1395
+ onSlashActiveIndexChange,
1396
+ onSlashQueryChange,
1397
+ onSlashOpenChange
1398
+ } = params;
1399
+ const currentSnapshot = this.controller.getSnapshot();
1400
+ const action = resolveChatComposerKeyboardAction({
1401
+ key: event.key,
1402
+ shiftKey: event.shiftKey,
1403
+ isComposing: event.nativeEvent.isComposing,
1404
+ isSlashMenuOpen: currentSnapshot.slashTrigger !== null,
1405
+ slashItemCount: slashItems.length,
1406
+ activeSlashIndex,
1407
+ isSending: actions.isSending,
1408
+ canStopGeneration: actions.canStopGeneration
1409
+ });
1410
+ if (action.type === "noop") {
1411
+ return;
1412
+ }
1413
+ event.preventDefault();
1414
+ if (action.type === "move-slash-index") {
1415
+ onSlashActiveIndexChange(action.index);
1416
+ return;
1417
+ }
1418
+ if (action.type === "insert-active-slash-item") {
1419
+ if (activeSlashItem) {
1420
+ insertSkillToken(activeSlashItem.value ?? activeSlashItem.key, activeSlashItem.title);
1421
+ }
1422
+ return;
1423
+ }
1424
+ if (action.type === "close-slash") {
1425
+ onSlashQueryChange?.(null);
1426
+ onSlashOpenChange(false);
1427
+ return;
1428
+ }
1429
+ if (action.type === "stop-generation") {
1430
+ void actions.onStop();
1431
+ return;
1432
+ }
1433
+ if (action.type === "insert-line-break") {
1434
+ commitSnapshot(this.controller.insertText("\n"));
1435
+ return;
1436
+ }
1437
+ if (action.type === "send-message") {
1438
+ void actions.onSend();
1439
+ return;
1440
+ }
1441
+ if (action.type === "delete-content") {
1442
+ commitSnapshot(this.controller.deleteContent(action.direction));
1443
+ }
1444
+ };
1445
+ this.handlePaste = (params) => {
1446
+ const { event, commitSnapshot } = params;
1447
+ const text = event.clipboardData.getData("text/plain");
1448
+ if (!text) {
1449
+ return;
1450
+ }
1451
+ event.preventDefault();
1452
+ commitSnapshot(this.controller.insertText(text));
1453
+ };
1454
+ this.handleBlur = (params) => {
1455
+ const { setSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
1456
+ setSelectedRange(null);
1457
+ onSlashQueryChange?.(null);
1458
+ onSlashOpenChange(false);
1459
+ };
1460
+ }
1461
+ };
1462
+
1463
+ // src/components/chat/ui/chat-input-bar/chat-composer-runtime.ts
1464
+ var ChatComposerRuntime = class {
1465
+ constructor() {
1466
+ this.controller = new ChatComposerController();
1467
+ this.viewController = new ChatComposerViewController(this.controller);
1468
+ this.rootElement = null;
1469
+ this.selection = null;
1470
+ this.selectedRange = null;
1471
+ this.snapshot = this.controller.getSnapshot();
1472
+ this.config = null;
1473
+ this.isComposing = false;
1474
+ this.bindRootElement = (node) => {
1475
+ this.rootElement = node;
1476
+ };
1477
+ this.update = (config) => {
1478
+ this.config = config;
1479
+ this.snapshot = this.viewController.sync(config.nodes, this.selectedRange);
1480
+ return {
1481
+ snapshot: this.snapshot,
1482
+ selectedRange: this.selectedRange,
1483
+ bindRootElement: this.bindRootElement,
1484
+ handleBeforeInput: this.handleBeforeInput,
1485
+ handleInput: this.handleInput,
1486
+ handleCompositionStart: this.handleCompositionStart,
1487
+ handleCompositionEnd: this.handleCompositionEnd,
1488
+ handleKeyDown: this.handleKeyDown,
1489
+ handlePaste: this.handlePaste,
1490
+ handleBlur: this.handleBlur,
1491
+ syncSelectionState: this.syncSelectionState,
1492
+ imperativeHandle: this.createHandle()
1493
+ };
1494
+ };
1495
+ this.createHandle = () => {
1496
+ return {
1497
+ insertSlashItem: (item) => {
1498
+ this.viewController.insertSlashItem(item, this.commitSnapshot);
1499
+ },
1500
+ syncSelectedSkills: (nextKeys, options) => {
1501
+ this.viewController.syncSelectedSkills(nextKeys, options, this.commitSnapshot);
1502
+ }
1503
+ };
1504
+ };
1505
+ this.restoreDomAfterCommit = () => {
1506
+ if (this.isComposing) {
1507
+ return;
1508
+ }
1509
+ this.viewController.restoreSelectionIfFocused(this.rootElement, this.selection);
1510
+ };
1511
+ this.renderSurface = () => {
1512
+ if (this.isComposing) {
1513
+ return;
1514
+ }
1515
+ this.viewController.renderSurface({
1516
+ root: this.rootElement,
1517
+ snapshot: this.snapshot,
1518
+ selectedRange: this.selectedRange
1519
+ });
1520
+ };
1521
+ this.syncViewport = () => {
1522
+ this.viewController.syncViewport(this.rootElement);
1523
+ };
1524
+ this.syncSelectionState = () => {
1525
+ if (!this.rootElement || this.isComposing) {
1526
+ return;
1527
+ }
1528
+ const nextSnapshot = this.viewController.syncSelectionFromRoot(this.rootElement);
1529
+ this.selection = nextSnapshot.selection;
1530
+ this.selectedRange = nextSnapshot.selection;
1531
+ this.syncSlashState(nextSnapshot);
1532
+ this.requestRender();
1533
+ };
1534
+ this.handleBeforeInput = (event) => {
1535
+ this.viewController.handleBeforeInput({
1536
+ event,
1537
+ disabled: this.requireConfig().disabled,
1538
+ isComposing: this.isComposing,
1539
+ commitSnapshot: this.commitSnapshot
1540
+ });
1541
+ };
1542
+ this.handleInput = (event) => {
1543
+ this.viewController.handleInput({
1544
+ event,
1545
+ isComposing: this.isComposing,
1546
+ commitSnapshot: this.commitSnapshot
1547
+ });
1548
+ };
1549
+ this.handleCompositionStart = () => {
1550
+ this.isComposing = true;
1551
+ };
1552
+ this.handleCompositionEnd = (event) => {
1553
+ this.isComposing = false;
1554
+ this.viewController.handleCompositionEnd({
1555
+ event,
1556
+ commitSnapshot: this.commitSnapshot
1557
+ });
1558
+ };
1559
+ this.handleKeyDown = (event) => {
1560
+ const config = this.requireConfig();
1561
+ const activeSlashItem = config.slashItems[config.activeSlashIndex] ?? null;
1562
+ this.viewController.handleKeyDown({
1563
+ event,
1564
+ slashItems: config.slashItems,
1565
+ activeSlashIndex: config.activeSlashIndex,
1566
+ activeSlashItem,
1567
+ actions: config.actions,
1568
+ commitSnapshot: this.commitSnapshot,
1569
+ insertSkillToken: this.insertSkillToken,
1570
+ onSlashActiveIndexChange: config.onSlashActiveIndexChange,
1571
+ onSlashQueryChange: config.onSlashQueryChange,
1572
+ onSlashOpenChange: config.onSlashOpenChange
1573
+ });
1574
+ };
1575
+ this.handlePaste = (event) => {
1576
+ this.viewController.handlePaste({
1577
+ event,
1578
+ commitSnapshot: this.commitSnapshot
1579
+ });
1580
+ };
1581
+ this.handleBlur = () => {
1582
+ const config = this.requireConfig();
1583
+ this.isComposing = false;
1584
+ this.viewController.handleBlur({
1585
+ setSelectedRange: this.setSelectedRange,
1586
+ onSlashQueryChange: config.onSlashQueryChange,
1587
+ onSlashOpenChange: config.onSlashOpenChange
1588
+ });
1589
+ };
1590
+ this.setSelectedRange = (selection) => {
1591
+ this.selectedRange = selection;
1592
+ this.selection = selection;
1593
+ this.requestRender();
1594
+ };
1595
+ this.commitSnapshot = (nextSnapshot) => {
1596
+ const config = this.requireConfig();
1597
+ this.selection = nextSnapshot.selection;
1598
+ this.selectedRange = nextSnapshot.selection;
1599
+ this.snapshot = nextSnapshot;
1600
+ config.onNodesChange(nextSnapshot.nodes);
1601
+ this.syncSlashState(nextSnapshot);
1602
+ };
1603
+ this.insertSkillToken = (tokenKey, label) => {
1604
+ this.commitSnapshot(this.controller.insertSkillToken(tokenKey, label));
1605
+ };
1606
+ this.syncSlashState = (nextSnapshot) => {
1607
+ const config = this.requireConfig();
1608
+ config.onSlashQueryChange?.(nextSnapshot.slashTrigger?.query ?? null);
1609
+ config.onSlashOpenChange(nextSnapshot.slashTrigger !== null);
1610
+ };
1611
+ this.requestRender = () => {
1612
+ this.requireConfig().requestRender();
1613
+ };
1614
+ this.requireConfig = () => {
1615
+ if (!this.config) {
1616
+ throw new Error("ChatComposerRuntime is not configured.");
1617
+ }
1618
+ return this.config;
1619
+ };
1620
+ }
1621
+ };
1622
+
1623
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-tokenized-composer.tsx
1624
+ import { jsx as jsx10 } from "react/jsx-runtime";
1625
+ var ChatInputBarTokenizedComposer = forwardRef6(function ChatInputBarTokenizedComposer2(props, ref) {
1626
+ const {
1627
+ nodes,
1628
+ placeholder,
1629
+ disabled,
1630
+ slashItems,
1631
+ actions,
1632
+ onNodesChange,
1633
+ onSlashQueryChange,
1634
+ onSlashOpenChange,
1635
+ onSlashActiveIndexChange,
1636
+ activeSlashIndex
1637
+ } = props;
1638
+ const [renderTick, setRenderTick] = useState3(0);
1639
+ const [runtime] = useState3(() => new ChatComposerRuntime());
1640
+ const {
1641
+ snapshot,
1642
+ bindRootElement,
1643
+ handleBeforeInput,
1644
+ handleInput,
1645
+ handleCompositionStart,
1646
+ handleCompositionEnd,
1647
+ handleKeyDown,
1648
+ handlePaste,
1649
+ handleBlur,
1650
+ syncSelectionState,
1651
+ imperativeHandle
1652
+ } = runtime.update({
1653
+ nodes,
1654
+ disabled,
1655
+ slashItems,
1656
+ actions,
1657
+ onNodesChange,
1658
+ onSlashQueryChange,
1659
+ onSlashOpenChange,
1660
+ onSlashActiveIndexChange,
1661
+ activeSlashIndex,
1662
+ requestRender: () => setRenderTick((value) => value + 1)
1663
+ });
1664
+ void renderTick;
1665
+ useImperativeHandle(ref, () => imperativeHandle, [imperativeHandle]);
1666
+ useLayoutEffect(() => {
1667
+ runtime.renderSurface();
1668
+ runtime.restoreDomAfterCommit();
1669
+ runtime.syncViewport();
1670
+ }, [runtime, snapshot.nodes, snapshot.nodeStartMap, renderTick]);
1671
+ return /* @__PURE__ */ jsx10("div", { className: "px-4 py-2.5", children: /* @__PURE__ */ jsx10("div", { className: "min-h-[60px]", children: /* @__PURE__ */ jsx10(
1672
+ "div",
1673
+ {
1674
+ ref: bindRootElement,
1675
+ contentEditable: !disabled,
1676
+ suppressContentEditableWarning: true,
1677
+ role: "textbox",
1678
+ "aria-multiline": "true",
1679
+ "data-placeholder": placeholder,
1680
+ onBeforeInput: handleBeforeInput,
1681
+ onInput: handleInput,
1682
+ onCompositionStart: handleCompositionStart,
1683
+ onCompositionEnd: handleCompositionEnd,
1684
+ onKeyDown: handleKeyDown,
1685
+ onKeyUp: syncSelectionState,
1686
+ onMouseUp: syncSelectionState,
1687
+ onFocus: syncSelectionState,
1688
+ onBlur: handleBlur,
1689
+ onPaste: handlePaste,
1690
+ className: "min-h-7 max-h-[188px] w-full overflow-y-auto whitespace-pre-wrap break-words bg-transparent py-0.5 text-sm leading-6 text-gray-800 outline-none empty:before:pointer-events-none empty:before:text-gray-400 empty:before:content-[attr(data-placeholder)]"
1691
+ }
1692
+ ) }) });
1693
+ });
1694
+
689
1695
  // src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
690
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1696
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
691
1697
  function InputBarHint({ hint }) {
692
1698
  if (!hint) {
693
1699
  return null;
694
1700
  }
695
1701
  if (hint.loading) {
696
- return /* @__PURE__ */ jsx12("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs7("div", { className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2", children: [
697
- /* @__PURE__ */ jsx12("span", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
698
- /* @__PURE__ */ jsx12("span", { className: "h-3 w-16 animate-pulse rounded bg-gray-200" })
1702
+ return /* @__PURE__ */ jsx11("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs6("div", { className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2", children: [
1703
+ /* @__PURE__ */ jsx11("span", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
1704
+ /* @__PURE__ */ jsx11("span", { className: "h-3 w-16 animate-pulse rounded bg-gray-200" })
699
1705
  ] }) });
700
1706
  }
701
1707
  const toneClassName = hint.tone === "warning" ? "border-amber-200 bg-amber-50 text-amber-800" : "border-gray-200 bg-gray-50 text-gray-700";
702
- return /* @__PURE__ */ jsx12("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs7("div", { className: `inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs ${toneClassName}`, children: [
703
- hint.text ? /* @__PURE__ */ jsx12("span", { children: hint.text }) : null,
704
- hint.actionLabel && hint.onAction ? /* @__PURE__ */ jsx12(
1708
+ return /* @__PURE__ */ jsx11("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs6("div", { className: `inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs ${toneClassName}`, children: [
1709
+ hint.text ? /* @__PURE__ */ jsx11("span", { children: hint.text }) : null,
1710
+ hint.actionLabel && hint.onAction ? /* @__PURE__ */ jsx11(
705
1711
  "button",
706
1712
  {
707
1713
  type: "button",
@@ -713,49 +1719,133 @@ function InputBarHint({ hint }) {
713
1719
  ] }) });
714
1720
  }
715
1721
  function ChatInputBar(props) {
716
- return /* @__PURE__ */ jsx12("div", { className: "border-t border-gray-200/80 bg-white p-4", children: /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-[min(1120px,100%)]", children: /* @__PURE__ */ jsxs7("div", { className: "overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card", children: [
717
- /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
718
- /* @__PURE__ */ jsx12(
719
- ChatInputBarTextarea,
1722
+ const composerRef = useRef4(null);
1723
+ const [slashQuery, setSlashQuery] = useState4(null);
1724
+ const [activeSlashIndex, setActiveSlashIndex] = useState4(0);
1725
+ const isSlashPanelOpen = slashQuery !== null;
1726
+ const activeSlashItem = props.slashMenu.items[activeSlashIndex] ?? null;
1727
+ useEffect4(() => {
1728
+ setActiveSlashIndex((current) => {
1729
+ if (props.slashMenu.items.length === 0) {
1730
+ return 0;
1731
+ }
1732
+ return Math.min(current, props.slashMenu.items.length - 1);
1733
+ });
1734
+ }, [props.slashMenu.items.length]);
1735
+ useEffect4(() => {
1736
+ if (slashQuery !== null) {
1737
+ setActiveSlashIndex(0);
1738
+ }
1739
+ }, [slashQuery]);
1740
+ const toolbar = useMemo3(() => {
1741
+ if (!props.toolbar.skillPicker) {
1742
+ return props.toolbar;
1743
+ }
1744
+ return {
1745
+ ...props.toolbar,
1746
+ skillPicker: {
1747
+ ...props.toolbar.skillPicker,
1748
+ onSelectedKeysChange: (nextKeys) => {
1749
+ composerRef.current?.syncSelectedSkills(nextKeys, props.toolbar.skillPicker?.options ?? []);
1750
+ }
1751
+ }
1752
+ };
1753
+ }, [props.toolbar]);
1754
+ return /* @__PURE__ */ jsx11("div", { className: "border-t border-gray-200/80 bg-white p-4", children: /* @__PURE__ */ jsx11("div", { className: "mx-auto w-full max-w-[min(1120px,100%)]", children: /* @__PURE__ */ jsxs6("div", { className: "overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card", children: [
1755
+ /* @__PURE__ */ jsxs6("div", { className: "relative", children: [
1756
+ /* @__PURE__ */ jsx11(
1757
+ ChatInputBarTokenizedComposer,
720
1758
  {
721
- value: props.value,
722
- placeholder: props.placeholder,
723
- disabled: props.disabled,
724
- onValueChange: props.onValueChange,
725
- onKeyDown: props.onKeyDown
1759
+ ref: composerRef,
1760
+ nodes: props.composer.nodes,
1761
+ placeholder: props.composer.placeholder,
1762
+ disabled: props.composer.disabled,
1763
+ slashItems: props.slashMenu.items,
1764
+ actions: props.toolbar.actions,
1765
+ activeSlashIndex,
1766
+ onNodesChange: props.composer.onNodesChange,
1767
+ onSlashQueryChange: (query) => {
1768
+ setSlashQuery(query);
1769
+ props.composer.onSlashQueryChange?.(query);
1770
+ },
1771
+ onSlashOpenChange: (open) => {
1772
+ if (!open) {
1773
+ setSlashQuery(null);
1774
+ }
1775
+ },
1776
+ onSlashActiveIndexChange: setActiveSlashIndex
726
1777
  }
727
1778
  ),
728
- /* @__PURE__ */ jsx12(ChatSlashMenu, { ...props.slashMenu })
1779
+ /* @__PURE__ */ jsx11(
1780
+ ChatSlashMenu,
1781
+ {
1782
+ isOpen: isSlashPanelOpen,
1783
+ isLoading: props.slashMenu.isLoading,
1784
+ items: props.slashMenu.items,
1785
+ activeIndex: activeSlashIndex,
1786
+ activeItem: activeSlashItem,
1787
+ texts: props.slashMenu.texts,
1788
+ onSelectItem: (item) => {
1789
+ composerRef.current?.insertSlashItem(item);
1790
+ },
1791
+ onOpenChange: (open) => {
1792
+ if (!open) {
1793
+ setSlashQuery(null);
1794
+ }
1795
+ },
1796
+ onSetActiveIndex: setActiveSlashIndex
1797
+ }
1798
+ )
729
1799
  ] }),
730
- /* @__PURE__ */ jsx12(InputBarHint, { hint: props.hint }),
731
- /* @__PURE__ */ jsx12(ChatInputBarSelectedItems, { items: props.selectedItems.items, onRemove: props.selectedItems.onRemove }),
732
- /* @__PURE__ */ jsx12(ChatInputBarToolbar, { ...props.toolbar })
1800
+ /* @__PURE__ */ jsx11(InputBarHint, { hint: props.hint }),
1801
+ /* @__PURE__ */ jsx11(ChatInputBarToolbar, { ...toolbar })
733
1802
  ] }) }) });
734
1803
  }
735
1804
 
736
1805
  // src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
737
1806
  import { Bot, User, Wrench } from "lucide-react";
738
- import { jsx as jsx13 } from "react/jsx-runtime";
1807
+ import { jsx as jsx12 } from "react/jsx-runtime";
739
1808
  function ChatMessageAvatar({ role }) {
740
1809
  if (role === "user") {
741
- return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white shadow-sm", children: /* @__PURE__ */ jsx13(User, { className: "h-4 w-4" }) });
1810
+ return /* @__PURE__ */ jsx12(
1811
+ "div",
1812
+ {
1813
+ "data-testid": "chat-message-avatar-user",
1814
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-sm",
1815
+ children: /* @__PURE__ */ jsx12(User, { className: "h-4 w-4" })
1816
+ }
1817
+ );
742
1818
  }
743
- if (role === "assistant") {
744
- return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-slate-900 text-white shadow-sm", children: /* @__PURE__ */ jsx13(Bot, { className: "h-4 w-4" }) });
1819
+ if (role === "tool") {
1820
+ return /* @__PURE__ */ jsx12(
1821
+ "div",
1822
+ {
1823
+ "data-testid": "chat-message-avatar-tool",
1824
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm",
1825
+ children: /* @__PURE__ */ jsx12(Wrench, { className: "h-4 w-4" })
1826
+ }
1827
+ );
745
1828
  }
746
- return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm", children: /* @__PURE__ */ jsx13(Wrench, { className: "h-4 w-4" }) });
1829
+ return /* @__PURE__ */ jsx12(
1830
+ "div",
1831
+ {
1832
+ "data-testid": "chat-message-avatar-assistant",
1833
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-gray-200/80 bg-gray-900 text-white shadow-sm",
1834
+ children: /* @__PURE__ */ jsx12(Bot, { className: "h-4 w-4" })
1835
+ }
1836
+ );
747
1837
  }
748
1838
 
749
1839
  // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
750
- import { useMemo as useMemo4 } from "react";
1840
+ import { useMemo as useMemo5 } from "react";
751
1841
  import ReactMarkdown from "react-markdown";
752
1842
  import remarkGfm from "remark-gfm";
753
1843
 
754
1844
  // src/components/chat/ui/chat-message-list/chat-code-block.tsx
755
- import { useMemo as useMemo3 } from "react";
1845
+ import { useMemo as useMemo4 } from "react";
756
1846
 
757
1847
  // src/components/chat/hooks/use-copy-feedback.ts
758
- import { useCallback, useEffect as useEffect4, useState as useState3 } from "react";
1848
+ import { useCallback, useEffect as useEffect5, useState as useState5 } from "react";
759
1849
 
760
1850
  // src/components/chat/utils/copy-text.ts
761
1851
  function canUseClipboardApi() {
@@ -841,7 +1931,7 @@ async function copyText(text) {
841
1931
  // src/components/chat/hooks/use-copy-feedback.ts
842
1932
  var DEFAULT_RESET_DELAY_MS = 1300;
843
1933
  function useCopyFeedback(params) {
844
- const [copied, setCopied] = useState3(false);
1934
+ const [copied, setCopied] = useState5(false);
845
1935
  const copy = useCallback(async () => {
846
1936
  if (!params.text) {
847
1937
  return;
@@ -853,7 +1943,7 @@ function useCopyFeedback(params) {
853
1943
  setCopied(false);
854
1944
  }
855
1945
  }, [params.text]);
856
- useEffect4(() => {
1946
+ useEffect5(() => {
857
1947
  if (!copied || typeof window === "undefined") {
858
1948
  return;
859
1949
  }
@@ -868,7 +1958,7 @@ function useCopyFeedback(params) {
868
1958
 
869
1959
  // src/components/chat/ui/chat-message-list/chat-code-block.tsx
870
1960
  import { Check as Check3, Copy } from "lucide-react";
871
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1961
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
872
1962
  var CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
873
1963
  function flattenNodeText(value) {
874
1964
  if (typeof value === "string" || typeof value === "number") {
@@ -888,13 +1978,13 @@ function resolveCodeLanguage(className) {
888
1978
  return match?.[1]?.toLowerCase() || "text";
889
1979
  }
890
1980
  function ChatCodeBlock(props) {
891
- const language = useMemo3(() => resolveCodeLanguage(props.className), [props.className]);
892
- const codeText = useMemo3(() => normalizeCodeText(props.children), [props.children]);
1981
+ const language = useMemo4(() => resolveCodeLanguage(props.className), [props.className]);
1982
+ const codeText = useMemo4(() => normalizeCodeText(props.children), [props.children]);
893
1983
  const { copied, copy } = useCopyFeedback({ text: codeText });
894
- return /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock", children: [
895
- /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock-toolbar", children: [
896
- /* @__PURE__ */ jsx14("span", { className: "chat-codeblock-language", children: language }),
897
- /* @__PURE__ */ jsxs8(
1984
+ return /* @__PURE__ */ jsxs7("div", { className: "chat-codeblock", children: [
1985
+ /* @__PURE__ */ jsxs7("div", { className: "chat-codeblock-toolbar", children: [
1986
+ /* @__PURE__ */ jsx13("span", { className: "chat-codeblock-language", children: language }),
1987
+ /* @__PURE__ */ jsxs7(
898
1988
  "button",
899
1989
  {
900
1990
  type: "button",
@@ -902,18 +1992,18 @@ function ChatCodeBlock(props) {
902
1992
  onClick: () => void copy(),
903
1993
  "aria-label": copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel,
904
1994
  children: [
905
- copied ? /* @__PURE__ */ jsx14(Check3, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx14(Copy, { className: "h-3.5 w-3.5" }),
906
- /* @__PURE__ */ jsx14("span", { children: copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel })
1995
+ copied ? /* @__PURE__ */ jsx13(Check3, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx13(Copy, { className: "h-3.5 w-3.5" }),
1996
+ /* @__PURE__ */ jsx13("span", { children: copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel })
907
1997
  ]
908
1998
  }
909
1999
  )
910
2000
  ] }),
911
- /* @__PURE__ */ jsx14("pre", { children: /* @__PURE__ */ jsx14("code", { className: props.className, children: codeText }) })
2001
+ /* @__PURE__ */ jsx13("pre", { children: /* @__PURE__ */ jsx13("code", { className: props.className, children: codeText }) })
912
2002
  ] });
913
2003
  }
914
2004
 
915
2005
  // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
916
- import { jsx as jsx15 } from "react/jsx-runtime";
2006
+ import { jsx as jsx14 } from "react/jsx-runtime";
917
2007
  var MARKDOWN_MAX_CHARS = 14e4;
918
2008
  var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
919
2009
  function trimMarkdown(value) {
@@ -943,14 +2033,14 @@ function isExternalHref(href) {
943
2033
  }
944
2034
  function ChatMessageMarkdown(props) {
945
2035
  const isUser = props.role === "user";
946
- const markdownComponents = useMemo4(() => ({
2036
+ const markdownComponents = useMemo5(() => ({
947
2037
  a: ({ href, children, ...rest }) => {
948
2038
  const safeHref = resolveSafeHref(href);
949
2039
  if (!safeHref) {
950
- return /* @__PURE__ */ jsx15("span", { className: "chat-link-invalid", children });
2040
+ return /* @__PURE__ */ jsx14("span", { className: "chat-link-invalid", children });
951
2041
  }
952
2042
  const external = isExternalHref(safeHref);
953
- return /* @__PURE__ */ jsx15(
2043
+ return /* @__PURE__ */ jsx14(
954
2044
  "a",
955
2045
  {
956
2046
  ...rest,
@@ -961,38 +2051,38 @@ function ChatMessageMarkdown(props) {
961
2051
  }
962
2052
  );
963
2053
  },
964
- table: ({ children, ...rest }) => /* @__PURE__ */ jsx15("div", { className: "chat-table-wrap", children: /* @__PURE__ */ jsx15("table", { ...rest, children }) }),
2054
+ table: ({ children, ...rest }) => /* @__PURE__ */ jsx14("div", { className: "chat-table-wrap", children: /* @__PURE__ */ jsx14("table", { ...rest, children }) }),
965
2055
  input: ({ type, checked, ...rest }) => {
966
2056
  if (type !== "checkbox") {
967
- return /* @__PURE__ */ jsx15("input", { ...rest, type });
2057
+ return /* @__PURE__ */ jsx14("input", { ...rest, type });
968
2058
  }
969
- return /* @__PURE__ */ jsx15("input", { ...rest, type: "checkbox", checked, readOnly: true, disabled: true, className: "chat-task-checkbox" });
2059
+ return /* @__PURE__ */ jsx14("input", { ...rest, type: "checkbox", checked, readOnly: true, disabled: true, className: "chat-task-checkbox" });
970
2060
  },
971
2061
  img: ({ src, alt, ...rest }) => {
972
2062
  const safeSrc = resolveSafeHref(src);
973
2063
  if (!safeSrc) {
974
2064
  return null;
975
2065
  }
976
- return /* @__PURE__ */ jsx15("img", { ...rest, src: safeSrc, alt: alt || "", loading: "lazy", decoding: "async" });
2066
+ return /* @__PURE__ */ jsx14("img", { ...rest, src: safeSrc, alt: alt || "", loading: "lazy", decoding: "async" });
977
2067
  },
978
2068
  code: ({ className, children, ...rest }) => {
979
2069
  const plainText = String(children ?? "");
980
2070
  const isInlineCode = !className && !plainText.includes("\n");
981
2071
  if (isInlineCode) {
982
- return /* @__PURE__ */ jsx15("code", { ...rest, className: cn("chat-inline-code", className), children });
2072
+ return /* @__PURE__ */ jsx14("code", { ...rest, className: cn("chat-inline-code", className), children });
983
2073
  }
984
- return /* @__PURE__ */ jsx15(ChatCodeBlock, { className, texts: props.texts, children });
2074
+ return /* @__PURE__ */ jsx14(ChatCodeBlock, { className, texts: props.texts, children });
985
2075
  }
986
2076
  }), [props.texts]);
987
- return /* @__PURE__ */ jsx15("div", { className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"), children: /* @__PURE__ */ jsx15(ReactMarkdown, { skipHtml: true, remarkPlugins: [remarkGfm], components: markdownComponents, children: trimMarkdown(props.text) }) });
2077
+ return /* @__PURE__ */ jsx14("div", { className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"), children: /* @__PURE__ */ jsx14(ReactMarkdown, { skipHtml: true, remarkPlugins: [remarkGfm], components: markdownComponents, children: trimMarkdown(props.text) }) });
988
2078
  }
989
2079
 
990
2080
  // src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
991
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
2081
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
992
2082
  function ChatReasoningBlock(props) {
993
- return /* @__PURE__ */ jsxs9("details", { className: "mt-3", children: [
994
- /* @__PURE__ */ jsx16("summary", { className: cn("cursor-pointer text-xs", props.isUser ? "text-primary-100" : "text-gray-500"), children: props.label }),
995
- /* @__PURE__ */ jsx16(
2083
+ return /* @__PURE__ */ jsxs8("details", { className: "mt-3", open: true, children: [
2084
+ /* @__PURE__ */ jsx15("summary", { className: cn("cursor-pointer text-xs", props.isUser ? "text-primary-100" : "text-gray-500"), children: props.label }),
2085
+ /* @__PURE__ */ jsx15(
996
2086
  "pre",
997
2087
  {
998
2088
  className: cn(
@@ -1007,87 +2097,87 @@ function ChatReasoningBlock(props) {
1007
2097
 
1008
2098
  // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
1009
2099
  import { Clock3, FileSearch, Globe, Search as Search2, SendHorizontal, Terminal, Wrench as Wrench2 } from "lucide-react";
1010
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2100
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1011
2101
  var TOOL_OUTPUT_PREVIEW_MAX = 220;
1012
2102
  function renderToolIcon(toolName) {
1013
2103
  const lowered = toolName.toLowerCase();
1014
2104
  if (lowered.includes("exec") || lowered.includes("shell") || lowered.includes("command")) {
1015
- return /* @__PURE__ */ jsx17(Terminal, { className: "h-3.5 w-3.5" });
2105
+ return /* @__PURE__ */ jsx16(Terminal, { className: "h-3.5 w-3.5" });
1016
2106
  }
1017
2107
  if (lowered.includes("search")) {
1018
- return /* @__PURE__ */ jsx17(Search2, { className: "h-3.5 w-3.5" });
2108
+ return /* @__PURE__ */ jsx16(Search2, { className: "h-3.5 w-3.5" });
1019
2109
  }
1020
2110
  if (lowered.includes("fetch") || lowered.includes("http") || lowered.includes("web")) {
1021
- return /* @__PURE__ */ jsx17(Globe, { className: "h-3.5 w-3.5" });
2111
+ return /* @__PURE__ */ jsx16(Globe, { className: "h-3.5 w-3.5" });
1022
2112
  }
1023
2113
  if (lowered.includes("read") || lowered.includes("file")) {
1024
- return /* @__PURE__ */ jsx17(FileSearch, { className: "h-3.5 w-3.5" });
2114
+ return /* @__PURE__ */ jsx16(FileSearch, { className: "h-3.5 w-3.5" });
1025
2115
  }
1026
2116
  if (lowered.includes("message") || lowered.includes("send")) {
1027
- return /* @__PURE__ */ jsx17(SendHorizontal, { className: "h-3.5 w-3.5" });
2117
+ return /* @__PURE__ */ jsx16(SendHorizontal, { className: "h-3.5 w-3.5" });
1028
2118
  }
1029
2119
  if (lowered.includes("cron") || lowered.includes("schedule")) {
1030
- return /* @__PURE__ */ jsx17(Clock3, { className: "h-3.5 w-3.5" });
2120
+ return /* @__PURE__ */ jsx16(Clock3, { className: "h-3.5 w-3.5" });
1031
2121
  }
1032
- return /* @__PURE__ */ jsx17(Wrench2, { className: "h-3.5 w-3.5" });
2122
+ return /* @__PURE__ */ jsx16(Wrench2, { className: "h-3.5 w-3.5" });
1033
2123
  }
1034
2124
  function ChatToolCard({ card }) {
1035
2125
  const output = card.output?.trim() ?? "";
1036
2126
  const showDetails = output.length > TOOL_OUTPUT_PREVIEW_MAX || output.includes("\n");
1037
2127
  const preview = showDetails ? `${output.slice(0, TOOL_OUTPUT_PREVIEW_MAX)}...` : output;
1038
2128
  const showOutputSection = card.kind === "result" || card.hasResult;
1039
- return /* @__PURE__ */ jsxs10("div", { className: "rounded-xl border border-amber-200/80 bg-amber-50/60 px-3 py-2.5", children: [
1040
- /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-800", children: [
2129
+ return /* @__PURE__ */ jsxs9("div", { className: "rounded-xl border border-amber-200/80 bg-amber-50/60 px-3 py-2.5", children: [
2130
+ /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-800", children: [
1041
2131
  renderToolIcon(card.toolName),
1042
- /* @__PURE__ */ jsx17("span", { children: card.titleLabel }),
1043
- /* @__PURE__ */ jsx17("span", { className: "font-mono text-[11px] text-amber-900/80", children: card.toolName })
2132
+ /* @__PURE__ */ jsx16("span", { children: card.titleLabel }),
2133
+ /* @__PURE__ */ jsx16("span", { className: "font-mono text-[11px] text-amber-900/80", children: card.toolName })
1044
2134
  ] }),
1045
- card.summary ? /* @__PURE__ */ jsx17("div", { className: "mt-1 break-words font-mono text-[11px] text-amber-800/90", children: card.summary }) : null,
1046
- showOutputSection ? /* @__PURE__ */ jsx17("div", { className: "mt-2", children: !output ? /* @__PURE__ */ jsx17("div", { className: "text-[11px] text-amber-700/80", children: card.emptyLabel }) : showDetails ? /* @__PURE__ */ jsxs10("details", { className: "group", children: [
1047
- /* @__PURE__ */ jsx17("summary", { className: "cursor-pointer text-[11px] text-amber-700", children: card.outputLabel }),
1048
- /* @__PURE__ */ jsx17("pre", { className: "mt-2 whitespace-pre-wrap break-words rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] text-amber-900", children: output })
1049
- ] }) : /* @__PURE__ */ jsx17("pre", { className: "rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] whitespace-pre-wrap break-words text-amber-900", children: preview }) }) : null
2135
+ card.summary ? /* @__PURE__ */ jsx16("div", { className: "mt-1 break-words font-mono text-[11px] text-amber-800/90", children: card.summary }) : null,
2136
+ showOutputSection ? /* @__PURE__ */ jsx16("div", { className: "mt-2", children: !output ? /* @__PURE__ */ jsx16("div", { className: "text-[11px] text-amber-700/80", children: card.emptyLabel }) : showDetails ? /* @__PURE__ */ jsxs9("details", { className: "group", children: [
2137
+ /* @__PURE__ */ jsx16("summary", { className: "cursor-pointer text-[11px] text-amber-700", children: card.outputLabel }),
2138
+ /* @__PURE__ */ jsx16("pre", { className: "mt-2 whitespace-pre-wrap break-words rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] text-amber-900", children: output })
2139
+ ] }) : /* @__PURE__ */ jsx16("pre", { className: "rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] whitespace-pre-wrap break-words text-amber-900", children: preview }) }) : null
1050
2140
  ] });
1051
2141
  }
1052
2142
 
1053
2143
  // src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
1054
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2144
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1055
2145
  function ChatUnknownPart(props) {
1056
- return /* @__PURE__ */ jsxs11("div", { className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600", children: [
1057
- /* @__PURE__ */ jsxs11("div", { className: "font-semibold text-gray-700", children: [
2146
+ return /* @__PURE__ */ jsxs10("div", { className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600", children: [
2147
+ /* @__PURE__ */ jsxs10("div", { className: "font-semibold text-gray-700", children: [
1058
2148
  props.label,
1059
2149
  ": ",
1060
2150
  props.rawType
1061
2151
  ] }),
1062
- props.text ? /* @__PURE__ */ jsx18("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
2152
+ props.text ? /* @__PURE__ */ jsx17("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
1063
2153
  ] });
1064
2154
  }
1065
2155
 
1066
2156
  // src/components/chat/ui/chat-message-list/chat-message.tsx
1067
- import { jsx as jsx19 } from "react/jsx-runtime";
2157
+ import { jsx as jsx18 } from "react/jsx-runtime";
1068
2158
  function ChatMessage(props) {
1069
2159
  const { message, texts } = props;
1070
2160
  const { role } = message;
1071
2161
  const isUser = role === "user";
1072
- return /* @__PURE__ */ jsx19(
2162
+ return /* @__PURE__ */ jsx18(
1073
2163
  "div",
1074
2164
  {
1075
2165
  className: cn(
1076
2166
  "inline-block w-fit max-w-full rounded-2xl border px-4 py-3 shadow-sm",
1077
2167
  isUser ? "border-primary bg-primary text-white" : role === "assistant" ? "border-gray-200 bg-white text-gray-900" : "border-orange-200/80 bg-orange-50/70 text-gray-900"
1078
2168
  ),
1079
- children: /* @__PURE__ */ jsx19("div", { className: "space-y-2", children: message.parts.map((part, index) => {
2169
+ children: /* @__PURE__ */ jsx18("div", { className: "space-y-2", children: message.parts.map((part, index) => {
1080
2170
  if (part.type === "markdown") {
1081
- return /* @__PURE__ */ jsx19(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
2171
+ return /* @__PURE__ */ jsx18(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
1082
2172
  }
1083
2173
  if (part.type === "reasoning") {
1084
- return /* @__PURE__ */ jsx19(ChatReasoningBlock, { label: part.label, text: part.text, isUser }, `reasoning-${index}`);
2174
+ return /* @__PURE__ */ jsx18(ChatReasoningBlock, { label: part.label, text: part.text, isUser }, `reasoning-${index}`);
1085
2175
  }
1086
2176
  if (part.type === "tool-card") {
1087
- return /* @__PURE__ */ jsx19("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx19(ChatToolCard, { card: part.card }) }, `tool-${index}`);
2177
+ return /* @__PURE__ */ jsx18("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx18(ChatToolCard, { card: part.card }) }, `tool-${index}`);
1088
2178
  }
1089
2179
  if (part.type === "unknown") {
1090
- return /* @__PURE__ */ jsx19(ChatUnknownPart, { label: part.label, rawType: part.rawType, text: part.text }, `unknown-${index}`);
2180
+ return /* @__PURE__ */ jsx18(ChatUnknownPart, { label: part.label, rawType: part.rawType, text: part.text }, `unknown-${index}`);
1091
2181
  }
1092
2182
  return null;
1093
2183
  }) })
@@ -1096,48 +2186,77 @@ function ChatMessage(props) {
1096
2186
  }
1097
2187
 
1098
2188
  // src/components/chat/ui/chat-message-list/chat-message-meta.tsx
1099
- import { jsxs as jsxs12 } from "react/jsx-runtime";
2189
+ import { jsxs as jsxs11 } from "react/jsx-runtime";
1100
2190
  function ChatMessageMeta(props) {
1101
- return /* @__PURE__ */ jsxs12("div", { className: cn("px-1 text-[11px]", props.isUser ? "text-primary-300" : "text-gray-400"), children: [
1102
- props.roleLabel,
1103
- " \xB7 ",
1104
- props.timestampLabel
1105
- ] });
2191
+ return /* @__PURE__ */ jsxs11(
2192
+ "div",
2193
+ {
2194
+ className: cn(
2195
+ "px-1 text-[11px] leading-4 text-gray-400",
2196
+ props.isUser ? "text-right" : "text-left"
2197
+ ),
2198
+ children: [
2199
+ props.roleLabel,
2200
+ " \xB7 ",
2201
+ props.timestampLabel
2202
+ ]
2203
+ }
2204
+ );
1106
2205
  }
1107
2206
 
1108
2207
  // src/components/chat/ui/chat-message-list/chat-message-list.tsx
1109
- import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
2208
+ import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2209
+ var INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
2210
+ function hasRenderableText(value) {
2211
+ const trimmed = value.trim();
2212
+ if (!trimmed) {
2213
+ return false;
2214
+ }
2215
+ return trimmed.replace(INVISIBLE_ONLY_TEXT_PATTERN, "").trim().length > 0;
2216
+ }
2217
+ function hasRenderableMessageContent(message) {
2218
+ return message.parts.some((part) => {
2219
+ if (part.type === "markdown" || part.type === "reasoning") {
2220
+ return hasRenderableText(part.text);
2221
+ }
2222
+ return true;
2223
+ });
2224
+ }
1110
2225
  function ChatMessageList(props) {
1111
- return /* @__PURE__ */ jsxs13("div", { className: cn("space-y-5", props.className), children: [
1112
- props.messages.map((message) => {
2226
+ const visibleMessages = props.messages.filter(hasRenderableMessageContent);
2227
+ const hasRenderableAssistantDraft = visibleMessages.some(
2228
+ (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending")
2229
+ );
2230
+ return /* @__PURE__ */ jsxs12("div", { className: cn("space-y-5", props.className), children: [
2231
+ visibleMessages.map((message) => {
1113
2232
  const isUser = message.role === "user";
1114
- return /* @__PURE__ */ jsxs13("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
1115
- !isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null,
1116
- /* @__PURE__ */ jsxs13("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
1117
- /* @__PURE__ */ jsx20(ChatMessage, { message, texts: props.texts }),
1118
- /* @__PURE__ */ jsx20(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser })
2233
+ return /* @__PURE__ */ jsxs12("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
2234
+ !isUser ? /* @__PURE__ */ jsx19(ChatMessageAvatar, { role: message.role }) : null,
2235
+ /* @__PURE__ */ jsxs12("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
2236
+ /* @__PURE__ */ jsx19(ChatMessage, { message, texts: props.texts }),
2237
+ /* @__PURE__ */ jsx19(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser })
1119
2238
  ] }),
1120
- isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null
2239
+ isUser ? /* @__PURE__ */ jsx19(ChatMessageAvatar, { role: message.role }) : null
1121
2240
  ] }, message.id);
1122
2241
  }),
1123
- props.isSending && !props.hasStreamingDraft ? /* @__PURE__ */ jsxs13("div", { className: "flex justify-start gap-3", children: [
1124
- /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: "assistant" }),
1125
- /* @__PURE__ */ jsx20("div", { className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm", children: props.texts.typingLabel })
2242
+ props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs12("div", { className: "flex justify-start gap-3", children: [
2243
+ /* @__PURE__ */ jsx19(ChatMessageAvatar, { role: "assistant" }),
2244
+ /* @__PURE__ */ jsx19("div", { className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm", children: props.texts.typingLabel })
1126
2245
  ] }) : null
1127
2246
  ] });
1128
2247
  }
1129
2248
 
1130
2249
  // src/components/chat/hooks/use-sticky-bottom-scroll.ts
1131
- import { useCallback as useCallback2, useEffect as useEffect5, useLayoutEffect, useRef as useRef4 } from "react";
2250
+ import { useCallback as useCallback2, useEffect as useEffect6, useLayoutEffect as useLayoutEffect2, useRef as useRef5 } from "react";
1132
2251
  var DEFAULT_STICKY_THRESHOLD_PX = 10;
1133
2252
  function scrollElementToBottom(element) {
1134
2253
  element.scrollTop = element.scrollHeight;
1135
2254
  }
1136
2255
  function useStickyBottomScroll(params) {
1137
- const isStickyRef = useRef4(true);
1138
- const isProgrammaticScrollRef = useRef4(false);
1139
- const previousResetKeyRef = useRef4(null);
1140
- const pendingInitialScrollRef = useRef4(false);
2256
+ const isStickyRef = useRef5(true);
2257
+ const isProgrammaticScrollRef = useRef5(false);
2258
+ const previousResetKeyRef = useRef5(null);
2259
+ const pendingInitialScrollRef = useRef5(false);
1141
2260
  const onScroll = useCallback2(() => {
1142
2261
  if (isProgrammaticScrollRef.current) {
1143
2262
  isProgrammaticScrollRef.current = false;
@@ -1150,7 +2269,7 @@ function useStickyBottomScroll(params) {
1150
2269
  const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
1151
2270
  isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
1152
2271
  }, [params.scrollRef, params.stickyThresholdPx]);
1153
- useEffect5(() => {
2272
+ useEffect6(() => {
1154
2273
  if (previousResetKeyRef.current === params.resetKey) {
1155
2274
  return;
1156
2275
  }
@@ -1158,7 +2277,7 @@ function useStickyBottomScroll(params) {
1158
2277
  isStickyRef.current = true;
1159
2278
  pendingInitialScrollRef.current = true;
1160
2279
  }, [params.resetKey]);
1161
- useLayoutEffect(() => {
2280
+ useLayoutEffect2(() => {
1162
2281
  if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
1163
2282
  return;
1164
2283
  }
@@ -1170,7 +2289,7 @@ function useStickyBottomScroll(params) {
1170
2289
  isProgrammaticScrollRef.current = true;
1171
2290
  scrollElementToBottom(element);
1172
2291
  }, [params.hasContent, params.isLoading, params.scrollRef]);
1173
- useLayoutEffect(() => {
2292
+ useLayoutEffect2(() => {
1174
2293
  if (!isStickyRef.current || !params.hasContent) {
1175
2294
  return;
1176
2295
  }
@@ -1187,6 +2306,17 @@ export {
1187
2306
  ChatInputBar,
1188
2307
  ChatMessageList,
1189
2308
  copyText,
2309
+ createChatComposerNodesFromText,
2310
+ createChatComposerTextNode,
2311
+ createChatComposerTokenNode,
2312
+ createEmptyChatComposerNodes,
2313
+ extractChatComposerTokenKeys,
2314
+ normalizeChatComposerNodes,
2315
+ removeChatComposerTokenNodes,
2316
+ replaceChatComposerRange,
2317
+ resolveChatComposerSlashTrigger,
2318
+ serializeChatComposerDocument,
2319
+ serializeChatComposerPlainText,
1190
2320
  useActiveItemScroll,
1191
2321
  useCopyFeedback,
1192
2322
  useElementWidth,