@nextclaw/agent-chat-ui 0.2.0 → 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 +49 -12
  2. package/dist/index.js +1345 -255
  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,15 +334,15 @@ 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
342
  import { ArrowUp } from "lucide-react";
378
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
343
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
379
344
  function StopIcon() {
380
- return /* @__PURE__ */ jsx9(
345
+ return /* @__PURE__ */ jsx7(
381
346
  "span",
382
347
  {
383
348
  "aria-hidden": "true",
@@ -388,9 +353,9 @@ function StopIcon() {
388
353
  }
389
354
  function ChatInputBarActions(props) {
390
355
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
391
- return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-end gap-1", children: [
392
- props.sendError?.trim() ? /* @__PURE__ */ jsx9("div", { className: "max-w-[420px] text-right text-[11px] text-red-600", children: props.sendError }) : null,
393
- /* @__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(
394
359
  ChatButton,
395
360
  {
396
361
  size: "icon",
@@ -399,10 +364,10 @@ function ChatInputBarActions(props) {
399
364
  "aria-label": props.stopButtonLabel,
400
365
  onClick: () => void props.onStop(),
401
366
  disabled: props.stopDisabled,
402
- children: /* @__PURE__ */ jsx9(StopIcon, {})
367
+ children: /* @__PURE__ */ jsx7(StopIcon, {})
403
368
  }
404
- ) : /* @__PURE__ */ jsx9(TooltipProvider, { children: /* @__PURE__ */ jsxs4(Tooltip, { children: [
405
- /* @__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(
406
371
  ChatButton,
407
372
  {
408
373
  size: "icon",
@@ -410,11 +375,11 @@ function ChatInputBarActions(props) {
410
375
  className: "h-8 w-8 rounded-full",
411
376
  "aria-label": props.stopButtonLabel,
412
377
  disabled: true,
413
- children: /* @__PURE__ */ jsx9(StopIcon, {})
378
+ children: /* @__PURE__ */ jsx7(StopIcon, {})
414
379
  }
415
380
  ) }) }),
416
- /* @__PURE__ */ jsx9(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx9("p", { className: "text-xs", children: props.stopHint }) })
417
- ] }) }) : /* @__PURE__ */ jsx9(
381
+ /* @__PURE__ */ jsx7(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx7("p", { className: "text-xs", children: props.stopHint }) })
382
+ ] }) }) : /* @__PURE__ */ jsx7(
418
383
  ChatButton,
419
384
  {
420
385
  size: "icon",
@@ -422,7 +387,7 @@ function ChatInputBarActions(props) {
422
387
  "aria-label": props.sendButtonLabel,
423
388
  onClick: () => void props.onSend(),
424
389
  disabled: props.sendDisabled,
425
- children: /* @__PURE__ */ jsx9(ArrowUp, { className: "h-5 w-5" })
390
+ children: /* @__PURE__ */ jsx7(ArrowUp, { className: "h-5 w-5" })
426
391
  }
427
392
  ) })
428
393
  ] });
