@nextclaw/agent-chat-ui 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1194 @@
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
+ }
38
+
39
+ // src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
40
+ import { useMemo, useRef as useRef2 } from "react";
41
+
42
+ // src/components/chat/hooks/use-active-item-scroll.ts
43
+ import { useEffect } from "react";
44
+ var defaultGetItemSelector = (index) => `[data-item-index="${index}"]`;
45
+ function useActiveItemScroll(params) {
46
+ const getItemSelector = params.getItemSelector ?? defaultGetItemSelector;
47
+ useEffect(() => {
48
+ if (!params.isEnabled || params.itemCount === 0) {
49
+ return;
50
+ }
51
+ const container = params.containerRef.current;
52
+ if (!container) {
53
+ return;
54
+ }
55
+ const activeItem = container.querySelector(getItemSelector(params.activeIndex));
56
+ activeItem?.scrollIntoView({ block: "nearest", inline: "nearest" });
57
+ }, [getItemSelector, params.activeIndex, params.containerRef, params.isEnabled, params.itemCount]);
58
+ }
59
+
60
+ // src/components/chat/hooks/use-element-width.ts
61
+ import { useEffect as useEffect2, useRef, useState } from "react";
62
+ function useElementWidth() {
63
+ const elementRef = useRef(null);
64
+ const [width, setWidth] = useState(null);
65
+ useEffect2(() => {
66
+ const element = elementRef.current;
67
+ if (!element || typeof ResizeObserver === "undefined") {
68
+ return;
69
+ }
70
+ const updateWidth = () => {
71
+ setWidth(element.getBoundingClientRect().width);
72
+ };
73
+ updateWidth();
74
+ const observer = new ResizeObserver(() => updateWidth());
75
+ observer.observe(element);
76
+ return () => observer.disconnect();
77
+ }, []);
78
+ return {
79
+ elementRef,
80
+ width
81
+ };
82
+ }
83
+
84
+ // src/components/chat/default-skin/input.tsx
85
+ import * as React from "react";
86
+
87
+ // src/components/chat/internal/cn.ts
88
+ import { clsx } from "clsx";
89
+ import { twMerge } from "tailwind-merge";
90
+ function cn(...inputs) {
91
+ return twMerge(clsx(inputs));
92
+ }
93
+
94
+ // 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(
97
+ "input",
98
+ {
99
+ type,
100
+ className: cn(
101
+ "flex h-9 w-full rounded-xl border border-gray-200/80 bg-white px-3.5 py-2 text-sm text-gray-900 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-300 placeholder:font-normal focus:outline-none focus:ring-1 focus:ring-primary/40 focus:border-primary/40 transition-colors disabled:cursor-not-allowed disabled:opacity-50",
102
+ className
103
+ ),
104
+ ref,
105
+ ...props
106
+ }
107
+ ));
108
+ ChatInput.displayName = "ChatInput";
109
+
110
+ // src/components/chat/default-skin/popover.tsx
111
+ import * as React2 from "react";
112
+ import * as PopoverPrimitive from "@radix-ui/react-popover";
113
+ import { jsx as jsx4 } from "react/jsx-runtime";
114
+ var ChatPopover = PopoverPrimitive.Root;
115
+ var ChatPopoverTrigger = PopoverPrimitive.Trigger;
116
+ var ChatPopoverAnchor = PopoverPrimitive.Anchor;
117
+ var ChatPopoverContent = React2.forwardRef(({ className, sideOffset = 8, align = "start", ...props }, ref) => /* @__PURE__ */ jsx4(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
118
+ PopoverPrimitive.Content,
119
+ {
120
+ ref,
121
+ sideOffset,
122
+ align,
123
+ className: cn(
124
+ "z-[var(--z-popover,50)] w-72 overflow-hidden rounded-2xl border border-gray-200/50 bg-white p-4 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
125
+ className
126
+ ),
127
+ ...props
128
+ }
129
+ ) }));
130
+ ChatPopoverContent.displayName = PopoverPrimitive.Content.displayName;
131
+
132
+ // src/components/chat/default-skin/select.tsx
133
+ import * as React3 from "react";
134
+ import * as SelectPrimitive from "@radix-ui/react-select";
135
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
136
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
137
+ var ChatSelect = SelectPrimitive.Root;
138
+ var ChatSelectValue = SelectPrimitive.Value;
139
+ var ChatSelectTrigger = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(
140
+ SelectPrimitive.Trigger,
141
+ {
142
+ ref,
143
+ className: cn(
144
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl border border-gray-200/80 bg-white px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
145
+ className
146
+ ),
147
+ ...props,
148
+ children: [
149
+ children,
150
+ /* @__PURE__ */ jsx5(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx5(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
151
+ ]
152
+ }
153
+ ));
154
+ 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" }) }));
156
+ 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" }) }));
158
+ ChatSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
159
+ var ChatSelectContent = React3.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx5(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs2(
160
+ SelectPrimitive.Content,
161
+ {
162
+ ref,
163
+ className: cn(
164
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md bg-white text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
165
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
166
+ className
167
+ ),
168
+ position,
169
+ ...props,
170
+ children: [
171
+ /* @__PURE__ */ jsx5(ChatSelectScrollUpButton, {}),
172
+ /* @__PURE__ */ jsx5(
173
+ SelectPrimitive.Viewport,
174
+ {
175
+ className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
176
+ children
177
+ }
178
+ ),
179
+ /* @__PURE__ */ jsx5(ChatSelectScrollDownButton, {})
180
+ ]
181
+ }
182
+ ) }));
183
+ ChatSelectContent.displayName = SelectPrimitive.Content.displayName;
184
+ var ChatSelectItem = React3.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs2(
185
+ SelectPrimitive.Item,
186
+ {
187
+ ref,
188
+ className: cn(
189
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-gray-100 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-gray-100",
190
+ className
191
+ ),
192
+ ...props,
193
+ 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 })
196
+ ]
197
+ }
198
+ ));
199
+ ChatSelectItem.displayName = SelectPrimitive.Item.displayName;
200
+
201
+ // src/components/chat/default-skin/tooltip.tsx
202
+ import * as React4 from "react";
203
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
204
+ import { jsx as jsx6 } from "react/jsx-runtime";
205
+ var ChatTooltipProvider = TooltipPrimitive.Provider;
206
+ var ChatTooltip = TooltipPrimitive.Root;
207
+ var ChatTooltipTrigger = TooltipPrimitive.Trigger;
208
+ var ChatTooltipContent = React4.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx6(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx6(
209
+ TooltipPrimitive.Content,
210
+ {
211
+ ref,
212
+ sideOffset,
213
+ className: cn(
214
+ "z-[var(--z-tooltip)] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
215
+ className
216
+ ),
217
+ ...props
218
+ }
219
+ ) }));
220
+ ChatTooltipContent.displayName = TooltipPrimitive.Content.displayName;
221
+
222
+ // src/components/chat/ui/primitives/chat-ui-primitives.tsx
223
+ var ChatUiPrimitives = {
224
+ Popover: ChatPopover,
225
+ PopoverAnchor: ChatPopoverAnchor,
226
+ PopoverContent: ChatPopoverContent,
227
+ PopoverTrigger: ChatPopoverTrigger,
228
+ Input: ChatInput,
229
+ Select: ChatSelect,
230
+ SelectContent: ChatSelectContent,
231
+ SelectItem: ChatSelectItem,
232
+ SelectTrigger: ChatSelectTrigger,
233
+ SelectValue: ChatSelectValue,
234
+ Tooltip: ChatTooltip,
235
+ TooltipContent: ChatTooltipContent,
236
+ TooltipProvider: ChatTooltipProvider,
237
+ TooltipTrigger: ChatTooltipTrigger
238
+ };
239
+
240
+ // src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
241
+ import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
242
+ var SLASH_PANEL_MAX_WIDTH = 680;
243
+ var SLASH_PANEL_DESKTOP_SHRINK_RATIO = 0.82;
244
+ var SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
245
+ function ChatSlashMenu(props) {
246
+ const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
247
+ const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
248
+ const listRef = useRef2(null);
249
+ const {
250
+ isOpen,
251
+ isLoading,
252
+ items,
253
+ activeIndex,
254
+ activeItem,
255
+ texts,
256
+ onSelectItem,
257
+ onOpenChange,
258
+ onSetActiveIndex
259
+ } = props;
260
+ const resolvedWidth = useMemo(() => {
261
+ if (!panelWidth) {
262
+ return void 0;
263
+ }
264
+ return Math.min(
265
+ panelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO : panelWidth,
266
+ SLASH_PANEL_MAX_WIDTH
267
+ );
268
+ }, [panelWidth]);
269
+ useActiveItemScroll({
270
+ containerRef: listRef,
271
+ activeIndex,
272
+ itemCount: items.length,
273
+ isEnabled: isOpen && !isLoading,
274
+ getItemSelector: (index) => `[data-slash-index="${index}"]`
275
+ });
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(
279
+ PopoverContent,
280
+ {
281
+ side: "top",
282
+ align: "start",
283
+ sideOffset: 10,
284
+ 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
+ onOpenAutoFocus: (event) => event.preventDefault(),
286
+ 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(
289
+ "div",
290
+ {
291
+ ref: listRef,
292
+ role: "listbox",
293
+ "aria-label": texts.slashSectionLabel,
294
+ 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) => {
298
+ const isActive = index === activeIndex;
299
+ return /* @__PURE__ */ jsxs3(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ role: "option",
304
+ "aria-selected": isActive,
305
+ "data-slash-index": index,
306
+ onMouseEnter: () => onSetActiveIndex(index),
307
+ onClick: () => onSelectItem(item),
308
+ 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
+ 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 })
312
+ ]
313
+ },
314
+ item.key
315
+ );
316
+ }) })
317
+ ] })
318
+ }
319
+ ),
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 })
324
+ ] }),
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 }) })
329
+ ] })
330
+ }
331
+ )
332
+ ] });
333
+ }
334
+
335
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-toolbar.tsx
336
+ import { Brain, Paperclip, Sparkles } from "lucide-react";
337
+
338
+ // src/components/chat/default-skin/button.tsx
339
+ import * as React5 from "react";
340
+ import { cva } from "class-variance-authority";
341
+ import { jsx as jsx8 } from "react/jsx-runtime";
342
+ var buttonVariants = cva(
343
+ "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
+ {
345
+ variants: {
346
+ variant: {
347
+ default: "bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm",
348
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
349
+ outline: "border border-gray-200 bg-white hover:bg-gray-50 hover:text-gray-800 text-gray-600",
350
+ secondary: "bg-gray-100 text-gray-700 hover:bg-gray-200/80",
351
+ ghost: "hover:bg-gray-100/80 hover:text-gray-800",
352
+ link: "text-primary underline-offset-4 hover:underline",
353
+ primary: "bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm",
354
+ subtle: "bg-gray-100 text-gray-600 hover:bg-gray-200/80",
355
+ "primary-outline": "border border-primary/30 text-primary hover:bg-primary hover:text-primary-foreground"
356
+ },
357
+ size: {
358
+ default: "h-9 px-4 py-2",
359
+ sm: "h-8 px-3 text-xs",
360
+ lg: "h-11 px-5 text-[14px]",
361
+ xl: "h-12 px-6 text-[15px]",
362
+ icon: "h-9 w-9"
363
+ }
364
+ },
365
+ defaultVariants: {
366
+ variant: "default",
367
+ size: "default"
368
+ }
369
+ }
370
+ );
371
+ var ChatButton = React5.forwardRef(
372
+ ({ className, variant, size, ...props }, ref) => /* @__PURE__ */ jsx8("button", { className: cn(buttonVariants({ variant, size, className })), ref, ...props })
373
+ );
374
+ ChatButton.displayName = "ChatButton";
375
+
376
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-actions.tsx
377
+ import { ArrowUp, Square } from "lucide-react";
378
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
379
+ function ChatInputBarActions(props) {
380
+ const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
381
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-end gap-1", children: [
382
+ props.sendError?.trim() ? /* @__PURE__ */ jsx9("div", { className: "max-w-[420px] text-right text-[11px] text-red-600", children: props.sendError }) : null,
383
+ /* @__PURE__ */ jsx9("div", { className: "flex items-center gap-2", children: props.isSending ? props.canStopGeneration ? /* @__PURE__ */ jsx9(
384
+ ChatButton,
385
+ {
386
+ size: "icon",
387
+ variant: "outline",
388
+ className: "h-8 w-8 rounded-full",
389
+ "aria-label": props.stopButtonLabel,
390
+ onClick: () => void props.onStop(),
391
+ disabled: props.stopDisabled,
392
+ children: /* @__PURE__ */ jsx9(Square, { className: "h-3 w-3 fill-current" })
393
+ }
394
+ ) : /* @__PURE__ */ jsx9(TooltipProvider, { children: /* @__PURE__ */ jsxs4(Tooltip, { children: [
395
+ /* @__PURE__ */ jsx9(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx9("span", { children: /* @__PURE__ */ jsx9(
396
+ ChatButton,
397
+ {
398
+ size: "icon",
399
+ variant: "outline",
400
+ className: "h-8 w-8 rounded-full",
401
+ "aria-label": props.stopButtonLabel,
402
+ disabled: true,
403
+ children: /* @__PURE__ */ jsx9(Square, { className: "h-3 w-3 fill-current" })
404
+ }
405
+ ) }) }),
406
+ /* @__PURE__ */ jsx9(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx9("p", { className: "text-xs", children: props.stopHint }) })
407
+ ] }) }) : /* @__PURE__ */ jsx9(
408
+ ChatButton,
409
+ {
410
+ size: "icon",
411
+ className: "h-8 w-8 rounded-full",
412
+ "aria-label": props.sendButtonLabel,
413
+ onClick: () => void props.onSend(),
414
+ disabled: props.sendDisabled,
415
+ children: /* @__PURE__ */ jsx9(ArrowUp, { className: "h-5 w-5" })
416
+ }
417
+ ) })
418
+ ] });
419
+ }
420
+
421
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-skill-picker.tsx
422
+ import { useEffect as useEffect3, useId, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
423
+ import { BrainCircuit, Check as Check2, ExternalLink, Puzzle, Search } from "lucide-react";
424
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
425
+ function filterOptions(options, query) {
426
+ const keyword = query.trim().toLowerCase();
427
+ if (!keyword) {
428
+ return options;
429
+ }
430
+ return options.filter((option) => {
431
+ const haystack = [option.label, option.key, option.description].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase();
432
+ return haystack.includes(keyword);
433
+ });
434
+ }
435
+ function ChatInputBarSkillPicker(props) {
436
+ const { Input, Popover, PopoverContent, PopoverTrigger } = ChatUiPrimitives;
437
+ const { picker } = props;
438
+ const listRef = useRef3(null);
439
+ const listId = useId();
440
+ const [query, setQuery] = useState2("");
441
+ const [activeIndex, setActiveIndex] = useState2(0);
442
+ const selectedSet = useMemo2(() => new Set(picker.selectedKeys), [picker.selectedKeys]);
443
+ const selectedCount = picker.selectedKeys.length;
444
+ const filteredOptions = useMemo2(() => filterOptions(picker.options, query), [picker.options, query]);
445
+ useEffect3(() => {
446
+ setActiveIndex(0);
447
+ }, [query]);
448
+ useEffect3(() => {
449
+ setActiveIndex((current) => {
450
+ if (filteredOptions.length === 0) {
451
+ return 0;
452
+ }
453
+ return Math.min(current, filteredOptions.length - 1);
454
+ });
455
+ }, [filteredOptions.length]);
456
+ useActiveItemScroll({
457
+ containerRef: listRef,
458
+ activeIndex,
459
+ itemCount: filteredOptions.length,
460
+ isEnabled: filteredOptions.length > 0,
461
+ getItemSelector: (index) => `[data-skill-index="${index}"]`
462
+ });
463
+ const onToggleOption = (optionKey) => {
464
+ if (selectedSet.has(optionKey)) {
465
+ picker.onSelectedKeysChange(picker.selectedKeys.filter((item) => item !== optionKey));
466
+ return;
467
+ }
468
+ picker.onSelectedKeysChange([...picker.selectedKeys, optionKey]);
469
+ };
470
+ const onSearchKeyDown = (event) => {
471
+ if (filteredOptions.length === 0) {
472
+ return;
473
+ }
474
+ if (event.key === "ArrowDown") {
475
+ event.preventDefault();
476
+ setActiveIndex((current) => Math.min(current + 1, filteredOptions.length - 1));
477
+ return;
478
+ }
479
+ if (event.key === "ArrowUp") {
480
+ event.preventDefault();
481
+ setActiveIndex((current) => Math.max(current - 1, 0));
482
+ return;
483
+ }
484
+ if (event.key === "Enter") {
485
+ event.preventDefault();
486
+ const activeOption = filteredOptions[activeIndex];
487
+ if (activeOption) {
488
+ onToggleOption(activeOption.key);
489
+ }
490
+ }
491
+ };
492
+ return /* @__PURE__ */ jsxs5(Popover, { children: [
493
+ /* @__PURE__ */ jsx10(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs5(
494
+ "button",
495
+ {
496
+ type: "button",
497
+ "aria-haspopup": "listbox",
498
+ className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900",
499
+ children: [
500
+ /* @__PURE__ */ jsx10(BrainCircuit, { className: "h-4 w-4" }),
501
+ /* @__PURE__ */ jsx10("span", { children: picker.title }),
502
+ selectedCount > 0 ? /* @__PURE__ */ jsx10("span", { className: "ml-0.5 flex h-[18px] min-w-[18px] items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary", children: selectedCount }) : null
503
+ ]
504
+ }
505
+ ) }),
506
+ /* @__PURE__ */ jsxs5(PopoverContent, { side: "top", align: "start", className: "w-[360px] p-0", children: [
507
+ /* @__PURE__ */ jsxs5("div", { className: "space-y-2 border-b border-gray-100 px-4 py-3", children: [
508
+ /* @__PURE__ */ jsx10("div", { className: "text-sm font-semibold text-gray-900", children: picker.title }),
509
+ /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
510
+ /* @__PURE__ */ jsx10(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-gray-400" }),
511
+ /* @__PURE__ */ jsx10(
512
+ Input,
513
+ {
514
+ value: query,
515
+ onChange: (event) => setQuery(event.target.value),
516
+ onKeyDown: onSearchKeyDown,
517
+ placeholder: picker.searchPlaceholder,
518
+ role: "combobox",
519
+ "aria-controls": listId,
520
+ "aria-expanded": "true",
521
+ "aria-autocomplete": "list",
522
+ "aria-activedescendant": filteredOptions[activeIndex] ? `${listId}-option-${activeIndex}` : void 0,
523
+ className: "h-8 rounded-lg pl-8 text-xs"
524
+ }
525
+ )
526
+ ] })
527
+ ] }),
528
+ /* @__PURE__ */ jsx10(
529
+ "div",
530
+ {
531
+ ref: listRef,
532
+ id: listId,
533
+ role: "listbox",
534
+ "aria-multiselectable": "true",
535
+ className: "custom-scrollbar max-h-[320px] overflow-y-auto",
536
+ children: picker.isLoading ? /* @__PURE__ */ jsx10("div", { className: "p-4 text-xs text-gray-500", children: picker.loadingLabel }) : filteredOptions.length === 0 ? /* @__PURE__ */ jsx10("div", { className: "p-4 text-center text-xs text-gray-500", children: picker.emptyLabel }) : /* @__PURE__ */ jsx10("div", { className: "py-1", children: filteredOptions.map((option, index) => {
537
+ const isSelected = selectedSet.has(option.key);
538
+ const isActive = index === activeIndex;
539
+ return /* @__PURE__ */ jsxs5(
540
+ "button",
541
+ {
542
+ type: "button",
543
+ id: `${listId}-option-${index}`,
544
+ role: "option",
545
+ "aria-selected": isSelected,
546
+ "data-skill-index": index,
547
+ onClick: () => onToggleOption(option.key),
548
+ onMouseEnter: () => setActiveIndex(index),
549
+ className: `flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${isActive ? "bg-gray-50" : "hover:bg-gray-50"}`,
550
+ children: [
551
+ /* @__PURE__ */ jsx10("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-100 text-gray-500", children: /* @__PURE__ */ jsx10(Puzzle, { className: "h-4 w-4" }) }),
552
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
553
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
554
+ /* @__PURE__ */ jsx10("span", { className: "truncate text-sm text-gray-900", children: option.label }),
555
+ option.badgeLabel ? /* @__PURE__ */ jsx10("span", { className: "shrink-0 rounded-full bg-primary/10 px-1.5 py-0.5 text-[10px] font-medium text-primary", children: option.badgeLabel }) : null
556
+ ] }),
557
+ /* @__PURE__ */ jsx10("div", { className: "mt-0.5 truncate text-xs text-gray-500", children: option.description || option.key })
558
+ ] }),
559
+ /* @__PURE__ */ jsx10("div", { className: "ml-3 shrink-0", children: /* @__PURE__ */ jsx10(
560
+ "span",
561
+ {
562
+ className: isSelected ? "inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary text-white" : "inline-flex h-5 w-5 items-center justify-center rounded-full border border-gray-300 bg-white",
563
+ children: isSelected ? /* @__PURE__ */ jsx10(Check2, { className: "h-3 w-3" }) : null
564
+ }
565
+ ) })
566
+ ]
567
+ },
568
+ option.key
569
+ );
570
+ }) })
571
+ }
572
+ ),
573
+ picker.manageHref && picker.manageLabel ? /* @__PURE__ */ jsx10("div", { className: "border-t border-gray-100 px-4 py-2.5", children: /* @__PURE__ */ jsxs5(
574
+ "a",
575
+ {
576
+ href: picker.manageHref,
577
+ className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary transition-colors hover:text-primary/80",
578
+ children: [
579
+ picker.manageLabel,
580
+ /* @__PURE__ */ jsx10(ExternalLink, { className: "h-3 w-3" })
581
+ ]
582
+ }
583
+ ) }) : null
584
+ ] })
585
+ ] });
586
+ }
587
+
588
+ // src/components/chat/ui/chat-input-bar/chat-input-bar-toolbar.tsx
589
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
590
+ function ToolbarIcon({ icon }) {
591
+ if (icon === "sparkles") {
592
+ return /* @__PURE__ */ jsx11(Sparkles, { className: "h-3.5 w-3.5 shrink-0 text-primary" });
593
+ }
594
+ if (icon === "brain") {
595
+ return /* @__PURE__ */ jsx11(Brain, { className: "h-3.5 w-3.5 shrink-0 text-gray-500" });
596
+ }
597
+ return null;
598
+ }
599
+ function AccessoryIcon({ icon }) {
600
+ if (icon === "paperclip") {
601
+ return /* @__PURE__ */ jsx11(Paperclip, { className: "h-4 w-4" });
602
+ }
603
+ return /* @__PURE__ */ jsx11(ToolbarIcon, { icon });
604
+ }
605
+ function resolveTriggerWidth(key) {
606
+ if (key === "model") {
607
+ return "min-w-[220px]";
608
+ }
609
+ if (key === "session-type") {
610
+ return "min-w-[140px]";
611
+ }
612
+ if (key === "thinking") {
613
+ return "min-w-[150px]";
614
+ }
615
+ return "";
616
+ }
617
+ function resolveContentWidth(key) {
618
+ if (key === "model") {
619
+ return "w-[320px]";
620
+ }
621
+ if (key === "session-type") {
622
+ return "w-[220px]";
623
+ }
624
+ if (key === "thinking") {
625
+ return "w-[180px]";
626
+ }
627
+ return "";
628
+ }
629
+ function ToolbarSelect({ item }) {
630
+ const { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } = ChatUiPrimitives;
631
+ return /* @__PURE__ */ jsxs6(Select, { value: item.value, onValueChange: item.onValueChange, disabled: item.disabled, children: [
632
+ /* @__PURE__ */ jsx11(
633
+ SelectTrigger,
634
+ {
635
+ className: `h-8 w-auto rounded-lg border-0 bg-transparent px-3 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0 ${resolveTriggerWidth(item.key)}`,
636
+ children: item.selectedLabel ? /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 items-center gap-2 text-left", children: [
637
+ /* @__PURE__ */ jsx11(ToolbarIcon, { icon: item.icon }),
638
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-700", children: item.selectedLabel })
639
+ ] }) : item.loading ? /* @__PURE__ */ jsx11("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" }) : /* @__PURE__ */ jsx11(SelectValue, { placeholder: item.placeholder })
640
+ }
641
+ ),
642
+ /* @__PURE__ */ jsxs6(SelectContent, { className: resolveContentWidth(item.key), children: [
643
+ item.options.length === 0 ? item.loading ? /* @__PURE__ */ jsxs6("div", { className: "space-y-2 px-3 py-2", children: [
644
+ /* @__PURE__ */ jsx11("div", { className: "h-3 w-36 animate-pulse rounded bg-gray-200" }),
645
+ /* @__PURE__ */ jsx11("div", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
646
+ /* @__PURE__ */ jsx11("div", { className: "h-3 w-32 animate-pulse rounded bg-gray-200" })
647
+ ] }) : item.emptyLabel ? /* @__PURE__ */ jsx11("div", { className: "px-3 py-2 text-xs text-gray-500", children: item.emptyLabel }) : null : null,
648
+ item.options.map((option) => /* @__PURE__ */ jsx11(SelectItem, { value: option.value, className: "py-2", children: option.description ? /* @__PURE__ */ jsxs6("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
649
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }),
650
+ /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-gray-500", children: option.description })
651
+ ] }) : /* @__PURE__ */ jsx11("span", { className: "truncate text-xs font-semibold text-gray-800", children: option.label }) }, option.value))
652
+ ] })
653
+ ] });
654
+ }
655
+ function ChatInputBarToolbar(props) {
656
+ const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
657
+ return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-3 pb-3", children: [
658
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
659
+ props.skillPicker ? /* @__PURE__ */ jsx11(ChatInputBarSkillPicker, { picker: props.skillPicker }) : null,
660
+ props.selects.map((item) => /* @__PURE__ */ jsx11(ToolbarSelect, { item }, item.key)),
661
+ props.accessories?.map((item) => {
662
+ const button = /* @__PURE__ */ jsxs6(
663
+ "button",
664
+ {
665
+ type: "button",
666
+ className: "inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 disabled:cursor-not-allowed disabled:text-gray-400",
667
+ onClick: item.onClick,
668
+ disabled: item.disabled,
669
+ "aria-label": item.label,
670
+ children: [
671
+ /* @__PURE__ */ jsx11(AccessoryIcon, { icon: item.icon }),
672
+ /* @__PURE__ */ jsx11("span", { children: item.label })
673
+ ]
674
+ }
675
+ );
676
+ if (!item.tooltip) {
677
+ return /* @__PURE__ */ jsx11("div", { children: button }, item.key);
678
+ }
679
+ return /* @__PURE__ */ jsx11(TooltipProvider, { children: /* @__PURE__ */ jsxs6(Tooltip, { children: [
680
+ /* @__PURE__ */ jsx11(TooltipTrigger, { asChild: true, children: button }),
681
+ /* @__PURE__ */ jsx11(TooltipContent, { side: "top", children: /* @__PURE__ */ jsx11("p", { className: "text-xs", children: item.tooltip }) })
682
+ ] }) }, item.key);
683
+ })
684
+ ] }),
685
+ /* @__PURE__ */ jsx11(ChatInputBarActions, { ...props.actions })
686
+ ] });
687
+ }
688
+
689
+ // src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
690
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
691
+ function InputBarHint({ hint }) {
692
+ if (!hint) {
693
+ return null;
694
+ }
695
+ if (hint.loading) {
696
+ return /* @__PURE__ */ jsx12("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs7("div", { className: "inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2", children: [
697
+ /* @__PURE__ */ jsx12("span", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
698
+ /* @__PURE__ */ jsx12("span", { className: "h-3 w-16 animate-pulse rounded bg-gray-200" })
699
+ ] }) });
700
+ }
701
+ const toneClassName = hint.tone === "warning" ? "border-amber-200 bg-amber-50 text-amber-800" : "border-gray-200 bg-gray-50 text-gray-700";
702
+ return /* @__PURE__ */ jsx12("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs7("div", { className: `inline-flex items-center gap-2 rounded-lg px-3 py-1.5 text-xs ${toneClassName}`, children: [
703
+ hint.text ? /* @__PURE__ */ jsx12("span", { children: hint.text }) : null,
704
+ hint.actionLabel && hint.onAction ? /* @__PURE__ */ jsx12(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ onClick: hint.onAction,
709
+ className: "font-semibold underline-offset-2 hover:underline",
710
+ children: hint.actionLabel
711
+ }
712
+ ) : null
713
+ ] }) });
714
+ }
715
+ function ChatInputBar(props) {
716
+ return /* @__PURE__ */ jsx12("div", { className: "border-t border-gray-200/80 bg-white p-4", children: /* @__PURE__ */ jsx12("div", { className: "mx-auto w-full max-w-[min(1120px,100%)]", children: /* @__PURE__ */ jsxs7("div", { className: "overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card", children: [
717
+ /* @__PURE__ */ jsxs7("div", { className: "relative", children: [
718
+ /* @__PURE__ */ jsx12(
719
+ ChatInputBarTextarea,
720
+ {
721
+ value: props.value,
722
+ placeholder: props.placeholder,
723
+ disabled: props.disabled,
724
+ onValueChange: props.onValueChange,
725
+ onKeyDown: props.onKeyDown
726
+ }
727
+ ),
728
+ /* @__PURE__ */ jsx12(ChatSlashMenu, { ...props.slashMenu })
729
+ ] }),
730
+ /* @__PURE__ */ jsx12(InputBarHint, { hint: props.hint }),
731
+ /* @__PURE__ */ jsx12(ChatInputBarSelectedItems, { items: props.selectedItems.items, onRemove: props.selectedItems.onRemove }),
732
+ /* @__PURE__ */ jsx12(ChatInputBarToolbar, { ...props.toolbar })
733
+ ] }) }) });
734
+ }
735
+
736
+ // src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
737
+ import { Bot, User, Wrench } from "lucide-react";
738
+ import { jsx as jsx13 } from "react/jsx-runtime";
739
+ function ChatMessageAvatar({ role }) {
740
+ if (role === "user") {
741
+ return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white shadow-sm", children: /* @__PURE__ */ jsx13(User, { className: "h-4 w-4" }) });
742
+ }
743
+ if (role === "assistant") {
744
+ return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-slate-900 text-white shadow-sm", children: /* @__PURE__ */ jsx13(Bot, { className: "h-4 w-4" }) });
745
+ }
746
+ return /* @__PURE__ */ jsx13("div", { className: "flex h-8 w-8 items-center justify-center rounded-full bg-amber-100 text-amber-700 shadow-sm", children: /* @__PURE__ */ jsx13(Wrench, { className: "h-4 w-4" }) });
747
+ }
748
+
749
+ // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
750
+ import { useMemo as useMemo4 } from "react";
751
+ import ReactMarkdown from "react-markdown";
752
+ import remarkGfm from "remark-gfm";
753
+
754
+ // src/components/chat/ui/chat-message-list/chat-code-block.tsx
755
+ import { useMemo as useMemo3 } from "react";
756
+
757
+ // src/components/chat/hooks/use-copy-feedback.ts
758
+ import { useCallback, useEffect as useEffect4, useState as useState3 } from "react";
759
+
760
+ // src/components/chat/utils/copy-text.ts
761
+ function canUseClipboardApi() {
762
+ return typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
763
+ }
764
+ function restoreSelection(ranges) {
765
+ if (typeof document === "undefined") {
766
+ return;
767
+ }
768
+ const selection = document.getSelection();
769
+ if (!selection) {
770
+ return;
771
+ }
772
+ selection.removeAllRanges();
773
+ ranges.forEach((range) => selection.addRange(range));
774
+ }
775
+ function fallbackCopyText(text) {
776
+ if (typeof document === "undefined" || !document.body) {
777
+ return false;
778
+ }
779
+ const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
780
+ const activeInput = activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement ? activeElement : null;
781
+ const activeSelection = activeInput ? {
782
+ start: activeInput.selectionStart,
783
+ end: activeInput.selectionEnd
784
+ } : null;
785
+ const selection = document.getSelection();
786
+ const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index).cloneRange()) : [];
787
+ const textarea = document.createElement("textarea");
788
+ textarea.value = text;
789
+ textarea.setAttribute("readonly", "");
790
+ textarea.setAttribute("aria-hidden", "true");
791
+ textarea.style.position = "fixed";
792
+ textarea.style.top = "0";
793
+ textarea.style.left = "-9999px";
794
+ textarea.style.opacity = "0";
795
+ textarea.style.pointerEvents = "none";
796
+ textarea.style.fontSize = "12pt";
797
+ document.body.appendChild(textarea);
798
+ try {
799
+ textarea.focus({ preventScroll: true });
800
+ } catch {
801
+ textarea.focus();
802
+ }
803
+ textarea.select();
804
+ textarea.setSelectionRange(0, textarea.value.length);
805
+ let copied = false;
806
+ try {
807
+ copied = typeof document.execCommand === "function" && document.execCommand("copy");
808
+ } catch {
809
+ copied = false;
810
+ }
811
+ textarea.remove();
812
+ if (activeElement) {
813
+ try {
814
+ activeElement.focus({ preventScroll: true });
815
+ } catch {
816
+ activeElement.focus();
817
+ }
818
+ }
819
+ if (activeInput && activeSelection) {
820
+ activeInput.setSelectionRange(activeSelection.start, activeSelection.end);
821
+ } else {
822
+ restoreSelection(ranges);
823
+ }
824
+ return copied;
825
+ }
826
+ async function copyText(text) {
827
+ if (!text) {
828
+ return false;
829
+ }
830
+ if (canUseClipboardApi()) {
831
+ try {
832
+ await navigator.clipboard.writeText(text);
833
+ return true;
834
+ } catch {
835
+ return fallbackCopyText(text);
836
+ }
837
+ }
838
+ return fallbackCopyText(text);
839
+ }
840
+
841
+ // src/components/chat/hooks/use-copy-feedback.ts
842
+ var DEFAULT_RESET_DELAY_MS = 1300;
843
+ function useCopyFeedback(params) {
844
+ const [copied, setCopied] = useState3(false);
845
+ const copy = useCallback(async () => {
846
+ if (!params.text) {
847
+ return;
848
+ }
849
+ const didCopy = await copyText(params.text);
850
+ if (didCopy) {
851
+ setCopied(true);
852
+ } else {
853
+ setCopied(false);
854
+ }
855
+ }, [params.text]);
856
+ useEffect4(() => {
857
+ if (!copied || typeof window === "undefined") {
858
+ return;
859
+ }
860
+ const timer = window.setTimeout(() => setCopied(false), params.resetDelayMs ?? DEFAULT_RESET_DELAY_MS);
861
+ return () => window.clearTimeout(timer);
862
+ }, [copied, params.resetDelayMs]);
863
+ return {
864
+ copied,
865
+ copy
866
+ };
867
+ }
868
+
869
+ // src/components/chat/ui/chat-message-list/chat-code-block.tsx
870
+ import { Check as Check3, Copy } from "lucide-react";
871
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
872
+ var CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
873
+ function flattenNodeText(value) {
874
+ if (typeof value === "string" || typeof value === "number") {
875
+ return String(value);
876
+ }
877
+ if (Array.isArray(value)) {
878
+ return value.map(flattenNodeText).join("");
879
+ }
880
+ return "";
881
+ }
882
+ function normalizeCodeText(value) {
883
+ const content = flattenNodeText(value);
884
+ return content.endsWith("\n") ? content.slice(0, -1) : content;
885
+ }
886
+ function resolveCodeLanguage(className) {
887
+ const match = className ? CODE_LANGUAGE_REGEX.exec(className) : null;
888
+ return match?.[1]?.toLowerCase() || "text";
889
+ }
890
+ function ChatCodeBlock(props) {
891
+ const language = useMemo3(() => resolveCodeLanguage(props.className), [props.className]);
892
+ const codeText = useMemo3(() => normalizeCodeText(props.children), [props.children]);
893
+ const { copied, copy } = useCopyFeedback({ text: codeText });
894
+ return /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock", children: [
895
+ /* @__PURE__ */ jsxs8("div", { className: "chat-codeblock-toolbar", children: [
896
+ /* @__PURE__ */ jsx14("span", { className: "chat-codeblock-language", children: language }),
897
+ /* @__PURE__ */ jsxs8(
898
+ "button",
899
+ {
900
+ type: "button",
901
+ className: "chat-codeblock-copy",
902
+ onClick: () => void copy(),
903
+ "aria-label": copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel,
904
+ children: [
905
+ copied ? /* @__PURE__ */ jsx14(Check3, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx14(Copy, { className: "h-3.5 w-3.5" }),
906
+ /* @__PURE__ */ jsx14("span", { children: copied ? props.texts.copiedCodeLabel : props.texts.copyCodeLabel })
907
+ ]
908
+ }
909
+ )
910
+ ] }),
911
+ /* @__PURE__ */ jsx14("pre", { children: /* @__PURE__ */ jsx14("code", { className: props.className, children: codeText }) })
912
+ ] });
913
+ }
914
+
915
+ // src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
916
+ import { jsx as jsx15 } from "react/jsx-runtime";
917
+ var MARKDOWN_MAX_CHARS = 14e4;
918
+ var SAFE_LINK_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
919
+ function trimMarkdown(value) {
920
+ if (value.length <= MARKDOWN_MAX_CHARS) {
921
+ return value;
922
+ }
923
+ return `${value.slice(0, MARKDOWN_MAX_CHARS)}
924
+
925
+ ...`;
926
+ }
927
+ function resolveSafeHref(href) {
928
+ if (!href) {
929
+ return null;
930
+ }
931
+ if (href.startsWith("#") || href.startsWith("/") || href.startsWith("./") || href.startsWith("../")) {
932
+ return href;
933
+ }
934
+ try {
935
+ const url = new URL(href);
936
+ return SAFE_LINK_PROTOCOLS.has(url.protocol) ? href : null;
937
+ } catch {
938
+ return null;
939
+ }
940
+ }
941
+ function isExternalHref(href) {
942
+ return /^https?:\/\//i.test(href);
943
+ }
944
+ function ChatMessageMarkdown(props) {
945
+ const isUser = props.role === "user";
946
+ const markdownComponents = useMemo4(() => ({
947
+ a: ({ href, children, ...rest }) => {
948
+ const safeHref = resolveSafeHref(href);
949
+ if (!safeHref) {
950
+ return /* @__PURE__ */ jsx15("span", { className: "chat-link-invalid", children });
951
+ }
952
+ const external = isExternalHref(safeHref);
953
+ return /* @__PURE__ */ jsx15(
954
+ "a",
955
+ {
956
+ ...rest,
957
+ href: safeHref,
958
+ target: external ? "_blank" : void 0,
959
+ rel: external ? "noreferrer noopener" : void 0,
960
+ children
961
+ }
962
+ );
963
+ },
964
+ table: ({ children, ...rest }) => /* @__PURE__ */ jsx15("div", { className: "chat-table-wrap", children: /* @__PURE__ */ jsx15("table", { ...rest, children }) }),
965
+ input: ({ type, checked, ...rest }) => {
966
+ if (type !== "checkbox") {
967
+ return /* @__PURE__ */ jsx15("input", { ...rest, type });
968
+ }
969
+ return /* @__PURE__ */ jsx15("input", { ...rest, type: "checkbox", checked, readOnly: true, disabled: true, className: "chat-task-checkbox" });
970
+ },
971
+ img: ({ src, alt, ...rest }) => {
972
+ const safeSrc = resolveSafeHref(src);
973
+ if (!safeSrc) {
974
+ return null;
975
+ }
976
+ return /* @__PURE__ */ jsx15("img", { ...rest, src: safeSrc, alt: alt || "", loading: "lazy", decoding: "async" });
977
+ },
978
+ code: ({ className, children, ...rest }) => {
979
+ const plainText = String(children ?? "");
980
+ const isInlineCode = !className && !plainText.includes("\n");
981
+ if (isInlineCode) {
982
+ return /* @__PURE__ */ jsx15("code", { ...rest, className: cn("chat-inline-code", className), children });
983
+ }
984
+ return /* @__PURE__ */ jsx15(ChatCodeBlock, { className, texts: props.texts, children });
985
+ }
986
+ }), [props.texts]);
987
+ return /* @__PURE__ */ jsx15("div", { className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"), children: /* @__PURE__ */ jsx15(ReactMarkdown, { skipHtml: true, remarkPlugins: [remarkGfm], components: markdownComponents, children: trimMarkdown(props.text) }) });
988
+ }
989
+
990
+ // src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
991
+ import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
992
+ function ChatReasoningBlock(props) {
993
+ return /* @__PURE__ */ jsxs9("details", { className: "mt-3", children: [
994
+ /* @__PURE__ */ jsx16("summary", { className: cn("cursor-pointer text-xs", props.isUser ? "text-primary-100" : "text-gray-500"), children: props.label }),
995
+ /* @__PURE__ */ jsx16(
996
+ "pre",
997
+ {
998
+ className: cn(
999
+ "mt-2 whitespace-pre-wrap break-words rounded-lg p-2 text-[11px]",
1000
+ props.isUser ? "bg-primary-700/60" : "bg-gray-100"
1001
+ ),
1002
+ children: props.text
1003
+ }
1004
+ )
1005
+ ] });
1006
+ }
1007
+
1008
+ // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
1009
+ import { Clock3, FileSearch, Globe, Search as Search2, SendHorizontal, Terminal, Wrench as Wrench2 } from "lucide-react";
1010
+ import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
1011
+ var TOOL_OUTPUT_PREVIEW_MAX = 220;
1012
+ function renderToolIcon(toolName) {
1013
+ const lowered = toolName.toLowerCase();
1014
+ if (lowered.includes("exec") || lowered.includes("shell") || lowered.includes("command")) {
1015
+ return /* @__PURE__ */ jsx17(Terminal, { className: "h-3.5 w-3.5" });
1016
+ }
1017
+ if (lowered.includes("search")) {
1018
+ return /* @__PURE__ */ jsx17(Search2, { className: "h-3.5 w-3.5" });
1019
+ }
1020
+ if (lowered.includes("fetch") || lowered.includes("http") || lowered.includes("web")) {
1021
+ return /* @__PURE__ */ jsx17(Globe, { className: "h-3.5 w-3.5" });
1022
+ }
1023
+ if (lowered.includes("read") || lowered.includes("file")) {
1024
+ return /* @__PURE__ */ jsx17(FileSearch, { className: "h-3.5 w-3.5" });
1025
+ }
1026
+ if (lowered.includes("message") || lowered.includes("send")) {
1027
+ return /* @__PURE__ */ jsx17(SendHorizontal, { className: "h-3.5 w-3.5" });
1028
+ }
1029
+ if (lowered.includes("cron") || lowered.includes("schedule")) {
1030
+ return /* @__PURE__ */ jsx17(Clock3, { className: "h-3.5 w-3.5" });
1031
+ }
1032
+ return /* @__PURE__ */ jsx17(Wrench2, { className: "h-3.5 w-3.5" });
1033
+ }
1034
+ function ChatToolCard({ card }) {
1035
+ const output = card.output?.trim() ?? "";
1036
+ const showDetails = output.length > TOOL_OUTPUT_PREVIEW_MAX || output.includes("\n");
1037
+ const preview = showDetails ? `${output.slice(0, TOOL_OUTPUT_PREVIEW_MAX)}...` : output;
1038
+ const showOutputSection = card.kind === "result" || card.hasResult;
1039
+ return /* @__PURE__ */ jsxs10("div", { className: "rounded-xl border border-amber-200/80 bg-amber-50/60 px-3 py-2.5", children: [
1040
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-2 text-xs font-semibold text-amber-800", children: [
1041
+ renderToolIcon(card.toolName),
1042
+ /* @__PURE__ */ jsx17("span", { children: card.titleLabel }),
1043
+ /* @__PURE__ */ jsx17("span", { className: "font-mono text-[11px] text-amber-900/80", children: card.toolName })
1044
+ ] }),
1045
+ card.summary ? /* @__PURE__ */ jsx17("div", { className: "mt-1 break-words font-mono text-[11px] text-amber-800/90", children: card.summary }) : null,
1046
+ showOutputSection ? /* @__PURE__ */ jsx17("div", { className: "mt-2", children: !output ? /* @__PURE__ */ jsx17("div", { className: "text-[11px] text-amber-700/80", children: card.emptyLabel }) : showDetails ? /* @__PURE__ */ jsxs10("details", { className: "group", children: [
1047
+ /* @__PURE__ */ jsx17("summary", { className: "cursor-pointer text-[11px] text-amber-700", children: card.outputLabel }),
1048
+ /* @__PURE__ */ jsx17("pre", { className: "mt-2 whitespace-pre-wrap break-words rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] text-amber-900", children: output })
1049
+ ] }) : /* @__PURE__ */ jsx17("pre", { className: "rounded-lg border border-amber-200 bg-amber-100/40 p-2 text-[11px] whitespace-pre-wrap break-words text-amber-900", children: preview }) }) : null
1050
+ ] });
1051
+ }
1052
+
1053
+ // src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
1054
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1055
+ function ChatUnknownPart(props) {
1056
+ return /* @__PURE__ */ jsxs11("div", { className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600", children: [
1057
+ /* @__PURE__ */ jsxs11("div", { className: "font-semibold text-gray-700", children: [
1058
+ props.label,
1059
+ ": ",
1060
+ props.rawType
1061
+ ] }),
1062
+ props.text ? /* @__PURE__ */ jsx18("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
1063
+ ] });
1064
+ }
1065
+
1066
+ // src/components/chat/ui/chat-message-list/chat-message.tsx
1067
+ import { jsx as jsx19 } from "react/jsx-runtime";
1068
+ function ChatMessage(props) {
1069
+ const { message, texts } = props;
1070
+ const { role } = message;
1071
+ const isUser = role === "user";
1072
+ return /* @__PURE__ */ jsx19(
1073
+ "div",
1074
+ {
1075
+ className: cn(
1076
+ "inline-block w-fit max-w-full rounded-2xl border px-4 py-3 shadow-sm",
1077
+ isUser ? "border-primary bg-primary text-white" : role === "assistant" ? "border-gray-200 bg-white text-gray-900" : "border-orange-200/80 bg-orange-50/70 text-gray-900"
1078
+ ),
1079
+ children: /* @__PURE__ */ jsx19("div", { className: "space-y-2", children: message.parts.map((part, index) => {
1080
+ if (part.type === "markdown") {
1081
+ return /* @__PURE__ */ jsx19(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
1082
+ }
1083
+ if (part.type === "reasoning") {
1084
+ return /* @__PURE__ */ jsx19(ChatReasoningBlock, { label: part.label, text: part.text, isUser }, `reasoning-${index}`);
1085
+ }
1086
+ if (part.type === "tool-card") {
1087
+ return /* @__PURE__ */ jsx19("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx19(ChatToolCard, { card: part.card }) }, `tool-${index}`);
1088
+ }
1089
+ if (part.type === "unknown") {
1090
+ return /* @__PURE__ */ jsx19(ChatUnknownPart, { label: part.label, rawType: part.rawType, text: part.text }, `unknown-${index}`);
1091
+ }
1092
+ return null;
1093
+ }) })
1094
+ }
1095
+ );
1096
+ }
1097
+
1098
+ // src/components/chat/ui/chat-message-list/chat-message-meta.tsx
1099
+ import { jsxs as jsxs12 } from "react/jsx-runtime";
1100
+ function ChatMessageMeta(props) {
1101
+ return /* @__PURE__ */ jsxs12("div", { className: cn("px-1 text-[11px]", props.isUser ? "text-primary-300" : "text-gray-400"), children: [
1102
+ props.roleLabel,
1103
+ " \xB7 ",
1104
+ props.timestampLabel
1105
+ ] });
1106
+ }
1107
+
1108
+ // src/components/chat/ui/chat-message-list/chat-message-list.tsx
1109
+ import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
1110
+ function ChatMessageList(props) {
1111
+ return /* @__PURE__ */ jsxs13("div", { className: cn("space-y-5", props.className), children: [
1112
+ props.messages.map((message) => {
1113
+ const isUser = message.role === "user";
1114
+ return /* @__PURE__ */ jsxs13("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
1115
+ !isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null,
1116
+ /* @__PURE__ */ jsxs13("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
1117
+ /* @__PURE__ */ jsx20(ChatMessage, { message, texts: props.texts }),
1118
+ /* @__PURE__ */ jsx20(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser })
1119
+ ] }),
1120
+ isUser ? /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: message.role }) : null
1121
+ ] }, message.id);
1122
+ }),
1123
+ props.isSending && !props.hasStreamingDraft ? /* @__PURE__ */ jsxs13("div", { className: "flex justify-start gap-3", children: [
1124
+ /* @__PURE__ */ jsx20(ChatMessageAvatar, { role: "assistant" }),
1125
+ /* @__PURE__ */ jsx20("div", { className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm", children: props.texts.typingLabel })
1126
+ ] }) : null
1127
+ ] });
1128
+ }
1129
+
1130
+ // src/components/chat/hooks/use-sticky-bottom-scroll.ts
1131
+ import { useCallback as useCallback2, useEffect as useEffect5, useLayoutEffect, useRef as useRef4 } from "react";
1132
+ var DEFAULT_STICKY_THRESHOLD_PX = 10;
1133
+ function scrollElementToBottom(element) {
1134
+ element.scrollTop = element.scrollHeight;
1135
+ }
1136
+ function useStickyBottomScroll(params) {
1137
+ const isStickyRef = useRef4(true);
1138
+ const isProgrammaticScrollRef = useRef4(false);
1139
+ const previousResetKeyRef = useRef4(null);
1140
+ const pendingInitialScrollRef = useRef4(false);
1141
+ const onScroll = useCallback2(() => {
1142
+ if (isProgrammaticScrollRef.current) {
1143
+ isProgrammaticScrollRef.current = false;
1144
+ return;
1145
+ }
1146
+ const element = params.scrollRef.current;
1147
+ if (!element) {
1148
+ return;
1149
+ }
1150
+ const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
1151
+ isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
1152
+ }, [params.scrollRef, params.stickyThresholdPx]);
1153
+ useEffect5(() => {
1154
+ if (previousResetKeyRef.current === params.resetKey) {
1155
+ return;
1156
+ }
1157
+ previousResetKeyRef.current = params.resetKey;
1158
+ isStickyRef.current = true;
1159
+ pendingInitialScrollRef.current = true;
1160
+ }, [params.resetKey]);
1161
+ useLayoutEffect(() => {
1162
+ if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
1163
+ return;
1164
+ }
1165
+ const element = params.scrollRef.current;
1166
+ if (!element) {
1167
+ return;
1168
+ }
1169
+ pendingInitialScrollRef.current = false;
1170
+ isProgrammaticScrollRef.current = true;
1171
+ scrollElementToBottom(element);
1172
+ }, [params.hasContent, params.isLoading, params.scrollRef]);
1173
+ useLayoutEffect(() => {
1174
+ if (!isStickyRef.current || !params.hasContent) {
1175
+ return;
1176
+ }
1177
+ const element = params.scrollRef.current;
1178
+ if (!element) {
1179
+ return;
1180
+ }
1181
+ isProgrammaticScrollRef.current = true;
1182
+ scrollElementToBottom(element);
1183
+ }, [params.contentVersion, params.hasContent, params.scrollRef]);
1184
+ return { onScroll };
1185
+ }
1186
+ export {
1187
+ ChatInputBar,
1188
+ ChatMessageList,
1189
+ copyText,
1190
+ useActiveItemScroll,
1191
+ useCopyFeedback,
1192
+ useElementWidth,
1193
+ useStickyBottomScroll
1194
+ };