@nextclaw/agent-chat-ui 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { twMerge } from "tailwind-merge";
5
5
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
6
  import * as PopoverPrimitive from "@radix-ui/react-popover";
7
7
  import * as SelectPrimitive from "@radix-ui/react-select";
8
- import { AlertTriangle, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, Globe, Loader2, Minus, Paperclip, Puzzle, Search, Sparkles, Star, Terminal, User, Wrench } from "lucide-react";
8
+ import { AlertTriangle, AppWindow, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, Globe, ImageIcon, Loader2, Minus, Paperclip, Puzzle, Search, Sparkles, Star, Terminal, User, Wrench } from "lucide-react";
9
9
  import * as TooltipPrimitive from "@radix-ui/react-tooltip";
10
10
  import { cva } from "class-variance-authority";
11
11
  import { ContentEditable } from "@lexical/react/LexicalContentEditable";
@@ -30,6 +30,180 @@ import sql from "highlight.js/lib/languages/sql";
30
30
  import typescript from "highlight.js/lib/languages/typescript";
31
31
  import xml from "highlight.js/lib/languages/xml";
32
32
  import yaml from "highlight.js/lib/languages/yaml";
33
+ const CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC = {
34
+ key: "slash",
35
+ marker: "/"
36
+ };
37
+ function createComposerNodeId() {
38
+ return `composer-${Math.random().toString(36).slice(2, 10)}`;
39
+ }
40
+ function createChatComposerTextNode(text = "") {
41
+ return {
42
+ id: createComposerNodeId(),
43
+ type: "text",
44
+ text
45
+ };
46
+ }
47
+ function createChatComposerTokenNode(params) {
48
+ return {
49
+ id: createComposerNodeId(),
50
+ type: "token",
51
+ tokenKind: params.tokenKind,
52
+ tokenKey: params.tokenKey,
53
+ label: params.label
54
+ };
55
+ }
56
+ function getChatComposerNodeLength(node) {
57
+ return node.type === "text" ? node.text.length : 1;
58
+ }
59
+ function createEmptyChatComposerNodes() {
60
+ return [createChatComposerTextNode("")];
61
+ }
62
+ function createChatComposerNodesFromText(text) {
63
+ return [createChatComposerTextNode(text)];
64
+ }
65
+ function normalizeChatComposerNodes(nodes) {
66
+ const normalized = [];
67
+ for (const node of nodes) {
68
+ if (node.type === "text") {
69
+ if (node.text.length === 0) continue;
70
+ const previous = normalized[normalized.length - 1];
71
+ if (previous?.type === "text") {
72
+ normalized[normalized.length - 1] = {
73
+ ...previous,
74
+ text: previous.text + node.text
75
+ };
76
+ continue;
77
+ }
78
+ }
79
+ normalized.push(node);
80
+ }
81
+ if (normalized.length === 0) return createEmptyChatComposerNodes();
82
+ return normalized;
83
+ }
84
+ function serializeChatComposerDocument(nodes) {
85
+ return nodes.map((node) => node.type === "text" ? node.text : "").join("");
86
+ }
87
+ function serializeChatComposerPlainText(nodes) {
88
+ return nodes.filter((node) => node.type === "text").map((node) => node.text).join("");
89
+ }
90
+ function extractChatComposerTokenKeys(nodes, tokenKind) {
91
+ const keys = [];
92
+ const keySet = /* @__PURE__ */ new Set();
93
+ for (const node of nodes) {
94
+ if (node.type !== "token" || node.tokenKind !== tokenKind || keySet.has(node.tokenKey)) continue;
95
+ keySet.add(node.tokenKey);
96
+ keys.push(node.tokenKey);
97
+ }
98
+ return keys;
99
+ }
100
+ function buildTrimmedTextEdges(node, nodeStart, rangeStart, rangeEnd) {
101
+ const prefixLength = Math.max(0, rangeStart - nodeStart);
102
+ const suffixLength = Math.max(0, nodeStart + node.text.length - rangeEnd);
103
+ const edges = [];
104
+ if (prefixLength > 0) edges.push({
105
+ ...node,
106
+ text: node.text.slice(0, prefixLength)
107
+ });
108
+ if (suffixLength > 0) edges.push({
109
+ ...node,
110
+ text: node.text.slice(node.text.length - suffixLength)
111
+ });
112
+ return edges;
113
+ }
114
+ function isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd) {
115
+ return nodeEnd <= boundedStart || nodeStart >= boundedEnd;
116
+ }
117
+ function appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd) {
118
+ if (!inserted && nodeStart >= boundedEnd) {
119
+ nextNodes.push(...replacement);
120
+ return true;
121
+ }
122
+ return inserted;
123
+ }
124
+ function appendTextNodeWithReplacement(nextNodes, node, nodeStart, boundedStart, boundedEnd, replacement, inserted) {
125
+ const edges = buildTrimmedTextEdges(node, nodeStart, boundedStart, boundedEnd);
126
+ if (edges[0]) nextNodes.push(edges[0]);
127
+ let didInsert = inserted;
128
+ if (!didInsert) {
129
+ nextNodes.push(...replacement);
130
+ didInsert = true;
131
+ }
132
+ if (edges[1]) nextNodes.push(edges[1]);
133
+ return didInsert;
134
+ }
135
+ function appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart) {
136
+ if (!inserted && boundedStart <= nodeStart) {
137
+ nextNodes.push(...replacement);
138
+ return true;
139
+ }
140
+ nextNodes.push(node);
141
+ return inserted;
142
+ }
143
+ function replaceChatComposerRange(nodes, start, end, replacement) {
144
+ const boundedStart = Math.max(0, start);
145
+ const boundedEnd = Math.max(boundedStart, end);
146
+ const nextNodes = [];
147
+ let cursor = 0;
148
+ let inserted = false;
149
+ for (const node of nodes) {
150
+ const nodeLength = getChatComposerNodeLength(node);
151
+ const nodeStart = cursor;
152
+ const nodeEnd = cursor + nodeLength;
153
+ if (isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd)) {
154
+ inserted = appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd);
155
+ nextNodes.push(node);
156
+ cursor = nodeEnd;
157
+ continue;
158
+ }
159
+ if (node.type === "text") inserted = appendTextNodeWithReplacement(nextNodes, node, nodeStart, boundedStart, boundedEnd, replacement, inserted);
160
+ else inserted = appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart);
161
+ cursor = nodeEnd;
162
+ }
163
+ if (!inserted) nextNodes.push(...replacement);
164
+ return normalizeChatComposerNodes(nextNodes);
165
+ }
166
+ function removeChatComposerTokenNodes(nodes, predicate) {
167
+ return normalizeChatComposerNodes(nodes.filter((node) => node.type !== "token" || !predicate(node)));
168
+ }
169
+ function hasTriggerBoundary(prefix, markerStart) {
170
+ return markerStart === 0 || /\s/.test(prefix[markerStart - 1] ?? "");
171
+ }
172
+ function resolveChatComposerInputSurfaceTrigger(nodes, selection, triggerSpec) {
173
+ if (!selection || selection.start !== selection.end) return null;
174
+ const documentText = serializeChatComposerDocument(nodes);
175
+ const caret = selection.end;
176
+ const prefix = documentText.slice(0, caret);
177
+ const markerStart = prefix.lastIndexOf(triggerSpec.marker);
178
+ if (markerStart < 0 || !hasTriggerBoundary(prefix, markerStart)) return null;
179
+ const rawQuery = prefix.slice(markerStart + triggerSpec.marker.length);
180
+ if (/[\s\uFFFC]/.test(rawQuery)) return null;
181
+ return {
182
+ key: triggerSpec.key,
183
+ marker: triggerSpec.marker,
184
+ query: rawQuery.trim().toLowerCase(),
185
+ start: markerStart,
186
+ end: caret
187
+ };
188
+ }
189
+ function resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs = [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC]) {
190
+ let activeTrigger = null;
191
+ for (const triggerSpec of triggerSpecs) {
192
+ const trigger = resolveChatComposerInputSurfaceTrigger(nodes, selection, triggerSpec);
193
+ if (!trigger) continue;
194
+ if (!activeTrigger || trigger.start > activeTrigger.start) activeTrigger = trigger;
195
+ }
196
+ return activeTrigger;
197
+ }
198
+ function resolveChatComposerSlashTrigger(nodes, selection) {
199
+ const trigger = resolveChatComposerInputSurfaceTrigger(nodes, selection, CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC);
200
+ return trigger ? {
201
+ query: trigger.query,
202
+ start: trigger.start,
203
+ end: trigger.end
204
+ } : null;
205
+ }
206
+ //#endregion
33
207
  //#region src/components/chat/hooks/use-active-item-scroll.ts
34
208
  const defaultGetItemSelector = (index) => `[data-item-index="${index}"]`;