@@ -431,7 +396,7 @@ function ChatInputBarActions(props) {
431
396
  // src/components/chat/ui/chat-input-bar/chat-input-bar-skill-picker.tsx
432
397
  import { useEffect as useEffect3, useId, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
433
398
  import { BrainCircuit, Check as Check2, ExternalLink, Puzzle, Search } from "lucide-react";
434
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
399
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
435
400
  function filterOptions(options, query) {
436
401
  const keyword = query.trim().toLowerCase();
437
402
  if (!keyword) {
@@ -499,26 +464,26 @@ function ChatInputBarSkillPicker(props) {
499
464
  }
500
465
  }
501
466
  };
502
- return /* @__PURE__ */ jsxs5(Popover, { children: [
503
- /* @__PURE__ */ jsx10(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
467
+ return /* @__PURE__ */ jsxs4(Popover, { children: [
468
+ /* @__PURE__ */ jsx8(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs4(
504
469
  "button",
505
470
  {
506
471
  type: "button",
507
472
  "aria-haspopup": "listbox",
508
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",
509
474
  children: [
510
- /* @__PURE__ */ jsx10(BrainCircuit, { className: "h-4 w-4" }),
511
- /* @__PURE__ */ jsx10("span", { children: picker.title }),
512
- 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
513
478
  ]
514
479
  }
515
480
  ) }),
516
- /* @__PURE__ */ jsxs5(PopoverContent, { side: "top", align: "start", className: "w-[360px] p-0", children: [
517
- /* @__PURE__ */ jsxs5("div", { className: "space-y-2 border-b border-gray-100 px-4 py-3", children: [
518
- /* @__PURE__ */ jsx10("div", { className: "text-sm font-semibold text-gray-900", children: picker.title }),
519
- /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
520
- /* @__PURE__ */ jsx10(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-gray-400" }),
521
- /* @__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(
522
487
  Input,
523
488
  {
524
489
  value: query,
@@ -535,7 +500,7 @@ function ChatInputBarSkillPicker(props) {
535
500
  )
536
501
  ] })
537
502
  ] }),
538
- /* @__PURE__ */ jsx10(
503
+ /* @__PURE__ */ jsx8(
539
504
  "div",
540
505
  {
541
506
  ref: listRef,
@@ -543,10 +508,10 @@ function ChatInputBarSkillPicker(props) {
543
508
  role: "listbox",
544
509
  "aria-multiselectable": "true",
545
510
  className: "custom-scrollbar max-h-[320px] overflow-y-auto",
546
- 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) => {
547
512
  const isSelected = selectedSet.has(option.key);
548
513
  const isActive = index === activeIndex;
549
- return /* @__PURE__ */ jsxs5(
514
+ return /* @__PURE__ */ jsxs4(
550
515
  "button",
551
516
  {
552
517
  type: "button",
@@ -558,19 +523,19 @@ function ChatInputBarSkillPicker(props) {
558
523
  onMouseEnter: () => setActiveIndex(index),
559
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"}`,
560
525
  children: [
561
- /* @__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" }) }),
562
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
563
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
564
- /* @__PURE__ */ jsx10("span", { className: "truncate text-sm text-gray-900", children: option.label }),
565
- 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
566
531
  ] }),
567
- /* @__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 })
568
533
  ] }),
569
- /* @__PURE__ */ jsx10("div", { className: "ml-3 shrink-0", children: /* @__PURE__ */ jsx10(
534
+ /* @__PURE__ */ jsx8("div", { className: "ml-3 shrink-0", children: /* @__PURE__ */ jsx8(
570
535
  "span",
571
536
  {
572
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",
573
- children: isSelected ? /* @__PURE__ */ jsx10(Check2, { className: "h-3 w-3" }) : null
538
+ children: isSelected ? /* @__PURE__ */ jsx8(Check2, { className: "h-3 w-3" }) : null
574
539
  }
575
540
  ) })
576
541
  ]
@@ -580,14 +545,14 @@ function ChatInputBarSkillPicker(props) {
580
545
  }) })
581
546
  }
582
547
  ),
583
- 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(
584
549
  "a",
585
550
  {
586
551
  href: picker.manageHref,
587
552
  className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary transition-colors hover:text-primary/80",
588
553
  children: [
589
554
  picker.manageLabel,
590
- /* @__PURE__ */ jsx10(ExternalLink, { className: "h-3 w-3" })
555
+ /* @__PURE__ */ jsx8(ExternalLink, { className: "h-3 w-3" })
591
556
  ]
592
557
  }
593
558
  ) }) : null
@@ -596,21 +561,21 @@ function ChatInputBarSkillPicker(props) {
596
561
  }
597
562
 
598
563
  // src/components/chat/ui/chat-input-bar/chat-input-bar-toolbar.tsx
599
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
564
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
600
565
  function ToolbarIcon({ icon }) {
601
566
  if (icon === "sparkles") {
602
- 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" });
603
568
  }
604
569
  if (icon === "brain") {
605
- 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" });
606
571
  }
607
572
  return null;
608
573
  }
609
574
  function AccessoryIcon({ icon }) {
610
575
  if (icon === "paperclip") {
611
- return /* @__PURE__ */ jsx11(Paperclip, { className: "h-4 w-4" });
576
+ return /* @__PURE__ */ jsx9(Paperclip, { className: "h-4 w-4" });
612
577
  }
613
- return /* @__PURE__ */ jsx11(ToolbarIcon, { icon });
578
+ return /* @__PURE__ */ jsx9(ToolbarIcon, { icon });
614
579
  }
615
580
  function resolveTriggerWidth(key) {
616
581
  if (key === "model") {
@@ -638,80 +603,1111 @@ function resolveContentWidth(key) {
638
603
  }
639
604
  function ToolbarSelect({ item }) {
640
605
  const { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } = ChatUiPrimitives;
641
- return /* @__PURE__ */ jsxs6(Select, { value: item.value, onValueChange: item.onValueChange, disabled: item.disabled, children: [
642
- /* @__PURE__ */ jsx11(
606
+ return /* @__PURE__ */ jsxs5(Select, { value: item.value, onValueChange: item.onValueChange, disabled: item.disabled, children: [
607
+ /* @__PURE__ */ jsx9(
643
608
  SelectTrigger,
644
609
  {
645
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)}`,
646
- children: item.selectedLabel ? /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 items-center gap-2 text-left", children: [
647
- /* @__PURE__ */ jsx11(ToolbarIcon, { icon: item.icon }),
648
- /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-700", children: item.selectedLabel })
649
- ] }) : 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 })
650
615
  }
651
616
  ),
652
- /* @__PURE__ */ jsxs6(SelectContent, { className: resolveContentWidth(item.key), children: [
653
- item.options.length === 0 ? item.loading ? /* @__PURE__ */ jsxs6("div", { className: "space-y-2 px-3 py-2", children: [
654
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-36 animate-pulse rounded bg-gray-200" }),
655
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
656
- /* @__PURE__ */ jsx11("div", { className: "h-3 w-32 animate-pulse rounded bg-gray-200" })
657
- ] }) : item.emptyLabel ? /* @__PURE__ */ jsx11("div", { className: "px-3 py-2 text-xs text-gray-500", children: item.emptyLabel }) : null : null,
658
- 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: [
659
- /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }),
660
- /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-gray-500", children: option.description })
661
- ] }) : /* @__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))
662
627
  ] })
663
628
  ] });
664
629
  }
665
630
  function ChatInputBarToolbar(props) {
666
631
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
667
- return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-3 pb-3", children: [
668
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
669
- props.skillPicker ? /* @__PURE__ */ jsx11(ChatInputBarSkillPicker, { picker: props.skillPicker }) : null,
670
- 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)),
671
636
  props.accessories?.map((item) => {
672
- const button = /* @__PURE__ */ jsxs6(
637
+ const button = /* @__PURE__ */ jsxs5(
673
638
  "button",
674
639
  {
675
640
  type: "button",
676
- 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"}`,
677
642
  onClick: item.onClick,
678
643
  disabled: item.disabled,
679
644
  "aria-label": item.label,
680
645
  children: [
681
- /* @__PURE__ */ jsx11(AccessoryIcon, { icon: item.icon }),
682
- /* @__PURE__ */ jsx11("span", { children: item.label })
646
+ /* @__PURE__ */ jsx9(AccessoryIcon, { icon: item.icon }),
647
+ item.iconOnly ? null : /* @__PURE__ */ jsx9("span", { children: item.label })
683
648
  ]
684
649
  }
685
650
  );
686
651
  if (!item.tooltip) {
687
- return /* @__PURE__ */ jsx11("div", { children: button }, item.key);
652
+ return /* @__PURE__ */ jsx9("div", { children: button }, item.key);
688
653
  }
689
- return /* @__PURE__ */ jsx11(TooltipProvider, { children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
690
- /* @__PURE__ */ jsx11(TooltipTrigger, { asChild: true, children: button }),
691
- /* @__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 }) })
692
658
  ] }) }, item.key);
693
659
  })
694
660
  ] }),
695
- /* @__PURE__ */ jsx11(ChatInputBarActions, { ...props.actions })
661
+ /* @__PURE__ */ jsx9(ChatInputBarActions, { ...props.actions })
696
662
  ] });
697
663
  }
698
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
+
699
1695
  // src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
700
- import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1696
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
701
1697
  function InputBarHint({ hint }) {
702
1698
  if (!hint) {
703
1699
  return null;
704
1700
  }
705
1701
  if (hint.loading) {
706
- 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: [
707
- /* @__PURE__ */ jsx12("span", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
708
- /* @__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" })
709
1705
  ] }) });
710
1706
  }
711
1707
  const toneClassName = hint.tone === "warning" ? "border-amber-200 bg-amber-50 text-amber-800" : "border-gray-200 bg-gray-50 text-gray-700";
712
- 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: [
713
- hint.text ? /* @__PURE__ */ jsx12("span", { children: hint.text }) : null,
714
- 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(
715
1711
  "button",
716
1712
  {
717
1713
  type: "button",
@@ -723,70 +1719,133 @@ function InputBarHint({ hint }) {
723
1719
  ] }) });
724
1720
  }
725
1721
  function ChatInputBar(props) {
726
- 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: [
727
- /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
728
- /* @__PURE__ */ jsx12(
729
- 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,
730
1758
  {
731
- value: props.value,
732
- placeholder: props.placeholder,
733
- disabled: props.disabled,
734
- onValueChange: props.onValueChange,
735
- 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
736
1777
  }
737
1778
  ),
738
- /* @__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
+ )
739
1799
  ] }),
740
- /* @__PURE__ */ jsx12(InputBarHint, { hint: props.hint }),
741
- /* @__PURE__ */ jsx12(ChatInputBarSelectedItems, { items: props.selectedItems.items, onRemove: props.selectedItems.onRemove }),
742
- /* @__PURE__ */ jsx12(ChatInputBarToolbar, { ...props.toolbar })
1800
+ /* @__PURE__ */ jsx11(InputBarHint, { hint: props.hint }),
1801
+ /* @__PURE__ */ jsx11(ChatInputBarToolbar, { ...toolbar })
743
1802
  ] }) }) });
744
1803
  }
745
1804
 
746
1805
  // src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
747
1806
  import { Bot, User, Wrench } from "lucide-react";
748
- import { jsx as jsx13 } from "react/jsx-runtime";
1807
+ import { jsx as jsx12 } from "react/jsx-runtime";
749
1808
  function ChatMessageAvatar({ role }) {
750
1809
  if (role === "user") {
751
- return /* @__PURE__ */ jsx13(
1810
+ return /* @__PURE__ */ jsx12(
752
1811
  "div",
753
1812
  {
754
1813
  "data-testid": "chat-message-avatar-user",
755
1814
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-primary text-white shadow-sm",
756
- children: /* @__PURE__ */ jsx13(User, { className: "h-4 w-4" })
1815
+ children: /* @__PURE__ */ jsx12(User, { className: "h-4 w-4" })
757
1816
  }
758
1817
  );
759
1818
  }
760
1819
  if (role === "tool") {
761
- return /* @__PURE__ */ jsx13(
1820
+ return /* @__PURE__ */ jsx12(
762
1821
  "div",
763
1822
  {
764
1823
  "data-testid": "chat-message-avatar-tool",
765
1824
  className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm",
766
- children: /* @__PURE__ */ jsx13(Wrench, { className: "h-4 w-4" })
1825
+ children: /* @__PURE__ */ jsx12(Wrench, { className: "h-4 w-4" })
767
1826
  }
768
1827
  );
769
1828
  }
770
- return /* @__PURE__ */ jsx13(
1829
+ return /* @__PURE__ */ jsx12(
771
1830
  "div",
772
1831
  {
773
1832
  "data-testid": "chat-message-avatar-assistant",
774
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",
775
- children: /* @__PURE__ */ jsx13(Bot, { className: "h-4 w-4" })
1834
+ children: /* @__PURE__ */ jsx12(Bot, { className: "h-4 w-4" })
776
1835
  }
777
1836
  );
778
1837
  }
779
1838
 
780
1839
  // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
781
- import { useMemo as useMemo4 } from "react";
1840
+ import { useMemo as useMemo5 } from "react";
782
1841
  import ReactMarkdown from "react-markdown";
783
1842
  import remarkGfm from "remark-gfm";
784
1843
 
785
1844
  // src/components/chat/ui/chat-message-list/chat-code-block.tsx
786
- import { useMemo as useMemo3 } from "react";
1845
+ import { useMemo as useMemo4 } from "react";
787
1846
 
788
1847
  // src/components/chat/hooks/use-copy-feedback.ts
789
- import { useCallback, useEffect as useEffect4, useState as useState3 } from "react";
1848
+ import { useCallback, useEffect as useEffect5, useState as useState5 } from "react";
790
1849
 
791
1850
  // src/components/chat/utils/copy-text.ts
792
1851
  function canUseClipboardApi() {
@@ -872,7 +1931,7 @@ async function copyText(text) {
872
1931
  // src/components/chat/hooks/use-copy-feedback.ts
873
1932
  var DEFAULT_RESET_DELAY_MS = 1300;
874
1933
  function useCopyFeedback(params) {
875
- const [copied, setCopied] = useState3(false);
1934
+ const [copied, setCopied] = useState5(false);
876
1935
  const copy = useCallback(async () => {
877
1936
  if (!params.text) {
878
1937
  return;
@@ -884,7 +1943,7 @@ function useCopyFeedback(params) {
884
1943
  setCopied(false);
885
1944
  }
886
1945
  }, [params.text]);
887
- useEffect4(() => {
1946
+ useEffect5(() => {
888
1947
  if (!copied || typeof window === "undefined") {
889
1948
  return;
890
1949
  }
@@ -899,7 +1958,7 @@ function useCopyFeedback(params) {
899
1958
 
900
1959
  // src/components/chat/ui/chat-message-list/chat-code-block.tsx
901
1960
  import { Check as Check3, Copy } from "lucide-react";
902
- import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1961
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
903
1962
  var CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
904
1963
  function flattenNodeText(value) {
905
1964
  if (typeof value === "string" || typeof value === "number") {
@@ -919,13 +1978,13 @@ function resolveCodeLanguage(className) {
919
1978
  return match?.[1]?.toLowerCase() || "text";
920
1979
  }
921
1980
  function ChatCodeBlock(props) {
922
- const language = useMemo3(() => resolveCodeLanguage(props.className), [props.className]);
923
- 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]);
924
1983
  const { copied, copy } = useCopyFeedback({ text: codeText });
925
- return /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock", children: [
926
- /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock-toolbar", children: [
927
- /* @__PURE__ */ jsx14("span", { className: "chat-codeblock-language", children: language }),
928
- /* @__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(
929
1988
  "button",
930
1989
  {
931
1990
  type: "button",
@@ -933,18 +1992,18 @@ function ChatCodeBlock(props) {
933
1992
  onClick: () => void copy(),
934
1993
  "aria-label": copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel,
935
1994
  children: [
936
- copied ? /* @__PURE__ */ jsx14(Check3, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx14(Copy, { className: "h-3.5 w-3.5" }),
937
- /* @__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 })
938
1997
  ]
939
1998
  }
940
1999
  )
941
2000
  ] }),
942
- /* @__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 }) })
943
2002
  ] });
944
2003
  }
945
2004
 
946
2005
  // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
947
- import { jsx as jsx15 } from "react/jsx-runtime";
2006
+ import { jsx as jsx14 } from "react/jsx-runtime";
948
2007
  var MARKDOWN_MAX_CHARS = 14e4;
949
2008
  var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
950
2009
  function trimMarkdown(value) {
@@ -974,14 +2033,14 @@ function isExternalHref(href) {
974
2033
  }
975
2034
  function ChatMessageMarkdown(props) {
976
2035
  const isUser = props.role === "user";
977
- const markdownComponents = useMemo4(() => ({
2036
+ const markdownComponents = useMemo5(() => ({
978
2037
  a: ({ href, children, ...rest }) => {
979
2038
  const safeHref = resolveSafeHref(href);
980
2039
  if (!safeHref) {
981
- return /* @__PURE__ */ jsx15("span", { className: "chat-link-invalid", children });
2040
+ return /* @__PURE__ */ jsx14("span", { className: "chat-link-invalid", children });
982
2041
  }
983
2042
  const external = isExternalHref(safeHref);
984
- return /* @__PURE__ */ jsx15(
2043
+ return /* @__PURE__ */ jsx14(
985
2044
  "a",
986
2045
  {
987
2046
  ...rest,
@@ -992,38 +2051,38 @@ function ChatMessageMarkdown(props) {
992
2051
  }
993
2052
  );
994
2053
  },
995
- 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 }) }),
996
2055
  input: ({ type, checked, ...rest }) => {
997
2056
  if (type !== "checkbox") {
998
- return /* @__PURE__ */ jsx15("input", { ...rest, type });
2057
+ return /* @__PURE__ */ jsx14("input", { ...rest, type });
999
2058
  }
1000
- 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" });
1001
2060
  },
1002
2061
  img: ({ src, alt, ...rest }) => {
1003
2062
  const safeSrc = resolveSafeHref(src);
1004
2063
  if (!safeSrc) {
1005
2064
  return null;
1006
2065
  }
1007
- 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" });
1008
2067
  },
1009
2068
  code: ({ className, children, ...rest }) => {
1010
2069
  const plainText = String(children ?? "");
1011
2070
  const isInlineCode = !className && !plainText.includes("\n");
1012
2071
  if (isInlineCode) {
1013
- 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 });
1014
2073
  }
1015
- return /* @__PURE__ */ jsx15(ChatCodeBlock, { className, texts: props.texts, children });
2074
+ return /* @__PURE__ */ jsx14(ChatCodeBlock, { className, texts: props.texts, children });
1016
2075
  }
1017
2076
  }), [props.texts]);
1018
- 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) }) });
1019
2078
  }
1020
2079
 
1021
2080
  // src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
1022
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
2081
+ import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1023
2082
  function ChatReasoningBlock(props) {
1024
- return /* @__PURE__ */ jsxs9("details", { className: "mt-3", open: true, children: [
1025
- /* @__PURE__ */ jsx16("summary", { className: cn("cursor-pointer text-xs", props.isUser ? "text-primary-100" : "text-gray-500"), children: props.label }),
1026
- /* @__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(
1027
2086
  "pre",
1028
2087
  {
1029
2088
  className: cn(
@@ -1038,87 +2097,87 @@ function ChatReasoningBlock(props) {
1038
2097
 
1039
2098
  // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
1040
2099
  import { Clock3, FileSearch, Globe, Search as Search2, SendHorizontal, Terminal, Wrench as Wrench2 } from "lucide-react";
1041
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2100
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
1042
2101
  var TOOL_OUTPUT_PREVIEW_MAX = 220;
1043
2102
  function renderToolIcon(toolName) {
1044
2103
  const lowered = toolName.toLowerCase();
1045
2104
  if (lowered.includes("exec") || lowered.includes("shell") || lowered.includes("command")) {
1046
- return /* @__PURE__ */ jsx17(Terminal, { className: "h-3.5 w-3.5" });
2105
+ return /* @__PURE__ */ jsx16(Terminal, { className: "h-3.5 w-3.5" });
1047
2106
  }
1048
2107
  if (lowered.includes("search")) {
1049
- return /* @__PURE__ */ jsx17(Search2, { className: "h-3.5 w-3.5" });
2108
+ return /* @__PURE__ */ jsx16(Search2, { className: "h-3.5 w-3.5" });
1050
2109
  }
1051
2110
  if (lowered.includes("fetch") || lowered.includes("http") || lowered.includes("web")) {
1052
- return /* @__PURE__ */ jsx17(Globe, { className: "h-3.5 w-3.5" });
2111
+ return /* @__PURE__ */ jsx16(Globe, { className: "h-3.5 w-3.5" });
1053
2112
  }
1054
2113
  if (lowered.includes("read") || lowered.includes("file")) {
1055
- return /* @__PURE__ */ jsx17(FileSearch, { className: "h-3.5 w-3.5" });
2114
+ return /* @__PURE__ */ jsx16(FileSearch, { className: "h-3.5 w-3.5" });
1056
2115
  }
1057
2116
  if (lowered.includes("message") || lowered.includes("send")) {
1058
- return /* @__PURE__ */ jsx17(SendHorizontal, { className: "h-3.5 w-3.5" });
2117
+ return /* @__PURE__ */ jsx16(SendHorizontal, { className: "h-3.5 w-3.5" });
1059
2118
  }
1060
2119
  if (lowered.includes("cron") || lowered.includes("schedule")) {
1061
- return /* @__PURE__ */ jsx17(Clock3, { className: "h-3.5 w-3.5" });
2120
+ return /* @__PURE__ */ jsx16(Clock3, { className: "h-3.5 w-3.5" });
1062
2121
  }
1063
- return /* @__PURE__ */ jsx17(Wrench2, { className: "h-3.5 w-3.5" });
2122
+ return /* @__PURE__ */ jsx16(Wrench2, { className: "h-3.5 w-3.5" });
1064
2123
  }
1065
2124
  function ChatToolCard({ card }) {
1066
2125
  const output = card.output?.trim() ?? "";
1067
2126
  const showDetails = output.length > TOOL_OUTPUT_PREVIEW_MAX || output.includes("\n");
1068
2127
  const preview = showDetails ? `${output.slice(0, TOOL_OUTPUT_PREVIEW_MAX)}...` : output;
1069
2128
  const showOutputSection = card.kind === "result" || card.hasResult;
1070
- return /* @__PURE__ */ jsxs10("div", { className: "rounded-xl border border-amber-200/80 bg-amber-50/60 px-3 py-2.5", children: [
1071
- /* @__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: [
1072
2131
  renderToolIcon(card.toolName),
1073
- /* @__PURE__ */ jsx17("span", { children: card.titleLabel }),
1074
- /* @__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 })
1075
2134
  ] }),
1076
- card.summary ? /* @__PURE__ */ jsx17("div", { className: "mt-1 break-words font-mono text-[11px] text-amber-800/90", children: card.summary }) : null,
1077
- 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: [
1078
- /* @__PURE__ */ jsx17("summary", { className: "cursor-pointer text-[11px] text-amber-700", children: card.outputLabel }),
1079
- /* @__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 })
1080
- ] }) : /* @__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
1081
2140
  ] });
1082
2141
  }
1083
2142
 
1084
2143
  // src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
1085
- import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2144
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1086
2145
  function ChatUnknownPart(props) {
1087
- 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: [
1088
- /* @__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: [
1089
2148
  props.label,
1090
2149
  ": ",
1091
2150
  props.rawType
1092
2151
  ] }),
1093
- 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
1094
2153
  ] });
1095
2154
  }
1096
2155
 
1097
2156
  // src/components/chat/ui/chat-message-list/chat-message.tsx
1098
- import { jsx as jsx19 } from "react/jsx-runtime";
2157
+ import { jsx as jsx18 } from "react/jsx-runtime";
1099
2158
  function ChatMessage(props) {
1100
2159
  const { message, texts } = props;
1101
2160
  const { role } = message;
1102
2161
  const isUser = role === "user";
1103
- return /* @__PURE__ */ jsx19(
2162
+ return /* @__PURE__ */ jsx18(
1104
2163
  "div",
1105
2164
  {
1106
2165
  className: cn(
1107
2166
  "inline-block w-fit max-w-full rounded-2xl border px-4 py-3 shadow-sm",
1108
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"
1109
2168
  ),
1110
- 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) => {
1111
2170
  if (part.type === "markdown") {
1112
- return /* @__PURE__ */ jsx19(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
2171
+ return /* @__PURE__ */ jsx18(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
1113
2172
  }
1114
2173
  if (part.type === "reasoning") {
1115
- 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}`);
1116
2175
  }
1117
2176
  if (part.type === "tool-card") {
1118
- 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}`);
1119
2178
  }
1120
2179
  if (part.type === "unknown") {
1121
- 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}`);
1122
2181
  }
1123
2182
  return null;
1124
2183
  }) })
@@ -1127,9 +2186,9 @@ function ChatMessage(props) {
1127
2186
  }
1128
2187
 
1129
2188
  // src/components/chat/ui/chat-message-list/chat-message-meta.tsx
1130
- import { jsxs as jsxs12 } from "react/jsx-runtime";
2189
+ import { jsxs as jsxs11 } from "react/jsx-runtime";
1131
2190
  function ChatMessageMeta(props) {
1132
- return /* @__PURE__ */ jsxs12(
2191
+ return /* @__PURE__ */ jsxs11(
1133
2192
  "div",
1134
2193
  {
1135
2194
  className: cn(
@@ -1146,38 +2205,58 @@ function ChatMessageMeta(props) {
1146
2205
  }
1147
2206
 
1148
2207
  // src/components/chat/ui/chat-message-list/chat-message-list.tsx
1149
- 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
+ }
1150
2225
  function ChatMessageList(props) {
1151
- return /* @__PURE__ */ jsxs13("div", { className: cn("space-y-5", props.className), children: [
1152
- 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) => {
1153
2232
  const isUser = message.role === "user";
1154
- return /* @__PURE__ */ jsxs13("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
1155
- !isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null,
1156
- /* @__PURE__ */ jsxs13("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
1157
- /* @__PURE__ */ jsx20(ChatMessage, { message, texts: props.texts }),
1158
- /* @__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 })
1159
2238
  ] }),
1160
- isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null
2239
+ isUser ? /* @__PURE__ */ jsx19(ChatMessageAvatar, { role: message.role }) : null
1161
2240
  ] }, message.id);
1162
2241
  }),
1163
- props.isSending && !props.hasAssistantDraft ? /* @__PURE__ */ jsxs13("div", { className: "flex justify-start gap-3", children: [
1164
- /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: "assistant" }),
1165
- /* @__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 })
1166
2245
  ] }) : null
1167
2246
  ] });
1168
2247
  }
1169
2248
 
1170
2249
  // src/components/chat/hooks/use-sticky-bottom-scroll.ts
1171
- 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";
1172
2251
  var DEFAULT_STICKY_THRESHOLD_PX = 10;
1173
2252
  function scrollElementToBottom(element) {
1174
2253
  element.scrollTop = element.scrollHeight;
1175
2254
  }
1176
2255
  function useStickyBottomScroll(params) {
1177
- const isStickyRef = useRef4(true);
1178
- const isProgrammaticScrollRef = useRef4(false);
1179
- const previousResetKeyRef = useRef4(null);
1180
- const pendingInitialScrollRef = useRef4(false);
2256
+ const isStickyRef = useRef5(true);
2257
+ const isProgrammaticScrollRef = useRef5(false);
2258
+ const previousResetKeyRef = useRef5(null);
2259
+ const pendingInitialScrollRef = useRef5(false);
1181
2260
  const onScroll = useCallback2(() => {
1182
2261
  if (isProgrammaticScrollRef.current) {
1183
2262
  isProgrammaticScrollRef.current = false;
@@ -1190,7 +2269,7 @@ function useStickyBottomScroll(params) {
1190
2269
  const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
1191
2270
  isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
1192
2271
  }, [params.scrollRef, params.stickyThresholdPx]);
1193
- useEffect5(() => {
2272
+ useEffect6(() => {
1194
2273
  if (previousResetKeyRef.current === params.resetKey) {
1195
2274
  return;
1196
2275
  }
@@ -1198,7 +2277,7 @@ function useStickyBottomScroll(params) {
1198
2277
  isStickyRef.current = true;
1199
2278
  pendingInitialScrollRef.current = true;
1200
2279
  }, [params.resetKey]);
1201
- useLayoutEffect(() => {
2280
+ useLayoutEffect2(() => {
1202
2281
  if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
1203
2282
  return;
1204
2283
  }
@@ -1210,7 +2289,7 @@ function useStickyBottomScroll(params) {
1210
2289
  isProgrammaticScrollRef.current = true;
1211
2290
  scrollElementToBottom(element);
1212
2291
  }, [params.hasContent, params.isLoading, params.scrollRef]);
1213
- useLayoutEffect(() => {
2292
+ useLayoutEffect2(() => {
1214
2293
  if (!isStickyRef.current || !params.hasContent) {
1215
2294
  return;
1216
2295
  }
@@ -1227,6 +2306,17 @@ export {
1227
2306
  ChatInputBar,
1228
2307
  ChatMessageList,
1229
2308
  copyText,
2309
+ createChatComposerNodesFromText,
2310
+ createChatComposerTextNode,
2311
+ createChatComposerTokenNode,
2312
+ createEmptyChatComposerNodes,
2313
+ extractChatComposerTokenKeys,
2314
+ normalizeChatComposerNodes,
2315
+ removeChatComposerTokenNodes,
2316
+ replaceChatComposerRange,
2317
+ resolveChatComposerSlashTrigger,
2318
+ serializeChatComposerDocument,
2319
+ serializeChatComposerPlainText,
1230
2320
  useActiveItemScroll,
1231
2321
  useCopyFeedback,
1232
2322
  useElementWidth,