@nextclaw/agent-chat-ui 0.6.5 → 0.6.6

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
@@ -1,18 +1,18 @@
1
1
  import * as React from "react";
2
2
  import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { AlertTriangle, AppWindow, ArrowLeft, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, CalendarClock, Check, ChevronDown, ChevronRight, ChevronUp, CircleAlert, Code2, Command, Copy, Expand, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, Files, Folder, FolderTree, Globe, Globe2, ImageIcon, ListChecks, Loader2, MessageSquare, Minus, PanelsTopLeft, Paperclip, Puzzle, Search, Send, Settings, Sparkles, Star, Terminal, User, Workflow, Wrench, X } from "lucide-react";
3
4
  import { clsx } from "clsx";
4
5
  import { twMerge } from "tailwind-merge";
5
6
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
7
  import * as PopoverPrimitive from "@radix-ui/react-popover";
7
8
  import * as SelectPrimitive from "@radix-ui/react-select";
8
- import { AlertTriangle, AppWindow, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, CalendarClock, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, Expand, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, FolderTree, Globe, Globe2, ImageIcon, ListChecks, Loader2, MessageSquare, Minus, Paperclip, Puzzle, Search, Send, Settings, Sparkles, Star, Terminal, User, Workflow, Wrench, X } 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";
12
12
  import { LexicalComposer } from "@lexical/react/LexicalComposer";
13
13
  import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
14
14
  import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
15
- import { $applyNodeReplacement, $createLineBreakNode, $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, $setSelection, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, DecoratorNode, KEY_DOWN_COMMAND, SELECTION_CHANGE_COMMAND, SKIP_DOM_SELECTION_TAG, mergeRegister } from "lexical";
15
+ import { $applyNodeReplacement, $createLineBreakNode, $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, $setSelection, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, COMPOSITION_END_TAG, DecoratorNode, KEY_DOWN_COMMAND, SELECTION_CHANGE_COMMAND, SKIP_DOM_SELECTION_TAG, mergeRegister } from "lexical";
16
16
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
17
  import ReactMarkdown from "react-markdown";
18
18
  import remarkGfm from "remark-gfm";
@@ -394,16 +394,142 @@ const ChatUiPrimitives = {
394
394
  TooltipTrigger: ChatTooltipTrigger
395
395
  };
396
396
  //#endregion
397
+ //#region src/components/chat/ui/input-surface/chat-input-surface-path-preview.tsx
398
+ function ChatInputSurfacePathPreview({ pathPreview }) {
399
+ return /* @__PURE__ */ jsxs("div", {
400
+ className: "rounded-lg border border-gray-200 bg-gray-50/70 px-3 py-2.5",
401
+ children: [/* @__PURE__ */ jsxs("div", {
402
+ className: "flex items-center gap-2 text-xs font-medium text-gray-600",
403
+ children: [/* @__PURE__ */ jsx(Folder, {
404
+ "aria-hidden": "true",
405
+ className: "h-4 w-4 text-gray-400"
406
+ }), /* @__PURE__ */ jsx("span", {
407
+ className: "truncate",
408
+ children: pathPreview.rootLabel
409
+ })]
410
+ }), /* @__PURE__ */ jsx("div", {
411
+ className: "mt-2",
412
+ children: pathPreview.segments.map((segment, index) => {
413
+ const Icon = segment.kind === "directory" ? Folder : File;
414
+ return /* @__PURE__ */ jsxs("div", {
415
+ className: "relative flex min-w-0 items-center gap-2 py-1 text-xs text-gray-700",
416
+ style: { paddingLeft: `${(index + 1) * 18}px` },
417
+ children: [
418
+ /* @__PURE__ */ jsx("span", {
419
+ "aria-hidden": "true",
420
+ className: "absolute bottom-1/2 top-[-0.25rem] w-px bg-gray-200",
421
+ style: { left: `${index * 18 + 7}px` }
422
+ }),
423
+ /* @__PURE__ */ jsx(Icon, {
424
+ "aria-hidden": "true",
425
+ className: "h-4 w-4 shrink-0 text-gray-400"
426
+ }),
427
+ /* @__PURE__ */ jsx("span", {
428
+ className: "truncate font-medium",
429
+ children: segment.label
430
+ })
431
+ ]
432
+ }, `${index}:${segment.label}`);
433
+ })
434
+ })]
435
+ });
436
+ }
437
+ //#endregion
397
438
  //#region src/components/chat/ui/input-surface/chat-input-surface-menu.tsx
398
439
  const INPUT_SURFACE_PANEL_MAX_WIDTH = 680;
399
440
  const INPUT_SURFACE_PANEL_DESKTOP_SHRINK_RATIO = .82;
400
441
  const INPUT_SURFACE_PANEL_DESKTOP_MIN_WIDTH = 560;
401
442
  const INPUT_SURFACE_PANEL_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
402
443
  const INPUT_SURFACE_PANEL_MIN_HEIGHT = createChatPopoverAvailableHeightLimit("240px");
444
+ const INPUT_SURFACE_ITEM_ICONS = {
445
+ back: ArrowLeft,
446
+ command: Command,
447
+ file: File,
448
+ files: Files,
449
+ folder: Folder,
450
+ "panel-app": PanelsTopLeft,
451
+ skill: Puzzle
452
+ };
403
453
  function itemMatchesFilter(item, filter) {
404
454
  if (!filter.sectionKeys || filter.sectionKeys.length === 0) return true;
405
455
  return Boolean(item.sectionKey && filter.sectionKeys.includes(item.sectionKey));
406
456
  }
457
+ function resolveActiveFilterKey(filters, activeFilterKey) {
458
+ if (!filters?.length) return null;
459
+ return filters.some((filter) => filter.key === activeFilterKey) ? activeFilterKey : filters[0].key;
460
+ }
461
+ function InputSurfaceItemIcon({ icon }) {
462
+ const Icon = INPUT_SURFACE_ITEM_ICONS[icon];
463
+ return /* @__PURE__ */ jsx("span", {
464
+ "aria-hidden": "true",
465
+ "data-input-surface-icon": icon,
466
+ className: "inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-gray-100 text-gray-500",
467
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" })
468
+ });
469
+ }
470
+ function InputSurfaceItemDetails({ item, texts }) {
471
+ if (!item) return /* @__PURE__ */ jsx("div", {
472
+ className: "text-xs text-gray-500",
473
+ children: texts.hintLabel
474
+ });
475
+ return /* @__PURE__ */ jsxs("div", {
476
+ className: "space-y-3",
477
+ children: [
478
+ /* @__PURE__ */ jsxs("div", {
479
+ className: "flex items-center gap-2",
480
+ children: [item.subtitle ? /* @__PURE__ */ jsx("span", {
481
+ className: "inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary",
482
+ children: item.subtitle
483
+ }) : null, /* @__PURE__ */ jsx("span", {
484
+ className: "text-sm font-semibold text-gray-900",
485
+ children: item.title
486
+ })]
487
+ }),
488
+ /* @__PURE__ */ jsx("p", {
489
+ className: "text-xs leading-5 text-gray-600",
490
+ children: item.description
491
+ }),
492
+ item.pathPreview ? /* @__PURE__ */ jsx(ChatInputSurfacePathPreview, { pathPreview: item.pathPreview }) : null,
493
+ /* @__PURE__ */ jsx("div", {
494
+ className: "space-y-1",
495
+ children: item.detailLines.map((line) => /* @__PURE__ */ jsx("div", {
496
+ className: "min-w-0 break-all rounded-md bg-gray-50 px-2 py-1 text-[11px] leading-5 text-gray-600",
497
+ children: line
498
+ }, line))
499
+ }),
500
+ /* @__PURE__ */ jsx("div", {
501
+ className: "pt-1 text-[11px] text-gray-500",
502
+ children: item.hintLabel ?? texts.itemHintLabel
503
+ })
504
+ ]
505
+ });
506
+ }
507
+ function handleInputSurfaceMenuKeyDown(params) {
508
+ const { activeIndex, event, isOpen, items, onOpenChange, onSelectItem, setActiveIndex } = params;
509
+ if (!isOpen) return false;
510
+ if (event.key === "Escape") {
511
+ event.preventDefault();
512
+ onOpenChange(false);
513
+ return true;
514
+ }
515
+ if (items.length === 0) return false;
516
+ if (event.key === "ArrowDown") {
517
+ event.preventDefault();
518
+ setActiveIndex((index) => (index + 1) % items.length);
519
+ return true;
520
+ }
521
+ if (event.key === "ArrowUp") {
522
+ event.preventDefault();
523
+ setActiveIndex((index) => (index - 1 + items.length) % items.length);
524
+ return true;
525
+ }
526
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Tab") {
527
+ event.preventDefault();
528
+ onSelectItem(items[activeIndex]);
529
+ return true;
530
+ }
531
+ return false;
532
+ }
407
533
  const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref) {
408
534
  const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
409
535
  const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
@@ -412,12 +538,9 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
412
538
  index: 0,
413
539
  itemsSignature: ""
414
540
  });