35
209
  function useActiveItemScroll(params) {
@@ -220,27 +394,77 @@ const ChatUiPrimitives = {
220
394
  TooltipTrigger: ChatTooltipTrigger
221
395
  };
222
396
  //#endregion
223
- //#region src/components/chat/ui/chat-input-bar/chat-slash-menu.tsx
224
- const SLASH_PANEL_MAX_WIDTH = 680;
225
- const SLASH_PANEL_DESKTOP_SHRINK_RATIO = .82;
226
- const SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
227
- const SLASH_PANEL_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
228
- const SLASH_PANEL_MIN_HEIGHT = createChatPopoverAvailableHeightLimit("240px");
229
- function ChatSlashMenu(props) {
397
+ //#region src/components/chat/ui/input-surface/chat-input-surface-menu.tsx
398
+ const INPUT_SURFACE_PANEL_MAX_WIDTH = 680;
399
+ const INPUT_SURFACE_PANEL_DESKTOP_SHRINK_RATIO = .82;
400
+ const INPUT_SURFACE_PANEL_DESKTOP_MIN_WIDTH = 560;
401
+ const INPUT_SURFACE_PANEL_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
402
+ const INPUT_SURFACE_PANEL_MIN_HEIGHT = createChatPopoverAvailableHeightLimit("240px");
403
+ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref) {
230
404
  const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
231
405
  const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
232
406
  const listRef = useRef(null);
233
- const { isOpen, isLoading, items, activeIndex, activeItem, texts, onSelectItem, onOpenChange, onDetailsPointerDown, onSetActiveIndex } = props;
407
+ const [activeState, setActiveState] = useState({
408
+ index: 0,
409
+ itemsSignature: ""
410
+ });
411
+ const { isOpen, isLoading, items, texts, onSelectItem, onOpenChange, onDetailsPointerDown } = props;
412
+ const itemsSignature = useMemo(() => items.map((item) => item.key).join(""), [items]);
413
+ const hasItemSections = useMemo(() => items.some((item) => Boolean(item.sectionLabel?.trim())), [items]);
414
+ const activeIndex = activeState.itemsSignature === itemsSignature ? activeState.index : 0;
415
+ const activeIndexInRange = items.length === 0 ? 0 : Math.min(activeIndex, items.length - 1);
416
+ const activeItem = items[activeIndexInRange] ?? null;
417
+ const setActiveIndexForCurrentItems = useCallback((nextIndex) => {
418
+ setActiveState((currentState) => {
419
+ const currentIndex = currentState.itemsSignature === itemsSignature ? currentState.index : 0;
420
+ return {
421
+ index: typeof nextIndex === "function" ? nextIndex(currentIndex) : nextIndex,
422
+ itemsSignature
423
+ };
424
+ });
425
+ }, [itemsSignature]);
234
426
  const resolvedWidth = useMemo(() => {
235
427
  if (!panelWidth) return;
236
- return Math.min(panelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO : panelWidth, SLASH_PANEL_MAX_WIDTH);
428
+ return Math.min(panelWidth > INPUT_SURFACE_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * INPUT_SURFACE_PANEL_DESKTOP_SHRINK_RATIO : panelWidth, INPUT_SURFACE_PANEL_MAX_WIDTH);
237
429
  }, [panelWidth]);
430
+ useImperativeHandle(ref, () => ({ handleKeyDown: (event) => {
431
+ if (!isOpen) return false;
432
+ if (event.key === "Escape") {
433
+ event.preventDefault();
434
+ onOpenChange(false);
435
+ return true;
436
+ }
437
+ if (items.length === 0) return false;
438
+ if (event.key === "ArrowDown") {
439
+ event.preventDefault();
440
+ setActiveIndexForCurrentItems((index) => (index + 1) % items.length);
441
+ return true;
442
+ }
443
+ if (event.key === "ArrowUp") {
444
+ event.preventDefault();
445
+ setActiveIndexForCurrentItems((index) => (index - 1 + items.length) % items.length);
446
+ return true;
447
+ }
448
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Tab") {
449
+ event.preventDefault();
450
+ onSelectItem(items[activeIndexInRange]);
451
+ return true;
452
+ }
453
+ return false;
454
+ } }), [
455
+ activeIndexInRange,
456
+ isOpen,
457
+ items,
458
+ onOpenChange,
459
+ onSelectItem,
460
+ setActiveIndexForCurrentItems
461
+ ]);
238
462
  useActiveItemScroll({
239
463
  containerRef: listRef,
240
- activeIndex,
464
+ activeIndex: activeIndexInRange,
241
465
  itemCount: items.length,
242
466
  isEnabled: isOpen && !isLoading,
243
- getItemSelector: (index) => `[data-slash-index="${index}"]`
467
+ getItemSelector: (index) => `[data-input-surface-index="${index}"]`
244
468
  });
245
469
  return /* @__PURE__ */ jsxs(Popover, {
246
470
  open: isOpen,
@@ -249,7 +473,7 @@ function ChatSlashMenu(props) {
249
473
  asChild: true,
250
474
  children: /* @__PURE__ */ jsx("div", {
251
475
  ref: anchorRef,
252
- className: "pointer-events-none absolute bottom-full left-3 right-3 h-0"
476
+ className: "pointer-events-none absolute bottom-0 left-3 right-3 top-0"
253
477
  })
254
478
  }), /* @__PURE__ */ jsx(PopoverContent, {
255
479
  side: "top",
@@ -258,46 +482,63 @@ function ChatSlashMenu(props) {
258
482
  className: "z-[70] flex max-w-[calc(100vw-1.5rem)] flex-col overflow-hidden rounded-2xl border border-gray-200 bg-white/95 p-0 shadow-2xl backdrop-blur-md",
259
483
  onOpenAutoFocus: (event) => event.preventDefault(),
260
484
  style: {
261
- maxHeight: SLASH_PANEL_MAX_HEIGHT,
485
+ maxHeight: INPUT_SURFACE_PANEL_MAX_HEIGHT,
262
486
  width: resolvedWidth ? `${resolvedWidth}px` : void 0
263
487
  },
264
488
  children: /* @__PURE__ */ jsxs("div", {
265
489
  className: "grid min-h-0 flex-1 grid-cols-[minmax(220px,300px)_minmax(0,1fr)]",
266
- style: { minHeight: SLASH_PANEL_MIN_HEIGHT },
490
+ style: { minHeight: INPUT_SURFACE_PANEL_MIN_HEIGHT },
267
491
  children: [/* @__PURE__ */ jsx("div", {
268
492
  ref: listRef,
269
493
  role: "listbox",
270
- "aria-label": texts.slashSectionLabel,
494
+ "aria-label": texts.sectionLabel,
271
495
  className: "custom-scrollbar min-h-0 overflow-y-auto overscroll-contain border-r border-gray-200 p-2.5",
272
496
  children: isLoading ? /* @__PURE__ */ jsx("div", {
273
497
  className: "p-2 text-xs text-gray-500",
274
- children: texts.slashLoadingLabel
275
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
498
+ children: texts.loadingLabel
499
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
276
500
  className: "mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
277
- children: texts.slashSectionLabel
278
- }), items.length === 0 ? /* @__PURE__ */ jsx("div", {
501
+ children: texts.sectionLabel
502
+ }) : null, items.length === 0 ? /* @__PURE__ */ jsx("div", {
279
503
  className: "px-2 text-xs text-gray-400",
280
- children: texts.slashEmptyLabel
504
+ children: texts.emptyLabel
281
505
  }) : /* @__PURE__ */ jsx("div", {
282
506
  className: "space-y-1",
283
507
  children: items.map((item, index) => {
284
- const isActive = index === activeIndex;
285
- return /* @__PURE__ */ jsxs("button", {
286
- type: "button",
287
- role: "option",
288
- "aria-selected": isActive,
289
- "data-slash-index": index,
290
- onMouseEnter: () => onSetActiveIndex(index),
291
- onClick: () => onSelectItem(item),
292
- 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"}`,
293
- children: [/* @__PURE__ */ jsx("span", {
294
- className: "truncate text-xs font-semibold",
295
- children: item.title
296
- }), /* @__PURE__ */ jsx("span", {
297
- className: "truncate text-xs text-gray-500",
298
- children: item.subtitle
508
+ const { key, sectionKey, sectionLabel, title, subtitle } = item;
509
+ const isActive = index === activeIndexInRange;
510
+ const previousItem = items[index - 1];
511
+ return /* @__PURE__ */ jsxs("div", {
512
+ className: "space-y-1",
513
+ children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
514
+ className: "px-2 pb-0.5 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
515
+ children: sectionLabel
516
+ }) : null, /* @__PURE__ */ jsxs("button", {
517
+ type: "button",
518
+ role: "option",
519
+ "aria-selected": isActive,
520
+ "data-input-surface-index": index,
521
+ onPointerMove: (event) => {
522
+ if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
523
+ },
524
+ onPointerDown: (event) => {
525
+ if (event.button > 0) return;
526
+ event.preventDefault();
527
+ onSelectItem(item);
528
+ },
529
+ onClick: (event) => {
530
+ if (event.detail === 0) onSelectItem(item);
531
+ },
532
+ 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"}`,
533
+ children: [/* @__PURE__ */ jsx("span", {
534
+ className: "truncate text-xs font-semibold",
535
+ children: title
536
+ }), /* @__PURE__ */ jsx("span", {
537
+ className: "truncate text-xs text-gray-500",
538
+ children: subtitle
539
+ })]
299
540
  })]
300
- }, item.key);
541
+ }, key);
301
542
  })
302
543
  })] })
303
544
  }), /* @__PURE__ */ jsx("div", {
@@ -329,17 +570,80 @@ function ChatSlashMenu(props) {
329
570
  }),
330
571
  /* @__PURE__ */ jsx("div", {
331
572
  className: "pt-1 text-[11px] text-gray-500",
332
- children: texts.slashSkillHintLabel
573
+ children: activeItem.hintLabel ?? texts.itemHintLabel
333
574
  })
334
575
  ]
335
576
  }) : /* @__PURE__ */ jsx("div", {
336
577
  className: "text-xs text-gray-500",
337
- children: texts.slashHintLabel
578
+ children: texts.hintLabel
338
579
  })
339
580
  })]
