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