415
- const { isOpen, isLoading, filterOptions, items, texts, onSelectItem, onOpenChange, onDetailsPointerDown } = props;
541
+ const { isOpen, isLoading, filterOptions, items, notice, texts, onSelectItem, onOpenChange, onDetailsPointerDown } = props;
416
542
  const [activeFilterKey, setActiveFilterKey] = useState(filterOptions?.[0]?.key ?? null);
417
- const resolvedActiveFilterKey = useMemo(() => {
418
- if (!filterOptions?.length) return null;
419
- return filterOptions.some((filter) => filter.key === activeFilterKey) ? activeFilterKey : filterOptions[0].key;
420
- }, [activeFilterKey, filterOptions]);
543
+ const resolvedActiveFilterKey = useMemo(() => resolveActiveFilterKey(filterOptions, activeFilterKey), [activeFilterKey, filterOptions]);
421
544
  const activeFilter = useMemo(() => filterOptions?.find((filter) => filter.key === resolvedActiveFilterKey) ?? null, [filterOptions, resolvedActiveFilterKey]);
422
545
  const filterViews = useMemo(() => filterOptions?.map((filter) => ({
423
546
  ...filter,
@@ -449,31 +572,15 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
449
572
  if (!panelWidth) return;
450
573
  return Math.min(panelWidth > INPUT_SURFACE_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * INPUT_SURFACE_PANEL_DESKTOP_SHRINK_RATIO : panelWidth, INPUT_SURFACE_PANEL_MAX_WIDTH);
451
574
  }, [panelWidth]);
452
- useImperativeHandle(ref, () => ({ handleKeyDown: (event) => {
453
- if (!isOpen) return false;
454
- if (event.key === "Escape") {
455
- event.preventDefault();
456
- onOpenChange(false);
457
- return true;
458
- }
459
- if (visibleItems.length === 0) return false;
460
- if (event.key === "ArrowDown") {
461
- event.preventDefault();
462
- setActiveIndexForCurrentItems((index) => (index + 1) % visibleItems.length);
463
- return true;
464
- }
465
- if (event.key === "ArrowUp") {
466
- event.preventDefault();
467
- setActiveIndexForCurrentItems((index) => (index - 1 + visibleItems.length) % visibleItems.length);
468
- return true;
469
- }
470
- if (event.key === "Enter" && !event.shiftKey || event.key === "Tab") {
471
- event.preventDefault();
472
- onSelectItem(visibleItems[activeIndexInRange]);
473
- return true;
474
- }
475
- return false;
476
- } }), [
575
+ useImperativeHandle(ref, () => ({ handleKeyDown: (event) => handleInputSurfaceMenuKeyDown({
576
+ activeIndex: activeIndexInRange,
577
+ event,
578
+ isOpen,
579
+ items: visibleItems,
580
+ onOpenChange,
581
+ onSelectItem,
582
+ setActiveIndex: setActiveIndexForCurrentItems
583
+ }) }), [
477
584
  activeIndexInRange,
478
585
  isOpen,
479
586
  onOpenChange,
@@ -512,105 +619,90 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
512
619
  style: { minHeight: INPUT_SURFACE_PANEL_MIN_HEIGHT },
513
620
  children: [/* @__PURE__ */ jsxs("div", {
514
621
  className: "flex min-h-0 flex-col border-r border-gray-200",
515
- children: [!isLoading && filterViews.length > 0 ? /* @__PURE__ */ jsx("div", {
516
- className: "flex shrink-0 gap-0.5 overflow-x-auto px-2 pb-1.5 pt-2",
517
- children: filterViews.map(({ count, key, label }) => {
518
- const isActive = key === resolvedActiveFilterKey;
519
- return /* @__PURE__ */ jsxs("button", {
520
- type: "button",
521
- "aria-pressed": isActive,
522
- onPointerDown: (event) => event.preventDefault(),
523
- onClick: () => handleFilterSelect(key),
524
- className: `inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}`,
525
- children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
526
- className: isActive ? "text-gray-500" : "text-gray-400",
527
- children: count
528
- })]
529
- }, key);
622
+ children: [
623
+ !isLoading && filterViews.length > 0 ? /* @__PURE__ */ jsx("div", {
624
+ className: "flex shrink-0 gap-0.5 overflow-x-auto px-2 pb-1.5 pt-2",
625
+ children: filterViews.map(({ count, key, label }) => {
626
+ const isActive = key === resolvedActiveFilterKey;
627
+ return /* @__PURE__ */ jsxs("button", {
628
+ type: "button",
629
+ "aria-pressed": isActive,
630
+ onPointerDown: (event) => event.preventDefault(),
631
+ onClick: () => handleFilterSelect(key),
632
+ className: `inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}`,
633
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
634
+ className: isActive ? "text-gray-500" : "text-gray-400",
635
+ children: count
636
+ })]
637
+ }, key);
638
+ })
639
+ }) : null,
640
+ !isLoading && notice ? /* @__PURE__ */ jsxs("div", {
641
+ className: "mx-2 mb-1.5 flex items-start gap-2 rounded-lg border border-red-200 bg-red-50 px-2.5 py-2 text-xs leading-4 text-red-700",
642
+ children: [/* @__PURE__ */ jsx(CircleAlert, {
643
+ "aria-hidden": "true",
644
+ className: "mt-0.5 h-3.5 w-3.5 shrink-0"
645
+ }), /* @__PURE__ */ jsx("span", { children: notice.message })]
646
+ }) : null,
647
+ /* @__PURE__ */ jsx("div", {
648
+ ref: listRef,
649
+ role: "listbox",
650
+ "aria-label": texts.sectionLabel,
651
+ className: `custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain ${!isLoading && filterViews.length > 0 ? "px-2 pb-2" : "p-2"}`,
652
+ children: isLoading ? /* @__PURE__ */ jsx("div", {
653
+ className: "p-2 text-xs text-gray-500",
654
+ children: texts.loadingLabel
655
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
656
+ className: "mb-1 px-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
657
+ children: texts.sectionLabel
658
+ }) : null, visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", {
659
+ className: "px-1.5 text-xs text-gray-400",
660
+ children: texts.emptyLabel
661
+ }) : /* @__PURE__ */ jsx("div", { children: visibleItems.map((item, index) => {
662
+ const { icon, key, sectionKey, sectionLabel, title, subtitle } = item;
663
+ const isActive = index === activeIndexInRange;
664
+ const previousItem = visibleItems[index - 1];
665
+ return /* @__PURE__ */ jsxs("div", { children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
666
+ className: "px-1.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
667
+ children: sectionLabel
668
+ }) : null, /* @__PURE__ */ jsxs("button", {
669
+ type: "button",
670
+ role: "option",
671
+ "aria-selected": isActive,
672
+ "data-input-surface-index": index,
673
+ onPointerMove: (event) => {
674
+ if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
675
+ },
676
+ onPointerDown: (event) => {
677
+ if (event.button > 0) return;
678
+ event.preventDefault();
679
+ onSelectItem(item);
680
+ },
681
+ onClick: (event) => {
682
+ if (event.detail === 0) onSelectItem(item);
683
+ },
684
+ className: `flex w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-left leading-4 transition-colors ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-100"}`,
685
+ children: [
686
+ icon ? /* @__PURE__ */ jsx(InputSurfaceItemIcon, { icon }) : null,
687
+ /* @__PURE__ */ jsx("span", {
688
+ className: "truncate text-xs font-medium",
689
+ children: title
690
+ }),
691
+ /* @__PURE__ */ jsx("span", {
692
+ className: "truncate text-xs text-gray-500",
693
+ children: subtitle
694
+ })
695
+ ]
696
+ })] }, key);
697
+ }) })] })
530
698
  })
531
- }) : null, /* @__PURE__ */ jsx("div", {
532
- ref: listRef,
533
- role: "listbox",
534
- "aria-label": texts.sectionLabel,
535
- className: `custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain ${!isLoading && filterViews.length > 0 ? "px-2 pb-2" : "p-2"}`,
536
- children: isLoading ? /* @__PURE__ */ jsx("div", {
537
- className: "p-2 text-xs text-gray-500",
538
- children: texts.loadingLabel
539
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
540
- className: "mb-1 px-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
541
- children: texts.sectionLabel
542
- }) : null, visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", {
543
- className: "px-1.5 text-xs text-gray-400",
544
- children: texts.emptyLabel
545
- }) : /* @__PURE__ */ jsx("div", { children: visibleItems.map((item, index) => {
546
- const { key, sectionKey, sectionLabel, title, subtitle } = item;
547
- const isActive = index === activeIndexInRange;
548
- const previousItem = visibleItems[index - 1];
549
- return /* @__PURE__ */ jsxs("div", { children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
550
- className: "px-1.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
551
- children: sectionLabel
552
- }) : null, /* @__PURE__ */ jsxs("button", {
553
- type: "button",
554
- role: "option",
555
- "aria-selected": isActive,
556
- "data-input-surface-index": index,
557
- onPointerMove: (event) => {
558
- if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
559
- },
560
- onPointerDown: (event) => {
561
- if (event.button > 0) return;
562
- event.preventDefault();
563
- onSelectItem(item);
564
- },
565
- onClick: (event) => {
566
- if (event.detail === 0) onSelectItem(item);
567
- },
568
- className: `flex w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-left leading-4 transition-colors ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-100"}`,
569
- children: [/* @__PURE__ */ jsx("span", {
570
- className: "truncate text-xs font-medium",
571
- children: title
572
- }), /* @__PURE__ */ jsx("span", {
573
- className: "truncate text-xs text-gray-500",
574
- children: subtitle
575
- })]
576
- })] }, key);
577
- }) })] })
578
- })]
699
+ ]
579
700
  }), /* @__PURE__ */ jsx("div", {
580
701
  className: "custom-scrollbar min-h-0 min-w-0 select-text overflow-y-auto overscroll-contain p-2.5",
581
702
  onPointerDown: onDetailsPointerDown,
582
- children: activeItem ? /* @__PURE__ */ jsxs("div", {
583
- className: "space-y-3",
584
- children: [
585
- /* @__PURE__ */ jsxs("div", {
586
- className: "flex items-center gap-2",
587
- children: [/* @__PURE__ */ jsx("span", {
588
- className: "inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary",
589
- children: activeItem.subtitle
590
- }), /* @__PURE__ */ jsx("span", {
591
- className: "text-sm font-semibold text-gray-900",
592
- children: activeItem.title
593
- })]
594
- }),
595
- /* @__PURE__ */ jsx("p", {
596
- className: "text-xs leading-5 text-gray-600",
597
- children: activeItem.description
598
- }),
599
- /* @__PURE__ */ jsx("div", {
600
- className: "space-y-1",
601
- children: activeItem.detailLines.map((line) => /* @__PURE__ */ jsx("div", {
602
- className: "min-w-0 break-all rounded-md bg-gray-50 px-2 py-1 text-[11px] leading-5 text-gray-600",
603
- children: line
604
- }, line))
605
- }),
606
- /* @__PURE__ */ jsx("div", {
607
- className: "pt-1 text-[11px] text-gray-500",
608
- children: activeItem.hintLabel ?? texts.itemHintLabel
609
- })
610
- ]
611
- }) : /* @__PURE__ */ jsx("div", {
612
- className: "text-xs text-gray-500",
613
- children: texts.hintLabel
703
+ children: /* @__PURE__ */ jsx(InputSurfaceItemDetails, {
704
+ item: activeItem,
705
+ texts
614
706
  })
615
707
  })]
616
708
  })