340
581
  })
341
582
  })]
342
583
  });
584
+ });
585
+ //#endregion
586
+ //#region src/components/chat/ui/input-surface/chat-input-surface-host.tsx
587
+ function getInputSurfaceTriggerIdentity(trigger) {
588
+ return `${trigger.key}:${trigger.marker}:${trigger.start}`;
589
+ }
590
+ function isInputSurfaceCreateEvent(trigger, reason) {
591
+ return reason.type === "insert-text" && reason.text === trigger.marker && trigger.query === "" && trigger.end === trigger.start + trigger.marker.length;
592
+ }
593
+ function resolveInputSurfaceTriggerIdentity(currentIdentity, trigger, reason) {
594
+ if (!trigger) return null;
595
+ const nextIdentity = getInputSurfaceTriggerIdentity(trigger);
596
+ if (currentIdentity === nextIdentity) return currentIdentity;
597
+ return isInputSurfaceCreateEvent(trigger, reason) ? nextIdentity : null;
598
+ }
599
+ function ChatInputSurfaceHost(props) {
600
+ const { children, inputSurface, onInputSurfaceTriggerChange, onSelectItem, triggerSpecs = [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC] } = props;
601
+ const menuRef = useRef(null);
602
+ const [activeTriggerIdentity, setActiveTriggerIdentity] = useState(null);
603
+ const isOpen = Boolean(inputSurface) && activeTriggerIdentity !== null;
604
+ const setActiveTrigger = useCallback((identity, trigger) => {
605
+ setActiveTriggerIdentity(identity);
606
+ onInputSurfaceTriggerChange?.(identity ? trigger : null);
607
+ }, [onInputSurfaceTriggerChange]);
608
+ const closeInputSurface = useCallback(() => {
609
+ setActiveTrigger(null, null);
610
+ }, [setActiveTrigger]);
611
+ const handleInputSurfaceSnapshotChange = useCallback((nodes, selection, reason) => {
612
+ const trigger = resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs);
613
+ const nextIdentity = resolveInputSurfaceTriggerIdentity(activeTriggerIdentity, trigger, reason);
614
+ setActiveTrigger(nextIdentity, nextIdentity ? trigger : null);
615
+ }, [
616
+ activeTriggerIdentity,
617
+ setActiveTrigger,
618
+ triggerSpecs
619
+ ]);
620
+ const handleInputSurfaceKeyDown = useCallback((event) => {
621
+ return menuRef.current?.handleKeyDown(event) ?? false;
622
+ }, []);
623
+ const handleInputSurfaceOpenChange = useCallback((open) => {
624
+ if (!open) closeInputSurface();
625
+ }, [closeInputSurface]);
626
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [children(useMemo(() => ({
627
+ onInputSurfaceKeyDown: handleInputSurfaceKeyDown,
628
+ onInputSurfaceOpenChange: handleInputSurfaceOpenChange,
629
+ onInputSurfaceSnapshotChange: handleInputSurfaceSnapshotChange
630
+ }), [
631
+ handleInputSurfaceKeyDown,
632
+ handleInputSurfaceOpenChange,
633
+ handleInputSurfaceSnapshotChange
634
+ ])), inputSurface && isOpen ? /* @__PURE__ */ jsx(ChatInputSurfaceMenu, {
635
+ ref: menuRef,
636
+ isOpen,
637
+ isLoading: inputSurface.isLoading,
638
+ items: inputSurface.items,
639
+ texts: inputSurface.texts,
640
+ onSelectItem: (item) => {
641
+ closeInputSurface();
642
+ onSelectItem(item);
643
+ },
644
+ onOpenChange: handleInputSurfaceOpenChange,
645
+ onDetailsPointerDown: (event) => event.preventDefault()
646
+ }, activeTriggerIdentity) : null] });
343
647
  }
344
648
  //#endregion
345
649
  //#region src/components/chat/default-skin/button.tsx
@@ -966,152 +1270,6 @@ function ChatInputBarToolbar({ actions, accessories, selects, skillPicker }) {
966
1270
  }), /* @__PURE__ */ jsx(ChatInputBarActions, { ...actions })]
967
1271
  });
968
1272
  }
969
- function createComposerNodeId() {
970
- return `composer-${Math.random().toString(36).slice(2, 10)}`;
971
- }
972
- function createChatComposerTextNode(text = "") {
973
- return {
974
- id: createComposerNodeId(),
975
- type: "text",
976
- text
977
- };
978
- }
979
- function createChatComposerTokenNode(params) {
980
- return {
981
- id: createComposerNodeId(),
982
- type: "token",
983
- tokenKind: params.tokenKind,
984
- tokenKey: params.tokenKey,
985
- label: params.label
986
- };
987
- }
988
- function getChatComposerNodeLength(node) {
989
- return node.type === "text" ? node.text.length : 1;
990
- }
991
- function createEmptyChatComposerNodes() {
992
- return [createChatComposerTextNode("")];
993
- }
994
- function createChatComposerNodesFromText(text) {
995
- return [createChatComposerTextNode(text)];
996
- }
997
- function normalizeChatComposerNodes(nodes) {
998
- const normalized = [];
999
- for (const node of nodes) {
1000
- if (node.type === "text") {
1001
- if (node.text.length === 0) continue;
1002
- const previous = normalized[normalized.length - 1];
1003
- if (previous?.type === "text") {
1004
- normalized[normalized.length - 1] = {
1005
- ...previous,
1006
- text: previous.text + node.text
1007
- };
1008
- continue;
1009
- }
1010
- }
1011
- normalized.push(node);
1012
- }
1013
- if (normalized.length === 0) return createEmptyChatComposerNodes();
1014
- return normalized;
1015
- }
1016
- function serializeChatComposerDocument(nodes) {
1017
- return nodes.map((node) => node.type === "text" ? node.text : "").join("");
1018
- }
1019
- function serializeChatComposerPlainText(nodes) {
1020
- return nodes.filter((node) => node.type === "text").map((node) => node.text).join("");
1021
- }
1022
- function extractChatComposerTokenKeys(nodes, tokenKind) {
1023
- const keys = [];
1024
- const keySet = /* @__PURE__ */ new Set();
1025
- for (const node of nodes) {
1026
- if (node.type !== "token" || node.tokenKind !== tokenKind || keySet.has(node.tokenKey)) continue;
1027
- keySet.add(node.tokenKey);
1028
- keys.push(node.tokenKey);
1029
- }
1030
- return keys;
1031
- }
1032
- function buildTrimmedTextEdges(node, nodeStart, rangeStart, rangeEnd) {
1033
- const prefixLength = Math.max(0, rangeStart - nodeStart);
1034
- const suffixLength = Math.max(0, nodeStart + node.text.length - rangeEnd);
1035
- const edges = [];
1036
- if (prefixLength > 0) edges.push({
1037
- ...node,
1038
- text: node.text.slice(0, prefixLength)
1039
- });
1040
- if (suffixLength > 0) edges.push({
1041
- ...node,
1042
- text: node.text.slice(node.text.length - suffixLength)
1043
- });
1044
- return edges;
1045
- }
1046
- function isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd) {
1047
- return nodeEnd <= boundedStart || nodeStart >= boundedEnd;
1048
- }
1049
- function appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd) {
1050
- if (!inserted && nodeStart >= boundedEnd) {
1051
- nextNodes.push(...replacement);
1052
- return true;
1053
- }
1054
- return inserted;
1055
- }
1056
- function appendTextNodeWithReplacement(nextNodes, node, nodeStart, boundedStart, boundedEnd, replacement, inserted) {
1057
- const edges = buildTrimmedTextEdges(node, nodeStart, boundedStart, boundedEnd);
1058
- if (edges[0]) nextNodes.push(edges[0]);
1059
- let didInsert = inserted;
1060
- if (!didInsert) {
1061
- nextNodes.push(...replacement);
1062
- didInsert = true;
1063
- }
1064
- if (edges[1]) nextNodes.push(edges[1]);
1065
- return didInsert;
1066
- }
1067
- function appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart) {
1068
- if (!inserted && boundedStart <= nodeStart) {
1069
- nextNodes.push(...replacement);
1070
- return true;
1071
- }
1072
- nextNodes.push(node);
1073
- return inserted;
1074
- }
1075
- function replaceChatComposerRange(nodes, start, end, replacement) {
1076
- const boundedStart = Math.max(0, start);
1077
- const boundedEnd = Math.max(boundedStart, end);
1078
- const nextNodes = [];
1079
- let cursor = 0;
1080
- let inserted = false;
1081
- for (const node of nodes) {
1082
- const nodeLength = getChatComposerNodeLength(node);
1083
- const nodeStart = cursor;
1084
- const nodeEnd = cursor + nodeLength;
1085
- if (isNodeOutsideComposerRange(nodeStart, nodeEnd, boundedStart, boundedEnd)) {
1086
- inserted = appendReplacementBeforeNode(nextNodes, replacement, inserted, nodeStart, boundedEnd);
1087
- nextNodes.push(node);
1088
- cursor = nodeEnd;
1089
- continue;
1090
- }
1091
- if (node.type === "text") inserted = appendTextNodeWithReplacement(nextNodes, node, nodeStart, boundedStart, boundedEnd, replacement, inserted);
1092
- else inserted = appendTokenNodeWithReplacement(nextNodes, node, replacement, inserted, boundedStart, nodeStart);
1093
- cursor = nodeEnd;
1094
- }
1095
- if (!inserted) nextNodes.push(...replacement);
1096
- return normalizeChatComposerNodes(nextNodes);
1097
- }
1098
- function removeChatComposerTokenNodes(nodes, predicate) {
1099
- return normalizeChatComposerNodes(nodes.filter((node) => node.type !== "token" || !predicate(node)));
1100
- }
1101
- function resolveChatComposerSlashTrigger(nodes, selection) {
1102
- if (!selection || selection.start !== selection.end) return null;
1103
- const documentText = serializeChatComposerDocument(nodes);
1104
- const caret = selection.end;
1105
- const prefix = documentText.slice(0, caret);
1106
- const match = /(?:^|\s)\/([^\s\uFFFC]*)$/.exec(prefix);
1107
- if (!match) return null;
1108
- const slashStart = caret - match[0].length + (match[0].startsWith("/") ? 0 : 1);
1109
- return {
1110
- query: (match[1] ?? "").trim().toLowerCase(),
1111
- start: slashStart,
1112
- end: caret
1113
- };
1114
- }
1115
1273
  //#endregion
