@nextclaw/agent-chat-ui 0.5.0 → 0.5.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js 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, Terminal, User, Wrench } from "lucide-react";
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";
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";
@@ -89,14 +89,24 @@ const ChatInput = React.forwardRef(({ className, type, ...props }, ref) => /* @_
89
89
  ChatInput.displayName = "ChatInput";
90
90
  //#endregion
91
91
  //#region src/components/chat/default-skin/popover.tsx
92
+ const CHAT_FLOATING_CONTENT_AVAILABLE_HEIGHT_GAP = "2rem";
93
+ function createChatPopoverAvailableHeightLimit(limit) {
94
+ return `min(${limit}, max(0px, calc(var(--radix-popover-content-available-height, 100vh) - ${CHAT_FLOATING_CONTENT_AVAILABLE_HEIGHT_GAP})))`;
95
+ }
96
+ const CHAT_POPOVER_CONTENT_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
92
97
  const ChatPopover = PopoverPrimitive.Root;
93
98
  const ChatPopoverTrigger = PopoverPrimitive.Trigger;
94
99
  const ChatPopoverAnchor = PopoverPrimitive.Anchor;
95
- const ChatPopoverContent = React.forwardRef(({ className, sideOffset = 8, align = "start", ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(PopoverPrimitive.Content, {
100
+ const ChatPopoverContent = React.forwardRef(({ className, sideOffset = 8, align = "start", collisionPadding = 12, style, ...props }, ref) => /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(PopoverPrimitive.Content, {
96
101
  ref,
97
102
  sideOffset,
98
103
  align,
99
- className: cn("z-[var(--z-popover,50)] w-72 overflow-hidden rounded-2xl border border-gray-200/50 bg-white p-4 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className),
104
+ collisionPadding,
105
+ className: cn("z-[var(--z-popover,50)] w-72 overflow-x-hidden overflow-y-auto rounded-2xl border border-gray-200/50 bg-white p-4 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className),
106
+ style: {
107
+ maxHeight: CHAT_POPOVER_CONTENT_MAX_HEIGHT,
108
+ ...style
109
+ },
100
110
  ...props
101
111
  }) }));
102
112
  ChatPopoverContent.displayName = PopoverPrimitive.Content.displayName;
@@ -105,6 +115,11 @@ ChatPopoverContent.displayName = PopoverPrimitive.Content.displayName;
105
115
  const ChatSelect = SelectPrimitive.Root;
106
116
  const ChatSelectGroup = SelectPrimitive.Group;
107
117
  const ChatSelectValue = SelectPrimitive.Value;
118
+ const CHAT_SELECT_CONTENT_AVAILABLE_HEIGHT_GAP = "2rem";
119
+ function createChatSelectAvailableHeightLimit(limit) {
120
+ return `min(${limit}, max(0px, calc(var(--radix-select-content-available-height, 100vh) - ${CHAT_SELECT_CONTENT_AVAILABLE_HEIGHT_GAP})))`;
121
+ }
122
+ const CHAT_SELECT_CONTENT_MAX_HEIGHT = createChatSelectAvailableHeightLimit("24rem");
108
123
  const ChatSelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(SelectPrimitive.Trigger, {
109
124
  ref,
110
125
  className: cn("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-xl border border-gray-200/80 bg-white px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary/40 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className),
@@ -129,15 +144,20 @@ const ChatSelectScrollDownButton = React.forwardRef(({ className, ...props }, re
129
144
  children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
130
145
  }));
131
146
  ChatSelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
132
- const ChatSelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(SelectPrimitive.Content, {
147
+ const ChatSelectContent = React.forwardRef(({ className, children, collisionPadding = 12, position = "popper", style, viewportClassName, ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(SelectPrimitive.Content, {
133
148
  ref,
134
- className: cn("relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md bg-white text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
149
+ className: cn("relative z-50 flex max-h-96 min-w-[8rem] flex-col overflow-hidden rounded-md bg-white text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
150
+ collisionPadding,
135
151
  position,
152
+ style: {
153
+ maxHeight: CHAT_SELECT_CONTENT_MAX_HEIGHT,
154
+ ...style
155
+ },
136
156
  ...props,
137
157
  children: [
138
158
  /* @__PURE__ */ jsx(ChatSelectScrollUpButton, {}),
139
159
  /* @__PURE__ */ jsx(SelectPrimitive.Viewport, {
140
- className: cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
160
+ className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain p-1", position === "popper" && "w-full min-w-[var(--radix-select-trigger-width)]", viewportClassName),
141
161
  children
142
162
  }),
143
163
  /* @__PURE__ */ jsx(ChatSelectScrollDownButton, {})
@@ -204,6 +224,8 @@ const ChatUiPrimitives = {
204
224
  const SLASH_PANEL_MAX_WIDTH = 680;
205
225
  const SLASH_PANEL_DESKTOP_SHRINK_RATIO = .82;
206
226
  const SLASH_PANEL_DESKTOP_MIN_WIDTH = 560;
227
+ const SLASH_PANEL_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
228
+ const SLASH_PANEL_MIN_HEIGHT = createChatPopoverAvailableHeightLimit("240px");
207
229
  function ChatSlashMenu(props) {
208
230
  const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
209
231
  const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
@@ -233,16 +255,20 @@ function ChatSlashMenu(props) {
233
255
  side: "top",
234
256
  align: "start",
235
257
  sideOffset: 10,
236
- 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",
258
+ 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",
237
259
  onOpenAutoFocus: (event) => event.preventDefault(),
238
- style: resolvedWidth ? { width: `${resolvedWidth}px` } : void 0,
260
+ style: {
261
+ maxHeight: SLASH_PANEL_MAX_HEIGHT,
262
+ width: resolvedWidth ? `${resolvedWidth}px` : void 0
263
+ },
239
264
  children: /* @__PURE__ */ jsxs("div", {
240
- className: "grid min-h-[240px] grid-cols-[minmax(220px,300px)_minmax(0,1fr)]",
265
+ className: "grid min-h-0 flex-1 grid-cols-[minmax(220px,300px)_minmax(0,1fr)]",
266
+ style: { minHeight: SLASH_PANEL_MIN_HEIGHT },
241
267
  children: [/* @__PURE__ */ jsx("div", {
242
268
  ref: listRef,
243
269
  role: "listbox",
244
270
  "aria-label": texts.slashSectionLabel,
245
- className: "custom-scrollbar max-h-[320px] overflow-y-auto border-r border-gray-200 p-2.5",
271
+ className: "custom-scrollbar min-h-0 overflow-y-auto overscroll-contain border-r border-gray-200 p-2.5",
246
272
  children: isLoading ? /* @__PURE__ */ jsx("div", {
247
273
  className: "p-2 text-xs text-gray-500",
248
274
  children: texts.slashLoadingLabel
@@ -275,7 +301,7 @@ function ChatSlashMenu(props) {
275
301
  })
276
302
  })] })
277
303
  }), /* @__PURE__ */ jsx("div", {
278
- className: "min-w-0 p-2.5",
304
+ className: "custom-scrollbar min-h-0 min-w-0 overflow-y-auto overscroll-contain p-2.5",
279
305
  onPointerDown: onDetailsPointerDown,
280
306
  children: activeItem ? /* @__PURE__ */ jsxs("div", {
281
307
  className: "space-y-3",
@@ -494,6 +520,7 @@ function filterOptions(options, query) {
494
520
  ].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase().includes(keyword);
495
521
  });
496
522
  }
523
+ const SKILL_PICKER_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
497
524
  function ChatInputBarSkillPicker(props) {
498
525
  const { Input, Popover, PopoverContent, PopoverTrigger } = ChatUiPrimitives;
499
526
  const { picker } = props;
@@ -580,10 +607,11 @@ function ChatInputBarSkillPicker(props) {
580
607
  }), /* @__PURE__ */ jsxs(PopoverContent, {
581
608
  side: "top",
582
609
  align: "start",
583
- className: "w-[360px] p-0",
610
+ className: "flex w-[min(360px,calc(100vw-1rem))] flex-col overflow-hidden p-0",
611
+ style: { maxHeight: SKILL_PICKER_MAX_HEIGHT },
584
612
  children: [
585
613
  /* @__PURE__ */ jsxs("div", {
586
- className: "space-y-2 border-b border-gray-100 px-4 py-3",
614
+ className: "shrink-0 space-y-2 border-b border-gray-100 px-4 py-3",
587
615
  children: [/* @__PURE__ */ jsx("div", {
588
616
  className: "text-sm font-semibold text-gray-900",
589
617
  children: picker.title
@@ -608,7 +636,7 @@ function ChatInputBarSkillPicker(props) {
608
636
  id: listId,
609
637
  role: "listbox",
610
638
  "aria-multiselectable": "true",
611
- className: "custom-scrollbar max-h-[320px] overflow-y-auto",
639
+ className: "custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain",
612
640
  children: picker.isLoading ? /* @__PURE__ */ jsx("div", {
613
641
  className: "p-4 text-xs text-gray-500",
614
642
  children: picker.loadingLabel
@@ -676,7 +704,7 @@ function ChatInputBarSkillPicker(props) {
676
704
  })
677
705
  }),
678
706
  picker.manageHref && picker.manageLabel ? /* @__PURE__ */ jsx("div", {
679
- className: "border-t border-gray-100 px-4 py-2.5",
707
+ className: "shrink-0 border-t border-gray-100 px-4 py-2.5",
680
708
  children: /* @__PURE__ */ jsxs("a", {
681
709
  href: picker.manageHref,
682
710
  className: "inline-flex items-center gap-1.5 text-xs font-medium text-primary transition-colors hover:text-primary/80",
@@ -704,17 +732,173 @@ const CONTENT_WIDTH_BY_KEY = {
704
732
  "session-type": "w-[220px]",
705
733
  thinking: "w-[180px]"
706
734
  };
735
+ const TOOLBAR_POPOVER_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("18rem");
736
+ const TOOLBAR_SELECT_MAX_HEIGHT = createChatSelectAvailableHeightLimit("18rem");
707
737
  function resolveMobileSelectedLabel(item) {
708
738
  return item.key === "model" && item.selectedLabel ? item.selectedLabel.split("/").slice(1).join("/").trim() || item.selectedLabel : item.selectedLabel;
709
739
  }
710
- function ToolbarSelect({ item }) {
711
- const { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } = ChatUiPrimitives;
712
- const groups = item.groups?.filter((group) => group.options.length > 0) ?? (item.options.length > 0 ? [{
740
+ function buildSelectGroups(item) {
741
+ return item.groups?.filter((group) => group.options.length > 0) ?? (item.options.length > 0 ? [{
713
742
  key: `${item.key}-default`,
714
743
  options: item.options
715
744
  }] : []);
716
- const hasOptions = groups.some((group) => group.options.length > 0);
745
+ }
746
+ function ToolbarSelectTriggerContent({ item }) {
717
747
  const mobileSelectedLabel = resolveMobileSelectedLabel(item);
748
+ if (item.selectedLabel) return /* @__PURE__ */ jsxs("div", {
749
+ className: "flex min-w-0 items-center gap-2 text-left",
750
+ children: [
751
+ /* @__PURE__ */ jsx(ToolbarIcon, { icon: item.icon }),
752
+ /* @__PURE__ */ jsx("span", {
753
+ className: "nextclaw-chat-toolbar-mobile-label truncate text-xs font-semibold text-gray-700 sm:hidden [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
754
+ children: mobileSelectedLabel
755
+ }),
756
+ /* @__PURE__ */ jsx("span", {
757
+ className: "nextclaw-chat-toolbar-label hidden truncate text-xs font-semibold text-gray-700 sm:inline [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
758
+ children: item.selectedLabel
759
+ })
760
+ ]
761
+ });
762
+ if (item.loading) return /* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" });
763
+ return /* @__PURE__ */ jsx("span", {
764
+ className: "truncate",
765
+ children: item.placeholder
766
+ });
767
+ }
768
+ function ToolbarSelectOptionContent({ option }) {
769
+ return option.description ? /* @__PURE__ */ jsxs("div", {
770
+ className: "flex min-w-0 flex-col gap-0.5",
771
+ children: [/* @__PURE__ */ jsx("span", {
772
+ className: "truncate text-xs font-semibold text-gray-800",
773
+ children: option.label
774
+ }), /* @__PURE__ */ jsx("span", {
775
+ className: "truncate text-[11px] text-gray-500",
776
+ children: option.description
777
+ })]
778
+ }) : /* @__PURE__ */ jsx("span", {
779
+ className: "truncate text-xs font-semibold text-gray-800",
780
+ children: option.label
781
+ });
782
+ }
783
+ function ToolbarSearchableSelect({ item }) {
784
+ const { Input, Popover, PopoverContent, PopoverTrigger, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
785
+ const [open, setOpen] = useState(false);
786
+ const [query, setQuery] = useState("");
787
+ const groups = buildSelectGroups(item);
788
+ const filteredGroups = useMemo(() => {
789
+ const normalizedQuery = query.trim().toLowerCase();
790
+ if (!normalizedQuery) return groups;
791
+ return groups.map((group) => ({
792
+ ...group,
793
+ options: group.options.filter((option) => [
794
+ option.label,
795
+ option.value,
796
+ option.description
797
+ ].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes(normalizedQuery)))
798
+ })).filter((group) => group.options.length > 0);
799
+ }, [groups, query]);
800
+ const hasOptions = groups.some((group) => group.options.length > 0);
801
+ const hasFilteredOptions = filteredGroups.some((group) => group.options.length > 0);
802
+ const action = item.optionAction;
803
+ const activeValues = new Set(action?.activeValues ?? []);
804
+ return /* @__PURE__ */ jsxs(Popover, {
805
+ open,
806
+ onOpenChange: setOpen,
807
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
808
+ asChild: true,
809
+ children: /* @__PURE__ */ jsxs("button", {
810
+ type: "button",
811
+ "aria-label": item.selectedLabel ? `${item.placeholder}: ${item.selectedLabel}` : item.placeholder,
812
+ disabled: item.disabled,
813
+ className: `nextclaw-chat-toolbar-select-trigger inline-flex h-8 w-auto items-center justify-between rounded-lg border-0 bg-transparent px-2 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50 sm:px-3 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!basis-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!justify-center [@container_nextclaw-chat-input-bar_(max-width:440px)]:!max-w-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!min-w-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!px-0 ${TRIGGER_WIDTH_BY_KEY[item.key] ?? ""}`,
814
+ children: [/* @__PURE__ */ jsx(ToolbarSelectTriggerContent, { item }), /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 h-3.5 w-3.5 shrink-0 text-gray-400 [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden" })]
815
+ })
816
+ }), /* @__PURE__ */ jsxs(PopoverContent, {
817
+ className: `flex flex-col overflow-hidden p-2 ${CONTENT_WIDTH_BY_KEY[item.key] ?? ""}`,
818
+ style: { maxHeight: TOOLBAR_POPOVER_MAX_HEIGHT },
819
+ children: [
820
+ /* @__PURE__ */ jsxs("div", {
821
+ className: "relative mb-2 shrink-0",
822
+ children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-gray-400" }), /* @__PURE__ */ jsx(Input, {
823
+ value: query,
824
+ onChange: (event) => setQuery(event.currentTarget.value),
825
+ placeholder: item.search?.placeholder ?? item.placeholder,
826
+ className: "h-8 rounded-lg pl-8 text-xs"
827
+ })]
828
+ }),
829
+ !hasOptions ? item.loading ? /* @__PURE__ */ jsxs("div", {
830
+ className: "space-y-2 px-2 py-1",
831
+ children: [
832
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-36 animate-pulse rounded bg-gray-200" }),
833
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-28 animate-pulse rounded bg-gray-200" }),
834
+ /* @__PURE__ */ jsx("div", { className: "h-3 w-32 animate-pulse rounded bg-gray-200" })
835
+ ]
836
+ }) : item.emptyLabel ? /* @__PURE__ */ jsx("div", {
837
+ className: "px-2 py-1 text-xs text-gray-500",
838
+ children: item.emptyLabel
839
+ }) : null : null,
840
+ hasOptions && !hasFilteredOptions ? /* @__PURE__ */ jsx("div", {
841
+ className: "px-2 py-1 text-xs text-gray-500",
842
+ children: item.search?.emptyLabel ?? item.emptyLabel
843
+ }) : null,
844
+ /* @__PURE__ */ jsx("div", {
845
+ className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
846
+ children: filteredGroups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", {
847
+ className: groupIndex > 0 ? "border-t border-gray-100 pt-1" : void 0,
848
+ children: [group.label ? /* @__PURE__ */ jsx("div", {
849
+ className: "px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-gray-500",
850
+ children: group.label
851
+ }) : null, group.options.map((option) => {
852
+ const isSelected = item.value === option.value;
853
+ const isActive = activeValues.has(option.value);
854
+ const actionLabel = isActive ? action?.activeLabel : action?.inactiveLabel;
855
+ return /* @__PURE__ */ jsxs("div", {
856
+ className: "group flex items-center gap-1 rounded-md hover:bg-gray-100",
857
+ children: [/* @__PURE__ */ jsxs("button", {
858
+ type: "button",
859
+ className: "flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-2 text-left",
860
+ onClick: () => {
861
+ item.onValueChange(option.value);
862
+ setOpen(false);
863
+ setQuery("");
864
+ },
865
+ children: [/* @__PURE__ */ jsx("div", {
866
+ className: "min-w-0 flex-1",
867
+ children: /* @__PURE__ */ jsx(ToolbarSelectOptionContent, { option })
868
+ }), isSelected ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 shrink-0 text-primary" }) : null]
869
+ }), action && actionLabel ? /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
870
+ asChild: true,
871
+ children: /* @__PURE__ */ jsx("button", {
872
+ type: "button",
873
+ "aria-label": actionLabel,
874
+ "aria-pressed": isActive,
875
+ className: "mr-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-white hover:text-amber-500 focus:outline-none focus:ring-1 focus:ring-primary/40",
876
+ onClick: (event) => {
877
+ event.stopPropagation();
878
+ action.onToggle(option.value, !isActive);
879
+ },
880
+ children: /* @__PURE__ */ jsx(Star, { className: `h-3.5 w-3.5 ${isActive ? "fill-amber-400 text-amber-500" : ""}` })
881
+ })
882
+ }), /* @__PURE__ */ jsx(TooltipContent, {
883
+ side: "left",
884
+ children: /* @__PURE__ */ jsx("p", {
885
+ className: "text-xs",
886
+ children: actionLabel
887
+ })
888
+ })] }) }) : null]
889
+ }, option.value);
890
+ })]
891
+ }, group.key))
892
+ })
893
+ ]
894
+ })]
895
+ });
896
+ }
897
+ function ToolbarSelect({ item }) {
898
+ if (item.search) return /* @__PURE__ */ jsx(ToolbarSearchableSelect, { item });
899
+ const { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue } = ChatUiPrimitives;
900
+ const groups = buildSelectGroups(item);
901
+ const hasOptions = groups.some((group) => group.options.length > 0);
718
902
  return /* @__PURE__ */ jsxs(Select, {
719
903
  value: item.value,
720
904
  onValueChange: item.onValueChange,
@@ -723,22 +907,10 @@ function ToolbarSelect({ item }) {
723
907
  "aria-label": item.selectedLabel ? `${item.placeholder}: ${item.selectedLabel}` : item.placeholder,
724
908
  title: item.selectedLabel,
725
909
  className: `nextclaw-chat-toolbar-select-trigger h-8 w-auto rounded-lg border-0 bg-transparent px-2 text-xs font-medium text-gray-600 shadow-none hover:bg-gray-100 focus:ring-0 sm:px-3 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!basis-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!justify-center [@container_nextclaw-chat-input-bar_(max-width:440px)]:!max-w-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!min-w-8 [@container_nextclaw-chat-input-bar_(max-width:440px)]:!px-0 ${TRIGGER_WIDTH_BY_KEY[item.key] ?? ""}`,
726
- children: item.selectedLabel ? /* @__PURE__ */ jsxs("div", {
727
- className: "flex min-w-0 items-center gap-2 text-left",
728
- children: [
729
- /* @__PURE__ */ jsx(ToolbarIcon, { icon: item.icon }),
730
- /* @__PURE__ */ jsx("span", {
731
- className: "nextclaw-chat-toolbar-mobile-label truncate text-xs font-semibold text-gray-700 sm:hidden [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
732
- children: mobileSelectedLabel
733
- }),
734
- /* @__PURE__ */ jsx("span", {
735
- className: "nextclaw-chat-toolbar-label hidden truncate text-xs font-semibold text-gray-700 sm:inline [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
736
- children: item.selectedLabel
737
- })
738
- ]
739
- }) : item.loading ? /* @__PURE__ */ jsx("div", { className: "h-3 w-24 animate-pulse rounded bg-gray-200" }) : /* @__PURE__ */ jsx(SelectValue, { placeholder: item.placeholder })
910
+ children: item.selectedLabel || item.loading ? /* @__PURE__ */ jsx(ToolbarSelectTriggerContent, { item }) : /* @__PURE__ */ jsx(SelectValue, { placeholder: item.placeholder })
740
911
  }), /* @__PURE__ */ jsxs(SelectContent, {
741
912
  className: CONTENT_WIDTH_BY_KEY[item.key] ?? "",
913
+ style: { maxHeight: TOOLBAR_SELECT_MAX_HEIGHT },
742
914
  children: [!hasOptions ? item.loading ? /* @__PURE__ */ jsxs("div", {
743
915
  className: "space-y-2 px-3 py-2",
744
916
  children: [
@@ -752,19 +924,7 @@ function ToolbarSelect({ item }) {
752
924
  }) : null : null, groups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { children: [groupIndex > 0 ? /* @__PURE__ */ jsx(SelectSeparator, {}) : null, /* @__PURE__ */ jsxs(SelectGroup, { children: [group.label ? /* @__PURE__ */ jsx(SelectLabel, { children: group.label }) : null, group.options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
753
925
  value: option.value,
754
926
  className: "py-2",
755
- children: option.description ? /* @__PURE__ */ jsxs("div", {
756
- className: "flex min-w-0 flex-col gap-0.5",
757
- children: [/* @__PURE__ */ jsx("span", {
758
- className: "truncate text-xs font-semibold text-gray-800",
759
- children: option.label
760
- }), /* @__PURE__ */ jsx("span", {
761
- className: "truncate text-[11px] text-gray-500",
762
- children: option.description
763
- })]
764
- }) : /* @__PURE__ */ jsx("span", {
765
- className: "truncate text-xs font-semibold text-gray-800",
766
- children: option.label
767
- })
927
+ children: /* @__PURE__ */ jsx(ToolbarSelectOptionContent, { option })
768
928
  }, option.value))] })] }, group.key))]
769
929
  })]
770
930
  });
@@ -991,10 +1151,10 @@ function buildTokenClassName(tokenKind) {
991
1151
  "transition"
992
1152
  ].join(" ");
993
1153
  }
994
- function ChatComposerTokenChip(props) {
1154
+ function ChatComposerTokenChip({ label, tokenKind }) {
995
1155
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
996
- className: props.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",
997
- children: props.tokenKind === "file" ? /* @__PURE__ */ jsxs("svg", {
1156
+ 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", {
998
1158
  viewBox: "0 0 16 16",
999
1159
  fill: "none",
1000
1160
  stroke: "currentColor",
@@ -1025,8 +1185,8 @@ function ChatComposerTokenChip(props) {
1025
1185
  ]
1026
1186
  })
1027
1187
  }), /* @__PURE__ */ jsx("span", {
1028
- className: props.tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate",
1029
- children: props.label
1188
+ className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate",
1189
+ children: label
1030
1190
  })] });