@@ -623,7 +715,7 @@ function getInputSurfaceTriggerIdentity(trigger) {
623
715
  return `${trigger.key}:${trigger.marker}:${trigger.start}`;
624
716
  }
625
717
  function isInputSurfaceCreateEvent(trigger, reason) {
626
- return reason.type === "insert-text" && reason.text === trigger.marker && trigger.query === "" && trigger.end === trigger.start + trigger.marker.length;
718
+ return reason.type === "insert-text" && trigger.query === "" && trigger.end === trigger.start + trigger.marker.length;
627
719
  }
628
720
  function resolveInputSurfaceTriggerIdentity(currentIdentity, trigger, reason) {
629
721
  if (!trigger) return null;
@@ -635,21 +727,19 @@ function ChatInputSurfaceHost(props) {
635
727
  const { children, inputSurface, onInputSurfaceTriggerChange, onSelectItem, triggerSpecs = [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC] } = props;
636
728
  const menuRef = useRef(null);
637
729
  const isDetailsInteractionRef = useRef(false);
730
+ const activeTriggerIdentityRef = useRef(null);
638
731
  const [activeTriggerIdentity, setActiveTriggerIdentity] = useState(null);
639
732
  const isOpen = Boolean(inputSurface) && activeTriggerIdentity !== null;
640
733
  const setActiveTrigger = useCallback((identity, trigger) => {
734
+ activeTriggerIdentityRef.current = identity;
641
735
  setActiveTriggerIdentity(identity);
642
736
  onInputSurfaceTriggerChange?.(identity ? trigger : null);
643
737
  }, [onInputSurfaceTriggerChange]);
644
738
  const handleInputSurfaceSnapshotChange = useCallback((nodes, selection, reason) => {
645
739
  const trigger = resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs);
646
- const nextIdentity = resolveInputSurfaceTriggerIdentity(activeTriggerIdentity, trigger, reason);
740
+ const nextIdentity = resolveInputSurfaceTriggerIdentity(activeTriggerIdentityRef.current, trigger, reason);
647
741
  setActiveTrigger(nextIdentity, nextIdentity ? trigger : null);
648
- }, [
649
- activeTriggerIdentity,
650
- setActiveTrigger,
651
- triggerSpecs
652
- ]);
742
+ }, [setActiveTrigger, triggerSpecs]);
653
743
  const handleInputSurfaceKeyDown = useCallback((event) => {
654
744
  return menuRef.current?.handleKeyDown(event) ?? false;
655
745
  }, []);
@@ -670,9 +760,10 @@ function ChatInputSurfaceHost(props) {
670
760
  isLoading: inputSurface.isLoading,
671
761
  filterOptions: inputSurface.filterOptions,
672
762
  items: inputSurface.items,
763
+ notice: inputSurface.notice,
673
764
  texts: inputSurface.texts,
674
765
  onSelectItem: (item) => {
675
- setActiveTrigger(null, null);
766
+ if (item.selectionBehavior !== "navigate") setActiveTrigger(null, null);
676
767
  onSelectItem(item);
677
768
  },
678
769
  onOpenChange: handleInputSurfaceOpenChange,
@@ -824,42 +915,42 @@ function filterOptions(options, query) {
824
915
  ].filter((value) => typeof value === "string" && value.trim().length > 0).join(" ").toLowerCase().includes(keyword);
825
916
  });
826
917
  }