1116
1274
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-token-node.tsx
1117
1275
  function buildTokenClassName(tokenKind) {
@@ -1154,35 +1312,15 @@ function buildTokenClassName(tokenKind) {
1154
1312
  function ChatComposerTokenChip({ label, tokenKind }) {
1155
1313
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1156
1314
  className: tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-white text-slate-500 ring-1 ring-black/5" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70",
1157
- children: tokenKind === "file" ? /* @__PURE__ */ jsxs("svg", {
1158
- viewBox: "0 0 16 16",
1159
- fill: "none",
1160
- stroke: "currentColor",
1161
- strokeWidth: "1.25",
1162
- strokeLinecap: "round",
1163
- strokeLinejoin: "round",
1315
+ children: tokenKind === "file" ? /* @__PURE__ */ jsx(ImageIcon, {
1164
1316
  "aria-hidden": "true",
1165
- className: "h-3 w-3",
1166
- children: [
1167
- /* @__PURE__ */ jsx("path", { d: "M3.25 4.25A1.5 1.5 0 0 1 4.75 2.75h6.5a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-6.5a1.5 1.5 0 0 1-1.5-1.5v-7.5Z" }),
1168
- /* @__PURE__ */ jsx("path", { d: "m4.75 10 2.25-2.5 1.75 1.75 1.25-1.25 2 2" }),
1169
- /* @__PURE__ */ jsx("path", { d: "M9.75 6.25h.01" })
1170
- ]
1171
- }) : /* @__PURE__ */ jsxs("svg", {
1172
- viewBox: "0 0 16 16",
1173
- fill: "none",
1174
- stroke: "currentColor",
1175
- strokeWidth: "1.25",
1176
- strokeLinecap: "round",
1177
- strokeLinejoin: "round",
1317
+ className: "h-3 w-3"
1318
+ }) : tokenKind === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
1178
1319
  "aria-hidden": "true",
1179
- className: "h-3 w-3",
1180
- children: [
1181
- /* @__PURE__ */ jsx("path", { d: "M8.5 2.75 2.75 6l5.75 3.25L14.25 6 8.5 2.75Z" }),
1182
- /* @__PURE__ */ jsx("path", { d: "M2.75 10 8.5 13.25 14.25 10" }),
1183
- /* @__PURE__ */ jsx("path", { d: "M2.75 6v4l5.75 3.25V9.25L2.75 6Z" }),
1184
- /* @__PURE__ */ jsx("path", { d: "M14.25 6v4L8.5 13.25V9.25L14.25 6Z" })
1185
- ]
1320
+ className: "h-3 w-3"
1321
+ }) : /* @__PURE__ */ jsx(Puzzle, {
1322
+ "aria-hidden": "true",
1323
+ className: "h-3 w-3"
1186
1324
  })
1187
1325
  }), /* @__PURE__ */ jsx("span", {
1188
1326
  className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate",
@@ -1518,6 +1656,48 @@ function insertToken(params) {
1518
1656
  }
1519
1657
  };
1520
1658
  }
1659
+ function insertChatComposerTokenIntoChatComposer(params) {
1660
+ const { label, nodes, selection, tokenKey, tokenKind, triggerSpecs } = params;
1661
+ if (extractChatComposerTokenKeys(nodes, tokenKind).includes(tokenKey)) return {
1662
+ nodes: normalizeChatComposerNodes(nodes),
1663
+ selection
1664
+ };
1665
+ return insertToken({
1666
+ label,
1667
+ nodes,
1668
+ selection,
1669
+ tokenKey,
1670
+ tokenKind,
1671
+ trigger: resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs ?? [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC])
1672
+ });
1673
+ }
1674
+ function insertInputSurfaceItemIntoChatComposer(params) {
1675
+ const { item, nodes, selection, triggerSpecs } = params;
1676
+ const tokenKind = item.tokenKind ?? (item.value ? "skill" : void 0);
1677
+ const tokenKey = item.tokenKey ?? item.value;
1678
+ if (!tokenKind || !tokenKey) {
1679
+ const trigger = resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs ?? [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC]);
1680
+ if (trigger) return {
1681
+ nodes: normalizeChatComposerNodes(replaceChatComposerRange(nodes, trigger.start, trigger.end, [])),
1682
+ selection: {
1683
+ start: trigger.start,
1684
+ end: trigger.start
1685
+ }
1686
+ };
1687
+ return {
1688
+ nodes: normalizeChatComposerNodes(nodes),
1689
+ selection
1690
+ };
1691
+ }
1692
+ return insertChatComposerTokenIntoChatComposer({
1693
+ label: item.title,
1694
+ nodes,
1695
+ selection,
1696
+ tokenKey,
1697
+ tokenKind,
1698
+ triggerSpecs
1699
+ });
1700
+ }
1521
1701
  function getChatComposerNodesSignature(nodes) {
1522
1702
  return nodes.map((node) => node.type === "text" ? `text:${node.id}:${node.text}` : `token:${node.id}:${node.tokenKind}:${node.tokenKey}:${node.label}`).join("");
1523
1703
  }