1031
1191
  }
1032
1192
  var ChatComposerTokenNode = class ChatComposerTokenNode extends DecoratorNode {
@@ -1514,8 +1674,8 @@ var LexicalComposerHandleOwner = class {
1514
1674
  this.focusComposer = () => {
1515
1675
  this.params.focusComposer();
1516
1676
  };
1517
- this.focusComposerAtEnd = () => {
1518
- this.params.focusComposerAtEnd();
1677
+ this.focusComposerAtEnd = (nodes) => {
1678
+ this.params.focusComposerAtEnd(nodes);
1519
1679
  };
1520
1680
  this.syncSelectedSkills = (nextKeys, options) => {
1521
1681
  this.params.publishSnapshot(syncSelectedSkillsIntoChatComposer({
@@ -1703,7 +1863,10 @@ function ChatComposerBindingsPlugin({ disabled, editorRef, editorSignatureRef, i
1703
1863
  }
1704
1864
  //#endregion
1705
1865
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-input-bar-tokenized-composer.tsx
1706
- const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer(props, ref) {
1866
+ function getChatComposerDocumentLength(nodes) {
1867
+ return nodes.reduce((cursor, node) => cursor + (node.type === "text" ? node.text.length : 1), 0);
1868
+ }
1869
+ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer({ actions, activeSlashIndex, disabled, nodes, onFilesAdd, onNodesChange, onSlashActiveIndexChange, onSlashItemSelect, onSlashOpenChange, onSlashQueryChange, onSlashTriggerChange, placeholder, slashItems }, ref) {
1707
1870
  const editorRef = useRef(null);
1708
1871
  const selectionRef = useRef(null);
1709
1872
  const pendingSelectionRef = useRef(null);
@@ -1712,23 +1875,27 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1712
1875
  const isApplyingExternalUpdateRef = useRef(false);
1713
1876
  const editorSignatureRef = useRef("");
1714
1877
  const lastPublishedSignatureRef = useRef("");
1715
- const syncSlashState = (nodes, selection) => {
1878
+ const syncSlashState = useCallback((nodes, selection) => {
1716
1879
  const trigger = resolveChatComposerSlashTrigger(nodes, selection);
1717
- props.onSlashTriggerChange?.(trigger);
1718
- props.onSlashQueryChange?.(trigger?.query ?? null);
1719
- props.onSlashOpenChange(trigger !== null);
1720
- };
1721
- const readCurrentNodes = () => {
1722
- return props.nodes;
1723
- };
1724
- const readCurrentSelection = () => {
1880
+ onSlashTriggerChange?.(trigger);
1881
+ onSlashQueryChange?.(trigger?.query ?? null);
1882
+ onSlashOpenChange(trigger !== null);
1883
+ }, [
1884
+ onSlashOpenChange,
1885
+ onSlashQueryChange,
1886
+ onSlashTriggerChange
1887
+ ]);
1888
+ const readCurrentNodes = useCallback(() => {
1889
+ return nodes;
1890
+ }, [nodes]);
1891
+ const readCurrentSelection = useCallback(() => {
1725
1892
  if (selectionRef.current) return selectionRef.current;
1726
1893
  if (!editorRef.current) return null;
1727
1894
  const snapshot = readChatComposerSnapshotFromEditorState(editorRef.current.getEditorState());
1728
1895
  selectionRef.current = snapshot.selection;
1729
1896
  return snapshot.selection;
1730
- };
1731
- const publishSnapshot = (snapshot, options) => {
1897
+ }, []);
1898
+ const publishSnapshot = useCallback((snapshot, options) => {
1732
1899
  selectionRef.current = snapshot.selection;
1733
1900
  pendingSelectionRef.current = snapshot.selection;
1734
1901
  if (options?.focusAfterSync) shouldFocusAfterSyncRef.current = true;
@@ -1736,46 +1903,64 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1736
1903
  syncSlashState(snapshot.nodes, snapshot.selection);
1737
1904
  if (options?.forcePublish || signature !== lastPublishedSignatureRef.current) {
1738
1905
  lastPublishedSignatureRef.current = signature;
1739
- props.onNodesChange(snapshot.nodes);
1906
+ onNodesChange(snapshot.nodes);
1740
1907
  }
1741
- };
1742
- const focusComposer = () => {
1908
+ }, [onNodesChange, syncSlashState]);
1909
+ const focusComposer = useCallback(() => {
1743
1910
  const editor = editorRef.current;
1744
1911
  if (!editor) return;
1745
1912
  editor.getRootElement()?.focus({ preventScroll: true });
1746
1913
  const targetSelection = selectionRef.current;
1747
1914
  if (targetSelection) syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
1748
1915
  editor.focus();
1749
- };
1750
- const focusComposerAtEnd = () => {
1916
+ }, []);
1917
+ const focusComposerAtEnd = useCallback((nodes) => {
1751
1918
  const editor = editorRef.current;
1752
1919
  if (!editor) return;
1753
- const end = readChatComposerSnapshotFromEditorState(editor.getEditorState()).nodes.reduce((cursor, node) => cursor + (node.type === "text" ? node.text.length : 1), 0);
1920
+ const end = getChatComposerDocumentLength(nodes ?? readChatComposerSnapshotFromEditorState(editor.getEditorState()).nodes);
1754
1921
  const targetSelection = {
1755
1922
  start: end,
1756
1923
  end
1757
1924
  };
1758
1925
  selectionRef.current = targetSelection;
1926
+ pendingSelectionRef.current = targetSelection;
1759
1927
  editor.getRootElement()?.focus({ preventScroll: true });
1760
- syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
1761
- editor.focus();
1762
- };
1763
- const readComposerSnapshot = () => ({
1928
+ if (nodes) {
1929
+ isApplyingExternalUpdateRef.current = true;
1930
+ const signature = getChatComposerNodesSignature(nodes);
1931
+ syncLexicalEditorFromChatComposerState(editor, nodes, targetSelection);
1932
+ editorSignatureRef.current = signature;
1933
+ lastPublishedSignatureRef.current = signature;
1934
+ requestAnimationFrame(() => {
1935
+ isApplyingExternalUpdateRef.current = false;
1936
+ });
1937
+ } else syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
1938
+ editor.focus(() => {
1939
+ syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
1940
+ });
1941
+ }, []);
1942
+ const readComposerSnapshot = useCallback(() => ({
1764
1943
  nodes: readCurrentNodes(),
1765
1944
  selection: readCurrentSelection()
1766
- });
1945
+ }), [readCurrentNodes, readCurrentSelection]);
1767
1946
  useImperativeHandle(ref, () => createLexicalComposerHandle({
1768
1947
  focusComposer,
1769
1948
  focusComposerAtEnd,
1770
- onSlashItemSelect: props.onSlashItemSelect,
1949
+ onSlashItemSelect,
1771
1950
  optionsReader: readComposerSnapshot,
1772
1951
  publishSnapshot
1773
- }), [props.onSlashItemSelect]);
1952
+ }), [
1953
+ focusComposer,
1954
+ focusComposerAtEnd,
1955
+ onSlashItemSelect,
1956
+ publishSnapshot,
1957
+ readComposerSnapshot
1958
+ ]);
1774
1959
  return /* @__PURE__ */ jsxs(LexicalComposer, {
1775
1960
  initialConfig: useMemo(() => ({
1776
- editable: !props.disabled,
1961
+ editable: !disabled,
1777
1962
  editorState: () => {
1778
- writeChatComposerStateToLexicalRoot(props.nodes, null);
1963
+ writeChatComposerStateToLexicalRoot(nodes, null);
1779
1964
  },
1780
1965
  namespace: "NextClawChatComposerLexical",
1781
1966
  nodes: [ChatComposerTokenNode],
@@ -1783,7 +1968,7 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1783
1968
  throw error;
1784
1969
  },
1785
1970
  theme: {}
1786
- }), [props.disabled, props.nodes]),
1971
+ }), [disabled, nodes]),
1787
1972
  children: [
1788
1973
  /* @__PURE__ */ jsx("div", {
1789
1974
  className: "px-3 py-2 sm:px-4 sm:py-2.5",
@@ -1794,7 +1979,7 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1794
1979
  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",
1795
1980
  onBeforeInput: (event) => {
1796
1981
  handleLexicalComposerBeforeInput({
1797
- disabled: props.disabled,
1982
+ disabled,
1798
1983
  event,
1799
1984
  isComposing: isComposingRef.current,
1800
1985
  publishSnapshot,
@@ -1819,15 +2004,15 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1819
2004
  },
1820
2005
  onPaste: (event) => {
1821
2006
  const files = Array.from(event.clipboardData.files ?? []);
1822
- if (files.length > 0 && props.onFilesAdd) {
2007
+ if (files.length > 0 && onFilesAdd) {
1823
2008
  event.preventDefault();
1824
- props.onFilesAdd(files);
2009
+ onFilesAdd(files);
1825
2010
  }
1826
2011
  }
1827
2012
  }),
1828
2013
  placeholder: /* @__PURE__ */ jsx("div", {
1829
2014
  className: "pointer-events-none absolute left-3 top-2 select-none text-sm leading-6 text-gray-400 sm:left-4 sm:top-2.5",
1830
- children: props.placeholder
2015
+ children: placeholder
1831
2016
  }),
1832
2017
  ErrorBoundary: LexicalErrorBoundary
1833
2018
  })
@@ -1835,16 +2020,16 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1835
2020
  }),
1836
2021
  /* @__PURE__ */ jsx(EditorRefPlugin, { editorRef }),
1837
2022
  /* @__PURE__ */ jsx(ChatComposerBindingsPlugin, {
1838
- disabled: props.disabled,
2023
+ disabled,
1839
2024
  editorRef,
1840
2025
  editorSignatureRef,
1841
2026
  isApplyingExternalUpdateRef,
1842
2027
  isComposingRef,
1843
2028
  lastPublishedSignatureRef,
1844
- nodes: props.nodes,
2029
+ nodes,
1845
2030
  onBlur: () => {
1846
- props.onSlashQueryChange?.(null);
1847
- props.onSlashOpenChange(false);
2031
+ onSlashQueryChange?.(null);
2032
+ onSlashOpenChange(false);
1848
2033
  },
1849
2034
  onKeyDown: (event) => {
1850
2035
  const editor = editorRef.current;
@@ -1852,19 +2037,19 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
1852
2037
  const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
1853
2038
  selectionRef.current = snapshot.selection;
1854
2039
  return handleLexicalComposerKeyboardCommand({
1855
- actions: props.actions,
1856
- activeSlashIndex: props.activeSlashIndex,
2040
+ actions,
2041
+ activeSlashIndex,
1857
2042
  nativeEvent: event,
1858
- onSlashActiveIndexChange: props.onSlashActiveIndexChange,
1859
- onSlashItemSelect: props.onSlashItemSelect,
1860
- onSlashOpenChange: props.onSlashOpenChange,
1861
- onSlashQueryChange: props.onSlashQueryChange,
2043
+ onSlashActiveIndexChange,
2044
+ onSlashItemSelect,
2045
+ onSlashOpenChange,
2046
+ onSlashQueryChange,
1862
2047
  publishSnapshot,
1863
- slashItems: props.slashItems,
2048
+ slashItems,
1864
2049
  snapshot
1865
2050
  });
1866
2051
  },
1867
- onNodesChange: props.onNodesChange,
2052
+ onNodesChange,
1868
2053
  pendingSelectionRef,
1869
2054
  selectionRef,
1870
2055
  shouldFocusAfterSyncRef,
@@ -1898,45 +2083,37 @@ function InputBarHint({ hint }) {
1898
2083
  })
1899
2084
  });
1900
2085
  }
1901
- const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
2086
+ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, slashMenu, surface, toolbar: toolbarProps }, ref) {
1902
2087
  const composerRef = useRef(null);
1903
2088
  const isSlashMenuInteractionRef = useRef(false);
1904
2089
  const [activeSlashIndex, setActiveSlashIndex] = useState(0);
1905
2090
  const [activeSlashTriggerStart, setActiveSlashTriggerStart] = useState(null);
1906
2091
  const [dismissedSlashTriggerStart, setDismissedSlashTriggerStart] = useState(null);
1907
2092
  const isSlashPanelOpen = activeSlashTriggerStart !== null && dismissedSlashTriggerStart !== activeSlashTriggerStart;
1908
- const activeSlashItem = props.slashMenu.items[activeSlashIndex] ?? null;
2093
+ const activeSlashIndexInRange = slashMenu.items.length === 0 ? 0 : Math.min(activeSlashIndex, slashMenu.items.length - 1);
2094
+ const activeSlashItem = slashMenu.items[activeSlashIndexInRange] ?? null;
1909
2095
  const dismissSlashTrigger = () => activeSlashTriggerStart !== null && !isSlashMenuInteractionRef.current ? setDismissedSlashTriggerStart(activeSlashTriggerStart) : void 0;
1910
- useEffect(() => {
1911
- setActiveSlashIndex((current) => {
1912
- if (props.slashMenu.items.length === 0) return 0;
1913
- return Math.min(current, props.slashMenu.items.length - 1);
1914
- });
1915
- }, [props.slashMenu.items.length]);
1916
- useEffect(() => {
1917
- if (activeSlashTriggerStart === null && dismissedSlashTriggerStart !== null) setDismissedSlashTriggerStart(null);
1918
- }, [activeSlashTriggerStart, dismissedSlashTriggerStart]);
1919
2096
  const toolbar = useMemo(() => {
1920
- if (!props.toolbar.skillPicker) return props.toolbar;
2097
+ if (!toolbarProps.skillPicker) return toolbarProps;
1921
2098
  return {
1922
- ...props.toolbar,
2099
+ ...toolbarProps,
1923
2100
  skillPicker: {
1924
- ...props.toolbar.skillPicker,
2101
+ ...toolbarProps.skillPicker,
1925
2102
  onSelectedKeysChange: (nextKeys) => {
1926
- composerRef.current?.syncSelectedSkills(nextKeys, props.toolbar.skillPicker?.options ?? []);
1927
- props.toolbar.skillPicker?.onSelectedKeysChange(nextKeys);
2103
+ composerRef.current?.syncSelectedSkills(nextKeys, toolbarProps.skillPicker?.options ?? []);
2104
+ toolbarProps.skillPicker?.onSelectedKeysChange(nextKeys);
1928
2105
  }
1929
2106
  }
1930
2107
  };
1931
- }, [props.toolbar]);
2108
+ }, [toolbarProps]);
1932
2109
  useImperativeHandle(ref, () => ({
1933
2110
  insertFileToken: (tokenKey, label) => composerRef.current?.insertFileToken(tokenKey, label),
1934
2111
  insertFileTokens: (tokens) => composerRef.current?.insertFileTokens(tokens),
1935
2112
  focusComposer: () => composerRef.current?.focusComposer(),
1936
- focusComposerAtEnd: () => composerRef.current?.focusComposerAtEnd()
2113
+ focusComposerAtEnd: (nodes) => composerRef.current?.focusComposerAtEnd(nodes)
1937
2114
  }), []);