827
- const SKILL_PICKER_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
918
+ const SKILL_PICKER_HEIGHT = createChatPopoverAvailableHeightLimit("20rem");
828
919
  function ChatInputBarSkillPicker(props) {
829
920
  const { Input, Popover, PopoverContent, PopoverTrigger } = ChatUiPrimitives;
830
921
  const { picker } = props;
831
922
  const listRef = useRef(null);
832
923
  const listId = useId();
833
924
  const [query, setQuery] = useState("");
925
+ const [activeGroupKey, setActiveGroupKey] = useState(null);
834
926
  const [activeIndex, setActiveIndex] = useState(0);
835
927
  const selectedSet = useMemo(() => new Set(picker.selectedKeys), [picker.selectedKeys]);
836
928
  const selectedCount = picker.selectedKeys.length;
837
- const isSearchActive = query.trim().length > 0;
929
+ const availableGroups = useMemo(() => picker.groups?.filter((group) => group.options.length > 0) ?? [], [picker.groups]);
930
+ const resolvedActiveGroupKey = availableGroups.some((group) => group.key === activeGroupKey) ? activeGroupKey : null;
838
931
  const groupedOptions = useMemo(() => {
839
- if (isSearchActive) return null;
840
- const groups = picker.groups?.filter((group) => group.options.length > 0) ?? [];
841
- return groups.length > 0 ? groups : null;
842
- }, [isSearchActive, picker.groups]);
932
+ if (availableGroups.length === 0) return null;
933
+ return (resolvedActiveGroupKey ? availableGroups.filter((group) => group.key === resolvedActiveGroupKey) : availableGroups).map((group) => ({
934
+ ...group,
935
+ options: filterOptions(group.options, query)
936
+ })).filter((group) => group.options.length > 0);
937
+ }, [
938
+ availableGroups,
939
+ query,
940
+ resolvedActiveGroupKey
941
+ ]);
843
942
  const visibleOptions = useMemo(() => {
844
- if (groupedOptions) return groupedOptions.flatMap((group) => group.options);
943
+ if (groupedOptions !== null) return groupedOptions.flatMap((group) => group.options);
845
944
  return filterOptions(picker.options, query);
846
945
  }, [
847
946
  groupedOptions,
848
947
  picker.options,
849
948
  query
850
949
  ]);
851
- useEffect(() => {
852
- setActiveIndex(0);
853
- }, [query]);
854
- useEffect(() => {
855
- setActiveIndex((current) => {
856
- if (visibleOptions.length === 0) return 0;
857
- return Math.min(current, visibleOptions.length - 1);
858
- });
859
- }, [visibleOptions.length]);
950
+ const resolvedActiveIndex = visibleOptions.length === 0 ? 0 : Math.min(activeIndex, visibleOptions.length - 1);
860
951
  useActiveItemScroll({
861
952
  containerRef: listRef,
862
- activeIndex,
953
+ activeIndex: resolvedActiveIndex,
863
954
  itemCount: visibleOptions.length,
864
955
  isEnabled: visibleOptions.length > 0,
865
956
  getItemSelector: (index) => `[data-skill-index="${index}"]`
@@ -875,17 +966,17 @@ function ChatInputBarSkillPicker(props) {
875
966
  if (visibleOptions.length === 0) return;
876
967
  if (event.key === "ArrowDown") {
877
968
  event.preventDefault();
878
- setActiveIndex((current) => Math.min(current + 1, visibleOptions.length - 1));
969
+ setActiveIndex(Math.min(resolvedActiveIndex + 1, visibleOptions.length - 1));
879
970
  return;
880
971
  }
881
972
  if (event.key === "ArrowUp") {
882
973
  event.preventDefault();
883
- setActiveIndex((current) => Math.max(current - 1, 0));
974
+ setActiveIndex(Math.max(resolvedActiveIndex - 1, 0));
884
975
  return;
885
976
  }
886
977
  if (event.key === "Enter") {
887
978
  event.preventDefault();
888
- const activeOption = visibleOptions[activeIndex];
979
+ const activeOption = visibleOptions[resolvedActiveIndex];
889
980
  if (activeOption) onToggleOption(activeOption.key);
890
981
  }
891
982
  };
@@ -912,28 +1003,63 @@ function ChatInputBarSkillPicker(props) {
912
1003
  side: "top",
913
1004
  align: "start",
914
1005
  className: "flex w-[min(360px,calc(100vw-1rem))] flex-col overflow-hidden p-0",
915
- style: { maxHeight: SKILL_PICKER_MAX_HEIGHT },
1006
+ style: { height: SKILL_PICKER_HEIGHT },
916
1007
  children: [
917
1008
  /* @__PURE__ */ jsxs("div", {
918
1009
  className: "shrink-0 space-y-2 border-b border-border px-4 py-3",
919
- children: [/* @__PURE__ */ jsx("div", {
920
- className: "text-sm font-semibold text-foreground",
921
- children: picker.title
922
- }), /* @__PURE__ */ jsxs("div", {
923
- className: "relative",
924
- children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-muted-foreground/70" }), /* @__PURE__ */ jsx(Input, {
925
- value: query,
926
- onChange: (event) => setQuery(event.target.value),
927
- onKeyDown: onSearchKeyDown,
928
- placeholder: picker.searchPlaceholder,
929
- role: "combobox",
930
- "aria-controls": listId,
931
- "aria-expanded": "true",
932
- "aria-autocomplete": "list",
933
- "aria-activedescendant": visibleOptions[activeIndex] ? `${listId}-option-${activeIndex}` : void 0,
934
- className: "h-8 rounded-lg pl-8 text-xs"
935
- })]
936
- })]
1010
+ children: [
1011
+ /* @__PURE__ */ jsx("div", {
1012
+ className: "text-sm font-semibold text-foreground",
1013
+ children: picker.title
1014
+ }),
1015
+ /* @__PURE__ */ jsxs("div", {
1016
+ className: "relative",
1017
+ children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-3 top-2.5 h-3.5 w-3.5 text-muted-foreground/70" }), /* @__PURE__ */ jsx(Input, {
1018
+ value: query,
1019
+ onChange: (event) => {
1020
+ setQuery(event.target.value);
1021
+ setActiveIndex(0);
1022
+ },
1023
+ onKeyDown: onSearchKeyDown,
1024
+ placeholder: picker.searchPlaceholder,
1025
+ role: "combobox",
1026
+ "aria-controls": listId,
1027
+ "aria-expanded": "true",
1028
+ "aria-autocomplete": "list",
1029
+ "aria-activedescendant": visibleOptions[resolvedActiveIndex] ? `${listId}-option-${resolvedActiveIndex}` : void 0,
1030
+ className: "h-8 rounded-lg pl-8 text-xs"
1031
+ })]
1032
+ }),
1033
+ availableGroups.length > 0 ? /* @__PURE__ */ jsx("div", {
1034
+ className: "flex flex-wrap gap-1",
1035
+ "aria-label": picker.allGroupsLabel,
1036
+ children: [{
1037
+ key: null,
1038
+ label: picker.allGroupsLabel,
1039
+ count: picker.options.length
1040
+ }, ...availableGroups.map((group) => ({
1041
+ key: group.key,
1042
+ label: group.label || group.key,
1043
+ count: group.options.length
1044
+ }))].map((group) => {
1045
+ const isActive = group.key === resolvedActiveGroupKey;
1046
+ return /* @__PURE__ */ jsxs("button", {
1047
+ type: "button",
1048
+ "aria-pressed": isActive,
1049
+ onMouseDown: (event) => event.preventDefault(),
1050
+ onClick: () => {
1051
+ setActiveGroupKey(group.key);
1052
+ setActiveIndex(0);
1053
+ },
1054
+ className: `inline-flex h-6 items-center gap-1 rounded-md px-2 text-[10px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 ${isActive ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground hover:text-foreground"}`,
1055
+ children: [/* @__PURE__ */ jsx("span", { children: group.label }), /* @__PURE__ */ jsx("span", {
1056
+ className: "opacity-70",
1057
+ children: group.count
1058
+ })]
1059
+ }, group.key ?? "all-skills");
1060
+ })
1061
+ }) : null
1062
+ ]
937
1063
  }),