@@ -1606,21 +1786,9 @@ function deleteChatComposerContent(params) {
1606
1786
  //#endregion
1607
1787
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-controller.ts
1608
1788
  function resolveLexicalComposerKeyboardAction(params) {
1609
- const { activeSlashIndex, canStopGeneration, isComposing, isSending, isSlashMenuOpen, key, shiftKey, slashItemCount } = params;
1789
+ const { canStopGeneration, isComposing, isSending, key, shiftKey } = params;
1610
1790
  if (key === "Enter" && !shiftKey && isSending) return { type: "consume" };
1611
- if (isSlashMenuOpen && slashItemCount > 0) {
1612
- if (key === "ArrowDown") return {
1613
- type: "move-slash-index",
1614
- index: (activeSlashIndex + 1) % slashItemCount
1615
- };
1616
- if (key === "ArrowUp") return {
1617
- type: "move-slash-index",
1618
- index: (activeSlashIndex - 1 + slashItemCount) % slashItemCount
1619
- };
1620
- if (key === "Enter" && !shiftKey || key === "Tab") return { type: "insert-active-slash-item" };
1621
- }
1622
1791
  if (key === "Escape") {
1623
- if (isSlashMenuOpen) return { type: "close-slash" };
1624
1792
  if (isSending && canStopGeneration) return { type: "stop-generation" };
1625
1793
  return { type: "noop" };
1626
1794
  }
@@ -1635,16 +1803,18 @@ function resolveLexicalComposerKeyboardAction(params) {
1635
1803
  var LexicalComposerHandleOwner = class {
1636
1804
  constructor(params) {
1637
1805
  this.params = params;
1638
- this.insertSlashItem = (item) => {
1639
- if (!item.value) return;
1640
- this.params.onSlashItemSelect?.(item);
1641
- this.params.publishSnapshot(insertSkillTokenIntoChatComposer({
1642
- label: item.title,
1806
+ this.insertInputSurfaceItem = (item, triggerSpecs = [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC]) => {
1807
+ this.params.onInputSurfaceItemSelect?.(item);
1808
+ this.params.publishSnapshot(insertInputSurfaceItemIntoChatComposer({
1809
+ item,
1643
1810
  nodes: this.params.optionsReader().nodes,
1644
1811
  selection: this.params.optionsReader().selection,
1645
- tokenKey: item.value
1812
+ triggerSpecs
1646
1813
  }), { focusAfterSync: true });
1647
1814
  };
1815
+ this.insertSlashItem = (item) => {
1816
+ this.insertInputSurfaceItem(item);
1817
+ };
1648
1818
  this.insertFileToken = (tokenKey, label) => {
1649
1819
  this.params.publishSnapshot(insertFileTokenIntoChatComposer({
1650
1820
  label,
@@ -1711,7 +1881,10 @@ function handleLexicalComposerBeforeInput(params) {
1711
1881
  nodes: snapshotReader().nodes,
1712
1882
  selection: snapshotReader().selection,
1713
1883
  text: nativeEvent.data
1714
- }));
1884
+ }), { inputSurfaceReason: {
1885
+ type: "insert-text",
1886
+ text: nativeEvent.data
1887
+ } });
1715
1888
  }
1716
1889
  function handleLexicalComposerCompositionEnd(params) {
1717
1890
  const { data, fallbackSnapshot, publishSnapshot, snapshotReader } = params;
@@ -1721,40 +1894,25 @@ function handleLexicalComposerCompositionEnd(params) {
1721
1894
  nodes: currentSnapshot.nodes,
1722
1895
  selection: currentSnapshot.selection,
1723
1896
  text: data
1724
- }) : editorSnapshot, { forcePublish: true });
1897
+ }) : editorSnapshot, {
1898
+ forcePublish: true,
1899
+ inputSurfaceReason: {
1900
+ type: "insert-text",
1901
+ text: data
1902
+ }
1903
+ });
1725
1904
  }
1726
1905
  function handleLexicalComposerKeyboardCommand(params) {
1727
- const { actions, activeSlashIndex, nativeEvent, onSlashActiveIndexChange, onSlashItemSelect, onSlashOpenChange, onSlashQueryChange, publishSnapshot, slashItems, snapshot } = params;
1906
+ const { actions, nativeEvent, publishSnapshot, snapshot } = params;
1728
1907
  const action = resolveLexicalComposerKeyboardAction({
1729
- activeSlashIndex,
1730
1908
  canStopGeneration: actions.canStopGeneration,
1731
1909
  isComposing: nativeEvent.isComposing,
1732
1910
  isSending: actions.isSending,
1733
- isSlashMenuOpen: resolveChatComposerSlashTrigger(snapshot.nodes, snapshot.selection) !== null,
1734
1911
  key: nativeEvent.key,
1735
- shiftKey: nativeEvent.shiftKey,
1736
- slashItemCount: slashItems.length
1912
+ shiftKey: nativeEvent.shiftKey
1737
1913
  });
1738
- const activeSlashItem = slashItems[activeSlashIndex] ?? null;
1739
1914
  if (action.type !== "noop") nativeEvent.preventDefault();
1740
1915
  switch (action.type) {
1741
- case "move-slash-index":
1742
- onSlashActiveIndexChange(action.index);
1743
- return true;
1744
- case "insert-active-slash-item":
1745
- if (!activeSlashItem) return true;
1746
- onSlashItemSelect?.(activeSlashItem);
1747
- publishSnapshot(insertSkillTokenIntoChatComposer({
1748
- label: activeSlashItem.title,
1749
- nodes: snapshot.nodes,
1750
- selection: snapshot.selection,
1751
- tokenKey: activeSlashItem.value ?? activeSlashItem.key
1752
- }), { focusAfterSync: true });
1753
- return true;
1754
- case "close-slash":
1755
- onSlashQueryChange?.(null);
1756
- onSlashOpenChange(false);
1757
- return true;
1758
1916
  case "consume": return true;
1759
1917
  case "stop-generation":
1760
1918
  actions.onStop();
@@ -1764,7 +1922,10 @@ function handleLexicalComposerKeyboardCommand(params) {
1764
1922
  nodes: snapshot.nodes,
1765
1923
  selection: snapshot.selection,
1766
1924
  text: "\n"
1767
- }));
1925
+ }), { inputSurfaceReason: {
1926
+ type: "insert-text",
1927
+ text: "\n"
1928
+ } });
1768
1929
  return true;
1769
1930
  case "send-message":
1770
1931
  actions.onSend();
@@ -1774,14 +1935,14 @@ function handleLexicalComposerKeyboardCommand(params) {
1774
1935
  direction: action.direction,
1775
1936
  nodes: snapshot.nodes,
1776
1937
  selection: snapshot.selection
1777
- }));
1938
+ }), { inputSurfaceReason: { type: "delete-content" } });
1778
1939
  return true;
1779
1940
  case "noop": return false;
1780
1941
  }
1781
1942
  }
1782
1943
  //#endregion
1783
1944
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-plugins.tsx
1784
- function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, isApplyingExternalUpdateRef, isComposingRef, lastPublishedSignatureRef, nodes, onBlur, onKeyDown, onNodesChange, pendingSelectionRef, selectionRef, shouldFocusAfterSyncRef, syncSlashState }) {
1945
+ function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, isApplyingExternalUpdateRef, isComposingRef, lastPublishedSignatureRef, nodes, onBlur, consumeInputSurfaceReason, onKeyDown, onNodesChange, pendingSelectionRef, selectionRef, shouldFocusAfterSyncRef, syncInputSurfaceSnapshot }) {
1785
1946
  const [editor] = useLexicalComposerContext();
1786
1947
  useLayoutEffect(() => {
1787
1948
  editorRef.current = editor;
@@ -1833,15 +1994,15 @@ function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, i
1833
1994
  const signature = getChatComposerNodesSignature(snapshot.nodes);
1834
1995
  selectionRef.current = snapshot.selection;
1835
1996
  editorSignatureRef.current = signature;
1836
- syncSlashState(snapshot.nodes, snapshot.selection);
1837
1997
  if (isApplyingExternalUpdateRef.current || isComposingRef.current) return;
1998
+ syncInputSurfaceSnapshot(snapshot.nodes, snapshot.selection, consumeInputSurfaceReason() ?? { type: "sync" });
1838
1999
  if (signature === lastPublishedSignatureRef.current) return;
1839
2000
  lastPublishedSignatureRef.current = signature;
1840
2001
  onNodesChange(snapshot.nodes);
1841
2002
  }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
1842
2003
  const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
1843
2004
  selectionRef.current = snapshot.selection;
1844
- if (!isComposingRef.current) syncSlashState(snapshot.nodes, snapshot.selection);
2005
+ if (!isComposingRef.current) syncInputSurfaceSnapshot(snapshot.nodes, snapshot.selection, { type: "selection" });
1845
2006
  return false;
1846
2007
  }, COMMAND_PRIORITY_EDITOR), editor.registerCommand(BLUR_COMMAND, () => {
1847
2008
  onBlur();
@@ -1854,10 +2015,11 @@ function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, i
1854
2015
  isComposingRef,
1855
2016
  lastPublishedSignatureRef,
1856
2017
  onBlur,
2018
+ consumeInputSurfaceReason,
1857
2019
  onKeyDown,
1858
2020
  onNodesChange,
1859
2021
  selectionRef,
1860
- syncSlashState
2022
+ syncInputSurfaceSnapshot
1861
2023
  ]);
1862
2024
  return null;
1863
2025
  }
@@ -1866,25 +2028,19 @@ function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, i
1866
2028
  function getChatComposerDocumentLength(nodes) {
1867
2029
  return nodes.reduce((cursor, node) => cursor + (node.type === "text" ? node.text.length : 1), 0);
1868
2030
  }
1869
- const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer({ actions, activeSlashIndex, disabled, nodes, onFilesAdd, onNodesChange, onSlashActiveIndexChange, onSlashItemSelect, onSlashOpenChange, onSlashQueryChange, onSlashTriggerChange, placeholder, slashItems }, ref) {
2031
+ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer({ actions, disabled, nodes, onFilesAdd, onInputSurfaceItemSelect, onInputSurfaceKeyDown, onInputSurfaceOpenChange, onInputSurfaceSnapshotChange, onNodesChange, placeholder }, ref) {
1870
2032
  const editorRef = useRef(null);
1871
2033
  const selectionRef = useRef(null);
1872
2034
  const pendingSelectionRef = useRef(null);
1873
2035
  const shouldFocusAfterSyncRef = useRef(false);
1874
2036
  const isComposingRef = useRef(false);
1875
2037
  const isApplyingExternalUpdateRef = useRef(false);
2038
+ const pendingInputSurfaceReasonRef = useRef(null);
1876
2039
  const editorSignatureRef = useRef("");
1877
2040
  const lastPublishedSignatureRef = useRef("");
1878
- const syncSlashState = useCallback((nodes, selection) => {
1879
- const trigger = resolveChatComposerSlashTrigger(nodes, selection);
1880
- onSlashTriggerChange?.(trigger);
1881
- onSlashQueryChange?.(trigger?.query ?? null);
1882
- onSlashOpenChange(trigger !== null);
1883
- }, [
1884
- onSlashOpenChange,
1885
- onSlashQueryChange,
1886
- onSlashTriggerChange
1887
- ]);
2041
+ const syncInputSurfaceSnapshot = useCallback((nodes, selection, reason) => {
2042
+ onInputSurfaceSnapshotChange?.(nodes, selection, reason);
2043
+ }, [onInputSurfaceSnapshotChange]);
1888
2044
  const readCurrentNodes = useCallback(() => {
1889
2045
  return nodes;
1890
2046
  }, [nodes]);
@@ -1900,12 +2056,12 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1900
2056
  pendingSelectionRef.current = snapshot.selection;
1901
2057
  if (options?.focusAfterSync) shouldFocusAfterSyncRef.current = true;
1902
2058
  const signature = getChatComposerNodesSignature(snapshot.nodes);
1903
- syncSlashState(snapshot.nodes, snapshot.selection);
2059
+ syncInputSurfaceSnapshot(snapshot.nodes, snapshot.selection, options?.inputSurfaceReason ?? { type: "programmatic" });
1904
2060
  if (options?.forcePublish || signature !== lastPublishedSignatureRef.current) {
1905
2061
  lastPublishedSignatureRef.current = signature;
1906
2062
  onNodesChange(snapshot.nodes);
1907
2063
  }
1908
- }, [onNodesChange, syncSlashState]);
2064
+ }, [onNodesChange, syncInputSurfaceSnapshot]);
1909
2065
  const focusComposer = useCallback(() => {
1910
2066
  const editor = editorRef.current;
1911
2067
  if (!editor) return;
@@ -1943,16 +2099,21 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1943
2099
  nodes: readCurrentNodes(),
1944
2100
  selection: readCurrentSelection()
1945
2101
  }), [readCurrentNodes, readCurrentSelection]);