1938
2115
  return /* @__PURE__ */ jsx("div", {
1939
- className: "border-t border-gray-200/80 bg-white px-3 py-3 sm:px-4 sm:py-4",
2116
+ className: surface === "embedded" ? "bg-transparent px-0 py-0" : "border-t border-gray-200/80 bg-white px-3 py-3 sm:px-4 sm:py-4",
1940
2117
  children: /* @__PURE__ */ jsx("div", {
1941
2118
  className: "nextclaw-chat-input-bar-shell mx-auto w-full max-w-[min(1120px,100%)] [container:nextclaw-chat-input-bar/inline-size]",
1942
2119
  children: /* @__PURE__ */ jsxs("div", {
@@ -1946,22 +2123,24 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1946
2123
  className: "relative",
1947
2124
  children: [/* @__PURE__ */ jsx(ChatInputBarTokenizedComposer, {
1948
2125
  ref: composerRef,
1949
- nodes: props.composer.nodes,
1950
- placeholder: props.composer.placeholder,
1951
- disabled: props.composer.disabled,
1952
- slashItems: props.slashMenu.items,
1953
- onSlashItemSelect: props.slashMenu.onSelectItem,
1954
- actions: props.toolbar.actions,
1955
- activeSlashIndex,
1956
- onNodesChange: props.composer.onNodesChange,
1957
- onFilesAdd: props.composer.onFilesAdd,
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,
1958
2135
  onSlashQueryChange: (query) => {
1959
2136
  if (query === null && isSlashMenuInteractionRef.current) return;
1960
2137
  if (query !== null) setActiveSlashIndex(0);
1961
- props.composer.onSlashQueryChange?.(query);
2138
+ composer.onSlashQueryChange?.(query);
1962
2139
  },
1963
2140
  onSlashTriggerChange: (trigger) => {
1964
- setActiveSlashTriggerStart(trigger?.start ?? null);
2141
+ const nextTriggerStart = trigger?.start ?? null;
2142
+ setActiveSlashTriggerStart(nextTriggerStart);
2143
+ if (nextTriggerStart === null) setDismissedSlashTriggerStart(null);
1965
2144
  },
1966
2145
  onSlashOpenChange: (open) => {
1967
2146
  if (!open) dismissSlashTrigger();
@@ -1969,11 +2148,11 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1969
2148
  onSlashActiveIndexChange: setActiveSlashIndex
1970
2149
  }), /* @__PURE__ */ jsx(ChatSlashMenu, {
1971
2150
  isOpen: isSlashPanelOpen,
1972
- isLoading: props.slashMenu.isLoading,
1973
- items: props.slashMenu.items,
1974
- activeIndex: activeSlashIndex,
2151
+ isLoading: slashMenu.isLoading,
2152
+ items: slashMenu.items,
2153
+ activeIndex: activeSlashIndexInRange,
1975
2154
  activeItem: activeSlashItem,
1976
- texts: props.slashMenu.texts,
2155
+ texts: slashMenu.texts,
1977
2156
  onSelectItem: (item) => {
1978
2157
  setDismissedSlashTriggerStart(null);
1979
2158
  composerRef.current?.insertSlashItem(item);
@@ -1990,7 +2169,7 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
1990
2169
  onSetActiveIndex: setActiveSlashIndex
1991
2170
  })]
1992
2171
  }),
1993
- /* @__PURE__ */ jsx(InputBarHint, { hint: props.hint }),
2172
+ /* @__PURE__ */ jsx(InputBarHint, { hint }),
1994
2173
  /* @__PURE__ */ jsx(ChatInputBarToolbar, { ...toolbar })
1995
2174
  ]
1996
2175
  })
@@ -2021,7 +2200,7 @@ function ChatMessageAvatar({ role }) {
2021
2200
  });
2022
2201
  }
2023
2202
  //#endregion
2024
- //#region src/components/chat/utils/copy-text.ts
2203
+ //#region src/components/chat/utils/copy-text.utils.ts
2025
2204
  function canUseClipboardApi() {
2026
2205
  return typeof navigator !== "undefined" && typeof navigator.clipboard?.writeText === "function";
2027
2206
  }
@@ -2032,48 +2211,75 @@ function restoreSelection(ranges) {
2032
2211
  selection.removeAllRanges();
2033
2212
  ranges.forEach((range) => selection.addRange(range));
2034
2213
  }
2035
- function fallbackCopyText(text) {
2036
- if (typeof document === "undefined" || !document.body) return false;
2214
+ function snapshotCopyContext() {
2037
2215
  const activeElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2038
2216
  const activeInput = activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement ? activeElement : null;
2039
- const activeSelection = activeInput ? {
2217
+ const activeInputSelection = activeInput ? {
2218
+ input: activeInput,
2040
2219
  start: activeInput.selectionStart,
2041
2220
  end: activeInput.selectionEnd
2042
2221
  } : null;
2043
2222
  const selection = document.getSelection();
2044
- const ranges = selection ? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index).cloneRange()) : [];
2223
+ return {
2224
+ activeElement,
2225
+ activeInputSelection,
2226
+ ranges: selection ? Array.from({ length: selection.rangeCount }, (_, index) => selection.getRangeAt(index).cloneRange()) : []
2227
+ };
2228
+ }
2229
+ function createCopyTextarea(text) {
2045
2230
  const textarea = document.createElement("textarea");
2046
2231
  textarea.value = text;
2047
2232
  textarea.setAttribute("readonly", "");
2048
2233
  textarea.setAttribute("aria-hidden", "true");
2049
- textarea.style.position = "fixed";
2050
- textarea.style.top = "0";
2051
- textarea.style.left = "-9999px";
2052
- textarea.style.opacity = "0";
2053
- textarea.style.pointerEvents = "none";
2054
- textarea.style.fontSize = "12pt";
2055
- document.body.appendChild(textarea);
2234
+ Object.assign(textarea.style, {
2235
+ fontSize: "12pt",
2236
+ left: "-9999px",
2237
+ opacity: "0",
2238
+ pointerEvents: "none",
2239
+ position: "fixed",
2240
+ top: "0"
2241
+ });
2242
+ return textarea;
2243
+ }
2244
+ function focusWithoutScroll(element) {
2056
2245
  try {
2057
- textarea.focus({ preventScroll: true });
2246
+ element.focus({ preventScroll: true });
2058
2247
  } catch {
2059
- textarea.focus();
2248
+ element.focus();
2060
2249
  }
2250
+ }
2251
+ function selectTextarea(textarea) {
2252
+ focusWithoutScroll(textarea);
2061
2253
  textarea.select();
2062
2254
  textarea.setSelectionRange(0, textarea.value.length);
2063
- let copied = false;
2255
+ }
2256
+ function runCopyCommand() {
2064
2257
  try {
2065
- copied = typeof document.execCommand === "function" && document.execCommand("copy");
2258
+ return typeof document.execCommand === "function" && document.execCommand("copy");
2066
2259
  } catch {
2067
- copied = false;
2260
+ return false;
2068
2261
  }
2262
+ }
2263
+ function restoreInputSelection(snapshot) {
2264
+ if (!snapshot.activeInputSelection) return false;
2265
+ const { input, start, end } = snapshot.activeInputSelection;
2266
+ if (start === null || end === null) return false;
2267
+ input.setSelectionRange(start, end);
2268
+ return true;
2269
+ }
2270
+ function restoreCopyContext(snapshot) {
2271
+ if (snapshot.activeElement) focusWithoutScroll(snapshot.activeElement);
2272
+ if (!restoreInputSelection(snapshot)) restoreSelection(snapshot.ranges);
2273
+ }
2274
+ function fallbackCopyText(text) {
2275
+ if (typeof document === "undefined" || !document.body) return false;
2276
+ const snapshot = snapshotCopyContext();
2277
+ const textarea = createCopyTextarea(text);
2278
+ document.body.appendChild(textarea);
2279
+ selectTextarea(textarea);
2280
+ const copied = runCopyCommand();
2069
2281
  textarea.remove();
2070
- if (activeElement) try {
2071
- activeElement.focus({ preventScroll: true });
2072
- } catch {
2073
- activeElement.focus();
2074
- }
2075
- if (activeInput && activeSelection) activeInput.setSelectionRange(activeSelection.start, activeSelection.end);
2076
- else restoreSelection(ranges);
2282
+ restoreCopyContext(snapshot);
2077
2283
  return copied;
2078
2284
  }
2079
2285
  async function copyText(text) {
@@ -3713,6 +3919,20 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
3713
3919
  })] });