938
1064
  /* @__PURE__ */ jsx("div", {
939
1065
  ref: listRef,
@@ -962,7 +1088,7 @@ function ChatInputBarSkillPicker(props) {
962
1088
  const index = visibleIndex;
963
1089
  visibleIndex += 1;
964
1090
  const isSelected = selectedSet.has(option.key);
965
- const isActive = index === activeIndex;
1091
+ const isActive = index === resolvedActiveIndex;
966
1092
  return /* @__PURE__ */ jsxs("div", {
967
1093
  id: `${listId}-option-${index}`,
968
1094
  role: "option",
@@ -1316,11 +1442,18 @@ function buildTokenClassName(tokenKind) {
1316
1442
  ].join(" ");
1317
1443
  }
1318
1444
  function ChatComposerTokenChip({ label, tokenKind }) {
1445
+ const isWorkspaceReference = tokenKind === "workspace_file" || tokenKind === "workspace_directory";
1319
1446
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1320
1447
  className: tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-card text-muted-foreground ring-1 ring-border" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70",
1321
1448
  children: tokenKind === "file" ? /* @__PURE__ */ jsx(ImageIcon, {
1322
1449
  "aria-hidden": "true",
1323
1450
  className: "h-3 w-3"
1451
+ }) : tokenKind === "workspace_file" ? /* @__PURE__ */ jsx(FileText, {
1452
+ "aria-hidden": "true",
1453
+ className: "h-3 w-3"
1454
+ }) : tokenKind === "workspace_directory" ? /* @__PURE__ */ jsx(Folder, {
1455
+ "aria-hidden": "true",
1456
+ className: "h-3 w-3"
1324
1457
  }) : tokenKind === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
1325
1458
  "aria-hidden": "true",
1326
1459
  className: "h-3 w-3"
@@ -1329,7 +1462,7 @@ function ChatComposerTokenChip({ label, tokenKind }) {
1329
1462
  className: "h-3 w-3"
1330
1463
  })
1331
1464
  }), /* @__PURE__ */ jsx("span", {
1332
- className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-foreground" : "truncate",
1465
+ className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-foreground" : isWorkspaceReference ? "max-w-[16rem] truncate" : "truncate",
1333
1466
  children: label
1334
1467
  })] });
1335
1468
  }
@@ -1424,6 +1557,7 @@ function $isChatComposerTokenNode(node) {
1424
1557
  }
1425
1558
  //#endregion
1426
1559
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-editor-state.ts
1560
+ const CHAT_COMPOSER_EXTERNAL_UPDATE_TAG = "nextclaw-chat-composer-external-update";
1427
1561
  function getComposerLeafDescriptors() {
1428
1562
  const root = $getRoot();
1429
1563
  const paragraph = root.getFirstChild();
@@ -1628,7 +1762,7 @@ function writeChatComposerStateToLexicalRoot(nodes, selection) {
1628
1762
  function syncLexicalEditorFromChatComposerState(editor, nodes, selection, preserveDomSelection = false) {
1629
1763
  editor.update(() => {
1630
1764
  writeChatComposerStateToLexicalRoot(nodes, selection);
1631
- }, preserveDomSelection ? { tag: SKIP_DOM_SELECTION_TAG } : void 0);
1765
+ }, { tag: preserveDomSelection ? [CHAT_COMPOSER_EXTERNAL_UPDATE_TAG, SKIP_DOM_SELECTION_TAG] : CHAT_COMPOSER_EXTERNAL_UPDATE_TAG });
1632
1766
  }
1633
1767
  function syncLexicalSelectionFromChatComposerSelection(editor, selection, preserveDomSelection = false) {
1634
1768
  editor.update(() => {
@@ -1638,7 +1772,7 @@ function syncLexicalSelectionFromChatComposerSelection(editor, selection, preser
1638
1772
  nextSelection.anchor.set(anchor.key, anchor.offset, anchor.type);
1639
1773
  nextSelection.focus.set(focus.key, focus.offset, focus.type);
1640
1774
  $setSelection(nextSelection);
1641
- }, preserveDomSelection ? { tag: SKIP_DOM_SELECTION_TAG } : void 0);
1775
+ }, { tag: preserveDomSelection ? [CHAT_COMPOSER_EXTERNAL_UPDATE_TAG, SKIP_DOM_SELECTION_TAG] : CHAT_COMPOSER_EXTERNAL_UPDATE_TAG });
1642
1776
  }
1643
1777
  //#endregion