2102
+ const consumeInputSurfaceReason = useCallback(() => {
2103
+ const reason = pendingInputSurfaceReasonRef.current;
2104
+ pendingInputSurfaceReasonRef.current = null;
2105
+ return reason;
2106
+ }, []);
1946
2107
  useImperativeHandle(ref, () => createLexicalComposerHandle({
1947
2108
  focusComposer,
1948
2109
  focusComposerAtEnd,
1949
- onSlashItemSelect,
2110
+ onInputSurfaceItemSelect,
1950
2111
  optionsReader: readComposerSnapshot,
1951
2112
  publishSnapshot
1952
2113
  }), [
1953
2114
  focusComposer,
1954
2115
  focusComposerAtEnd,
1955
- onSlashItemSelect,
2116
+ onInputSurfaceItemSelect,
1956
2117
  publishSnapshot,
1957
2118
  readComposerSnapshot
1958
2119
  ]);
@@ -2028,24 +2189,23 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
2028
2189
  lastPublishedSignatureRef,
2029
2190
  nodes,
2030
2191
  onBlur: () => {
2031
- onSlashQueryChange?.(null);
2032
- onSlashOpenChange(false);
2192
+ onInputSurfaceOpenChange?.(false);
2033
2193
  },
2194
+ consumeInputSurfaceReason,
2034
2195
  onKeyDown: (event) => {
2196
+ if (event.key.length === 1 && !event.isComposing && !event.altKey && !event.ctrlKey && !event.metaKey) pendingInputSurfaceReasonRef.current = {
2197
+ type: "insert-text",
2198
+ text: event.key
2199
+ };
2035
2200
  const editor = editorRef.current;
2036
2201
  if (!editor) return false;
2037
2202
  const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
2038
2203
  selectionRef.current = snapshot.selection;
2204
+ if (onInputSurfaceKeyDown?.(event)) return true;
2039
2205
  return handleLexicalComposerKeyboardCommand({
2040
2206
  actions,
2041
- activeSlashIndex,
2042
2207
  nativeEvent: event,
2043
- onSlashActiveIndexChange,
2044
- onSlashItemSelect,
2045
- onSlashOpenChange,
2046
- onSlashQueryChange,
2047
2208
  publishSnapshot,
2048
- slashItems,
2049
2209
  snapshot
2050
2210
  });
2051
2211
  },
@@ -2053,7 +2213,7 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
2053
2213
  pendingSelectionRef,
2054
2214
  selectionRef,
2055
2215
  shouldFocusAfterSyncRef,
2056
- syncSlashState
2216
+ syncInputSurfaceSnapshot
2057
2217
  })
2058
2218
  ]
2059
2219
  });
@@ -2083,16 +2243,20 @@ function InputBarHint({ hint }) {
2083
2243
  })
2084
2244
  });
2085
2245
  }
