@nextclaw/agent-chat-ui 0.3.16 → 0.3.18
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 +2 -0
- package/dist/index.js +91 -21
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -146,6 +146,7 @@ type ChatSlashMenuProps = {
|
|
|
146
146
|
texts: Pick<ChatTexts, "slashLoadingLabel" | "slashSectionLabel" | "slashEmptyLabel" | "slashHintLabel" | "slashSkillHintLabel">;
|
|
147
147
|
onSelectItem: (item: ChatSlashItem) => void;
|
|
148
148
|
onOpenChange: (open: boolean) => void;
|
|
149
|
+
onDetailsPointerDown?: () => void;
|
|
149
150
|
onSetActiveIndex: (index: number) => void;
|
|
150
151
|
};
|
|
151
152
|
type ChatInputBarProps = {
|
|
@@ -306,6 +307,7 @@ type ChatInputBarHandle = {
|
|
|
306
307
|
label: string;
|
|
307
308
|
}>) => void;
|
|
308
309
|
focusComposer: () => void;
|
|
310
|
+
focusComposerAtEnd: () => void;
|
|
309
311
|
};
|
|
310
312
|
declare const ChatInputBar: _$react.ForwardRefExoticComponent<ChatInputBarProps & _$react.RefAttributes<ChatInputBarHandle>>;
|
|
311
313
|
//#endregion
|
package/dist/index.js
CHANGED
|
@@ -208,7 +208,7 @@ function ChatSlashMenu(props) {
|
|
|
208
208
|
const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
|
|
209
209
|
const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
|
|
210
210
|
const listRef = useRef(null);
|
|
211
|
-
const { isOpen, isLoading, items, activeIndex, activeItem, texts, onSelectItem, onOpenChange, onSetActiveIndex } = props;
|
|
211
|
+
const { isOpen, isLoading, items, activeIndex, activeItem, texts, onSelectItem, onOpenChange, onDetailsPointerDown, onSetActiveIndex } = props;
|
|
212
212
|
const resolvedWidth = useMemo(() => {
|
|
213
213
|
if (!panelWidth) return;
|
|
214
214
|
return Math.min(panelWidth > SLASH_PANEL_DESKTOP_MIN_WIDTH ? panelWidth * SLASH_PANEL_DESKTOP_SHRINK_RATIO : panelWidth, SLASH_PANEL_MAX_WIDTH);
|
|
@@ -276,6 +276,7 @@ function ChatSlashMenu(props) {
|
|
|
276
276
|
})] })
|
|
277
277
|
}), /* @__PURE__ */ jsx("div", {
|
|
278
278
|
className: "min-w-0 p-2.5",
|
|
279
|
+
onPointerDown: onDetailsPointerDown,
|
|
279
280
|
children: activeItem ? /* @__PURE__ */ jsxs("div", {
|
|
280
281
|
className: "space-y-3",
|
|
281
282
|
children: [
|
|
@@ -630,13 +631,11 @@ function ChatInputBarSkillPicker(props) {
|
|
|
630
631
|
visibleIndex += 1;
|
|
631
632
|
const isSelected = selectedSet.has(option.key);
|
|
632
633
|
const isActive = index === activeIndex;
|
|
633
|
-
return /* @__PURE__ */ jsxs("
|
|
634
|
-
type: "button",
|
|
634
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
635
635
|
id: `${listId}-option-${index}`,
|
|
636
636
|
role: "option",
|
|
637
637
|
"aria-selected": isSelected,
|
|
638
638
|
"data-skill-index": index,
|
|
639
|
-
onClick: () => onToggleOption(option.key),
|
|
640
639
|
onMouseEnter: () => setActiveIndex(index),
|
|
641
640
|
className: `flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors ${isActive ? "bg-gray-50" : "hover:bg-gray-50"}`,
|
|
642
641
|
children: [
|
|
@@ -645,7 +644,7 @@ function ChatInputBarSkillPicker(props) {
|
|
|
645
644
|
children: /* @__PURE__ */ jsx(Puzzle, { className: "h-4 w-4" })
|
|
646
645
|
}),
|
|
647
646
|
/* @__PURE__ */ jsxs("div", {
|
|
648
|
-
className: "min-w-0 flex-1",
|
|
647
|
+
className: "min-w-0 flex-1 select-text",
|
|
649
648
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
650
649
|
className: "flex items-center gap-1.5",
|
|
651
650
|
children: [/* @__PURE__ */ jsx("span", {
|
|
@@ -662,7 +661,10 @@ function ChatInputBarSkillPicker(props) {
|
|
|
662
661
|
}),
|
|
663
662
|
/* @__PURE__ */ jsx("div", {
|
|
664
663
|
className: "ml-3 shrink-0",
|
|
665
|
-
children: /* @__PURE__ */ jsx("
|
|
664
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
665
|
+
type: "button",
|
|
666
|
+
"aria-label": `${isSelected ? "Remove" : "Add"} ${option.label}`,
|
|
667
|
+
onClick: () => onToggleOption(option.key),
|
|
666
668
|
className: isSelected ? "inline-flex h-5 w-5 items-center justify-center rounded-full bg-primary text-white" : "inline-flex h-5 w-5 items-center justify-center rounded-full border border-gray-300 bg-white",
|
|
667
669
|
children: isSelected ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : null
|
|
668
670
|
})
|
|
@@ -1510,6 +1512,9 @@ var LexicalComposerHandleOwner = class {
|
|
|
1510
1512
|
this.focusComposer = () => {
|
|
1511
1513
|
this.params.focusComposer();
|
|
1512
1514
|
};
|
|
1515
|
+
this.focusComposerAtEnd = () => {
|
|
1516
|
+
this.params.focusComposerAtEnd();
|
|
1517
|
+
};
|
|
1513
1518
|
this.syncSelectedSkills = (nextKeys, options) => {
|
|
1514
1519
|
this.params.publishSnapshot(syncSelectedSkillsIntoChatComposer({
|
|
1515
1520
|
nextKeys,
|
|
@@ -1735,10 +1740,23 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
|
|
|
1735
1740
|
const focusComposer = () => {
|
|
1736
1741
|
const editor = editorRef.current;
|
|
1737
1742
|
if (!editor) return;
|
|
1743
|
+
editor.getRootElement()?.focus({ preventScroll: true });
|
|
1738
1744
|
const targetSelection = selectionRef.current;
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1745
|
+
if (targetSelection) syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
1746
|
+
editor.focus();
|
|
1747
|
+
};
|
|
1748
|
+
const focusComposerAtEnd = () => {
|
|
1749
|
+
const editor = editorRef.current;
|
|
1750
|
+
if (!editor) return;
|
|
1751
|
+
const end = readChatComposerSnapshotFromEditorState(editor.getEditorState()).nodes.reduce((cursor, node) => cursor + (node.type === "text" ? node.text.length : 1), 0);
|
|
1752
|
+
const targetSelection = {
|
|
1753
|
+
start: end,
|
|
1754
|
+
end
|
|
1755
|
+
};
|
|
1756
|
+
selectionRef.current = targetSelection;
|
|
1757
|
+
editor.getRootElement()?.focus({ preventScroll: true });
|
|
1758
|
+
syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
1759
|
+
editor.focus();
|
|
1742
1760
|
};
|
|
1743
1761
|
const readComposerSnapshot = () => ({
|
|
1744
1762
|
nodes: readCurrentNodes(),
|
|
@@ -1746,6 +1764,7 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
|
|
|
1746
1764
|
});
|
|
1747
1765
|
useImperativeHandle(ref, () => createLexicalComposerHandle({
|
|
1748
1766
|
focusComposer,
|
|
1767
|
+
focusComposerAtEnd,
|
|
1749
1768
|
onSlashItemSelect: props.onSlashItemSelect,
|
|
1750
1769
|
optionsReader: readComposerSnapshot,
|
|
1751
1770
|
publishSnapshot
|
|
@@ -1879,21 +1898,19 @@ function InputBarHint({ hint }) {
|
|
|
1879
1898
|
}
|
|
1880
1899
|
const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
1881
1900
|
const composerRef = useRef(null);
|
|
1882
|
-
const
|
|
1901
|
+
const isSlashMenuInteractionRef = useRef(false);
|
|
1883
1902
|
const [activeSlashIndex, setActiveSlashIndex] = useState(0);
|
|
1884
1903
|
const [activeSlashTriggerStart, setActiveSlashTriggerStart] = useState(null);
|
|
1885
1904
|
const [dismissedSlashTriggerStart, setDismissedSlashTriggerStart] = useState(null);
|
|
1886
1905
|
const isSlashPanelOpen = activeSlashTriggerStart !== null && dismissedSlashTriggerStart !== activeSlashTriggerStart;
|
|
1887
1906
|
const activeSlashItem = props.slashMenu.items[activeSlashIndex] ?? null;
|
|
1907
|
+
const dismissSlashTrigger = () => activeSlashTriggerStart !== null && !isSlashMenuInteractionRef.current ? setDismissedSlashTriggerStart(activeSlashTriggerStart) : void 0;
|
|
1888
1908
|
useEffect(() => {
|
|
1889
1909
|
setActiveSlashIndex((current) => {
|
|
1890
1910
|
if (props.slashMenu.items.length === 0) return 0;
|
|
1891
1911
|
return Math.min(current, props.slashMenu.items.length - 1);
|
|
1892
1912
|
});
|
|
1893
1913
|
}, [props.slashMenu.items.length]);
|
|
1894
|
-
useEffect(() => {
|
|
1895
|
-
if (slashQuery !== null) setActiveSlashIndex(0);
|
|
1896
|
-
}, [slashQuery]);
|
|
1897
1914
|
useEffect(() => {
|
|
1898
1915
|
if (activeSlashTriggerStart === null && dismissedSlashTriggerStart !== null) setDismissedSlashTriggerStart(null);
|
|
1899
1916
|
}, [activeSlashTriggerStart, dismissedSlashTriggerStart]);
|
|
@@ -1913,7 +1930,8 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
|
1913
1930
|
useImperativeHandle(ref, () => ({
|
|
1914
1931
|
insertFileToken: (tokenKey, label) => composerRef.current?.insertFileToken(tokenKey, label),
|
|
1915
1932
|
insertFileTokens: (tokens) => composerRef.current?.insertFileTokens(tokens),
|
|
1916
|
-
focusComposer: () => composerRef.current?.focusComposer()
|
|
1933
|
+
focusComposer: () => composerRef.current?.focusComposer(),
|
|
1934
|
+
focusComposerAtEnd: () => composerRef.current?.focusComposerAtEnd()
|
|
1917
1935
|
}), []);
|
|
1918
1936
|
return /* @__PURE__ */ jsx("div", {
|
|
1919
1937
|
className: "border-t border-gray-200/80 bg-white p-4",
|
|
@@ -1936,14 +1954,15 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
|
1936
1954
|
onNodesChange: props.composer.onNodesChange,
|
|
1937
1955
|
onFilesAdd: props.composer.onFilesAdd,
|
|
1938
1956
|
onSlashQueryChange: (query) => {
|
|
1939
|
-
|
|
1957
|
+
if (query === null && isSlashMenuInteractionRef.current) return;
|
|
1958
|
+
if (query !== null) setActiveSlashIndex(0);
|
|
1940
1959
|
props.composer.onSlashQueryChange?.(query);
|
|
1941
1960
|
},
|
|
1942
1961
|
onSlashTriggerChange: (trigger) => {
|
|
1943
1962
|
setActiveSlashTriggerStart(trigger?.start ?? null);
|
|
1944
1963
|
},
|
|
1945
1964
|
onSlashOpenChange: (open) => {
|
|
1946
|
-
if (!open
|
|
1965
|
+
if (!open) dismissSlashTrigger();
|
|
1947
1966
|
},
|
|
1948
1967
|
onSlashActiveIndexChange: setActiveSlashIndex
|
|
1949
1968
|
}), /* @__PURE__ */ jsx(ChatSlashMenu, {
|
|
@@ -1958,7 +1977,13 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
|
1958
1977
|
composerRef.current?.insertSlashItem(item);
|
|
1959
1978
|
},
|
|
1960
1979
|
onOpenChange: (open) => {
|
|
1961
|
-
if (!open
|
|
1980
|
+
if (!open) dismissSlashTrigger();
|
|
1981
|
+
},
|
|
1982
|
+
onDetailsPointerDown: () => {
|
|
1983
|
+
isSlashMenuInteractionRef.current = true;
|
|
1984
|
+
requestAnimationFrame(() => {
|
|
1985
|
+
isSlashMenuInteractionRef.current = false;
|
|
1986
|
+
});
|
|
1962
1987
|
},
|
|
1963
1988
|
onSetActiveIndex: setActiveSlashIndex
|
|
1964
1989
|
})]
|
|
@@ -3785,6 +3810,45 @@ function ChatMessageActionCopy({ message, texts }) {
|
|
|
3785
3810
|
//#endregion
|
|
3786
3811
|
//#region src/components/chat/ui/chat-message-list/chat-message-list.tsx
|
|
3787
3812
|
const INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
|
|
3813
|
+
const TYPING_TEXT_SHEEN_CSS = `
|
|
3814
|
+
@keyframes nextclaw-chat-typing-text-sheen {
|
|
3815
|
+
0% {
|
|
3816
|
+
background-position: 160% 0;
|
|
3817
|
+
}
|
|
3818
|
+
100% {
|
|
3819
|
+
background-position: -60% 0;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
.nextclaw-chat-typing-indicator__text {
|
|
3824
|
+
background-image: linear-gradient(
|
|
3825
|
+
100deg,
|
|
3826
|
+
#6b7280 0%,
|
|
3827
|
+
#6b7280 34%,
|
|
3828
|
+
#a6adba 43%,
|
|
3829
|
+
#f8fafc 50%,
|
|
3830
|
+
#a6adba 57%,
|
|
3831
|
+
#6b7280 66%,
|
|
3832
|
+
#6b7280 100%
|
|
3833
|
+
);
|
|
3834
|
+
background-size: 240% 100%;
|
|
3835
|
+
background-position: 160% 0;
|
|
3836
|
+
-webkit-background-clip: text;
|
|
3837
|
+
background-clip: text;
|
|
3838
|
+
color: transparent;
|
|
3839
|
+
-webkit-text-fill-color: transparent;
|
|
3840
|
+
animation: nextclaw-chat-typing-text-sheen 2.5s linear infinite;
|
|
3841
|
+
}
|
|
3842
|
+
|
|
3843
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3844
|
+
.nextclaw-chat-typing-indicator__text {
|
|
3845
|
+
animation: none;
|
|
3846
|
+
background-image: none;
|
|
3847
|
+
color: #6b7280;
|
|
3848
|
+
-webkit-text-fill-color: currentColor;
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
`;
|
|
3788
3852
|
function hasRenderableText(value) {
|
|
3789
3853
|
const trimmed = value.trim();
|
|
3790
3854
|
if (!trimmed) return false;
|
|
@@ -3809,6 +3873,15 @@ function ChatMessageTypingFooter() {
|
|
|
3809
3873
|
})
|
|
3810
3874
|
});
|
|
3811
3875
|
}
|
|
3876
|
+
function ChatTypingIndicator({ label }) {
|
|
3877
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3878
|
+
className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm",
|
|
3879
|
+
children: [/* @__PURE__ */ jsx("style", { children: TYPING_TEXT_SHEEN_CSS }), /* @__PURE__ */ jsx("span", {
|
|
3880
|
+
className: "nextclaw-chat-typing-indicator__text",
|
|
3881
|
+
children: label
|
|
3882
|
+
})]
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3812
3885
|
function ChatMessageList(props) {
|
|
3813
3886
|
const visibleMessages = props.messages.filter(hasRenderableMessageContent);
|
|
3814
3887
|
const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
|
|
@@ -3846,10 +3919,7 @@ function ChatMessageList(props) {
|
|
|
3846
3919
|
}, message.id);
|
|
3847
3920
|
}), props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs("div", {
|
|
3848
3921
|
className: "flex justify-start gap-3",
|
|
3849
|
-
children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx(
|
|
3850
|
-
className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm",
|
|
3851
|
-
children: props.texts.typingLabel
|
|
3852
|
-
})]
|
|
3922
|
+
children: [/* @__PURE__ */ jsx(ChatMessageAvatar, { role: "assistant" }), /* @__PURE__ */ jsx(ChatTypingIndicator, { label: props.texts.typingLabel })]
|
|
3853
3923
|
}) : null]
|
|
3854
3924
|
});
|
|
3855
3925
|
}
|