@nextclaw/agent-chat-ui 0.6.4 → 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.d.ts +29 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +816 -556
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Fragment, forwardRef, memo, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
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, 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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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: [
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
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:
|
|
583
|
-
|
|
584
|
-
|
|
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" &&
|
|
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(
|
|
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
|
|
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
|
|
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 (
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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[
|
|
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: {
|
|
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: [
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
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 ===
|
|
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();
|
|
@@ -1625,12 +1759,12 @@ function writeChatComposerStateToLexicalRoot(nodes, selection) {
|
|
|
1625
1759
|
nextSelection.focus.set(focus.key, focus.offset, focus.type);
|
|
1626
1760
|
$setSelection(nextSelection);
|
|
1627
1761
|
}
|
|
1628
|
-
function syncLexicalEditorFromChatComposerState(editor, nodes, selection) {
|
|
1762
|
+
function syncLexicalEditorFromChatComposerState(editor, nodes, selection, preserveDomSelection = false) {
|
|
1629
1763
|
editor.update(() => {
|
|
1630
1764
|
writeChatComposerStateToLexicalRoot(nodes, selection);
|
|
1631
|
-
});
|
|
1765
|
+
}, { tag: preserveDomSelection ? [CHAT_COMPOSER_EXTERNAL_UPDATE_TAG, SKIP_DOM_SELECTION_TAG] : CHAT_COMPOSER_EXTERNAL_UPDATE_TAG });
|
|
1632
1766
|
}
|
|
1633
|
-
function syncLexicalSelectionFromChatComposerSelection(editor, selection) {
|
|
1767
|
+
function syncLexicalSelectionFromChatComposerSelection(editor, selection, preserveDomSelection = false) {
|
|
1634
1768
|
editor.update(() => {
|
|
1635
1769
|
const nextSelection = $createRangeSelection();
|
|
1636
1770
|
const anchor = buildSelectionPointFromOffset(selection.start);
|
|
@@ -1638,7 +1772,7 @@ function syncLexicalSelectionFromChatComposerSelection(editor, selection) {
|
|
|
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
|
-
});
|
|
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 (
|
|
1797
|
-
|
|
1798
|
-
|
|
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,
|
|
1923
|
+
const { actions, isComposing, nativeEvent } = params;
|
|
1853
1924
|
const action = resolveLexicalComposerKeyboardAction({
|
|
1854
1925
|
canStopGeneration: actions.canStopGeneration,
|
|
1855
|
-
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 (
|
|
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,12 @@ var ChatComposerLexicalOwner = class {
|
|
|
1948
2001
|
}
|
|
1949
2002
|
const shouldSyncDocument = nextSignature !== this.editorSignatureRef.current;
|
|
1950
2003
|
if (!shouldSyncDocument && !pendingSelection) return;
|
|
1951
|
-
|
|
2004
|
+
const preserveDomSelection = editor.getRootElement() !== document.activeElement;
|
|
1952
2005
|
if (shouldSyncDocument) {
|
|
1953
|
-
syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection);
|
|
1954
2006
|
this.editorSignatureRef.current = nextSignature;
|
|
1955
2007
|
this.lastPublishedSignatureRef.current = nextSignature;
|
|
1956
|
-
|
|
2008
|
+
syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection, preserveDomSelection);
|
|
2009
|
+
} else if (pendingSelection) syncLexicalSelectionFromChatComposerSelection(editor, pendingSelection, preserveDomSelection);
|
|
1957
2010
|
if (pendingSelection) {
|
|
1958
2011
|
this.selectionRef.current = pendingSelection;
|
|
1959
2012
|
this.pendingSelectionRef.current = null;
|
|
@@ -1974,12 +2027,11 @@ var ChatComposerLexicalOwner = class {
|
|
|
1974
2027
|
const { editor } = this;
|
|
1975
2028
|
this.pendingOwnerSignatureRef.current = signature;
|
|
1976
2029
|
if (editor) {
|
|
1977
|
-
this.startApplyingExternalUpdate();
|
|
1978
|
-
syncLexicalEditorFromChatComposerState(editor, snapshot.nodes, snapshot.selection);
|
|
1979
2030
|
this.editorSignatureRef.current = signature;
|
|
2031
|
+
syncLexicalEditorFromChatComposerState(editor, snapshot.nodes, snapshot.selection);
|
|
1980
2032
|
}
|
|
1981
2033
|
callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, options?.inputSurfaceReason ?? { type: "programmatic" });
|
|
1982
|
-
if (
|
|
2034
|
+
if (signature !== this.lastPublishedSignatureRef.current) {
|
|
1983
2035
|
this.lastPublishedSignatureRef.current = signature;
|
|
1984
2036
|
callbacks.onNodesChange(snapshot.nodes);
|
|
1985
2037
|
}
|
|
@@ -2004,11 +2056,10 @@ var ChatComposerLexicalOwner = class {
|
|
|
2004
2056
|
this.pendingSelectionRef.current = targetSelection;
|
|
2005
2057
|
editor.getRootElement()?.focus({ preventScroll: true });
|
|
2006
2058
|
if (nodes) {
|
|
2007
|
-
this.startApplyingExternalUpdate();
|
|
2008
2059
|
const signature = getChatComposerNodesSignature(nodes);
|
|
2009
|
-
syncLexicalEditorFromChatComposerState(editor, nodes, targetSelection);
|
|
2010
2060
|
this.editorSignatureRef.current = signature;
|
|
2011
2061
|
this.lastPublishedSignatureRef.current = signature;
|
|
2062
|
+
syncLexicalEditorFromChatComposerState(editor, nodes, targetSelection);
|
|
2012
2063
|
} else syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
2013
2064
|
editor.focus(() => {
|
|
2014
2065
|
syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
@@ -2068,55 +2119,35 @@ var ChatComposerLexicalOwner = class {
|
|
|
2068
2119
|
}
|
|
2069
2120
|
};
|
|
2070
2121
|
};
|
|
2071
|
-
this.handleBeforeInput = (params) => {
|
|
2072
|
-
const { disabled, event } = params;
|
|
2073
|
-
const { callbacks, fallbackNodes } = this.getRuntime();
|
|
2074
|
-
handleLexicalComposerBeforeInput({
|
|
2075
|
-
disabled,
|
|
2076
|
-
event,
|
|
2077
|
-
isComposing: this.isComposingRef.current,
|
|
2078
|
-
publishSnapshot: (snapshot, options) => this.publishSnapshot(snapshot, callbacks, options),
|
|
2079
|
-
snapshotReader: () => this.readComposerSnapshot(fallbackNodes)
|
|
2080
|
-
});
|
|
2081
|
-
};
|
|
2082
|
-
this.handleCompositionStart = () => {
|
|
2083
|
-
const { fallbackNodes } = this.getRuntime();
|
|
2084
|
-
this.compositionStartSnapshotRef.current = this.readComposerSnapshot(fallbackNodes);
|
|
2085
|
-
this.isComposingRef.current = true;
|
|
2086
|
-
};
|
|
2087
|
-
this.handleCompositionEnd = (params) => {
|
|
2088
|
-
const { data } = params;
|
|
2089
|
-
const { callbacks, fallbackNodes } = this.getRuntime();
|
|
2090
|
-
this.isComposingRef.current = false;
|
|
2091
|
-
handleLexicalComposerCompositionEnd({
|
|
2092
|
-
compositionStartSnapshot: this.compositionStartSnapshotRef.current,
|
|
2093
|
-
data,
|
|
2094
|
-
fallbackSnapshot: () => this.readComposerSnapshot(fallbackNodes),
|
|
2095
|
-
publishSnapshot: (snapshot, options) => this.publishSnapshot(snapshot, callbacks, options),
|
|
2096
|
-
snapshotReader: () => this.readComposerSnapshot(fallbackNodes)
|
|
2097
|
-
});
|
|
2098
|
-
this.compositionStartSnapshotRef.current = null;
|
|
2099
|
-
};
|
|
2100
2122
|
this.handleKeyDown = (params) => {
|
|
2101
|
-
const { actions, callbacks, event
|
|
2102
|
-
|
|
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 = {
|
|
2103
2126
|
type: "insert-text",
|
|
2104
2127
|
text: event.key
|
|
2105
2128
|
};
|
|
2106
|
-
|
|
2107
|
-
if (
|
|
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;
|
|
2108
2139
|
return handleLexicalComposerKeyboardCommand({
|
|
2109
2140
|
actions,
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
snapshot
|
|
2141
|
+
isComposing,
|
|
2142
|
+
nativeEvent: event
|
|
2113
2143
|
});
|
|
2114
2144
|
};
|
|
2115
|
-
this.handleEditorUpdate = (editorState, callbacks) => {
|
|
2145
|
+
this.handleEditorUpdate = (editorState, callbacks, tags) => {
|
|
2116
2146
|
const snapshot = readChatComposerSnapshotFromEditorState(editorState);
|
|
2117
2147
|
const signature = getChatComposerNodesSignature(snapshot.nodes);
|
|
2148
|
+
const expectedExternalSignature = this.editorSignatureRef.current;
|
|
2118
2149
|
this.editorSignatureRef.current = signature;
|
|
2119
|
-
if (
|
|
2150
|
+
if (tags.has("nextclaw-chat-composer-external-update") && signature === expectedExternalSignature || this.editor?.isComposing() && !tags.has(COMPOSITION_END_TAG)) return;
|
|
2120
2151
|
this.selectionRef.current = snapshot.selection;
|
|
2121
2152
|
callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, this.consumeInputSurfaceReason() ?? { type: "sync" });
|
|
2122
2153
|
if (signature === this.lastPublishedSignatureRef.current) return;
|
|
@@ -2125,7 +2156,7 @@ var ChatComposerLexicalOwner = class {
|
|
|
2125
2156
|
};
|
|
2126
2157
|
this.handleSelectionChange = (editor, callbacks) => {
|
|
2127
2158
|
const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
|
|
2128
|
-
if (
|
|
2159
|
+
if (editor.isComposing()) return;
|
|
2129
2160
|
this.selectionRef.current = snapshot.selection;
|
|
2130
2161
|
callbacks.onInputSurfaceSnapshotChange?.(snapshot.nodes, snapshot.selection, { type: "selection" });
|
|
2131
2162
|
};
|
|
@@ -2138,12 +2169,6 @@ var ChatComposerLexicalOwner = class {
|
|
|
2138
2169
|
const { callbacks, fallbackNodes } = this.getRuntime();
|
|
2139
2170
|
this.publishSnapshot(createSnapshot(this.readComposerSnapshot(fallbackNodes)), callbacks, options);
|
|
2140
2171
|
};
|
|
2141
|
-
this.startApplyingExternalUpdate = () => {
|
|
2142
|
-
this.isApplyingExternalUpdateRef.current = true;
|
|
2143
|
-
requestAnimationFrame(() => {
|
|
2144
|
-
this.isApplyingExternalUpdateRef.current = false;
|
|
2145
|
-
});
|
|
2146
|
-
};
|
|
2147
2172
|
this.getRuntime = () => {
|
|
2148
2173
|
if (!this.runtime) throw new Error("ChatComposerLexicalOwner runtime has not been configured.");
|
|
2149
2174
|
return this.runtime;
|
|
@@ -2214,19 +2239,6 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
|
|
|
2214
2239
|
children: /* @__PURE__ */ jsx(PlainTextPlugin, {
|
|
2215
2240
|
contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
|
|
2216
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",
|
|
2217
|
-
onBeforeInput: (event) => {
|
|
2218
|
-
owner.handleBeforeInput({
|
|
2219
|
-
disabled,
|
|
2220
|
-
event
|
|
2221
|
-
});
|
|
2222
|
-
},
|
|
2223
|
-
onCompositionEnd: (event) => {
|
|
2224
|
-
const nativeEvent = event.nativeEvent;
|
|
2225
|
-
owner.handleCompositionEnd({ data: typeof nativeEvent.data === "string" ? nativeEvent.data : "" });
|
|
2226
|
-
},
|
|
2227
|
-
onCompositionStart: () => {
|
|
2228
|
-
owner.handleCompositionStart();
|
|
2229
|
-
},
|
|
2230
2242
|
onPaste: (event) => {
|
|
2231
2243
|
const files = Array.from(event.clipboardData.files ?? []);
|
|
2232
2244
|
if (files.length > 0 && onFilesAdd) {
|
|
@@ -2366,7 +2378,13 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSur
|
|
|
2366
2378
|
children: /* @__PURE__ */ jsx(ChatInputSurfaceHost, {
|
|
2367
2379
|
inputSurface: resolvedInputSurface,
|
|
2368
2380
|
onInputSurfaceTriggerChange: composer.onInputSurfaceTriggerChange,
|
|
2369
|
-
onSelectItem: (item) =>
|
|
2381
|
+
onSelectItem: (item) => {
|
|
2382
|
+
if (item.selectionBehavior === "navigate") {
|
|
2383
|
+
resolvedInputSurface?.onSelectItem?.(item);
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
composerRef.current?.insertInputSurfaceItem(item, composer.inputSurfaceTriggerSpecs);
|
|
2387
|
+
},
|
|
2370
2388
|
triggerSpecs: composer.inputSurfaceTriggerSpecs,
|
|
2371
2389
|
children: ({ onInputSurfaceKeyDown, onInputSurfaceOpenChange, onInputSurfaceSnapshotChange }) => /* @__PURE__ */ jsx(ChatInputBarTokenizedComposer, {
|
|
2372
2390
|
ref: composerRef,
|
|
@@ -2425,6 +2443,7 @@ function ChatMessageAvatar({ role, size = "default" }) {
|
|
|
2425
2443
|
function resolveInlineTokenTone(kind) {
|
|
2426
2444
|
if (kind === "skill") return "skill";
|
|
2427
2445
|
if (kind === "panel_app") return "panel_app";
|
|
2446
|
+
if (kind === "workspace_file" || kind === "workspace_directory") return "workspace";
|
|
2428
2447
|
return "default";
|
|
2429
2448
|
}
|
|
2430
2449
|
function resolveInlineTokenBadgeClassName(tone, isUser, interactive) {
|
|
@@ -2437,10 +2456,16 @@ function resolveInlineTokenIconClassName(tone, isUser) {
|
|
|
2437
2456
|
if (tone === "panel_app") return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2438
2457
|
return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2439
2458
|
}
|
|
2440
|
-
function renderInlineTokenIcon(tone) {
|
|
2459
|
+
function renderInlineTokenIcon(tone, kind) {
|
|
2441
2460
|
return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
|
|
2442
2461
|
"aria-hidden": "true",
|
|
2443
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"
|
|
2444
2469
|
}) : /* @__PURE__ */ jsx(Puzzle, {
|
|
2445
2470
|
"aria-hidden": "true",
|
|
2446
2471
|
className: "h-3 w-3"
|
|
@@ -2452,7 +2477,7 @@ function ChatInlineTokenBadge({ kind, label, isUser, onClick }) {
|
|
|
2452
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));
|
|
2453
2478
|
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2454
2479
|
className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", resolveInlineTokenIconClassName(tone, isUser)),
|
|
2455
|
-
children: renderInlineTokenIcon(tone)
|
|
2480
|
+
children: renderInlineTokenIcon(tone, kind)
|
|
2456
2481
|
}), /* @__PURE__ */ jsx("span", {
|
|
2457
2482
|
className: "truncate",
|
|
2458
2483
|
children: label
|
|
@@ -2780,7 +2805,7 @@ function isRecord$1(value) {
|
|
|
2780
2805
|
function readString(value) {
|
|
2781
2806
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2782
2807
|
}
|
|
2783
|
-
function readPositiveInteger(value) {
|
|
2808
|
+
function readPositiveInteger$1(value) {
|
|
2784
2809
|
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : void 0;
|
|
2785
2810
|
}
|
|
2786
2811
|
function readFilePreviewViewer(value) {
|
|
@@ -2800,8 +2825,8 @@ function readFileTarget(record) {
|
|
|
2800
2825
|
type: "file",
|
|
2801
2826
|
payload: {
|
|
2802
2827
|
path,
|
|
2803
|
-
line: readPositiveInteger(payload.line),
|
|
2804
|
-
column: readPositiveInteger(payload.column),
|
|
2828
|
+
line: readPositiveInteger$1(payload.line),
|
|
2829
|
+
column: readPositiveInteger$1(payload.column),
|
|
2805
2830
|
viewer: readFilePreviewViewer(payload.viewer)
|
|
2806
2831
|
}
|
|
2807
2832
|
};
|
|
@@ -2936,6 +2961,114 @@ function ChatInlineDisplay({ display, renderInlineDisplay }) {
|
|
|
2936
2961
|
return /* @__PURE__ */ jsx(ChatInlineDisplayFallback, { display });
|
|
2937
2962
|
}
|
|
2938
2963
|
//#endregion
|
|
2964
|
+
//#region src/components/chat/ui/chat-message-list/mermaid/chat-mermaid-diagram.tsx
|
|
2965
|
+
const STREAMING_RENDER_DELAY_MS = 300;
|
|
2966
|
+
let mermaidModulePromise = null;
|
|
2967
|
+
function loadMermaid() {
|
|
2968
|
+
mermaidModulePromise ??= import("mermaid").then((module) => module.default);
|
|
2969
|
+
return mermaidModulePromise;
|
|
2970
|
+
}
|
|
2971
|
+
function readMermaidTheme() {
|
|
2972
|
+
if (typeof document === "undefined") return "default";
|
|
2973
|
+
const root = document.documentElement;
|
|
2974
|
+
return root.getAttribute("data-theme-appearance") === "dark" || root.classList.contains("dark") ? "dark" : "default";
|
|
2975
|
+
}
|
|
2976
|
+
function useMermaidTheme() {
|
|
2977
|
+
const [theme, setTheme] = useState(readMermaidTheme);
|
|
2978
|
+
useEffect(() => {
|
|
2979
|
+
const root = document.documentElement;
|
|
2980
|
+
const observer = new MutationObserver(() => setTheme(readMermaidTheme()));
|
|
2981
|
+
observer.observe(root, {
|
|
2982
|
+
attributes: true,
|
|
2983
|
+
attributeFilter: ["class", "data-theme-appearance"]
|
|
2984
|
+
});
|
|
2985
|
+
return () => observer.disconnect();
|
|
2986
|
+
}, []);
|
|
2987
|
+
return theme;
|
|
2988
|
+
}
|
|
2989
|
+
function ChatMermaidDiagram({ isStreaming, source, texts }) {
|
|
2990
|
+
const reactId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2991
|
+
const renderSequence = useRef(0);
|
|
2992
|
+
const theme = useMermaidTheme();
|
|
2993
|
+
const normalizedSource = source.trim();
|
|
2994
|
+
const renderKey = `${theme}:${normalizedSource}`;
|
|
2995
|
+
const [state, setState] = useState(null);
|
|
2996
|
+
const diagramLabel = texts.mermaidDiagramLabel ?? "Mermaid diagram";
|
|
2997
|
+
const errorLabel = texts.mermaidRenderErrorLabel ?? "Diagram could not be rendered";
|
|
2998
|
+
useEffect(() => {
|
|
2999
|
+
let active = true;
|
|
3000
|
+
const renderDiagram = async () => {
|
|
3001
|
+
try {
|
|
3002
|
+
const mermaid = await loadMermaid();
|
|
3003
|
+
if (!active) return;
|
|
3004
|
+
renderSequence.current += 1;
|
|
3005
|
+
const diagramId = `nextclaw-mermaid-${reactId}-${renderSequence.current}`;
|
|
3006
|
+
mermaid.initialize({
|
|
3007
|
+
securityLevel: "strict",
|
|
3008
|
+
startOnLoad: false,
|
|
3009
|
+
suppressErrorRendering: true,
|
|
3010
|
+
theme
|
|
3011
|
+
});
|
|
3012
|
+
const valid = await mermaid.parse(normalizedSource, { suppressErrors: true });
|
|
3013
|
+
if (!active) return;
|
|
3014
|
+
if (!valid) throw new Error("Invalid Mermaid diagram");
|
|
3015
|
+
const { svg } = await mermaid.render(diagramId, normalizedSource);
|
|
3016
|
+
if (active) setState({
|
|
3017
|
+
key: renderKey,
|
|
3018
|
+
status: "rendered",
|
|
3019
|
+
svg
|
|
3020
|
+
});
|
|
3021
|
+
} catch {
|
|
3022
|
+
if (active && !isStreaming) setState({
|
|
3023
|
+
key: renderKey,
|
|
3024
|
+
status: "error"
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
};
|
|
3028
|
+
const timeout = window.setTimeout(() => void renderDiagram(), isStreaming ? STREAMING_RENDER_DELAY_MS : 0);
|
|
3029
|
+
return () => {
|
|
3030
|
+
active = false;
|
|
3031
|
+
window.clearTimeout(timeout);
|
|
3032
|
+
};
|
|
3033
|
+
}, [
|
|
3034
|
+
isStreaming,
|
|
3035
|
+
normalizedSource,
|
|
3036
|
+
reactId,
|
|
3037
|
+
renderKey,
|
|
3038
|
+
theme
|
|
3039
|
+
]);
|
|
3040
|
+
const currentState = state?.key === renderKey ? state : null;
|
|
3041
|
+
if (currentState?.status === "error") return /* @__PURE__ */ jsxs("div", {
|
|
3042
|
+
"data-chat-mermaid-error": "true",
|
|
3043
|
+
className: "my-3 overflow-hidden rounded-xl border border-border bg-muted/25",
|
|
3044
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3045
|
+
className: "border-b border-border px-3 py-2 text-xs text-muted-foreground",
|
|
3046
|
+
children: errorLabel
|
|
3047
|
+
}), /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3048
|
+
className: "language-mermaid",
|
|
3049
|
+
texts,
|
|
3050
|
+
children: normalizedSource
|
|
3051
|
+
})]
|
|
3052
|
+
});
|
|
3053
|
+
const renderedState = state?.status === "rendered" ? state : null;
|
|
3054
|
+
if (!renderedState) return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3055
|
+
className: "language-mermaid",
|
|
3056
|
+
texts,
|
|
3057
|
+
children: normalizedSource
|
|
3058
|
+
});
|
|
3059
|
+
return /* @__PURE__ */ jsx("figure", {
|
|
3060
|
+
"aria-label": diagramLabel,
|
|
3061
|
+
"aria-busy": !currentState,
|
|
3062
|
+
"data-chat-mermaid-diagram": "true",
|
|
3063
|
+
"data-chat-mermaid-updating": !currentState || void 0,
|
|
3064
|
+
className: "my-3 min-h-24 overflow-auto rounded-xl border border-border bg-muted/20 p-3",
|
|
3065
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3066
|
+
className: "min-w-fit [&_svg]:mx-auto [&_svg]:h-auto [&_svg]:max-w-full",
|
|
3067
|
+
dangerouslySetInnerHTML: { __html: renderedState.svg }
|
|
3068
|
+
})
|
|
3069
|
+
});
|
|
3070
|
+
}
|
|
3071
|
+
//#endregion
|
|
2939
3072
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/chat-message-image-preview.tsx
|
|
2940
3073
|
function ChatMessageImageLightbox({ alt, closeLabel, onClose, src }) {
|
|
2941
3074
|
const titleId = useId();
|
|
@@ -3042,7 +3175,7 @@ function ChatMessageImagePreview({ alt, expandLabel, closeLabel, sizeLabel, src
|
|
|
3042
3175
|
}
|
|
3043
3176
|
//#endregion
|
|
3044
3177
|
//#region src/components/chat/ui/chat-message-list/utils/chat-local-resource.utils.ts
|
|
3045
|
-
const
|
|
3178
|
+
const SAFE_BROWSER_LINK_PROTOCOLS = new Set([
|
|
3046
3179
|
"http:",
|
|
3047
3180
|
"https:",
|
|
3048
3181
|
"mailto:",
|
|
@@ -3050,37 +3183,90 @@ const SAFE_LINK_PROTOCOLS = new Set([
|
|
|
3050
3183
|
]);
|
|
3051
3184
|
const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
|
|
3052
3185
|
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[a-z]:[\\/]/i;
|
|
3186
|
+
const WINDOWS_FILE_URI_PATH_PATTERN = /^\/[a-z]:[\\/]/i;
|
|
3187
|
+
const FILE_URI_PROTOCOL = "file:";
|
|
3053
3188
|
function isSchemeLessResourceHref(href) {
|
|
3054
3189
|
return !href.startsWith("//") && !URI_SCHEME_PATTERN.test(href);
|
|
3055
3190
|
}
|
|
3056
|
-
function
|
|
3057
|
-
|
|
3058
|
-
|
|
3191
|
+
function readPositiveInteger(value) {
|
|
3192
|
+
const parsed = Number(value);
|
|
3193
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
3194
|
+
}
|
|
3195
|
+
function readFileUriHref(href) {
|
|
3196
|
+
let url;
|
|
3059
3197
|
try {
|
|
3060
|
-
|
|
3061
|
-
return SAFE_LINK_PROTOCOLS.has(url.protocol) ? href : null;
|
|
3198
|
+
url = new URL(href);
|
|
3062
3199
|
} catch {
|
|
3063
3200
|
return null;
|
|
3064
3201
|
}
|
|
3202
|
+
if (url.protocol !== FILE_URI_PROTOCOL || url.hostname && url.hostname.toLowerCase() !== "localhost") return null;
|
|
3203
|
+
let path;
|
|
3204
|
+
try {
|
|
3205
|
+
path = decodeURIComponent(url.pathname);
|
|
3206
|
+
} catch {
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
if (WINDOWS_FILE_URI_PATH_PATTERN.test(path)) path = path.slice(1);
|
|
3210
|
+
return path ? {
|
|
3211
|
+
path,
|
|
3212
|
+
query: url.searchParams,
|
|
3213
|
+
fragment: url.hash.slice(1)
|
|
3214
|
+
} : null;
|
|
3065
3215
|
}
|
|
3066
|
-
function
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
const
|
|
3071
|
-
const
|
|
3216
|
+
function readSchemeLessFileHref(href) {
|
|
3217
|
+
const hashIndex = href.indexOf("#");
|
|
3218
|
+
const hrefWithoutFragment = hashIndex >= 0 ? href.slice(0, hashIndex) : href;
|
|
3219
|
+
const fragment = hashIndex >= 0 ? href.slice(hashIndex + 1) : "";
|
|
3220
|
+
const queryIndex = hrefWithoutFragment.indexOf("?");
|
|
3221
|
+
const encodedPath = queryIndex >= 0 ? hrefWithoutFragment.slice(0, queryIndex) : hrefWithoutFragment;
|
|
3222
|
+
const encodedQuery = queryIndex >= 0 ? hrefWithoutFragment.slice(queryIndex + 1) : "";
|
|
3072
3223
|
let path;
|
|
3073
3224
|
try {
|
|
3074
|
-
path = decodeURIComponent(encodedPath
|
|
3225
|
+
path = decodeURIComponent(encodedPath);
|
|
3075
3226
|
} catch {
|
|
3076
3227
|
return null;
|
|
3077
3228
|
}
|
|
3229
|
+
return path ? {
|
|
3230
|
+
path,
|
|
3231
|
+
query: new URLSearchParams(encodedQuery),
|
|
3232
|
+
fragment
|
|
3233
|
+
} : null;
|
|
3234
|
+
}
|
|
3235
|
+
function readLinePosition(fragment) {
|
|
3236
|
+
const match = /^L(\d+)(?:C(\d+))?(?:-L\d+(?:C\d+)?)?$/i.exec(fragment);
|
|
3237
|
+
const line = readPositiveInteger(match?.[1]);
|
|
3238
|
+
return {
|
|
3239
|
+
line,
|
|
3240
|
+
column: line ? readPositiveInteger(match?.[2]) : void 0
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
function transformChatResourceHref(href) {
|
|
3244
|
+
if (WINDOWS_ABSOLUTE_PATH_PATTERN.test(href) || isSchemeLessResourceHref(href)) return href;
|
|
3245
|
+
try {
|
|
3246
|
+
const url = new URL(href);
|
|
3247
|
+
if (SAFE_BROWSER_LINK_PROTOCOLS.has(url.protocol)) return href;
|
|
3248
|
+
return url.protocol === FILE_URI_PROTOCOL && readFileUriHref(href) ? href : "";
|
|
3249
|
+
} catch {
|
|
3250
|
+
return "";
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
function resolveSafeChatResourceHref(href) {
|
|
3254
|
+
if (!href) return null;
|
|
3255
|
+
return transformChatResourceHref(href) || null;
|
|
3256
|
+
}
|
|
3257
|
+
function isExternalChatResourceHref(href) {
|
|
3258
|
+
return /^https?:\/\//i.test(href);
|
|
3259
|
+
}
|
|
3260
|
+
function parseChatLocalFileAction(href) {
|
|
3261
|
+
const fileHref = href.toLowerCase().startsWith(FILE_URI_PROTOCOL) ? readFileUriHref(href) : readSchemeLessFileHref(href);
|
|
3262
|
+
const path = fileHref?.path;
|
|
3078
3263
|
if (!path || path.startsWith("#") || path.startsWith("//") || !WINDOWS_ABSOLUTE_PATH_PATTERN.test(path) && URI_SCHEME_PATTERN.test(path)) return null;
|
|
3079
|
-
const viewer =
|
|
3264
|
+
const viewer = fileHref.query.get("viewer");
|
|
3265
|
+
const fragmentPosition = readLinePosition(fileHref.fragment);
|
|
3080
3266
|
const lineMatch = /^(.*?)(?::(\d+)(?::(\d+))?)$/.exec(path);
|
|
3081
3267
|
const rawPath = lineMatch?.[1] ?? path;
|
|
3082
|
-
const line =
|
|
3083
|
-
const column =
|
|
3268
|
+
const line = fragmentPosition.line ?? readPositiveInteger(lineMatch?.[2]);
|
|
3269
|
+
const column = fragmentPosition.column ?? (line ? readPositiveInteger(lineMatch?.[3]) : void 0);
|
|
3084
3270
|
return {
|
|
3085
3271
|
path: rawPath,
|
|
3086
3272
|
label: rawPath.split(/[\\/]/).filter(Boolean).pop() ?? rawPath,
|
|
@@ -3180,153 +3366,170 @@ function splitTextNodeByInlineTokens(value, tokens) {
|
|
|
3180
3366
|
}];
|
|
3181
3367
|
}
|
|
3182
3368
|
function transformInlineTokenTextNodes(node, tokens) {
|
|
3183
|
-
if (node.type === "code" || node.type === "inlineCode" || !node.children) return;
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
continue;
|
|
3189
|
-
}
|
|
3190
|
-
transformInlineTokenTextNodes(child, tokens);
|
|
3191
|
-
nextChildren.push(child);
|
|
3192
|
-
}
|
|
3193
|
-
node.children = nextChildren;
|
|
3369
|
+
if (node.type === "code" || node.type === "inlineCode" || !node.children) return node;
|
|
3370
|
+
return {
|
|
3371
|
+
...node,
|
|
3372
|
+
children: node.children.flatMap((child) => child.type === "text" && typeof child.value === "string" ? splitTextNodeByInlineTokens(child.value, tokens) : [transformInlineTokenTextNodes(child, tokens)])
|
|
3373
|
+
};
|
|
3194
3374
|
}
|
|
3195
3375
|
function createRemarkInlineTokenPlugin(inlineTokens) {
|
|
3196
3376
|
const tokens = prepareInlineTokens(inlineTokens);
|
|
3197
|
-
return () => (tree) =>
|
|
3198
|
-
if (tokens.length === 0) return;
|
|
3199
|
-
transformInlineTokenTextNodes(tree, tokens);
|
|
3200
|
-
};
|
|
3377
|
+
return () => (tree) => tokens.length > 0 ? transformInlineTokenTextNodes(tree, tokens) : tree;
|
|
3201
3378
|
}
|
|
3202
3379
|
function readStringProp(props, key) {
|
|
3203
3380
|
const value = props[key];
|
|
3204
3381
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3205
3382
|
}
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3383
|
+
const ChatMessageMarkdownRuntimeContext = createContext(null);
|
|
3384
|
+
function useChatMessageMarkdownRuntime() {
|
|
3385
|
+
const runtime = useContext(ChatMessageMarkdownRuntimeContext);
|
|
3386
|
+
if (!runtime) throw new Error("Chat message Markdown renderer requires its runtime context");
|
|
3387
|
+
return runtime;
|
|
3388
|
+
}
|
|
3389
|
+
const CHAT_MESSAGE_MARKDOWN_COMPONENTS = {
|
|
3390
|
+
p: function ChatMarkdownParagraph({ node, children }) {
|
|
3391
|
+
const { inline } = useChatMessageMarkdownRuntime();
|
|
3392
|
+
return inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", {
|
|
3211
3393
|
"data-chat-image-row": isSingleLineImageParagraph(node) ? "three-column" : void 0,
|
|
3212
3394
|
children
|
|
3213
|
-
})
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3395
|
+
});
|
|
3396
|
+
},
|
|
3397
|
+
span: function ChatMarkdownSpan({ node: _node, children, ...rest }) {
|
|
3398
|
+
const { isUser, onInlineTokenClick } = useChatMessageMarkdownRuntime();
|
|
3399
|
+
const restProps = rest;
|
|
3400
|
+
const kind = readStringProp(restProps, INLINE_TOKEN_KIND_ATTR);
|
|
3401
|
+
const key = readStringProp(restProps, INLINE_TOKEN_KEY_ATTR);
|
|
3402
|
+
const label = readStringProp(restProps, INLINE_TOKEN_LABEL_ATTR);
|
|
3403
|
+
const rawText = readStringProp(restProps, INLINE_TOKEN_RAW_TEXT_ATTR);
|
|
3404
|
+
if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
|
|
3405
|
+
kind,
|
|
3406
|
+
label,
|
|
3407
|
+
isUser,
|
|
3408
|
+
onClick: onInlineTokenClick ? () => onInlineTokenClick({
|
|
3221
3409
|
kind,
|
|
3410
|
+
key,
|
|
3222
3411
|
label,
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
const external = safeHref ? isExternalChatResourceHref(safeHref) : false;
|
|
3239
|
-
const localFileAction = external ? null : safeHref ? parseChatLocalFileAction(safeHref) : null;
|
|
3240
|
-
const handleClick = (event) => {
|
|
3241
|
-
if (!safeHref) {
|
|
3242
|
-
event.preventDefault();
|
|
3243
|
-
return;
|
|
3244
|
-
}
|
|
3245
|
-
if (!onFileOpen || !localFileAction) return;
|
|
3246
|
-
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3412
|
+
rawText
|
|
3413
|
+
}) : void 0
|
|
3414
|
+
});
|
|
3415
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3416
|
+
...rest,
|
|
3417
|
+
children
|
|
3418
|
+
});
|
|
3419
|
+
},
|
|
3420
|
+
a: function ChatMarkdownLink({ node: _node, href, children, ...rest }) {
|
|
3421
|
+
const { onFileOpen } = useChatMessageMarkdownRuntime();
|
|
3422
|
+
const safeHref = resolveSafeChatResourceHref(href);
|
|
3423
|
+
const external = safeHref ? isExternalChatResourceHref(safeHref) : false;
|
|
3424
|
+
const localFileAction = external ? null : safeHref ? parseChatLocalFileAction(safeHref) : null;
|
|
3425
|
+
const handleClick = (event) => {
|
|
3426
|
+
if (!safeHref) {
|
|
3247
3427
|
event.preventDefault();
|
|
3248
|
-
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3428
|
+
return;
|
|
3429
|
+
}
|
|
3430
|
+
if (!onFileOpen || !localFileAction) return;
|
|
3431
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3432
|
+
event.preventDefault();
|
|
3433
|
+
onFileOpen(localFileAction);
|
|
3434
|
+
};
|
|
3435
|
+
return /* @__PURE__ */ jsx("a", {
|
|
3436
|
+
...rest,
|
|
3437
|
+
className: cn(rest.className, !safeHref && "chat-link-invalid"),
|
|
3438
|
+
href: safeHref ?? "#",
|
|
3439
|
+
"aria-disabled": !safeHref || void 0,
|
|
3440
|
+
onClick: handleClick,
|
|
3441
|
+
target: external ? "_blank" : void 0,
|
|
3442
|
+
rel: external ? "noreferrer noopener" : void 0,
|
|
3443
|
+
children
|
|
3444
|
+
});
|
|
3445
|
+
},
|
|
3446
|
+
table: function ChatMarkdownTable({ node: _node, children, ...rest }) {
|
|
3447
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3262
3448
|
className: "chat-table-wrap",
|
|
3263
3449
|
children: /* @__PURE__ */ jsx("table", {
|
|
3264
3450
|
...rest,
|
|
3265
3451
|
children
|
|
3266
3452
|
})
|
|
3267
|
-
})
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3453
|
+
});
|
|
3454
|
+
},
|
|
3455
|
+
input: function ChatMarkdownInput({ node: _node, type, checked, ...rest }) {
|
|
3456
|
+
if (type !== "checkbox") return /* @__PURE__ */ jsx("input", {
|
|
3457
|
+
...rest,
|
|
3458
|
+
type
|
|
3459
|
+
});
|
|
3460
|
+
return /* @__PURE__ */ jsx("input", {
|
|
3461
|
+
...rest,
|
|
3462
|
+
type: "checkbox",
|
|
3463
|
+
checked,
|
|
3464
|
+
readOnly: true,
|
|
3465
|
+
disabled: true,
|
|
3466
|
+
className: "chat-task-checkbox"
|
|
3467
|
+
});
|
|
3468
|
+
},
|
|
3469
|
+
img: function ChatMarkdownImage({ node: _node, src, alt }) {
|
|
3470
|
+
const { resolveFileContentUrl, texts } = useChatMessageMarkdownRuntime();
|
|
3471
|
+
const safeSrc = resolveSafeChatResourceHref(src);
|
|
3472
|
+
if (!safeSrc) return null;
|
|
3473
|
+
const localFileAction = parseChatLocalFileAction(safeSrc);
|
|
3474
|
+
const resolvedSrc = localFileAction && resolveFileContentUrl ? resolveFileContentUrl(localFileAction) : safeSrc;
|
|
3475
|
+
if (!resolvedSrc) return null;
|
|
3476
|
+
return /* @__PURE__ */ jsx(ChatMessageImagePreview, {
|
|
3477
|
+
alt: alt || "",
|
|
3478
|
+
closeLabel: texts.attachmentCloseLabel ?? "Close preview",
|
|
3479
|
+
expandLabel: texts.attachmentExpandLabel ?? "Expand image",
|
|
3480
|
+
sizeLabel: null,
|
|
3481
|
+
src: resolvedSrc
|
|
3482
|
+
});
|
|
3483
|
+
},
|
|
3484
|
+
code: function ChatMarkdownCode({ node: _node, className, children, ...rest }) {
|
|
3485
|
+
const { isStreaming, renderInlineDisplay, texts } = useChatMessageMarkdownRuntime();
|
|
3486
|
+
const plainText = String(children ?? "");
|
|
3487
|
+
if (!className && !plainText.includes("\n")) return /* @__PURE__ */ jsx("code", {
|
|
3488
|
+
...rest,
|
|
3489
|
+
className: cn("chat-inline-code", className),
|
|
3490
|
+
children
|
|
3491
|
+
});
|
|
3492
|
+
const inlineDisplay = isChatInlineDisplayLanguage(className) ? parseChatInlineDisplayDirective(plainText) : null;
|
|
3493
|
+
if (inlineDisplay) return /* @__PURE__ */ jsx(ChatInlineDisplay, {
|
|
3494
|
+
display: inlineDisplay,
|
|
3495
|
+
renderInlineDisplay
|
|
3496
|
+
});
|
|
3497
|
+
if (className?.split(" ").includes("language-mermaid")) return /* @__PURE__ */ jsx(ChatMermaidDiagram, {
|
|
3498
|
+
isStreaming,
|
|
3499
|
+
source: plainText,
|
|
3500
|
+
texts
|
|
3501
|
+
});
|
|
3502
|
+
return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3503
|
+
className,
|
|
3504
|
+
texts,
|
|
3505
|
+
children
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
function ChatMessageMarkdown({ text, role, texts, inline = false, isStreaming = false, inlineTokens, onFileOpen, onInlineTokenClick, resolveFileContentUrl, renderInlineDisplay }) {
|
|
3510
|
+
const isUser = role === "user";
|
|
3511
|
+
const remarkPlugins = inlineTokens?.length ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm];
|
|
3512
|
+
const WrapperTag = inline ? "span" : "div";
|
|
3513
|
+
return /* @__PURE__ */ jsx(ChatMessageMarkdownRuntimeContext.Provider, {
|
|
3514
|
+
value: {
|
|
3515
|
+
inline,
|
|
3516
|
+
isStreaming,
|
|
3517
|
+
isUser,
|
|
3518
|
+
onFileOpen,
|
|
3519
|
+
onInlineTokenClick,
|
|
3520
|
+
renderInlineDisplay,
|
|
3521
|
+
resolveFileContentUrl,
|
|
3522
|
+
texts
|
|
3295
3523
|
},
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
display: inlineDisplay,
|
|
3306
|
-
renderInlineDisplay
|
|
3307
|
-
});
|
|
3308
|
-
return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3309
|
-
className,
|
|
3310
|
-
texts,
|
|
3311
|
-
children
|
|
3312
|
-
});
|
|
3313
|
-
}
|
|
3314
|
-
}), [
|
|
3315
|
-
inline,
|
|
3316
|
-
isUser,
|
|
3317
|
-
onFileOpen,
|
|
3318
|
-
onInlineTokenClick,
|
|
3319
|
-
renderInlineDisplay,
|
|
3320
|
-
resolveFileContentUrl,
|
|
3321
|
-
texts
|
|
3322
|
-
]);
|
|
3323
|
-
return /* @__PURE__ */ jsx(inline ? "span" : "div", {
|
|
3324
|
-
className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
|
|
3325
|
-
children: /* @__PURE__ */ jsx(ReactMarkdown, {
|
|
3326
|
-
skipHtml: true,
|
|
3327
|
-
remarkPlugins,
|
|
3328
|
-
components: markdownComponents,
|
|
3329
|
-
children: trimMarkdown(text)
|
|
3524
|
+
children: /* @__PURE__ */ jsx(WrapperTag, {
|
|
3525
|
+
className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
|
|
3526
|
+
children: /* @__PURE__ */ jsx(ReactMarkdown, {
|
|
3527
|
+
skipHtml: true,
|
|
3528
|
+
remarkPlugins,
|
|
3529
|
+
components: CHAT_MESSAGE_MARKDOWN_COMPONENTS,
|
|
3530
|
+
urlTransform: transformChatResourceHref,
|
|
3531
|
+
children: trimMarkdown(text)
|
|
3532
|
+
})
|
|
3330
3533
|
})
|
|
3331
3534
|
});
|
|
3332
3535
|
}
|
|
@@ -4210,8 +4413,10 @@ const FILE_TEXT_CLASS_NAME = "font-mono text-[11px] leading-5";
|
|
|
4210
4413
|
const FILE_ROW_CLASS_NAME = `flex h-5 w-full ${FILE_TEXT_CLASS_NAME}`;
|
|
4211
4414
|
const FILE_LINE_NUMBER_CELL_CLASS_NAME = "flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
|
|
4212
4415
|
function readVisibleLineNumber(line) {
|
|
4213
|
-
|
|
4214
|
-
|
|
4416
|
+
return String(line.newLineNumber ?? line.oldLineNumber ?? "");
|
|
4417
|
+
}
|
|
4418
|
+
function isTargetLine(line, targetLine) {
|
|
4419
|
+
return typeof targetLine === "number" && (line.newLineNumber ?? line.oldLineNumber) === targetLine;
|
|
4215
4420
|
}
|
|
4216
4421
|
function readLineKey(prefix, line, index) {
|
|
4217
4422
|
return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
|
|
@@ -4256,25 +4461,35 @@ function getCodeRowTone(line) {
|
|
|
4256
4461
|
if (line.kind === "add") return "bg-emerald-50 text-emerald-950";
|
|
4257
4462
|
return "bg-card text-foreground";
|
|
4258
4463
|
}
|
|
4259
|
-
function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
|
|
4464
|
+
function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth, target, targetRef }) {
|
|
4260
4465
|
return /* @__PURE__ */ jsx("span", {
|
|
4466
|
+
ref: targetRef,
|
|
4261
4467
|
"data-file-line-number-cell": "true",
|
|
4262
4468
|
style: layout === "compact" ? {
|
|
4263
4469
|
width: lineNumberColumnWidth,
|
|
4264
4470
|
minWidth: lineNumberColumnWidth
|
|
4265
4471
|
} : void 0,
|
|
4266
|
-
className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line)),
|
|
4472
|
+
className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line), target ? "bg-gray-200 font-medium text-gray-950" : null),
|
|
4267
4473
|
children: readVisibleLineNumber(line)
|
|
4268
4474
|
});
|
|
4269
4475
|
}
|
|
4270
|
-
function FileOperationCodeCell({ language, line }) {
|
|
4476
|
+
function FileOperationCodeCell({ language, line, target, targetColumn, targetRef }) {
|
|
4271
4477
|
const highlightedCode = useMemo(() => chatCodeSyntaxHighlighter.highlight(line.text || " ", language), [language, line.text]);
|
|
4272
|
-
|
|
4478
|
+
const visibleTargetColumn = typeof targetColumn === "number" && Number.isSafeInteger(targetColumn) && targetColumn > 0 ? Math.min(targetColumn, line.text.length + 1) : null;
|
|
4479
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4480
|
+
ref: visibleTargetColumn ? void 0 : targetRef,
|
|
4273
4481
|
"data-file-code-row": "true",
|
|
4274
4482
|
"data-file-code-language": highlightedCode.language,
|
|
4275
4483
|
"data-highlighted": highlightedCode.highlighted ? "true" : "false",
|
|
4276
|
-
|
|
4277
|
-
|
|
4484
|
+
"data-file-target-column": visibleTargetColumn ?? void 0,
|
|
4485
|
+
className: cn("chat-file-code-syntax hljs relative block min-w-0 flex-1 whitespace-pre px-2.5", readLanguageClassName(highlightedCode.language), getCodeRowTone(line), target ? "bg-gray-100/90" : null),
|
|
4486
|
+
children: [/* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: highlightedCode.html } }), visibleTargetColumn ? /* @__PURE__ */ jsx("span", {
|
|
4487
|
+
ref: targetRef,
|
|
4488
|
+
"aria-hidden": "true",
|
|
4489
|
+
"data-file-target-caret": "true",
|
|
4490
|
+
className: "pointer-events-none absolute inset-y-0 w-px bg-primary",
|
|
4491
|
+
style: { left: `calc(0.625rem + ${visibleTargetColumn - 1}ch)` }
|
|
4492
|
+
}) : null]
|
|
4278
4493
|
});
|
|
4279
4494
|
}
|
|
4280
4495
|
function FileOperationLineRow({ language, line, showLineNumbers, lineNumberColumnWidth }) {
|
|
@@ -4291,11 +4506,22 @@ function FileOperationLineRow({ language, line, showLineNumbers, lineNumberColum
|
|
|
4291
4506
|
})]
|
|
4292
4507
|
});
|
|
4293
4508
|
}
|
|
4294
|
-
function FileOperationWorkspaceSurface({ block }) {
|
|
4509
|
+
function FileOperationWorkspaceSurface({ block, targetColumn, targetLine }) {
|
|
4295
4510
|
const showLineNumbers = readHasBlockLineNumbers(block);
|
|
4296
4511
|
const lineNumberColumnWidth = readLineNumberColumnWidth(block);
|
|
4297
4512
|
const codeColumnWidth = readCodeColumnWidth(block);
|
|
4298
4513
|
const language = readBlockLanguage(block);
|
|
4514
|
+
const targetRef = useRef(null);
|
|
4515
|
+
useLayoutEffect(() => {
|
|
4516
|
+
targetRef.current?.scrollIntoView({
|
|
4517
|
+
block: "center",
|
|
4518
|
+
inline: targetColumn ? "center" : "nearest"
|
|
4519
|
+
});
|
|
4520
|
+
}, [
|
|
4521
|
+
block,
|
|
4522
|
+
targetColumn,
|
|
4523
|
+
targetLine
|
|
4524
|
+
]);
|
|
4299
4525
|
return /* @__PURE__ */ jsxs("div", {
|
|
4300
4526
|
"data-file-code-surface": "true",
|
|
4301
4527
|
"data-file-code-surface-layout": "workspace",
|
|
@@ -4310,7 +4536,9 @@ function FileOperationWorkspaceSurface({ block }) {
|
|
|
4310
4536
|
children: [block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
|
|
4311
4537
|
layout: "workspace",
|
|
4312
4538
|
line,
|
|
4313
|
-
lineNumberColumnWidth
|
|
4539
|
+
lineNumberColumnWidth,
|
|
4540
|
+
target: isTargetLine(line, targetLine),
|
|
4541
|
+
targetRef: !targetColumn && isTargetLine(line, targetLine) ? targetRef : void 0
|
|
4314
4542
|
}, readLineKey("gutter", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-r border-border bg-muted" })]
|
|
4315
4543
|
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
4316
4544
|
"data-file-code-canvas": "true",
|
|
@@ -4319,20 +4547,32 @@ function FileOperationWorkspaceSurface({ block }) {
|
|
|
4319
4547
|
"data-file-code-stack": "true",
|
|
4320
4548
|
className: "min-w-full",
|
|
4321
4549
|
style: { minWidth: codeColumnWidth },
|
|
4322
|
-
children: block.lines.map((line, index) =>
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4550
|
+
children: block.lines.map((line, index) => {
|
|
4551
|
+
const target = isTargetLine(line, targetLine);
|
|
4552
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4553
|
+
"data-file-code-canvas-row": "true",
|
|
4554
|
+
"data-file-target-line": target ? "true" : void 0,
|
|
4555
|
+
"aria-current": target ? "location" : void 0,
|
|
4556
|
+
className: FILE_ROW_CLASS_NAME,
|
|
4557
|
+
children: /* @__PURE__ */ jsx(FileOperationCodeCell, {
|
|
4558
|
+
language,
|
|
4559
|
+
line,
|
|
4560
|
+
target,
|
|
4561
|
+
targetColumn: target ? targetColumn : null,
|
|
4562
|
+
targetRef: target && (targetColumn || !showLineNumbers) ? targetRef : void 0
|
|
4563
|
+
})
|
|
4564
|
+
}, readLineKey("code", line, index));
|
|
4565
|
+
})
|
|
4330
4566
|
}), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-card" })]
|
|
4331
4567
|
})]
|
|
4332
4568
|
});
|
|
4333
4569
|
}
|
|
4334
|
-
function FileOperationCodeSurface({ block, layout = "compact" }) {
|
|
4335
|
-
if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, {
|
|
4570
|
+
function FileOperationCodeSurface({ block, layout = "compact", targetColumn, targetLine }) {
|
|
4571
|
+
if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, {
|
|
4572
|
+
block,
|
|
4573
|
+
targetColumn,
|
|
4574
|
+
targetLine
|
|
4575
|
+
});
|
|
4336
4576
|
const showLineNumbers = readHasBlockLineNumbers(block);
|
|
4337
4577
|
const lineNumberColumnWidth = readLineNumberColumnWidth(block);
|
|
4338
4578
|
const codeColumnWidth = readCodeColumnWidth(block);
|
|
@@ -5243,6 +5483,9 @@ const MAX_SUMMARY_SEGMENTS = 3;
|
|
|
5243
5483
|
function isToolCardPart(part) {
|
|
5244
5484
|
return part.type === "tool-card";
|
|
5245
5485
|
}
|
|
5486
|
+
function isGroupableToolPart(part) {
|
|
5487
|
+
return part?.type === "reasoning" || part?.type === "tool-card" && !part.card.panelApp;
|
|
5488
|
+
}
|
|
5246
5489
|
function resolveToolActivityFamily(toolName) {
|
|
5247
5490
|
const lowered = toolName.toLowerCase();
|
|
5248
5491
|
if (lowered === "list_dir") return "directory";
|
|
@@ -5321,17 +5564,29 @@ function formatToolActivityGroupLabel(params) {
|
|
|
5321
5564
|
if (hiddenCount > 0) parts.push(`+${hiddenCount}`);
|
|
5322
5565
|
return parts.join(" · ");
|
|
5323
5566
|
}
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5567
|
+
function collectToolGroupParts(parts, startIndex) {
|
|
5568
|
+
const candidateParts = [];
|
|
5569
|
+
let index = startIndex;
|
|
5570
|
+
let lastToolOffset = -1;
|
|
5571
|
+
while (true) {
|
|
5572
|
+
const candidate = parts[index];
|
|
5573
|
+
if (!isGroupableToolPart(candidate)) break;
|
|
5574
|
+
candidateParts.push(candidate);
|
|
5575
|
+
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5576
|
+
index += 1;
|
|
5577
|
+
}
|
|
5578
|
+
return {
|
|
5579
|
+
candidateParts,
|
|
5580
|
+
lastToolOffset
|
|
5581
|
+
};
|
|
5582
|
+
}
|
|
5583
|
+
/** Groups tool cards across reasoning; content and interactive panel apps remain stable boundaries. */
|
|
5329
5584
|
function groupConsecutiveToolParts(parts, labels) {
|
|
5330
5585
|
const blocks = [];
|
|
5331
5586
|
let index = 0;
|
|
5332
5587
|
while (index < parts.length) {
|
|
5333
5588
|
const part = parts[index];
|
|
5334
|
-
if (part.type !== "tool-card") {
|
|
5589
|
+
if (part.type !== "tool-card" || part.card.panelApp) {
|
|
5335
5590
|
blocks.push({
|
|
5336
5591
|
kind: "part",
|
|
5337
5592
|
key: `part-${index}`,
|
|
@@ -5342,14 +5597,7 @@ function groupConsecutiveToolParts(parts, labels) {
|
|
|
5342
5597
|
continue;
|
|
5343
5598
|
}
|
|
5344
5599
|
const startIndex = index;
|
|
5345
|
-
const candidateParts =
|
|
5346
|
-
let lastToolOffset = -1;
|
|
5347
|
-
while (index < parts.length && (parts[index]?.type === "tool-card" || parts[index]?.type === "reasoning")) {
|
|
5348
|
-
const candidate = parts[index];
|
|
5349
|
-
candidateParts.push(candidate);
|
|
5350
|
-
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5351
|
-
index += 1;
|
|
5352
|
-
}
|
|
5600
|
+
const { candidateParts, lastToolOffset } = collectToolGroupParts(parts, startIndex);
|
|
5353
5601
|
const toolParts = candidateParts.filter(isToolCardPart);
|
|
5354
5602
|
if (toolParts.length === 1) {
|
|
5355
5603
|
blocks.push({
|
|
@@ -5464,6 +5712,7 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onAtta
|
|
|
5464
5712
|
text,
|
|
5465
5713
|
role,
|
|
5466
5714
|
texts,
|
|
5715
|
+
isStreaming: isInProgress && isLastPart,
|
|
5467
5716
|
inlineTokens,
|
|
5468
5717
|
onFileOpen,
|
|
5469
5718
|
onInlineTokenClick,
|
|
@@ -5666,14 +5915,25 @@ function ChatMessageActionCopy({ message, texts }) {
|
|
|
5666
5915
|
}).filter((text) => !!text && text.trim().length > 0).join("\n\n");
|
|
5667
5916
|
}, [message.parts]);
|
|
5668
5917
|
const { copied, copy } = useCopyFeedback({ text: messageText });
|
|
5918
|
+
const label = copied ? texts.copiedMessageLabel : texts.copyMessageLabel;
|
|
5919
|
+
const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
|
|
5669
5920
|
if (!messageText) return null;
|
|
5670
|
-
return /* @__PURE__ */ jsx(
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5921
|
+
return /* @__PURE__ */ jsx(TooltipProvider, {
|
|
5922
|
+
delayDuration: 250,
|
|
5923
|
+
children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
5924
|
+
asChild: true,
|
|
5925
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
5926
|
+
type: "button",
|
|
5927
|
+
onClick: () => void copy(),
|
|
5928
|
+
className: "flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
|
|
5929
|
+
"aria-label": label,
|
|
5930
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
|
|
5931
|
+
})
|
|
5932
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
5933
|
+
side: "top",
|
|
5934
|
+
className: "text-xs",
|
|
5935
|
+
children: label
|
|
5936
|
+
})] })
|
|
5677
5937
|
});
|
|
5678
5938
|
}
|
|
5679
5939
|
//#endregion
|
|
@@ -5824,10 +6084,10 @@ function ChatMessageList({ className, isSending, layout = "card", messages, onAt
|
|
|
5824
6084
|
" · ",
|
|
5825
6085
|
message.timestampLabel
|
|
5826
6086
|
]
|
|
5827
|
-
}),
|
|
6087
|
+
}), /* @__PURE__ */ jsx(ChatMessageActionCopy, {
|
|
5828
6088
|
message,
|
|
5829
6089
|
texts
|
|
5830
|
-
})
|
|
6090
|
+
})] })
|
|
5831
6091
|
})]
|
|
5832
6092
|
}),
|
|
5833
6093
|
isUser ? /* @__PURE__ */ jsx(ChatMessageAvatar, { role: message.role }) : null
|