3714
3920
  }
3715
3921
  //#endregion
3922
+ //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-panel-app.tsx
3923
+ function PanelAppInlineToolCard({ card, onToolAction, renderPanelAppCard }) {
3924
+ const { panelApp } = card;
3925
+ if (!panelApp) return /* @__PURE__ */ jsx(GenericToolCard, {
3926
+ card,
3927
+ onToolAction
3928
+ });
3929
+ if (renderPanelAppCard) return renderPanelAppCard(panelApp);
3930
+ return /* @__PURE__ */ jsx(GenericToolCard, {
3931
+ card,
3932
+ onToolAction
3933
+ });
3934
+ }
3935
+ //#endregion
3716
3936
  //#region src/components/chat/ui/chat-message-list/chat-tool-card.tsx
3717
3937
  function isTerminalTool(name) {
3718
3938
  const lowered = name.toLowerCase();
@@ -3726,7 +3946,12 @@ function isSearchTool(name) {
3726
3946
  const lowered = name.toLowerCase();
3727
3947
  return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
3728
3948
  }
3729
- function ChatToolCard({ card, onToolAction, onFileOpen, renderToolAgent }) {
3949
+ function ChatToolCard({ card, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
3950
+ if (card.panelApp) return /* @__PURE__ */ jsx(PanelAppInlineToolCard, {
3951
+ card,
3952
+ onToolAction,
3953
+ renderPanelAppCard
3954
+ });
3730
3955
  if (isTerminalTool(card.toolName)) return /* @__PURE__ */ jsx(TerminalExecutionView, { card });
3731
3956
  if (isFileEditTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, {
3732
3957
  card,
@@ -3740,27 +3965,8 @@ function ChatToolCard({ card, onToolAction, onFileOpen, renderToolAgent }) {
3740
3965
  });
3741
3966
  }
3742
3967
  //#endregion
3743
- //#region src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
3744
- function ChatUnknownPart(props) {
3745
- return /* @__PURE__ */ jsxs("div", {
3746
- className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600",
3747
- children: [/* @__PURE__ */ jsxs("div", {
3748
- className: "font-semibold text-gray-700",
3749
- children: [
3750
- props.label,
3751
- ": ",
3752
- props.rawType
3753
- ]
3754
- }), props.text ? /* @__PURE__ */ jsx("pre", {
3755
- className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500",
3756
- children: props.text
3757
- }) : null]
3758
- });
3759
- }
3760
- //#endregion
3761
3968
  //#region src/components/chat/ui/chat-message-list/chat-message.tsx
3762
- const ChatMessage = memo(function ChatMessage(props) {
3763
- const { message, texts, onToolAction, onFileOpen, renderToolAgent } = props;
3969
+ const ChatMessage = memo(function ChatMessage({ message, texts, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
3764
3970
  const { role } = message;
3765
3971
  const isUser = role === "user";
3766
3972
  const isMessageInProgress = message.status === "pending" || message.status === "streaming";
@@ -3770,61 +3976,77 @@ const ChatMessage = memo(function ChatMessage(props) {
3770
3976
  className: "space-y-2",
3771
3977
  children: message.parts.map((part, index) => {
3772
3978
  const { type } = part;
3773
- if (type === "markdown") return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
3774
- text: part.text,
3775
- role,
3776
- texts,
3777
- onFileOpen
3778
- }, `markdown-${index}`);
3779
- if (type === "inline-content") return /* @__PURE__ */ jsx(ChatMessageInlineContent, {
3780
- segments: part.segments,
3781
- role,
3782
- texts,
3783
- onFileOpen
3784
- }, `inline-content-${index}`);
3785
- if (type === "reasoning") return /* @__PURE__ */ jsx(ChatReasoningBlock, {
3786
- label: part.label,
3787
- text: part.text,
3788
- isUser,
3789
- isInProgress: isMessageInProgress && index === message.parts.length - 1
3790
- }, `reasoning-${index}`);
3791
- if (type === "tool-card") return /* @__PURE__ */ jsx("div", {
3792
- className: "mt-0.5",
3793
- children: /* @__PURE__ */ jsx(ChatToolCard, {
3794
- card: part.card,
3795
- onToolAction,
3796
- onFileOpen,
3797
- renderToolAgent
3798
- })
3799
- }, `tool-${index}`);
3800
- if (type === "file") return /* @__PURE__ */ jsx(ChatMessageFile, {
3801
- file: part.file,
3802
- isUser,
3803
- texts
3804
- }, `file-${index}`);
3805
- if (type === "unknown") return /* @__PURE__ */ jsx(ChatUnknownPart, {
3806
- label: part.label,
3807
- rawType: part.rawType,
3808
- text: part.text
3809
- }, `unknown-${index}`);
3979
+ if (type === "markdown") {
3980
+ const { text } = part;
3981
+ return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
3982
+ text,
3983
+ role,
3984
+ texts,
3985
+ onFileOpen
3986
+ }, `markdown-${index}`);
3987
+ }
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
+ if (type === "reasoning") {
3998
+ const { label, text } = part;
3999
+ return /* @__PURE__ */ jsx(ChatReasoningBlock, {
4000
+ label,
4001
+ text,
4002
+ isUser,
4003
+ isInProgress: isMessageInProgress && index === message.parts.length - 1
4004
+ }, `reasoning-${index}`);
4005
+ }
4006
+ if (type === "tool-card") {
4007
+ const { card } = part;
4008
+ return /* @__PURE__ */ jsx("div", {
4009
+ className: "mt-0.5",
4010
+ children: /* @__PURE__ */ jsx(ChatToolCard, {
4011
+ card,
4012
+ onToolAction,
4013
+ onFileOpen,
4014
+ renderToolAgent,
4015
+ renderPanelAppCard
4016
+ })
4017
+ }, `tool-${index}`);
4018
+ }
4019
+ if (type === "file") {
4020
+ const { file } = part;
4021
+ return /* @__PURE__ */ jsx(ChatMessageFile, {
4022
+ file,
4023
+ isUser,
4024
+ texts
4025
+ }, `file-${index}`);
4026
+ }
4027
+ if (type === "unknown") {
4028
+ const { label, rawType, text } = part;
4029
+ return /* @__PURE__ */ jsxs("div", {
4030
+ className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600",
4031
+ children: [/* @__PURE__ */ jsxs("div", {
4032
+ className: "font-semibold text-gray-700",
4033
+ children: [
4034
+ label,
4035
+ ": ",
4036
+ rawType
4037
+ ]
4038
+ }), text ? /* @__PURE__ */ jsx("pre", {
4039
+ className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500",
4040
+ children: text
4041
+ }) : null]
4042
+ }, `unknown-${index}`);
4043
+ }
3810
4044
  return null;
3811
4045
  })
3812
4046
  })
3813
4047
  });
3814
4048
  });
3815
4049
  //#endregion
3816
- //#region src/components/chat/ui/chat-message-list/chat-message-meta.tsx
3817
- function ChatMessageMeta(props) {
3818
- return /* @__PURE__ */ jsxs("div", {
3819
- className: cn("px-1 text-[11px] leading-4 text-gray-400", props.isUser ? "text-right" : "text-left"),
3820
- children: [
3821
- props.roleLabel,
3822
- " · ",
3823
- props.timestampLabel
3824
- ]
3825
- });
3826
- }
3827
- //#endregion
3828
4050
  //#region src/components/chat/ui/chat-message-list/chat-message-action-copy.tsx
3829
4051
  function ChatMessageActionCopy({ message, texts }) {
3830
4052
  const messageText = useMemo(() => {
@@ -3920,11 +4142,11 @@ function ChatTypingIndicator({ label }) {
3920
4142
  })]
3921
4143
  });