2086
- const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, slashMenu, surface, toolbar: toolbarProps }, ref) {
2246
+ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSurface, slashMenu, surface, toolbar: toolbarProps }, ref) {
2087
2247
  const composerRef = useRef(null);
2088
- const isSlashMenuInteractionRef = useRef(false);
2089
- const [activeSlashIndex, setActiveSlashIndex] = useState(0);
2090
- const [activeSlashTriggerStart, setActiveSlashTriggerStart] = useState(null);
2091
- const [dismissedSlashTriggerStart, setDismissedSlashTriggerStart] = useState(null);
2092
- const isSlashPanelOpen = activeSlashTriggerStart !== null && dismissedSlashTriggerStart !== activeSlashTriggerStart;
2093
- const activeSlashIndexInRange = slashMenu.items.length === 0 ? 0 : Math.min(activeSlashIndex, slashMenu.items.length - 1);
2094
- const activeSlashItem = slashMenu.items[activeSlashIndexInRange] ?? null;
2095
- const dismissSlashTrigger = () => activeSlashTriggerStart !== null && !isSlashMenuInteractionRef.current ? setDismissedSlashTriggerStart(activeSlashTriggerStart) : void 0;
2248
+ const resolvedInputSurface = inputSurface ?? (slashMenu ? {
2249
+ isLoading: slashMenu.isLoading,
2250
+ items: slashMenu.items,
2251
+ onSelectItem: slashMenu.onSelectItem,
2252
+ texts: {
2253
+ loadingLabel: slashMenu.texts.slashLoadingLabel,
2254
+ sectionLabel: slashMenu.texts.slashSectionLabel,
2255
+ emptyLabel: slashMenu.texts.slashEmptyLabel,
2256
+ hintLabel: slashMenu.texts.slashHintLabel,
2257
+ itemHintLabel: slashMenu.texts.slashSkillHintLabel
2258
+ }
2259
+ } : null);
2096
2260
  const toolbar = useMemo(() => {
2097
2261
  if (!toolbarProps.skillPicker) return toolbarProps;
2098
2262
  return {
@@ -2119,55 +2283,27 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, slashMen
2119
2283
  children: /* @__PURE__ */ jsxs("div", {
2120
2284
  className: "overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-card",
2121
2285
  children: [
2122
- /* @__PURE__ */ jsxs("div", {
2286
+ /* @__PURE__ */ jsx("div", {
2123
2287
  className: "relative",
2124
- children: [/* @__PURE__ */ jsx(ChatInputBarTokenizedComposer, {
2125
- ref: composerRef,
2126
- nodes: composer.nodes,
2127
- placeholder: composer.placeholder,
2128
- disabled: composer.disabled,
2129
- slashItems: slashMenu.items,
2130
- onSlashItemSelect: slashMenu.onSelectItem,
2131
- actions: toolbarProps.actions,
2132
- activeSlashIndex: activeSlashIndexInRange,
2133
- onNodesChange: composer.onNodesChange,
2134
- onFilesAdd: composer.onFilesAdd,
2135
- onSlashQueryChange: (query) => {
2136
- if (query === null && isSlashMenuInteractionRef.current) return;
2137
- if (query !== null) setActiveSlashIndex(0);
2138
- composer.onSlashQueryChange?.(query);
2139
- },
2140
- onSlashTriggerChange: (trigger) => {
2141
- const nextTriggerStart = trigger?.start ?? null;
2142
- setActiveSlashTriggerStart(nextTriggerStart);
2143
- if (nextTriggerStart === null) setDismissedSlashTriggerStart(null);
2144
- },
2145
- onSlashOpenChange: (open) => {
2146
- if (!open) dismissSlashTrigger();
2147
- },
2148
- onSlashActiveIndexChange: setActiveSlashIndex
2149
- }), /* @__PURE__ */ jsx(ChatSlashMenu, {
2150
- isOpen: isSlashPanelOpen,
2151
- isLoading: slashMenu.isLoading,
2152
- items: slashMenu.items,
2153
- activeIndex: activeSlashIndexInRange,
2154
- activeItem: activeSlashItem,
2155
- texts: slashMenu.texts,
2156
- onSelectItem: (item) => {
2157
- setDismissedSlashTriggerStart(null);
2158
- composerRef.current?.insertSlashItem(item);
2159
- },
2160
- onOpenChange: (open) => {
2161
- if (!open) dismissSlashTrigger();
2162
- },
2163
- onDetailsPointerDown: () => {
2164
- isSlashMenuInteractionRef.current = true;
2165
- requestAnimationFrame(() => {
2166
- isSlashMenuInteractionRef.current = false;
2167
- });
2168
- },
2169
- onSetActiveIndex: setActiveSlashIndex
2170
- })]
2288
+ children: /* @__PURE__ */ jsx(ChatInputSurfaceHost, {
2289
+ inputSurface: resolvedInputSurface,
2290
+ onInputSurfaceTriggerChange: composer.onInputSurfaceTriggerChange,
2291
+ onSelectItem: (item) => composerRef.current?.insertInputSurfaceItem(item, composer.inputSurfaceTriggerSpecs),
2292
+ triggerSpecs: composer.inputSurfaceTriggerSpecs,
2293
+ children: ({ onInputSurfaceKeyDown, onInputSurfaceOpenChange, onInputSurfaceSnapshotChange }) => /* @__PURE__ */ jsx(ChatInputBarTokenizedComposer, {
2294
+ ref: composerRef,
2295
+ nodes: composer.nodes,
2296
+ placeholder: composer.placeholder,
2297
+ disabled: composer.disabled,
2298
+ onInputSurfaceItemSelect: resolvedInputSurface?.onSelectItem,
2299
+ actions: toolbarProps.actions,
2300
+ onNodesChange: composer.onNodesChange,
2301
+ onFilesAdd: composer.onFilesAdd,
2302
+ onInputSurfaceSnapshotChange,
2303
+ onInputSurfaceOpenChange,
2304
+ onInputSurfaceKeyDown
2305
+ })
2306
+ })
2171
2307
  }),
2172
2308
  /* @__PURE__ */ jsx(InputBarHint, { hint }),
2173
2309
  /* @__PURE__ */ jsx(ChatInputBarToolbar, { ...toolbar })
@@ -2200,6 +2336,46 @@ function ChatMessageAvatar({ role }) {
2200
2336
  });
2201
2337
  }
2202
2338
  //#endregion
2339
+ //#region src/components/chat/ui/chat-message-list/chat-inline-token-badge.tsx
2340
+ function resolveInlineTokenTone(kind) {
2341
+ if (kind === "skill") return "skill";
2342
+ if (kind === "panel_app") return "panel_app";
2343
+ return "default";
2344
+ }
2345
+ function resolveInlineTokenBadgeClassName(tone, isUser) {
2346
+ if (tone === "skill") return isUser ? "border-emerald-200/35 bg-emerald-400/18 text-emerald-50/95" : "border-emerald-200/70 bg-emerald-50 text-emerald-700";
2347
+ if (tone === "panel_app") return isUser ? "border-sky-200/35 bg-sky-400/18 text-sky-50/95" : "border-sky-200/70 bg-sky-50 text-sky-700";
2348
+ return isUser ? "border-white/30 bg-white/18 text-white" : "border-slate-200/80 bg-slate-100 text-slate-700";
2349
+ }
2350
+ function resolveInlineTokenIconClassName(tone, isUser) {
2351
+ if (tone === "skill") return isUser ? "text-emerald-100/90" : "text-emerald-600";
2352
+ if (tone === "panel_app") return isUser ? "text-sky-100/90" : "text-sky-600";
2353
+ return isUser ? "text-white/70" : "text-slate-500";
2354
+ }
2355
+ function renderInlineTokenIcon(tone) {
2356
+ return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
2357
+ "aria-hidden": "true",
2358
+ className: "h-3 w-3"
2359
+ }) : /* @__PURE__ */ jsx(Puzzle, {
2360
+ "aria-hidden": "true",
2361
+ className: "h-3 w-3"
2362
+ });
2363
+ }
2364
+ function ChatInlineTokenBadge({ kind, label, isUser }) {
2365
+ const tone = resolveInlineTokenTone(kind);
2366
+ return /* @__PURE__ */ jsxs("span", {
2367
+ className: cn("mx-[2px] inline-flex h-7 max-w-full items-center gap-1.5 rounded-xl border px-2.5 align-baseline text-[11px] font-medium shadow-[0_0_0_1px_rgba(15,23,42,0.03)]", resolveInlineTokenBadgeClassName(tone, isUser)),
2368
+ title: label,
2369
+ children: [/* @__PURE__ */ jsx("span", {
2370
+ className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", resolveInlineTokenIconClassName(tone, isUser)),
2371
+ children: renderInlineTokenIcon(tone)
2372
+ }), /* @__PURE__ */ jsx("span", {
2373
+ className: "truncate",
2374
+ children: label
2375
+ })]
2376
+ });
2377
+ }
2378
+ //#endregion
2203
2379
  //#region src/components/chat/utils/copy-text.utils.ts
2204
2380
  function canUseClipboardApi() {
2205
2381
  return typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
@@ -2499,6 +2675,10 @@ const SAFE_LINK_PROTOCOLS = new Set([
2499
2675
  "mailto:",
2500
2676
  "tel:"
2501
2677
  ]);
2678
+ const INLINE_TOKEN_KIND_ATTR = "data-chat-inline-token-kind";
2679
+ const INLINE_TOKEN_KEY_ATTR = "data-chat-inline-token-key";
2680
+ const INLINE_TOKEN_LABEL_ATTR = "data-chat-inline-token-label";
2681
+ const INLINE_TOKEN_RAW_TEXT_ATTR = "data-chat-inline-token-raw-text";
2502
2682
  const PROJECT_RELATIVE_FILE_EXTENSIONS = [
2503
2683
  "cjs",
2504
2684
  "css",
@@ -2553,10 +2733,116 @@ function parseLocalFileAction(href) {
2553
2733
  ...typeof column === "number" ? { column } : {}
2554
2734
  };
2555
2735
  }
2556
- function ChatMessageMarkdown({ text, role, texts, inline = false, onFileOpen }) {
2736
+ function prepareInlineTokens(inlineTokens) {
2737
+ if (!inlineTokens || inlineTokens.length === 0) return [];
2738
+ const seenRawTexts = /* @__PURE__ */ new Set();
2739
+ const tokens = [];
2740
+ for (const token of inlineTokens) {
2741
+ if (!token.rawText || seenRawTexts.has(token.rawText)) continue;
2742
+ seenRawTexts.add(token.rawText);
2743
+ tokens.push(token);
2744
+ }
2745
+ return tokens.sort((left, right) => right.rawText.length - left.rawText.length);
2746
+ }
2747
+ function createInlineTokenNode(token) {
2748
+ return {
2749
+ type: "chatInlineToken",
2750
+ data: {
2751
+ hName: "span",
2752
+ hProperties: {
2753
+ [INLINE_TOKEN_KIND_ATTR]: token.kind,
2754
+ [INLINE_TOKEN_KEY_ATTR]: token.key,
2755
+ [INLINE_TOKEN_LABEL_ATTR]: token.label,
2756
+ [INLINE_TOKEN_RAW_TEXT_ATTR]: token.rawText
2757
+ },
2758
+ hChildren: [{
2759
+ type: "text",
2760
+ value: token.label
2761
+ }]
2762
+ }
2763
+ };
2764
+ }
2765
+ function findNextInlineToken(value, cursor, tokens) {
2766
+ let next = null;
2767
+ for (const token of tokens) {
2768
+ const index = value.indexOf(token.rawText, cursor);
2769
+ if (index < 0) continue;
2770
+ if (!next || index < next.index || index === next.index && token.rawText.length > next.token.rawText.length) next = {
2771
+ index,
2772
+ token
2773
+ };
2774
+ }
2775
+ return next;
2776
+ }
2777
+ function splitTextNodeByInlineTokens(value, tokens) {
2778
+ const output = [];
2779
+ let cursor = 0;
2780
+ while (cursor < value.length) {
2781
+ const next = findNextInlineToken(value, cursor, tokens);
2782
+ if (!next) {
2783
+ output.push({
2784
+ type: "text",
2785
+ value: value.slice(cursor)
2786
+ });
2787
+ break;
2788
+ }
2789
+ if (next.index > cursor) output.push({
2790
+ type: "text",
2791
+ value: value.slice(cursor, next.index)
2792
+ });
2793
+ output.push(createInlineTokenNode(next.token));
2794
+ cursor = next.index + next.token.rawText.length;
2795
+ }
2796
+ return output.length > 0 ? output : [{
2797
+ type: "text",
2798
+ value
2799
+ }];
2800
+ }
2801
+ function transformInlineTokenTextNodes(node, tokens) {
2802
+ if (node.type === "code" || node.type === "inlineCode" || !node.children) return;
2803
+ const nextChildren = [];
2804
+ for (const child of node.children) {
2805
+ if (child.type === "text" && typeof child.value === "string") {
2806
+ nextChildren.push(...splitTextNodeByInlineTokens(child.value, tokens));
2807
+ continue;
2808
+ }
2809
+ transformInlineTokenTextNodes(child, tokens);
2810
+ nextChildren.push(child);
2811
+ }
2812
+ node.children = nextChildren;
2813
+ }
2814
+ function createRemarkInlineTokenPlugin(inlineTokens) {
2815
+ const tokens = prepareInlineTokens(inlineTokens);
2816
+ return () => (tree) => {
2817
+ if (tokens.length === 0) return;
2818
+ transformInlineTokenTextNodes(tree, tokens);
2819
+ };
2820
+ }
2821
+ function readStringProp(props, key) {
2822
+ const value = props[key];
2823
+ return typeof value === "string" && value.length > 0 ? value : null;
2824
+ }
2825
+ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen }) {
2557
2826
  const isUser = role === "user";
2827
+ const remarkPlugins = useMemo(() => inlineTokens && inlineTokens.length > 0 ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm], [inlineTokens]);
2558
2828
  const markdownComponents = useMemo(() => ({
2559
2829
  p: ({ children }) => inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", { children }),
2830
+ span: ({ node: _node, children, ...rest }) => {
2831
+ const restProps = rest;
2832
+ const kind = readStringProp(restProps, INLINE_TOKEN_KIND_ATTR);
2833
+ const key = readStringProp(restProps, INLINE_TOKEN_KEY_ATTR);
2834
+ const label = readStringProp(restProps, INLINE_TOKEN_LABEL_ATTR);
2835
+ const rawText = readStringProp(restProps, INLINE_TOKEN_RAW_TEXT_ATTR);
2836
+ if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
2837
+ kind,
2838
+ label,
2839
+ isUser
2840
+ });
2841
+ return /* @__PURE__ */ jsx("span", {
2842
+ ...rest,
2843
+ children
2844
+ });
2845
+ },
2560
2846
  a: ({ href, children, ...rest }) => {
2561
2847
  const safeHref = resolveSafeHref(href);
2562
2848
  if (!safeHref) return /* @__PURE__ */ jsx("span", {
@@ -2626,6 +2912,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, onFileOpen })
2626
2912
  }
2627
2913
  }), [
2628
2914
  inline,
2915
+ isUser,
2629
2916
  onFileOpen,
2630
2917
  texts
2631
2918
  ]);
@@ -2633,74 +2920,13 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, onFileOpen })
2633
2920
  className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
2634
2921
  children: /* @__PURE__ */ jsx(ReactMarkdown, {
2635
2922
  skipHtml: true,
2636
- remarkPlugins: [remarkGfm],
2923
+ remarkPlugins,
2637
2924
  components: markdownComponents,
2638
2925
  children: trimMarkdown(text)
2639
2926
  })
2640
2927
  });