1644
1778
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-operations.ts
@@ -1707,19 +1841,6 @@ function insertInputSurfaceItemIntoChatComposer(params) {
1707
1841
  function getChatComposerNodesSignature(nodes) {
1708
1842
  return nodes.map((node) => node.type === "text" ? `text:${node.text}` : `token:${node.tokenKind}:${node.tokenKey}:${node.label}`).join("");
1709
1843
  }
1710
- function replaceChatComposerSelectionWithText(params) {
1711
- const { nodes, selection, text } = params;
1712
- const documentLength = getDocumentLength(nodes);
1713
- const [selectionStart, selectionEnd] = selection ? [Math.min(selection.start, selection.end), Math.max(selection.start, selection.end)] : [documentLength, documentLength];
1714
- const nextOffset = selectionStart + text.length;
1715
- return {
1716
- nodes: replaceChatComposerRange(nodes, selectionStart, selectionEnd, [createChatComposerTextNode(text)]),
1717
- selection: {
1718
- start: nextOffset,
1719
- end: nextOffset
1720
- }
1721
- };
1722
- }
1723
1844
  function insertFileTokenIntoChatComposer(params) {
1724
1845
  const { label, nodes, selection, tokenKey } = params;
1725
1846
  return insertToken({
@@ -1766,93 +1887,43 @@ function syncSelectedSkillsIntoChatComposer(params) {
1766
1887
  selection
1767
1888
  };
1768
1889
  }
1769
- function deleteChatComposerContent(params) {
1770
- const { direction, nodes, selection: currentSelection } = params;
1771
- const documentLength = getDocumentLength(nodes);
1772
- const [selectionStart, selectionEnd] = currentSelection ? [Math.min(currentSelection.start, currentSelection.end), Math.max(currentSelection.start, currentSelection.end)] : [documentLength, documentLength];
1773
- let rangeStart = selectionStart;
1774
- let rangeEnd = selectionEnd;
1775
- if (rangeStart === rangeEnd) if (direction === "backward" && rangeStart > 0) rangeStart -= 1;
1776
- else if (direction === "forward" && rangeEnd < documentLength) rangeEnd += 1;
1777
- else return {
1778
- nodes: normalizeChatComposerNodes(nodes),
1779
- selection: {
1780
- start: rangeStart,
1781
- end: rangeEnd
1782
- }
1783
- };
1784
- return {
1785
- nodes: replaceChatComposerRange(nodes, rangeStart, rangeEnd, []),
1786
- selection: {
1787
- start: rangeStart,
1788
- end: rangeStart
1789
- }
1790
- };
1791
- }
1792
1890
  //#endregion
1793
1891
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-controller.ts
1892
+ function deleteAdjacentChatComposerToken(event) {
1893
+ if (event.key !== "Backspace" && event.key !== "Delete") return false;
1894
+ const selection = $getSelection();
1895
+ if ($isRangeSelection(selection) && !selection.isCollapsed()) return false;
1896
+ let candidate = null;
1897
+ if ($isRangeSelection(selection)) {
1898
+ const { anchor } = selection;
1899
+ const anchorNode = anchor.getNode();
1900
+ if (anchor.type === "element" && $isElementNode(anchorNode)) {
1901
+ const candidateIndex = event.key === "Backspace" ? anchor.offset - 1 : anchor.offset;
1902
+ candidate = anchorNode.getChildAtIndex(candidateIndex);
1903
+ } else if (event.key === "Backspace" && anchor.offset === 0) candidate = anchorNode.getPreviousSibling();
1904
+ else if (event.key === "Delete" && anchor.offset === anchorNode.getTextContentSize()) candidate = anchorNode.getNextSibling();
1905
+ } else if (event.key === "Backspace") {
1906
+ const paragraph = $getRoot().getFirstChild();
1907
+ candidate = $isElementNode(paragraph) ? paragraph.getLastChild() : null;
1908
+ }
1909
+ if ($isTextNode(candidate) && candidate.getTextContentSize() === 0) candidate = event.key === "Backspace" ? candidate.getPreviousSibling() : candidate.getNextSibling();
1910
+ if (!$isChatComposerTokenNode(candidate)) return false;
1911
+ event.preventDefault();
1912
+ candidate.remove();
1913
+ return true;
1914
+ }
1794
1915
  function resolveLexicalComposerKeyboardAction(params) {
1795
1916
  const { canStopGeneration, isComposing, isSending, key, shiftKey } = params;
1796
- if (key === "Escape") {
1797
- if (isSending && canStopGeneration) return { type: "stop-generation" };
1798
- return { type: "noop" };
1799
- }
1800
- if (key === "Enter" && shiftKey) return { type: "insert-line-break" };
1801
- if (key === "Enter") return { type: "send-message" };
1802
- if (!isComposing && (key === "Backspace" || key === "Delete")) return {
1803
- type: "delete-content",
1804
- direction: key === "Backspace" ? "backward" : "forward"
1805
- };
1917
+ if (isComposing) return { type: "noop" };
1918
+ if (key === "Escape") return isSending && canStopGeneration ? { type: "stop-generation" } : { type: "noop" };
1919
+ if (key === "Enter" && !shiftKey) return { type: "send-message" };
1806
1920
  return { type: "noop" };
1807
1921
  }
1808
- function getChatComposerContentSignature(nodes) {
1809
- return JSON.stringify(nodes.map((node) => node.type === "text" ? {
1810
- text: node.text,
1811
- type: node.type
1812
- } : {
1813
- label: node.label,
1814
- tokenKey: node.tokenKey,
1815
- tokenKind: node.tokenKind,
1816
- type: node.type
1817
- }));
1818
- }
1819
- function handleLexicalComposerBeforeInput(params) {
1820
- const { disabled, event, isComposing, publishSnapshot, snapshotReader } = params;
1821
- const nativeEvent = event.nativeEvent;
1822
- const shouldInsertText = nativeEvent.inputType === "insertText" || nativeEvent.inputType === "insertReplacementText";
1823
- if (disabled || isComposing || nativeEvent.isComposing || !shouldInsertText || !nativeEvent.data) return;
1824
- event.preventDefault();
1825
- publishSnapshot(replaceChatComposerSelectionWithText({
1826
- nodes: snapshotReader().nodes,
1827
- selection: snapshotReader().selection,
1828
- text: nativeEvent.data
1829
- }), { inputSurfaceReason: {
1830
- type: "insert-text",
1831
- text: nativeEvent.data
1832
- } });
1833
- }
1834
- function handleLexicalComposerCompositionEnd(params) {
1835
- const { compositionStartSnapshot, data, fallbackSnapshot, publishSnapshot, snapshotReader } = params;
1836
- const currentSnapshot = snapshotReader();
1837
- const editorSnapshot = fallbackSnapshot();
1838
- const baseSnapshot = compositionStartSnapshot ?? currentSnapshot;
1839
- publishSnapshot(getChatComposerContentSignature(editorSnapshot.nodes) !== getChatComposerContentSignature(baseSnapshot.nodes) ? editorSnapshot : data.length > 0 ? replaceChatComposerSelectionWithText({
1840
- nodes: baseSnapshot.nodes,
1841
- selection: baseSnapshot.selection,
1842
- text: data
1843
- }) : editorSnapshot, {
1844
- forcePublish: true,
1845
- inputSurfaceReason: {
1846
- type: "insert-text",
1847
- text: data
1848
- }
1849
- });
1850
- }
1851
1922
  function handleLexicalComposerKeyboardCommand(params) {
1852
- const { actions, nativeEvent, publishSnapshot, snapshot } = params;
1923
+ const { actions, isComposing, nativeEvent } = params;
1853
1924
  const action = resolveLexicalComposerKeyboardAction({
1854
1925
  canStopGeneration: actions.canStopGeneration,
1855
- isComposing: nativeEvent.isComposing,
1926
+ isComposing,
1856
1927
  isSending: actions.isSending,
1857
1928
  key: nativeEvent.key,
1858
1929
  shiftKey: nativeEvent.shiftKey
@@ -1862,26 +1933,9 @@ function handleLexicalComposerKeyboardCommand(params) {
1862
1933
  case "stop-generation":
1863
1934
  actions.onStop();
1864
1935
  return true;
1865
- case "insert-line-break":
1866
- publishSnapshot(replaceChatComposerSelectionWithText({
1867
- nodes: snapshot.nodes,
1868
- selection: snapshot.selection,
1869
- text: "\n"
1870
- }), { inputSurfaceReason: {
1871
- type: "insert-text",
1872
- text: "\n"
1873
- } });
1874
- return true;
1875
1936
  case "send-message":
1876
1937
  actions.onSend();
1877
1938
  return true;
1878
- case "delete-content":
1879
- publishSnapshot(deleteChatComposerContent({
1880
- direction: action.direction,
1881
- nodes: snapshot.nodes,
1882
- selection: snapshot.selection
1883
- }), { inputSurfaceReason: { type: "delete-content" } });
1884
- return true;
1885
1939
  case "noop": return false;
1886
1940
  }
1887
1941
  }
@@ -1895,13 +1949,10 @@ function getChatComposerDocumentLength(nodes) {
1895
1949
  }
1896
1950
  var ChatComposerLexicalOwner = class {
1897
1951
  constructor() {
1898
- this.isApplyingExternalUpdateRef = createMutableRef(false);
1899
- this.isComposingRef = createMutableRef(false);
1900
1952
  this.pendingOwnerSignatureRef = createMutableRef(null);
1901
1953
  this.pendingSelectionRef = createMutableRef(null);
1902
1954
  this.selectionRef = createMutableRef(null);
1903
1955
  this.shouldFocusAfterSyncRef = createMutableRef(false);
1904
- this.compositionStartSnapshotRef = createMutableRef(null);
1905
1956
  this.editorSignatureRef = createMutableRef("");
1906
1957
  this.lastPublishedSignatureRef = createMutableRef("");
1907
1958
  this.pendingInputSurfaceReasonRef = createMutableRef(null);
@@ -1912,14 +1963,17 @@ var ChatComposerLexicalOwner = class {
1912
1963
  };
1913
1964
  this.bindEditor = (editor) => {
1914
1965
  this.editor = editor;
1966
+ const signature = getChatComposerNodesSignature(readChatComposerSnapshotFromEditorState(editor.getEditorState()).nodes);
1967
+ this.editorSignatureRef.current = signature;
1968
+ this.lastPublishedSignatureRef.current = signature;
1915
1969
  return () => {
1916
1970
  if (this.editor === editor) this.editor = null;
1917
1971
  };
1918
1972
  };
1919
1973
  this.registerEditorListeners = (editor) => {
1920
- return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
1974
+ return mergeRegister(editor.registerUpdateListener(({ editorState, tags }) => {
1921
1975
  const runtime = this.getRuntime();
1922
- this.handleEditorUpdate(editorState, runtime.callbacks);
1976
+ this.handleEditorUpdate(editorState, runtime.callbacks, tags);
1923
1977
  }), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
1924
1978
  const runtime = this.getRuntime();
1925
1979
  this.handleSelectionChange(editor, runtime.callbacks);
@@ -1932,13 +1986,12 @@ var ChatComposerLexicalOwner = class {
1932
1986
  return this.handleKeyDown({
1933
1987
  actions: runtime.actions,
1934
1988
  callbacks: runtime.callbacks,
1935
- event,
1936
- fallbackNodes: runtime.fallbackNodes
1989
+ event
1937
1990
  });
1938
1991
  }, COMMAND_PRIORITY_HIGH));
1939
1992
  };
1940
1993
  this.syncExternalState = (editor, nodes) => {
1941
- if (this.isComposingRef.current) return;
1994
+ if (editor.isComposing()) return;
1942
1995
  const nextSignature = getChatComposerNodesSignature(nodes);
1943
1996
  const pendingSelection = this.pendingSelectionRef.current;
1944
1997
  const pendingOwnerSignature = this.pendingOwnerSignatureRef.current;
@@ -1948,12 +2001,11 @@ var ChatComposerLexicalOwner = class {
1948
2001
  }
1949
2002
  const shouldSyncDocument = nextSignature !== this.editorSignatureRef.current;
1950
2003
  if (!shouldSyncDocument && !pendingSelection) return;
1951
- this.startApplyingExternalUpdate();
1952
2004
  const preserveDomSelection = editor.getRootElement() !== document.activeElement;
1953
2005
  if (shouldSyncDocument) {
1954
- syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection, preserveDomSelection);
1955
2006
  this.editorSignatureRef.current = nextSignature;
1956
2007
  this.lastPublishedSignatureRef.current = nextSignature;
2008
+ syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection, preserveDomSelection);
1957
2009
  } else if (pendingSelection) syncLexicalSelectionFromChatComposerSelection(editor, pendingSelection, preserveDomSelection);
1958
2010
  if (pendingSelection) {
1959
2011
  this.selectionRef.current = pendingSelection;
@@ -1975,12 +2027,11 @@ var ChatComposerLexicalOwner = class {
1975
2027
  const { editor } = this;
1976
2028
  this.pendingOwnerSignatureRef.current = signature;
1977
2029
  if (editor) {
1978
- this.startApplyingExternalUpdate();
1979
- syncLexicalEditorFromChatComposerState(editor, snapshot.nodes, snapshot.selection);
1980
2030
  this.editorSignatureRef.current = signature;
2031
+ syncLexicalEditorFromChatComposerState(editor, snapshot.nodes, snapshot.selection);
1981
2032
  }
1982
2033
  callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, options?.inputSurfaceReason ?? { type: "programmatic" });
1983
- if (options?.forcePublish || signature !== this.lastPublishedSignatureRef.current) {
2034
+ if (signature !== this.lastPublishedSignatureRef.current) {
1984
2035
  this.lastPublishedSignatureRef.current = signature;
1985
2036
  callbacks.onNodesChange(snapshot.nodes);
1986
2037
  }
@@ -2005,11 +2056,10 @@ var ChatComposerLexicalOwner = class {
2005
2056
  this.pendingSelectionRef.current = targetSelection;
2006
2057
  editor.getRootElement()?.focus({ preventScroll: true });
2007
2058
  if (nodes) {
2008
- this.startApplyingExternalUpdate();
2009
2059
  const signature = getChatComposerNodesSignature(nodes);
2010
- syncLexicalEditorFromChatComposerState(editor, nodes, targetSelection);
2011
2060
  this.editorSignatureRef.current = signature;
2012
2061
  this.lastPublishedSignatureRef.current = signature;
2062
+ syncLexicalEditorFromChatComposerState(editor, nodes, targetSelection);
2013
2063
  } else syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
2014
2064
  editor.focus(() => {
2015
2065
  syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
@@ -2069,55 +2119,35 @@ var ChatComposerLexicalOwner = class {
2069
2119
  }
2070
2120
  };
2071
2121
  };
2072
- this.handleBeforeInput = (params) => {
2073
- const { disabled, event } = params;
2074
- const { callbacks, fallbackNodes } = this.getRuntime();
2075
- handleLexicalComposerBeforeInput({
2076
- disabled,
2077
- event,
2078
- isComposing: this.isComposingRef.current,
2079
- publishSnapshot: (snapshot, options) => this.publishSnapshot(snapshot, callbacks, options),
2080
- snapshotReader: () => this.readComposerSnapshot(fallbackNodes)
2081
- });
2082
- };
2083
- this.handleCompositionStart = () => {
2084
- const { fallbackNodes } = this.getRuntime();
2085
- this.compositionStartSnapshotRef.current = this.readComposerSnapshot(fallbackNodes);
2086
- this.isComposingRef.current = true;
2087
- };
2088
- this.handleCompositionEnd = (params) => {
2089
- const { data } = params;
2090
- const { callbacks, fallbackNodes } = this.getRuntime();
2091
- this.isComposingRef.current = false;
2092
- handleLexicalComposerCompositionEnd({
2093
- compositionStartSnapshot: this.compositionStartSnapshotRef.current,
2094
- data,
2095
- fallbackSnapshot: () => this.readComposerSnapshot(fallbackNodes),
2096
- publishSnapshot: (snapshot, options) => this.publishSnapshot(snapshot, callbacks, options),
2097
- snapshotReader: () => this.readComposerSnapshot(fallbackNodes)
2098
- });
2099
- this.compositionStartSnapshotRef.current = null;
2100
- };
2101
2122
  this.handleKeyDown = (params) => {
2102
- const { actions, callbacks, event, fallbackNodes } = params;
2103
- if (event.key.length === 1 && !event.isComposing && !event.altKey && !event.ctrlKey && !event.metaKey) this.pendingInputSurfaceReasonRef.current = {
2123
+ const { actions, callbacks, event } = params;
2124
+ const isComposing = event.isComposing || (this.editor?.isComposing() ?? false);
2125
+ if (event.key.length === 1 && !isComposing && !event.altKey && !event.ctrlKey && !event.metaKey) this.pendingInputSurfaceReasonRef.current = {
2104
2126
  type: "insert-text",
2105
2127
  text: event.key
2106
2128
  };
2107
- const snapshot = this.readComposerSnapshot(fallbackNodes);
2108
- if (callbacks.onInputSurfaceKeyDown?.(event)) return true;
2129
+ else if (!isComposing && (event.key === "Backspace" || event.key === "Delete")) this.pendingInputSurfaceReasonRef.current = { type: "delete-content" };
2130
+ else if (!isComposing && event.key === "Enter" && event.shiftKey) this.pendingInputSurfaceReasonRef.current = {
2131
+ type: "insert-text",
2132
+ text: "\n"
2133
+ };
2134
+ if (callbacks.onInputSurfaceKeyDown?.(event)) {
2135
+ this.pendingInputSurfaceReasonRef.current = null;
2136
+ return true;
2137
+ }
2138
+ if (!isComposing && deleteAdjacentChatComposerToken(event)) return true;
2109
2139
  return handleLexicalComposerKeyboardCommand({
2110
2140
  actions,
2111
- nativeEvent: event,
2112
- publishSnapshot: (nextSnapshot, options) => this.publishSnapshot(nextSnapshot, callbacks, options),
2113
- snapshot
2141
+ isComposing,
2142
+ nativeEvent: event
2114
2143
  });
2115
2144
  };
2116
- this.handleEditorUpdate = (editorState, callbacks) => {
2145
+ this.handleEditorUpdate = (editorState, callbacks, tags) => {
2117
2146
  const snapshot = readChatComposerSnapshotFromEditorState(editorState);
2118
2147
  const signature = getChatComposerNodesSignature(snapshot.nodes);
2148
+ const expectedExternalSignature = this.editorSignatureRef.current;
2119
2149
  this.editorSignatureRef.current = signature;
2120
- if (this.isApplyingExternalUpdateRef.current || this.isComposingRef.current) return;
2150
+ if (tags.has("nextclaw-chat-composer-external-update") && signature === expectedExternalSignature || this.editor?.isComposing() && !tags.has(COMPOSITION_END_TAG)) return;
2121
2151
  this.selectionRef.current = snapshot.selection;
2122
2152
  callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, this.consumeInputSurfaceReason() ?? { type: "sync" });
2123
2153
  if (signature === this.lastPublishedSignatureRef.current) return;
@@ -2126,7 +2156,7 @@ var ChatComposerLexicalOwner = class {
2126
2156
  };
2127
2157
  this.handleSelectionChange = (editor, callbacks) => {
2128
2158
  const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
2129
- if (this.isComposingRef.current) return;
2159
+ if (editor.isComposing()) return;
2130
2160
  this.selectionRef.current = snapshot.selection;
2131
2161
  callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, { type: "selection" });
2132
2162
  };
@@ -2139,12 +2169,6 @@ var ChatComposerLexicalOwner = class {
2139
2169
  const { callbacks, fallbackNodes } = this.getRuntime();
2140
2170
  this.publishSnapshot(createSnapshot(this.readComposerSnapshot(fallbackNodes)), callbacks, options);
2141
2171
  };
2142
- this.startApplyingExternalUpdate = () => {
2143
- this.isApplyingExternalUpdateRef.current = true;
2144
- requestAnimationFrame(() => {
2145
- this.isApplyingExternalUpdateRef.current = false;
2146
- });
2147
- };
2148
2172
  this.getRuntime = () => {
2149
2173
  if (!this.runtime) throw new Error("ChatComposerLexicalOwner runtime has not been configured.");
2150
2174
  return this.runtime;
@@ -2215,19 +2239,6 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
2215
2239
  children: /* @__PURE__ */ jsx(PlainTextPlugin, {
2216
2240
  contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
2217
2241
  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-foreground outline-none",
2218
- onBeforeInput: (event) => {
2219
- owner.handleBeforeInput({
2220
- disabled,
2221
- event
2222
- });
2223
- },
2224
- onCompositionEnd: (event) => {
2225
- const nativeEvent = event.nativeEvent;
2226
- owner.handleCompositionEnd({ data: typeof nativeEvent.data === "string" ? nativeEvent.data : "" });
2227
- },
2228
- onCompositionStart: () => {
2229
- owner.handleCompositionStart();
2230
- },
2231
2242
  onPaste: (event) => {
2232
2243
  const files = Array.from(event.clipboardData.files ?? []);
2233
2244
  if (files.length > 0 && onFilesAdd) {
@@ -2367,7 +2378,13 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSur
2367
2378
  children: /* @__PURE__ */ jsx(ChatInputSurfaceHost, {
2368
2379
  inputSurface: resolvedInputSurface,
2369
2380
  onInputSurfaceTriggerChange: composer.onInputSurfaceTriggerChange,
2370
- onSelectItem: (item) => composerRef.current?.insertInputSurfaceItem(item, composer.inputSurfaceTriggerSpecs),
2381
+ onSelectItem: (item) => {
2382
+ if (item.selectionBehavior === "navigate") {
2383
+ resolvedInputSurface?.onSelectItem?.(item);
2384
+ return;
2385
+ }
2386
+ composerRef.current?.insertInputSurfaceItem(item, composer.inputSurfaceTriggerSpecs);
2387
+ },
2371
2388
  triggerSpecs: composer.inputSurfaceTriggerSpecs,
2372
2389
  children: ({ onInputSurfaceKeyDown, onInputSurfaceOpenChange, onInputSurfaceSnapshotChange }) => /* @__PURE__ */ jsx(ChatInputBarTokenizedComposer, {
2373
2390
  ref: composerRef,
@@ -2426,6 +2443,7 @@ function ChatMessageAvatar({ role, size = "default" }) {
2426
2443
  function resolveInlineTokenTone(kind) {
2427
2444
  if (kind === "skill") return "skill";
2428
2445
  if (kind === "panel_app") return "panel_app";
2446
+ if (kind === "workspace_file" || kind === "workspace_directory") return "workspace";
2429
2447
  return "default";
2430
2448
  }
2431
2449
  function resolveInlineTokenBadgeClassName(tone, isUser, interactive) {
@@ -2438,10 +2456,16 @@ function resolveInlineTokenIconClassName(tone, isUser) {
2438
2456
  if (tone === "panel_app") return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
2439
2457
  return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
2440
2458
  }
2441
- function renderInlineTokenIcon(tone) {
2459
+ function renderInlineTokenIcon(tone, kind) {
2442
2460
  return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
2443
2461
  "aria-hidden": "true",
2444
2462
  className: "h-3 w-3"
2463
+ }) : kind === "workspace_file" ? /* @__PURE__ */ jsx(FileText, {
2464
+ "aria-hidden": "true",
2465
+ className: "h-3 w-3"
2466
+ }) : kind === "workspace_directory" ? /* @__PURE__ */ jsx(Folder, {
2467
+ "aria-hidden": "true",
2468
+ className: "h-3 w-3"
2445
2469
  }) : /* @__PURE__ */ jsx(Puzzle, {
2446
2470
  "aria-hidden": "true",
2447
2471
  className: "h-3 w-3"
@@ -2453,7 +2477,7 @@ function ChatInlineTokenBadge({ kind, label, isUser, onClick }) {
2453
2477
  const className = cn("nextclaw-chat-inline-token mx-[2px] inline-flex max-w-full items-center gap-1 align-baseline text-[11px] font-medium", tone === "skill" ? "h-auto rounded-none px-0 py-0" : "h-7 rounded-xl border px-2.5", resolveInlineTokenBadgeClassName(tone, isUser, interactive));
2454
2478
  const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
2455
2479
  className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", resolveInlineTokenIconClassName(tone, isUser)),
2456
- children: renderInlineTokenIcon(tone)
2480
+ children: renderInlineTokenIcon(tone, kind)
2457
2481
  }), /* @__PURE__ */ jsx("span", {
2458
2482
  className: "truncate",
2459
2483
  children: label
@@ -4389,8 +4413,7 @@ const FILE_TEXT_CLASS_NAME = "font-mono text-[11px] leading-5";
4389
4413
  const FILE_ROW_CLASS_NAME = `flex h-5 w-full ${FILE_TEXT_CLASS_NAME}`;
4390
4414
  const FILE_LINE_NUMBER_CELL_CLASS_NAME = "flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
4391
4415
  function readVisibleLineNumber(line) {
4392
- const value = line.newLineNumber ?? line.oldLineNumber;
4393
- return typeof value === "number" ? String(value) : "";
4416
+ return String(line.newLineNumber ?? line.oldLineNumber ?? "");
4394
4417
  }
4395
4418
  function isTargetLine(line, targetLine) {
4396
4419
  return typeof targetLine === "number" && (line.newLineNumber ?? line.oldLineNumber) === targetLine;
@@ -4438,8 +4461,9 @@ function getCodeRowTone(line) {
4438
4461
  if (line.kind === "add") return "bg-emerald-50 text-emerald-950";
4439
4462
  return "bg-card text-foreground";
4440
4463
  }
4441
- function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth, target }) {
4464
+ function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth, target, targetRef }) {
4442
4465
  return /* @__PURE__ */ jsx("span", {
4466
+ ref: targetRef,
4443
4467
  "data-file-line-number-cell": "true",
4444
4468
  style: layout === "compact" ? {
4445
4469
  width: lineNumberColumnWidth,
@@ -4513,7 +4537,8 @@ function FileOperationWorkspaceSurface({ block, targetColumn, targetLine }) {
4513
4537
  layout: "workspace",
4514
4538
  line,
4515
4539
  lineNumberColumnWidth,
4516
- target: isTargetLine(line, targetLine)
4540
+ target: isTargetLine(line, targetLine),
4541
+ targetRef: !targetColumn && isTargetLine(line, targetLine) ? targetRef : void 0
4517
4542
  }, readLineKey("gutter", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-r border-border bg-muted" })]
4518
4543
  }) : null, /* @__PURE__ */ jsxs("div", {
4519
4544
  "data-file-code-canvas": "true",
@@ -4534,7 +4559,7 @@ function FileOperationWorkspaceSurface({ block, targetColumn, targetLine }) {
4534
4559
  line,
4535
4560
  target,
4536
4561
  targetColumn: target ? targetColumn : null,
4537
- targetRef: target ? targetRef : void 0
4562
+ targetRef: target && (targetColumn || !showLineNumbers) ? targetRef : void 0
4538
4563
  })
4539
4564
  }, readLineKey("code", line, index));
4540
4565
  })