3922
4144
  }
3923
- function ChatMessageList(props) {
3924
- const visibleMessages = props.messages.filter(hasRenderableMessageContent);
4145
+ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAction, renderPanelAppCard, renderToolAgent, texts }) {
4146
+ const visibleMessages = messages.filter(hasRenderableMessageContent);
3925
4147
  const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
3926
4148
  return /* @__PURE__ */ jsxs("div", {
3927
- className: cn("space-y-5", props.className),
4149
+ className: cn("space-y-5", className),
3928
4150
  children: [visibleMessages.map((message) => {
3929
4151
  const isUser = message.role === "user";
3930
4152
  const isGenerating = !isUser && (message.status === "streaming" || message.status === "pending");
@@ -3936,28 +4158,32 @@ function ChatMessageList(props) {
3936
4158
  className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"),
3937
4159
  children: [/* @__PURE__ */ jsx(ChatMessage, {
3938
4160
  message,
3939
- texts: props.texts,
3940
- onToolAction: props.onToolAction,
3941
- onFileOpen: props.onFileOpen,
3942
- renderToolAgent: props.renderToolAgent
4161
+ texts,
4162
+ onToolAction,
4163
+ onFileOpen,
4164
+ renderToolAgent,
4165
+ renderPanelAppCard
3943
4166
  }), /* @__PURE__ */ jsx("div", {
3944
4167
  className: cn("flex items-center gap-2", isUser && "justify-end"),
3945
- children: isGenerating ? /* @__PURE__ */ jsx(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ChatMessageMeta, {
3946
- roleLabel: message.roleLabel,
3947
- timestampLabel: message.timestampLabel,
3948
- isUser
4168
+ children: isGenerating ? /* @__PURE__ */ jsx(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
4169
+ className: cn("px-1 text-[11px] leading-4 text-gray-400", isUser ? "text-right" : "text-left"),
4170
+ children: [
4171
+ message.roleLabel,
4172
+ " · ",
4173
+ message.timestampLabel
4174
+ ]
3949
4175
  }), !isUser ? /* @__PURE__ */ jsx(ChatMessageActionCopy, {
3950
4176
  message,
3951
- texts: props.texts
4177
+ texts
3952
4178
  }) : null] })
3953
4179
  })]
3954
4180
  }),
3955
4181
  isUser ? /* @__PURE__ */ jsx(ChatMessageAvatar, { role: message.role }) : null
3956
4182
  ]
3957
4183
  }, message.id);
3958
- }), props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs("div", {
4184
+ }), isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs("div", {
3959
4185
  className: "flex justify-start gap-3",
3960
- children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx(ChatTypingIndicator, { label: props.texts.typingLabel })]
4186
+ children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx(ChatTypingIndicator, { label: texts.typingLabel })]
3961
4187
  }) : null]
3962
4188
  });
3963
4189
  }