@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.
- package/dist/index.d.ts +49 -12
- package/dist/index.js +1345 -255
- 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
|
|
2
|
-
import {
|
|
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
|
|
96
|
-
var ChatInput = React.forwardRef(({ className, type, ...props }, ref) => /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
172
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
195
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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__ */
|
|
277
|
-
/* @__PURE__ */
|
|
278
|
-
/* @__PURE__ */
|
|
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__ */
|
|
288
|
-
/* @__PURE__ */
|
|
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__ */
|
|
296
|
-
/* @__PURE__ */
|
|
297
|
-
items.length === 0 ? /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
311
|
-
/* @__PURE__ */
|
|
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__ */
|
|
321
|
-
/* @__PURE__ */
|
|
322
|
-
/* @__PURE__ */
|
|
323
|
-
/* @__PURE__ */
|
|
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__ */
|
|
326
|
-
/* @__PURE__ */
|
|
327
|
-
/* @__PURE__ */
|
|
328
|
-
] }) : /* @__PURE__ */
|
|
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
|
|
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__ */
|
|
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
|
|
343
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
379
344
|
function StopIcon() {
|
|
380
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
392
|
-
props.sendError?.trim() ? /* @__PURE__ */
|
|
393
|
-
/* @__PURE__ */
|
|
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__ */
|
|
367
|
+
children: /* @__PURE__ */ jsx7(StopIcon, {})
|
|
403
368
|
}
|
|
404
|
-
) : /* @__PURE__ */
|
|
405
|
-
/* @__PURE__ */
|
|
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__ */
|
|
378
|
+
children: /* @__PURE__ */ jsx7(StopIcon, {})
|
|
414
379
|
}
|
|
415
380
|
) }) }),
|
|
416
|
-
/* @__PURE__ */
|
|
417
|
-
] }) }) : /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
503
|
-
/* @__PURE__ */
|
|
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__ */
|
|
511
|
-
/* @__PURE__ */
|
|
512
|
-
selectedCount > 0 ? /* @__PURE__ */
|
|
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__ */
|
|
517
|
-
/* @__PURE__ */
|
|
518
|
-
/* @__PURE__ */
|
|
519
|
-
/* @__PURE__ */
|
|
520
|
-
/* @__PURE__ */
|
|
521
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
562
|
-
/* @__PURE__ */
|
|
563
|
-
/* @__PURE__ */
|
|
564
|
-
/* @__PURE__ */
|
|
565
|
-
option.badgeLabel ? /* @__PURE__ */
|
|
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__ */
|
|
532
|
+
/* @__PURE__ */ jsx8("div", { className: "mt-0.5 truncate text-xs text-gray-500", children: option.description || option.key })
|
|
568
533
|
] }),
|
|
569
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
576
|
+
return /* @__PURE__ */ jsx9(Paperclip, { className: "h-4 w-4" });
|
|
612
577
|
}
|
|
613
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
642
|
-
/* @__PURE__ */
|
|
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__ */
|
|
647
|
-
/* @__PURE__ */
|
|
648
|
-
/* @__PURE__ */
|
|
649
|
-
] }) : item.loading ? /* @__PURE__ */
|
|
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__ */
|
|
653
|
-
item.options.length === 0 ? item.loading ? /* @__PURE__ */
|
|
654
|
-
/* @__PURE__ */
|
|
655
|
-
/* @__PURE__ */
|
|
656
|
-
/* @__PURE__ */
|
|
657
|
-
] }) : item.emptyLabel ? /* @__PURE__ */
|
|
658
|
-
item.options.map((option) => /* @__PURE__ */
|
|
659
|
-
/* @__PURE__ */
|
|
660
|
-
/* @__PURE__ */
|
|
661
|
-
] }) : /* @__PURE__ */
|
|
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__ */
|
|
668
|
-
/* @__PURE__ */
|
|
669
|
-
props.skillPicker ? /* @__PURE__ */
|
|
670
|
-
props.selects.map((item) => /* @__PURE__ */
|
|
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__ */
|
|
637
|
+
const button = /* @__PURE__ */ jsxs5(
|
|
673
638
|
"button",
|
|
674
639
|
{
|
|
675
640
|
type: "button",
|
|
676
|
-
className:
|
|
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__ */
|
|
682
|
-
/* @__PURE__ */
|
|
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__ */
|
|
652
|
+
return /* @__PURE__ */ jsx9("div", { children: button }, item.key);
|
|
688
653
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
707
|
-
/* @__PURE__ */
|
|
708
|
-
/* @__PURE__ */
|
|
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__ */
|
|
713
|
-
hint.text ? /* @__PURE__ */
|
|
714
|
-
hint.actionLabel && hint.onAction ? /* @__PURE__ */
|
|
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
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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__ */
|
|
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__ */
|
|
741
|
-
/* @__PURE__ */
|
|
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
|
|
1807
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
749
1808
|
function ChatMessageAvatar({ role }) {
|
|
750
1809
|
if (role === "user") {
|
|
751
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
1815
|
+
children: /* @__PURE__ */ jsx12(User, { className: "h-4 w-4" })
|
|
757
1816
|
}
|
|
758
1817
|
);
|
|
759
1818
|
}
|
|
760
1819
|
if (role === "tool") {
|
|
761
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
1825
|
+
children: /* @__PURE__ */ jsx12(Wrench, { className: "h-4 w-4" })
|
|
767
1826
|
}
|
|
768
1827
|
);
|
|
769
1828
|
}
|
|
770
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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
|
|
1845
|
+
import { useMemo as useMemo4 } from "react";
|
|
787
1846
|
|
|
788
1847
|
// src/components/chat/hooks/use-copy-feedback.ts
|
|
789
|
-
import { useCallback, useEffect as
|
|
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] =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
923
|
-
const codeText =
|
|
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__ */
|
|
926
|
-
/* @__PURE__ */
|
|
927
|
-
/* @__PURE__ */
|
|
928
|
-
/* @__PURE__ */
|
|
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__ */
|
|
937
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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 =
|
|
2036
|
+
const markdownComponents = useMemo5(() => ({
|
|
978
2037
|
a: ({ href, children, ...rest }) => {
|
|
979
2038
|
const safeHref = resolveSafeHref(href);
|
|
980
2039
|
if (!safeHref) {
|
|
981
|
-
return /* @__PURE__ */
|
|
2040
|
+
return /* @__PURE__ */ jsx14("span", { className: "chat-link-invalid", children });
|
|
982
2041
|
}
|
|
983
2042
|
const external = isExternalHref(safeHref);
|
|
984
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2057
|
+
return /* @__PURE__ */ jsx14("input", { ...rest, type });
|
|
999
2058
|
}
|
|
1000
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2072
|
+
return /* @__PURE__ */ jsx14("code", { ...rest, className: cn("chat-inline-code", className), children });
|
|
1014
2073
|
}
|
|
1015
|
-
return /* @__PURE__ */
|
|
2074
|
+
return /* @__PURE__ */ jsx14(ChatCodeBlock, { className, texts: props.texts, children });
|
|
1016
2075
|
}
|
|
1017
2076
|
}), [props.texts]);
|
|
1018
|
-
return /* @__PURE__ */
|
|
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
|
|
2081
|
+
import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1023
2082
|
function ChatReasoningBlock(props) {
|
|
1024
|
-
return /* @__PURE__ */
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
/* @__PURE__ */
|
|
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
|
|
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__ */
|
|
2105
|
+
return /* @__PURE__ */ jsx16(Terminal, { className: "h-3.5 w-3.5" });
|
|
1047
2106
|
}
|
|
1048
2107
|
if (lowered.includes("search")) {
|
|
1049
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
2120
|
+
return /* @__PURE__ */ jsx16(Clock3, { className: "h-3.5 w-3.5" });
|
|
1062
2121
|
}
|
|
1063
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
1071
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1074
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1077
|
-
showOutputSection ? /* @__PURE__ */
|
|
1078
|
-
/* @__PURE__ */
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
] }) : /* @__PURE__ */
|
|
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
|
|
2144
|
+
import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1086
2145
|
function ChatUnknownPart(props) {
|
|
1087
|
-
return /* @__PURE__ */
|
|
1088
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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__ */
|
|
2169
|
+
children: /* @__PURE__ */ jsx18("div", { className: "space-y-2", children: message.parts.map((part, index) => {
|
|
1111
2170
|
if (part.type === "markdown") {
|
|
1112
|
-
return /* @__PURE__ */
|
|
2171
|
+
return /* @__PURE__ */ jsx18(ChatMessageMarkdown, { text: part.text, role, texts }, `markdown-${index}`);
|
|
1113
2172
|
}
|
|
1114
2173
|
if (part.type === "reasoning") {
|
|
1115
|
-
return /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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
|
|
2189
|
+
import { jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1131
2190
|
function ChatMessageMeta(props) {
|
|
1132
|
-
return /* @__PURE__ */
|
|
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
|
|
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
|
-
|
|
1152
|
-
|
|
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__ */
|
|
1155
|
-
!isUser ? /* @__PURE__ */
|
|
1156
|
-
/* @__PURE__ */
|
|
1157
|
-
/* @__PURE__ */
|
|
1158
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2239
|
+
isUser ? /* @__PURE__ */ jsx19(ChatMessageAvatar, { role: message.role }) : null
|
|
1161
2240
|
] }, message.id);
|
|
1162
2241
|
}),
|
|
1163
|
-
props.isSending && !
|
|
1164
|
-
/* @__PURE__ */
|
|
1165
|
-
/* @__PURE__ */
|
|
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
|
|
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 =
|
|
1178
|
-
const isProgrammaticScrollRef =
|
|
1179
|
-
const previousResetKeyRef =
|
|
1180
|
-
const pendingInitialScrollRef =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|