2641
2928
  }
2642
2929
  //#endregion
2643
- //#region src/components/chat/ui/chat-message-list/chat-message-inline-content.tsx
2644
- function hasVisibleInlineText(value) {
2645
- return value.split("​").join("").split("‌").join("").split("‍").join("").split("⁠").join("").split("").join("").length > 0;
2646
- }
2647
- function ChatInlineSkillIcon() {
2648
- return /* @__PURE__ */ jsxs("svg", {
2649
- viewBox: "0 0 16 16",
2650
- fill: "none",
2651
- stroke: "currentColor",
2652
- strokeWidth: "1.25",
2653
- strokeLinecap: "round",
2654
- strokeLinejoin: "round",
2655
- "aria-hidden": "true",
2656
- className: "h-3 w-3",
2657
- children: [
2658
- /* @__PURE__ */ jsx("path", { d: "M8.5 2.75 2.75 6l5.75 3.25L14.25 6 8.5 2.75Z" }),
2659
- /* @__PURE__ */ jsx("path", { d: "M2.75 10 8.5 13.25 14.25 10" }),
2660
- /* @__PURE__ */ jsx("path", { d: "M2.75 6v4l5.75 3.25V9.25L2.75 6Z" }),
2661
- /* @__PURE__ */ jsx("path", { d: "M14.25 6v4L8.5 13.25V9.25L14.25 6Z" })
2662
- ]
2663
- });
2664
- }
2665
- function ChatInlineTokenBadge({ kind, label, isUser }) {
2666
- const isSkill = kind === "skill";
2667
- return /* @__PURE__ */ jsxs("span", {
2668
- className: cn("mx-[2px] inline-flex h-7 max-w-full items-center gap-1.5 rounded-xl border px-2.5 align-baseline text-[11px] font-medium shadow-[0_0_0_1px_rgba(15,23,42,0.03)]", isSkill ? isUser ? "border-emerald-200/35 bg-emerald-400/18 text-emerald-50/95" : "border-emerald-200/70 bg-emerald-50 text-emerald-700" : isUser ? "border-white/30 bg-white/18 text-white" : "border-slate-200/80 bg-slate-100 text-slate-700"),
2669
- title: label,
2670
- children: [/* @__PURE__ */ jsx("span", {
2671
- className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", isSkill ? isUser ? "text-emerald-100/90" : "text-emerald-600" : isUser ? "text-white/70" : "text-slate-500"),
2672
- children: /* @__PURE__ */ jsx(ChatInlineSkillIcon, {})
2673
- }), /* @__PURE__ */ jsx("span", {
2674
- className: "truncate",
2675
- children: label
2676
- })]
2677
- });
2678
- }
2679
- function ChatMessageInlineContent({ segments, role, texts, onFileOpen }) {
2680
- const isUser = role === "user";
2681
- return /* @__PURE__ */ jsx("div", {
2682
- className: "whitespace-pre-wrap break-words leading-6",
2683
- children: segments.map((segment, index) => {
2684
- if (segment.type === "token") return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
2685
- kind: segment.token.kind,
2686
- label: segment.token.label,
2687
- isUser
2688
- }, `token-${index}-${segment.token.kind}-${segment.token.key}`);
2689
- if (!hasVisibleInlineText(segment.text)) return /* @__PURE__ */ jsx("span", {
2690
- className: "whitespace-pre-wrap",
2691
- children: segment.text
2692
- }, `space-${index}`);
2693
- return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
2694
- text: segment.text,
2695
- role,
2696
- texts,
2697
- inline: true,
2698
- onFileOpen
2699
- }, `markdown-${index}`);
2700
- })
2701
- });
2702
- }
2703
- //#endregion
2704
2930
  //#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
2705
2931
  const FILE_CATEGORY_TILE_CLASSES = {
2706
2932
  archive: "border-amber-200/80 bg-amber-50 text-amber-700",
@@ -3977,23 +4203,15 @@ const ChatMessage = memo(function ChatMessage({ message, texts, onToolAction, on
3977
4203
  children: message.parts.map((part, index) => {
3978
4204
  const { type } = part;
3979
4205
  if (type === "markdown") {
3980
- const { text } = part;
4206
+ const { inlineTokens, text } = part;
3981
4207
  return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
3982
4208
  text,
3983
4209
  role,
3984
4210
  texts,
4211
+ inlineTokens,
3985
4212
  onFileOpen
3986
4213
  }, `markdown-${index}`);
3987
4214
  }
3988
- if (type === "inline-content") {
3989
- const { segments } = part;
3990
- return /* @__PURE__ */ jsx(ChatMessageInlineContent, {
3991
- segments,
3992
- role,
3993
- texts,
3994
- onFileOpen
3995
- }, `inline-content-${index}`);
3996
- }
3997
4215
  if (type === "reasoning") {
3998
4216
  const { label, text } = part;
3999
4217
  return /* @__PURE__ */ jsx(ChatReasoningBlock, {
@@ -4188,6 +4406,54 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
4188
4406
  });
4189
4407
  }
4190
4408
  //#endregion
4191
- export { ChatInputBar, ChatMessageList, ChatMessageMarkdown, FileOperationCodeSurface, FileOperationLinesGrid, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
4409
+ //#region src/lib/input-surface/input-surface-plugin.utils.ts
4410
+ function createInputSurfaceTriggeredPanelPlugin(params) {
4411
+ return {
4412
+ key: params.key,
4413
+ triggerSpecs: [params.trigger],
4414
+ resolvePanel: (context) => context.trigger.key === params.trigger.key ? params.resolvePanel(context) : null
4415
+ };
4416
+ }
4417
+ function createInputSurfaceReferenceTokenPlugin(params) {
4418
+ return createInputSurfaceTriggeredPanelPlugin({
4419
+ key: params.key,
4420
+ trigger: params.trigger,
4421
+ resolvePanel: (context) => ({
4422
+ isLoading: params.getIsLoading?.(context) ?? false,
4423
+ items: params.getItems({
4424
+ context,
4425
+ records: params.getRecords(context),
4426
+ query: context.trigger.query,
4427
+ tokenKind: params.tokenKind
4428
+ }),
4429
+ onSelectItem: params.onSelectItem,
4430
+ texts: params.texts
4431
+ })
4432
+ });
4433
+ }
4434
+ function resolveChatInputSurfaceState(params) {
4435
+ const { data, plugins, trigger } = params;
4436
+ const triggerSpecs = plugins.flatMap((plugin) => [...plugin.triggerSpecs]);
4437
+ if (!trigger) return {
4438
+ triggerSpecs,
4439
+ panel: null
4440
+ };
4441
+ for (const plugin of plugins) {
4442
+ const panel = plugin.resolvePanel({
4443
+ data,
4444
+ trigger
4445
+ });
4446
+ if (panel) return {
4447
+ triggerSpecs,
4448
+ panel
4449
+ };
4450
+ }
4451
+ return {
4452
+ triggerSpecs,
4453
+ panel: null
4454
+ };
4455
+ }
4456
+ //#endregion
4457
+ export { CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC, ChatInputBar, ChatMessageList, ChatMessageMarkdown, FileOperationCodeSurface, FileOperationLinesGrid, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, createInputSurfaceReferenceTokenPlugin, createInputSurfaceTriggeredPanelPlugin, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerActiveInputSurfaceTrigger, resolveChatComposerInputSurfaceTrigger, resolveChatComposerSlashTrigger, resolveChatInputSurfaceState, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
4192
4458
 
4193
4459
  //# sourceMappingURL=index.js.map