@nextclaw/agent-chat-ui 0.3.2 → 0.3.3
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 +4 -0
- package/dist/index.js +1035 -950
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -5,29 +5,38 @@ import { twMerge } from "tailwind-merge";
|
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
7
7
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
8
|
-
import { AlertTriangle, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, ExternalLink, FileText, Globe, Loader2, Minus, Paperclip, Puzzle, Search, Sparkles, Terminal, User, Wrench } from "lucide-react";
|
|
8
|
+
import { AlertTriangle, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, ExternalLink, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, Globe, Loader2, Minus, Paperclip, Puzzle, Search, Sparkles, Terminal, User, Wrench } from "lucide-react";
|
|
9
9
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
10
10
|
import { cva } from "class-variance-authority";
|
|
11
|
+
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
12
|
+
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
13
|
+
import { EditorRefPlugin } from "@lexical/react/LexicalEditorRefPlugin";
|
|
14
|
+
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
15
|
+
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
|
|
16
|
+
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";
|
|
17
|
+
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
11
18
|
import ReactMarkdown from "react-markdown";
|
|
12
19
|
import remarkGfm from "remark-gfm";
|
|
13
20
|
//#region src/components/chat/hooks/use-active-item-scroll.ts
|
|
14
21
|
const defaultGetItemSelector = (index) => `[data-item-index="${index}"]`;
|
|
15
22
|
function useActiveItemScroll(params) {
|
|
16
|
-
const getItemSelector = params
|
|
23
|
+
const { activeIndex, containerRef, getItemSelector: customGetItemSelector, isEnabled, itemCount } = params;
|
|
24
|
+
const getItemSelector = customGetItemSelector ?? defaultGetItemSelector;
|
|
17
25
|
useEffect(() => {
|
|
18
|
-
if (!
|
|
19
|
-
const container =
|
|
26
|
+
if (!isEnabled || itemCount === 0) return;
|
|
27
|
+
const container = containerRef.current;
|
|
20
28
|
if (!container) return;
|
|
21
|
-
container.querySelector(getItemSelector(
|
|
29
|
+
const activeItem = container.querySelector(getItemSelector(activeIndex));
|
|
30
|
+
if (typeof activeItem?.scrollIntoView === "function") activeItem.scrollIntoView({
|
|
22
31
|
block: "nearest",
|
|
23
32
|
inline: "nearest"
|
|
24
33
|
});
|
|
25
34
|
}, [
|
|
35
|
+
activeIndex,
|
|
36
|
+
containerRef,
|
|
26
37
|
getItemSelector,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
params.isEnabled,
|
|
30
|
-
params.itemCount
|
|
38
|
+
isEnabled,
|
|
39
|
+
itemCount
|
|
31
40
|
]);
|
|
32
41
|
}
|
|
33
42
|
//#endregion
|
|
@@ -841,280 +850,502 @@ function resolveChatComposerSlashTrigger(nodes, selection) {
|
|
|
841
850
|
end: caret
|
|
842
851
|
};
|
|
843
852
|
}
|
|
844
|
-
function isChatComposerSelectionInsideRange(selection, start, end) {
|
|
845
|
-
if (!selection) return false;
|
|
846
|
-
return selection.start < end && selection.end > start;
|
|
847
|
-
}
|
|
848
853
|
//#endregion
|
|
849
|
-
//#region src/components/chat/ui/chat-input-bar/chat-composer-
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
854
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-token-node.tsx
|
|
855
|
+
function buildTokenClassName(tokenKind) {
|
|
856
|
+
if (tokenKind === "file") return [
|
|
857
|
+
"mx-[2px]",
|
|
858
|
+
"inline-flex",
|
|
859
|
+
"h-7",
|
|
860
|
+
"max-w-[min(100%,17rem)]",
|
|
861
|
+
"items-center",
|
|
862
|
+
"gap-1.5",
|
|
863
|
+
"rounded-lg",
|
|
864
|
+
"border",
|
|
865
|
+
"border-slate-200/80",
|
|
866
|
+
"bg-slate-50",
|
|
867
|
+
"px-2",
|
|
868
|
+
"align-baseline",
|
|
869
|
+
"text-slate-700",
|
|
870
|
+
"transition-[border-color,background-color,box-shadow,color]",
|
|
871
|
+
"duration-150"
|
|
872
|
+
].join(" ");
|
|
873
|
+
return [
|
|
874
|
+
"mx-[2px]",
|
|
875
|
+
"inline-flex",
|
|
876
|
+
"h-7",
|
|
877
|
+
"max-w-full",
|
|
878
|
+
"items-center",
|
|
879
|
+
"gap-1.5",
|
|
880
|
+
"rounded-lg",
|
|
881
|
+
"border",
|
|
882
|
+
"border-primary/12",
|
|
883
|
+
"bg-primary/8",
|
|
884
|
+
"px-2",
|
|
885
|
+
"align-baseline",
|
|
886
|
+
"text-[11px]",
|
|
887
|
+
"font-medium",
|
|
888
|
+
"text-primary",
|
|
889
|
+
"transition"
|
|
890
|
+
].join(" ");
|
|
891
|
+
}
|
|
892
|
+
function ChatComposerTokenChip(props) {
|
|
893
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
894
|
+
className: props.tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-white text-slate-500 ring-1 ring-black/5" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70",
|
|
895
|
+
children: props.tokenKind === "file" ? /* @__PURE__ */ jsxs("svg", {
|
|
896
|
+
viewBox: "0 0 16 16",
|
|
897
|
+
fill: "none",
|
|
898
|
+
stroke: "currentColor",
|
|
899
|
+
strokeWidth: "1.25",
|
|
900
|
+
strokeLinecap: "round",
|
|
901
|
+
strokeLinejoin: "round",
|
|
902
|
+
"aria-hidden": "true",
|
|
903
|
+
className: "h-3 w-3",
|
|
904
|
+
children: [
|
|
905
|
+
/* @__PURE__ */ jsx("path", { d: "M3.25 4.25A1.5 1.5 0 0 1 4.75 2.75h6.5a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-6.5a1.5 1.5 0 0 1-1.5-1.5v-7.5Z" }),
|
|
906
|
+
/* @__PURE__ */ jsx("path", { d: "m4.75 10 2.25-2.5 1.75 1.75 1.25-1.25 2 2" }),
|
|
907
|
+
/* @__PURE__ */ jsx("path", { d: "M9.75 6.25h.01" })
|
|
908
|
+
]
|
|
909
|
+
}) : /* @__PURE__ */ jsxs("svg", {
|
|
910
|
+
viewBox: "0 0 16 16",
|
|
911
|
+
fill: "none",
|
|
912
|
+
stroke: "currentColor",
|
|
913
|
+
strokeWidth: "1.25",
|
|
914
|
+
strokeLinecap: "round",
|
|
915
|
+
strokeLinejoin: "round",
|
|
916
|
+
"aria-hidden": "true",
|
|
917
|
+
className: "h-3 w-3",
|
|
918
|
+
children: [
|
|
919
|
+
/* @__PURE__ */ jsx("path", { d: "M8.5 2.75 2.75 6l5.75 3.25L14.25 6 8.5 2.75Z" }),
|
|
920
|
+
/* @__PURE__ */ jsx("path", { d: "M2.75 10 8.5 13.25 14.25 10" }),
|
|
921
|
+
/* @__PURE__ */ jsx("path", { d: "M2.75 6v4l5.75 3.25V9.25L2.75 6Z" }),
|
|
922
|
+
/* @__PURE__ */ jsx("path", { d: "M14.25 6v4L8.5 13.25V9.25L14.25 6Z" })
|
|
923
|
+
]
|
|
924
|
+
})
|
|
925
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
926
|
+
className: props.tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate",
|
|
927
|
+
children: props.label
|
|
928
|
+
})] });
|
|
929
|
+
}
|
|
930
|
+
var ChatComposerTokenNode = class ChatComposerTokenNode extends DecoratorNode {
|
|
931
|
+
static getType() {
|
|
932
|
+
return "chat-composer-token";
|
|
856
933
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
function resolveRootChildIndex(root, target) {
|
|
860
|
-
let current = target;
|
|
861
|
-
while (current && current.parentNode !== root) current = current.parentNode;
|
|
862
|
-
if (!current) return root.childNodes.length;
|
|
863
|
-
return Array.prototype.indexOf.call(root.childNodes, current);
|
|
864
|
-
}
|
|
865
|
-
function findNodeByDomChild(child, index, nodes) {
|
|
866
|
-
if (child instanceof HTMLElement) {
|
|
867
|
-
const nodeId = child.dataset.composerNodeId;
|
|
868
|
-
if (nodeId) return nodes.find((node) => node.id === nodeId);
|
|
934
|
+
static clone(node) {
|
|
935
|
+
return new ChatComposerTokenNode(node.__composerId, node.__tokenKind, node.__tokenKey, node.__label, node.__key);
|
|
869
936
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
937
|
+
static importJSON(serializedNode) {
|
|
938
|
+
return $createChatComposerTokenNode({
|
|
939
|
+
composerId: serializedNode.composerId,
|
|
940
|
+
label: serializedNode.label,
|
|
941
|
+
tokenKey: serializedNode.tokenKey,
|
|
942
|
+
tokenKind: serializedNode.tokenKind
|
|
943
|
+
});
|
|
877
944
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
945
|
+
constructor(composerId, tokenKind, tokenKey, label, key) {
|
|
946
|
+
super(key);
|
|
947
|
+
this.applyTokenDom = (element) => {
|
|
948
|
+
element.contentEditable = "false";
|
|
949
|
+
element.dataset.composerNodeId = this.__composerId;
|
|
950
|
+
element.dataset.composerNodeType = "token";
|
|
951
|
+
element.dataset.composerTokenKind = this.__tokenKind;
|
|
952
|
+
element.dataset.composerTokenKey = this.__tokenKey;
|
|
953
|
+
element.dataset.composerLabel = this.__label;
|
|
954
|
+
element.title = this.__label;
|
|
955
|
+
element.className = buildTokenClassName(this.__tokenKind);
|
|
956
|
+
};
|
|
957
|
+
this.createDOM = (_config, _editor) => {
|
|
958
|
+
const element = document.createElement("span");
|
|
959
|
+
this.applyTokenDom(element);
|
|
960
|
+
return element;
|
|
961
|
+
};
|
|
962
|
+
this.updateDOM = (_prevNode, dom) => {
|
|
963
|
+
this.applyTokenDom(dom);
|
|
964
|
+
return false;
|
|
965
|
+
};
|
|
966
|
+
this.decorate = () => {
|
|
967
|
+
return /* @__PURE__ */ jsx(ChatComposerTokenChip, {
|
|
968
|
+
label: this.__label,
|
|
969
|
+
tokenKind: this.__tokenKind
|
|
970
|
+
});
|
|
971
|
+
};
|
|
972
|
+
this.exportJSON = () => {
|
|
973
|
+
return {
|
|
974
|
+
composerId: this.__composerId,
|
|
975
|
+
label: this.__label,
|
|
976
|
+
tokenKey: this.__tokenKey,
|
|
977
|
+
tokenKind: this.__tokenKind,
|
|
978
|
+
type: "chat-composer-token",
|
|
979
|
+
version: 1
|
|
980
|
+
};
|
|
981
|
+
};
|
|
982
|
+
this.getComposerId = () => {
|
|
983
|
+
return this.getLatest().__composerId;
|
|
984
|
+
};
|
|
985
|
+
this.getTokenKind = () => {
|
|
986
|
+
return this.getLatest().__tokenKind;
|
|
987
|
+
};
|
|
988
|
+
this.getTokenKey = () => {
|
|
989
|
+
return this.getLatest().__tokenKey;
|
|
990
|
+
};
|
|
991
|
+
this.getLabel = () => {
|
|
992
|
+
return this.getLatest().__label;
|
|
993
|
+
};
|
|
994
|
+
this.getTextContent = () => {
|
|
995
|
+
return "";
|
|
996
|
+
};
|
|
997
|
+
this.isInline = () => {
|
|
998
|
+
return true;
|
|
999
|
+
};
|
|
1000
|
+
this.isIsolated = () => {
|
|
1001
|
+
return true;
|
|
1002
|
+
};
|
|
1003
|
+
this.isKeyboardSelectable = () => {
|
|
1004
|
+
return false;
|
|
1005
|
+
};
|
|
1006
|
+
this.__composerId = composerId;
|
|
1007
|
+
this.__tokenKind = tokenKind;
|
|
1008
|
+
this.__tokenKey = tokenKey;
|
|
1009
|
+
this.__label = label;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
function $createChatComposerTokenNode(params) {
|
|
1013
|
+
const { composerId, label, tokenKey, tokenKind } = params;
|
|
1014
|
+
return $applyNodeReplacement(new ChatComposerTokenNode(composerId, tokenKind, tokenKey, label));
|
|
1015
|
+
}
|
|
1016
|
+
function $isChatComposerTokenNode(node) {
|
|
1017
|
+
return node instanceof ChatComposerTokenNode;
|
|
1018
|
+
}
|
|
1019
|
+
//#endregion
|
|
1020
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-editor-state.ts
|
|
1021
|
+
function getComposerLeafDescriptors() {
|
|
1022
|
+
const root = $getRoot();
|
|
1023
|
+
const paragraph = root.getFirstChild();
|
|
1024
|
+
if (!$isElementNode(paragraph)) return {
|
|
1025
|
+
descriptors: [],
|
|
1026
|
+
paragraphKey: root.getKey(),
|
|
1027
|
+
paragraphSize: 0,
|
|
1028
|
+
totalLength: 0
|
|
1029
|
+
};
|
|
1030
|
+
const children = paragraph.getChildren();
|
|
1031
|
+
const descriptors = [];
|
|
1032
|
+
let cursor = 0;
|
|
1033
|
+
for (const [index, child] of children.entries()) {
|
|
1034
|
+
if ($isTextNode(child)) {
|
|
1035
|
+
const text = child.getTextContent();
|
|
1036
|
+
descriptors.push({
|
|
1037
|
+
index,
|
|
1038
|
+
key: child.getKey(),
|
|
1039
|
+
length: text.length,
|
|
1040
|
+
node: child,
|
|
1041
|
+
start: cursor,
|
|
1042
|
+
text,
|
|
1043
|
+
type: "text"
|
|
1044
|
+
});
|
|
1045
|
+
cursor += text.length;
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if ($isChatComposerTokenNode(child)) {
|
|
1049
|
+
descriptors.push({
|
|
1050
|
+
index,
|
|
1051
|
+
key: child.getKey(),
|
|
1052
|
+
length: 1,
|
|
1053
|
+
node: child,
|
|
1054
|
+
start: cursor,
|
|
1055
|
+
type: "token"
|
|
1056
|
+
});
|
|
1057
|
+
cursor += 1;
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
descriptors.push({
|
|
1061
|
+
index,
|
|
1062
|
+
key: child.getKey(),
|
|
1063
|
+
length: child.getTextContent().length,
|
|
1064
|
+
node: child,
|
|
1065
|
+
start: cursor,
|
|
1066
|
+
type: "linebreak"
|
|
1067
|
+
});
|
|
1068
|
+
cursor += child.getTextContent().length;
|
|
892
1069
|
}
|
|
893
|
-
if (container === element) return nodeStart + (offset > 0 ? 1 : 0);
|
|
894
|
-
return nodeStart + 1;
|
|
895
|
-
}
|
|
896
|
-
function resolveSelectionPointOffset(root, container, offset, nodes, nodeStartMap) {
|
|
897
|
-
if (container === root) return sumNodeLengthsBeforeChildIndex(root, nodes, offset);
|
|
898
|
-
if (container.nodeType === Node.TEXT_NODE && container.parentNode === root) return resolveDirectRootTextNodeOffset(root, container, offset, nodes);
|
|
899
|
-
const element = container instanceof HTMLElement ? container.closest("[data-composer-node-id]") : container.parentElement?.closest("[data-composer-node-id]") ?? null;
|
|
900
|
-
if (!(element instanceof HTMLElement)) return resolveSelectionPointOffset(root, root, resolveRootChildIndex(root, container), nodes, nodeStartMap);
|
|
901
|
-
const nodeId = element.dataset.composerNodeId;
|
|
902
|
-
if (!nodeId) return 0;
|
|
903
|
-
const matched = nodes.find((node) => node.id === nodeId);
|
|
904
|
-
const nodeStart = nodeStartMap.get(nodeId) ?? 0;
|
|
905
|
-
if (!matched) return nodeStart;
|
|
906
|
-
return resolveElementBackedOffset(container, offset, matched, nodeStart, element);
|
|
907
|
-
}
|
|
908
|
-
function readComposerSelection(root, nodes) {
|
|
909
|
-
const selection = window.getSelection();
|
|
910
|
-
if (!selection || selection.rangeCount === 0) return null;
|
|
911
|
-
const range = selection.getRangeAt(0);
|
|
912
|
-
if (!root.contains(range.startContainer) || !root.contains(range.endContainer)) return null;
|
|
913
|
-
const nodeStartMap = buildNodeStartMap(nodes);
|
|
914
|
-
const start = resolveSelectionPointOffset(root, range.startContainer, range.startOffset, nodes, nodeStartMap);
|
|
915
|
-
const end = resolveSelectionPointOffset(root, range.endContainer, range.endOffset, nodes, nodeStartMap);
|
|
916
1070
|
return {
|
|
917
|
-
|
|
918
|
-
|
|
1071
|
+
descriptors,
|
|
1072
|
+
paragraphKey: paragraph.getKey(),
|
|
1073
|
+
paragraphSize: children.length,
|
|
1074
|
+
totalLength: cursor
|
|
919
1075
|
};
|
|
920
1076
|
}
|
|
921
|
-
function
|
|
922
|
-
|
|
923
|
-
const
|
|
924
|
-
if (
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if (
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1077
|
+
function buildSelectionPointFromOffset(offset) {
|
|
1078
|
+
const { descriptors, paragraphKey, paragraphSize, totalLength } = getComposerLeafDescriptors();
|
|
1079
|
+
const boundedOffset = Math.max(0, Math.min(offset, totalLength));
|
|
1080
|
+
if (descriptors.length === 0) return {
|
|
1081
|
+
key: paragraphKey,
|
|
1082
|
+
offset: 0,
|
|
1083
|
+
type: "element"
|
|
1084
|
+
};
|
|
1085
|
+
for (const descriptor of descriptors) {
|
|
1086
|
+
const end = descriptor.start + descriptor.length;
|
|
1087
|
+
if (descriptor.type === "text" && boundedOffset >= descriptor.start && boundedOffset <= end) return {
|
|
1088
|
+
key: descriptor.key,
|
|
1089
|
+
offset: boundedOffset - descriptor.start,
|
|
1090
|
+
type: "text"
|
|
1091
|
+
};
|
|
1092
|
+
if (descriptor.type !== "text") {
|
|
1093
|
+
if (boundedOffset === descriptor.start) return {
|
|
1094
|
+
key: paragraphKey,
|
|
1095
|
+
offset: descriptor.index,
|
|
1096
|
+
type: "element"
|
|
1097
|
+
};
|
|
1098
|
+
if (boundedOffset === end) {
|
|
1099
|
+
const nextDescriptor = descriptors[descriptor.index + 1];
|
|
1100
|
+
if (nextDescriptor?.type === "text" && nextDescriptor.length === 0) return {
|
|
1101
|
+
key: nextDescriptor.key,
|
|
1102
|
+
offset: 0,
|
|
1103
|
+
type: "text"
|
|
946
1104
|
};
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
offset: index + 1
|
|
1105
|
+
return {
|
|
1106
|
+
key: paragraphKey,
|
|
1107
|
+
offset: descriptor.index + 1,
|
|
1108
|
+
type: "element"
|
|
950
1109
|
};
|
|
951
1110
|
}
|
|
952
|
-
cursor = nodeEnd;
|
|
953
1111
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
key: paragraphKey,
|
|
1115
|
+
offset: paragraphSize,
|
|
1116
|
+
type: "element"
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
function getOffsetFromElementPoint(point) {
|
|
1120
|
+
const { descriptors, paragraphKey, totalLength } = getComposerLeafDescriptors();
|
|
1121
|
+
if (point.key === paragraphKey) {
|
|
1122
|
+
const previous = descriptors[point.offset - 1];
|
|
1123
|
+
return previous ? previous.start + previous.length : 0;
|
|
1124
|
+
}
|
|
1125
|
+
const descriptor = descriptors.find((item) => item.key === point.key);
|
|
1126
|
+
if (!descriptor) return totalLength;
|
|
1127
|
+
return descriptor.start + Math.min(point.offset, descriptor.length);
|
|
1128
|
+
}
|
|
1129
|
+
function getOffsetFromPoint(point) {
|
|
1130
|
+
if (point.type === "element") return getOffsetFromElementPoint(point);
|
|
1131
|
+
const { descriptors, totalLength } = getComposerLeafDescriptors();
|
|
1132
|
+
const descriptor = descriptors.find((item) => item.key === point.key);
|
|
1133
|
+
if (!descriptor) return totalLength;
|
|
1134
|
+
return descriptor.start + Math.min(point.offset, descriptor.length);
|
|
1135
|
+
}
|
|
1136
|
+
function readNodesFromEditor() {
|
|
1137
|
+
const { descriptors } = getComposerLeafDescriptors();
|
|
1138
|
+
const nextNodes = [];
|
|
1139
|
+
let textBuffer = "";
|
|
1140
|
+
let textId = "";
|
|
1141
|
+
const flushTextBuffer = () => {
|
|
1142
|
+
if (textBuffer.length === 0) return;
|
|
1143
|
+
nextNodes.push({
|
|
1144
|
+
id: textId,
|
|
1145
|
+
type: "text",
|
|
1146
|
+
text: textBuffer
|
|
1147
|
+
});
|
|
1148
|
+
textBuffer = "";
|
|
1149
|
+
textId = "";
|
|
958
1150
|
};
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
range.setEnd(endBoundary.container, endBoundary.offset);
|
|
964
|
-
browserSelection.removeAllRanges();
|
|
965
|
-
browserSelection.addRange(range);
|
|
966
|
-
}
|
|
967
|
-
function parseComposerNodesFromDom(root) {
|
|
968
|
-
const parsedNodes = [];
|
|
969
|
-
for (const child of Array.from(root.childNodes)) {
|
|
970
|
-
if (child.nodeType === Node.TEXT_NODE) {
|
|
971
|
-
parsedNodes.push(createChatComposerTextNode(child.textContent ?? ""));
|
|
1151
|
+
for (const descriptor of descriptors) {
|
|
1152
|
+
if (descriptor.type === "text") {
|
|
1153
|
+
if (textBuffer.length === 0) textId = descriptor.key;
|
|
1154
|
+
textBuffer += descriptor.text;
|
|
972
1155
|
continue;
|
|
973
1156
|
}
|
|
974
|
-
if (
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const tokenKey = child.dataset.composerTokenKey;
|
|
978
|
-
const label = child.dataset.composerLabel;
|
|
979
|
-
if (tokenKind && tokenKey && label) parsedNodes.push({
|
|
980
|
-
id: child.dataset.composerNodeId ?? createChatComposerTokenNode({
|
|
981
|
-
tokenKind,
|
|
982
|
-
tokenKey,
|
|
983
|
-
label
|
|
984
|
-
}).id,
|
|
985
|
-
type: "token",
|
|
986
|
-
tokenKind,
|
|
987
|
-
tokenKey,
|
|
988
|
-
label
|
|
989
|
-
});
|
|
1157
|
+
if (descriptor.type === "linebreak") {
|
|
1158
|
+
if (textBuffer.length === 0) textId = descriptor.key;
|
|
1159
|
+
textBuffer += "\n";
|
|
990
1160
|
continue;
|
|
991
1161
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1162
|
+
flushTextBuffer();
|
|
1163
|
+
const tokenNode = descriptor.node;
|
|
1164
|
+
nextNodes.push({
|
|
1165
|
+
id: tokenNode.getComposerId(),
|
|
1166
|
+
label: tokenNode.getLabel(),
|
|
1167
|
+
tokenKey: tokenNode.getTokenKey(),
|
|
1168
|
+
tokenKind: tokenNode.getTokenKind(),
|
|
1169
|
+
type: "token"
|
|
997
1170
|
});
|
|
998
1171
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
function readComposerDocumentStateFromDom(root) {
|
|
1002
|
-
const nodes = parseComposerNodesFromDom(root);
|
|
1003
|
-
return {
|
|
1004
|
-
nodes,
|
|
1005
|
-
selection: readComposerSelection(root, nodes)
|
|
1006
|
-
};
|
|
1172
|
+
flushTextBuffer();
|
|
1173
|
+
return normalizeChatComposerNodes(nextNodes);
|
|
1007
1174
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
this.nodes = normalizeChatComposerNodes(nodes);
|
|
1016
|
-
this.selection = selection;
|
|
1017
|
-
return this.getSnapshot();
|
|
1175
|
+
function readChatComposerSnapshotFromEditorState(editorState) {
|
|
1176
|
+
return editorState.read(() => {
|
|
1177
|
+
const selection = $getSelection();
|
|
1178
|
+
const nodes = readNodesFromEditor();
|
|
1179
|
+
if (!$isRangeSelection(selection)) return {
|
|
1180
|
+
nodes,
|
|
1181
|
+
selection: null
|
|
1018
1182
|
};
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
this.nodes = normalizeChatComposerNodes(nodes);
|
|
1025
|
-
this.selection = selection;
|
|
1026
|
-
return this.getSnapshot();
|
|
1027
|
-
};
|
|
1028
|
-
this.insertText = (text) => {
|
|
1029
|
-
const selection = this.selection ?? {
|
|
1030
|
-
start: 0,
|
|
1031
|
-
end: 0
|
|
1032
|
-
};
|
|
1033
|
-
this.nodes = replaceChatComposerRange(this.nodes, selection.start, selection.end, [createChatComposerTextNode(text)]);
|
|
1034
|
-
const nextOffset = selection.start + text.length;
|
|
1035
|
-
this.selection = {
|
|
1036
|
-
start: nextOffset,
|
|
1037
|
-
end: nextOffset
|
|
1038
|
-
};
|
|
1039
|
-
return this.getSnapshot();
|
|
1040
|
-
};
|
|
1041
|
-
this.insertFileToken = (tokenKey, label) => {
|
|
1042
|
-
return this.insertToken("file", tokenKey, label);
|
|
1043
|
-
};
|
|
1044
|
-
this.insertSkillToken = (tokenKey, label) => {
|
|
1045
|
-
if (this.getSelectedSkillKeys().includes(tokenKey)) return this.getSnapshot();
|
|
1046
|
-
return this.insertToken("skill", tokenKey, label, this.getSlashTrigger());
|
|
1047
|
-
};
|
|
1048
|
-
this.syncSelectedSkills = (nextKeys, options) => {
|
|
1049
|
-
const selectedSkillKeys = this.getSelectedSkillKeys();
|
|
1050
|
-
const optionMap = new Map(options.map((option) => [option.key, option]));
|
|
1051
|
-
const addedKey = nextKeys.find((key) => !selectedSkillKeys.includes(key));
|
|
1052
|
-
if (addedKey) {
|
|
1053
|
-
const option = optionMap.get(addedKey);
|
|
1054
|
-
return this.insertSkillToken(addedKey, option?.label ?? addedKey);
|
|
1183
|
+
return {
|
|
1184
|
+
nodes,
|
|
1185
|
+
selection: {
|
|
1186
|
+
start: getOffsetFromPoint(selection.anchor),
|
|
1187
|
+
end: getOffsetFromPoint(selection.focus)
|
|
1055
1188
|
}
|
|
1056
|
-
const removedKey = selectedSkillKeys.find((key) => !nextKeys.includes(key));
|
|
1057
|
-
if (removedKey) this.nodes = removeChatComposerTokenNodes(this.nodes, (node) => node.tokenKind === "skill" && node.tokenKey === removedKey);
|
|
1058
|
-
return this.getSnapshot();
|
|
1059
|
-
};
|
|
1060
|
-
this.deleteContent = (direction) => {
|
|
1061
|
-
const documentLength = this.getDocumentLength();
|
|
1062
|
-
const selection = this.selection ?? {
|
|
1063
|
-
start: documentLength,
|
|
1064
|
-
end: documentLength
|
|
1065
|
-
};
|
|
1066
|
-
let rangeStart = selection.start;
|
|
1067
|
-
let rangeEnd = selection.end;
|
|
1068
|
-
if (selection.start === selection.end) if (direction === "backward" && selection.start > 0) rangeStart = selection.start - 1;
|
|
1069
|
-
else if (direction === "forward") rangeEnd = selection.end + 1;
|
|
1070
|
-
else return this.getSnapshot();
|
|
1071
|
-
this.nodes = replaceChatComposerRange(this.nodes, rangeStart, rangeEnd, []);
|
|
1072
|
-
this.selection = {
|
|
1073
|
-
start: rangeStart,
|
|
1074
|
-
end: rangeStart
|
|
1075
|
-
};
|
|
1076
|
-
return this.getSnapshot();
|
|
1077
|
-
};
|
|
1078
|
-
this.getSnapshot = () => {
|
|
1079
|
-
return {
|
|
1080
|
-
nodes: this.nodes,
|
|
1081
|
-
selection: this.selection,
|
|
1082
|
-
nodeStartMap: buildNodeStartMap(this.nodes),
|
|
1083
|
-
documentLength: this.getDocumentLength(),
|
|
1084
|
-
selectedSkillKeys: this.getSelectedSkillKeys(),
|
|
1085
|
-
slashTrigger: this.getSlashTrigger()
|
|
1086
|
-
};
|
|
1087
|
-
};
|
|
1088
|
-
this.getDocumentLength = () => {
|
|
1089
|
-
return this.nodes.reduce((sum, node) => sum + getChatComposerNodeLength(node), 0);
|
|
1090
|
-
};
|
|
1091
|
-
this.getSelectedSkillKeys = () => {
|
|
1092
|
-
return extractChatComposerTokenKeys(this.nodes, "skill");
|
|
1093
|
-
};
|
|
1094
|
-
this.insertToken = (tokenKind, tokenKey, label, trigger = null) => {
|
|
1095
|
-
const documentLength = this.getDocumentLength();
|
|
1096
|
-
const replaceStart = trigger?.start ?? this.selection?.start ?? documentLength;
|
|
1097
|
-
const replaceEnd = trigger?.end ?? this.selection?.end ?? replaceStart;
|
|
1098
|
-
this.nodes = replaceChatComposerRange(this.nodes, replaceStart, replaceEnd, [createChatComposerTokenNode({
|
|
1099
|
-
tokenKind,
|
|
1100
|
-
tokenKey,
|
|
1101
|
-
label
|
|
1102
|
-
})]);
|
|
1103
|
-
this.selection = {
|
|
1104
|
-
start: replaceStart + 1,
|
|
1105
|
-
end: replaceStart + 1
|
|
1106
|
-
};
|
|
1107
|
-
return this.getSnapshot();
|
|
1108
|
-
};
|
|
1109
|
-
this.getSlashTrigger = () => {
|
|
1110
|
-
return resolveChatComposerSlashTrigger(this.nodes, this.selection);
|
|
1111
1189
|
};
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
function writeChatComposerStateToLexicalRoot(nodes, selection) {
|
|
1193
|
+
const root = $getRoot();
|
|
1194
|
+
root.clear();
|
|
1195
|
+
const paragraph = $createParagraphNode();
|
|
1196
|
+
root.append(paragraph);
|
|
1197
|
+
for (const node of normalizeChatComposerNodes(nodes)) {
|
|
1198
|
+
if (node.type === "token") {
|
|
1199
|
+
paragraph.append($createChatComposerTokenNode({
|
|
1200
|
+
composerId: node.id,
|
|
1201
|
+
label: node.label,
|
|
1202
|
+
tokenKey: node.tokenKey,
|
|
1203
|
+
tokenKind: node.tokenKind
|
|
1204
|
+
}));
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
const parts = node.text.split("\n");
|
|
1208
|
+
for (const [index, part] of parts.entries()) {
|
|
1209
|
+
if (part.length > 0) paragraph.append($createTextNode(part));
|
|
1210
|
+
if (index < parts.length - 1) paragraph.append($createLineBreakNode());
|
|
1211
|
+
}
|
|
1112
1212
|
}
|
|
1113
|
-
|
|
1213
|
+
if ($isChatComposerTokenNode(paragraph.getLastChild())) paragraph.append($createTextNode(""));
|
|
1214
|
+
if (!selection) return;
|
|
1215
|
+
const nextSelection = $createRangeSelection();
|
|
1216
|
+
const anchor = buildSelectionPointFromOffset(selection.start);
|
|
1217
|
+
const focus = buildSelectionPointFromOffset(selection.end);
|
|
1218
|
+
nextSelection.anchor.set(anchor.key, anchor.offset, anchor.type);
|
|
1219
|
+
nextSelection.focus.set(focus.key, focus.offset, focus.type);
|
|
1220
|
+
$setSelection(nextSelection);
|
|
1221
|
+
}
|
|
1222
|
+
function syncLexicalEditorFromChatComposerState(editor, nodes, selection) {
|
|
1223
|
+
editor.update(() => {
|
|
1224
|
+
writeChatComposerStateToLexicalRoot(nodes, selection);
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function syncLexicalSelectionFromChatComposerSelection(editor, selection) {
|
|
1228
|
+
editor.update(() => {
|
|
1229
|
+
const nextSelection = $createRangeSelection();
|
|
1230
|
+
const anchor = buildSelectionPointFromOffset(selection.start);
|
|
1231
|
+
const focus = buildSelectionPointFromOffset(selection.end);
|
|
1232
|
+
nextSelection.anchor.set(anchor.key, anchor.offset, anchor.type);
|
|
1233
|
+
nextSelection.focus.set(focus.key, focus.offset, focus.type);
|
|
1234
|
+
$setSelection(nextSelection);
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
//#endregion
|
|
1238
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-operations.ts
|
|
1239
|
+
function getDocumentLength(nodes) {
|
|
1240
|
+
return nodes.reduce((sum, node) => sum + getChatComposerNodeLength(node), 0);
|
|
1241
|
+
}
|
|
1242
|
+
function insertToken(params) {
|
|
1243
|
+
const { label, nodes, selection, tokenKey, tokenKind, trigger } = params;
|
|
1244
|
+
const documentLength = getDocumentLength(nodes);
|
|
1245
|
+
const replaceStart = trigger?.start ?? selection?.start ?? documentLength;
|
|
1246
|
+
return {
|
|
1247
|
+
nodes: replaceChatComposerRange(nodes, replaceStart, trigger?.end ?? selection?.end ?? replaceStart, [createChatComposerTokenNode({
|
|
1248
|
+
label,
|
|
1249
|
+
tokenKey,
|
|
1250
|
+
tokenKind
|
|
1251
|
+
})]),
|
|
1252
|
+
selection: {
|
|
1253
|
+
start: replaceStart + 1,
|
|
1254
|
+
end: replaceStart + 1
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
function getChatComposerNodesSignature(nodes) {
|
|
1259
|
+
return nodes.map((node) => node.type === "text" ? `text:${node.id}:${node.text}` : `token:${node.id}:${node.tokenKind}:${node.tokenKey}:${node.label}`).join("");
|
|
1260
|
+
}
|
|
1261
|
+
function replaceChatComposerSelectionWithText(params) {
|
|
1262
|
+
const { nodes, selection, text } = params;
|
|
1263
|
+
const currentSelection = selection ?? {
|
|
1264
|
+
start: getDocumentLength(nodes),
|
|
1265
|
+
end: getDocumentLength(nodes)
|
|
1266
|
+
};
|
|
1267
|
+
const nextOffset = currentSelection.start + text.length;
|
|
1268
|
+
return {
|
|
1269
|
+
nodes: replaceChatComposerRange(nodes, currentSelection.start, currentSelection.end, [createChatComposerTextNode(text)]),
|
|
1270
|
+
selection: {
|
|
1271
|
+
start: nextOffset,
|
|
1272
|
+
end: nextOffset
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function insertFileTokenIntoChatComposer(params) {
|
|
1277
|
+
const { label, nodes, selection, tokenKey } = params;
|
|
1278
|
+
return insertToken({
|
|
1279
|
+
label,
|
|
1280
|
+
nodes,
|
|
1281
|
+
selection,
|
|
1282
|
+
tokenKey,
|
|
1283
|
+
tokenKind: "file"
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
function insertSkillTokenIntoChatComposer(params) {
|
|
1287
|
+
const { label, nodes, selection, tokenKey } = params;
|
|
1288
|
+
if (extractChatComposerTokenKeys(nodes, "skill").includes(tokenKey)) return {
|
|
1289
|
+
nodes: normalizeChatComposerNodes(nodes),
|
|
1290
|
+
selection
|
|
1291
|
+
};
|
|
1292
|
+
return insertToken({
|
|
1293
|
+
label,
|
|
1294
|
+
nodes,
|
|
1295
|
+
selection,
|
|
1296
|
+
tokenKey,
|
|
1297
|
+
tokenKind: "skill",
|
|
1298
|
+
trigger: resolveChatComposerSlashTrigger(nodes, selection)
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function syncSelectedSkillsIntoChatComposer(params) {
|
|
1302
|
+
const { nextKeys, nodes, options, selection } = params;
|
|
1303
|
+
const selectedSkillKeys = extractChatComposerTokenKeys(nodes, "skill");
|
|
1304
|
+
const optionMap = new Map(options.map((option) => [option.key, option]));
|
|
1305
|
+
const addedKey = nextKeys.find((key) => !selectedSkillKeys.includes(key));
|
|
1306
|
+
if (addedKey) return insertSkillTokenIntoChatComposer({
|
|
1307
|
+
label: optionMap.get(addedKey)?.label ?? addedKey,
|
|
1308
|
+
nodes,
|
|
1309
|
+
selection,
|
|
1310
|
+
tokenKey: addedKey
|
|
1311
|
+
});
|
|
1312
|
+
const removedKey = selectedSkillKeys.find((key) => !nextKeys.includes(key));
|
|
1313
|
+
if (!removedKey) return {
|
|
1314
|
+
nodes: normalizeChatComposerNodes(nodes),
|
|
1315
|
+
selection
|
|
1316
|
+
};
|
|
1317
|
+
return {
|
|
1318
|
+
nodes: removeChatComposerTokenNodes(nodes, (node) => node.tokenKind === "skill" && node.tokenKey === removedKey),
|
|
1319
|
+
selection
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function deleteChatComposerContent(params) {
|
|
1323
|
+
const { direction, nodes, selection: currentSelection } = params;
|
|
1324
|
+
const documentLength = getDocumentLength(nodes);
|
|
1325
|
+
const selection = currentSelection ?? {
|
|
1326
|
+
start: documentLength,
|
|
1327
|
+
end: documentLength
|
|
1328
|
+
};
|
|
1329
|
+
let rangeStart = selection.start;
|
|
1330
|
+
let rangeEnd = selection.end;
|
|
1331
|
+
if (selection.start === selection.end) if (direction === "backward" && selection.start > 0) rangeStart = selection.start - 1;
|
|
1332
|
+
else if (direction === "forward" && selection.end < documentLength) rangeEnd = selection.end + 1;
|
|
1333
|
+
else return {
|
|
1334
|
+
nodes: normalizeChatComposerNodes(nodes),
|
|
1335
|
+
selection
|
|
1336
|
+
};
|
|
1337
|
+
return {
|
|
1338
|
+
nodes: replaceChatComposerRange(nodes, rangeStart, rangeEnd, []),
|
|
1339
|
+
selection: {
|
|
1340
|
+
start: rangeStart,
|
|
1341
|
+
end: rangeStart
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1114
1345
|
//#endregion
|
|
1115
|
-
//#region src/components/chat/ui/chat-input-bar/chat-composer-
|
|
1116
|
-
function
|
|
1117
|
-
const {
|
|
1346
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-controller.ts
|
|
1347
|
+
function resolveLexicalComposerKeyboardAction(params) {
|
|
1348
|
+
const { activeSlashIndex, canStopGeneration, isComposing, isSending, isSlashMenuOpen, key, shiftKey, slashItemCount } = params;
|
|
1118
1349
|
if (key === "Enter" && !shiftKey && isSending) return { type: "consume" };
|
|
1119
1350
|
if (isSlashMenuOpen && slashItemCount > 0) {
|
|
1120
1351
|
if (key === "ArrowDown") return {
|
|
@@ -1140,569 +1371,391 @@ function resolveChatComposerKeyboardAction(params) {
|
|
|
1140
1371
|
};
|
|
1141
1372
|
return { type: "noop" };
|
|
1142
1373
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
root.replaceChildren(fragment);
|
|
1156
|
-
};
|
|
1157
|
-
this.createTextNodeElement = (node) => {
|
|
1158
|
-
if (node.text.length === 0) return null;
|
|
1159
|
-
const element = document.createElement("span");
|
|
1160
|
-
element.dataset.composerNodeId = node.id;
|
|
1161
|
-
element.dataset.composerNodeType = "text";
|
|
1162
|
-
element.textContent = node.text;
|
|
1163
|
-
return element;
|
|
1164
|
-
};
|
|
1165
|
-
this.createTokenNodeElement = (node, selectedRange, nodeStartMap) => {
|
|
1166
|
-
const nodeStart = nodeStartMap.get(node.id) ?? 0;
|
|
1167
|
-
const isSelected = isChatComposerSelectionInsideRange(selectedRange, nodeStart, nodeStart + 1);
|
|
1168
|
-
const element = document.createElement("span");
|
|
1169
|
-
element.contentEditable = "false";
|
|
1170
|
-
element.dataset.composerNodeId = node.id;
|
|
1171
|
-
element.dataset.composerNodeType = "token";
|
|
1172
|
-
element.dataset.composerTokenKind = node.tokenKind;
|
|
1173
|
-
element.dataset.composerTokenKey = node.tokenKey;
|
|
1174
|
-
element.dataset.composerLabel = node.label;
|
|
1175
|
-
element.title = node.label;
|
|
1176
|
-
element.className = this.buildTokenClassName(node.tokenKind, isSelected);
|
|
1177
|
-
element.append(this.createTokenIcon(node.tokenKind));
|
|
1178
|
-
const label = document.createElement("span");
|
|
1179
|
-
label.className = node.tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate";
|
|
1180
|
-
label.textContent = node.label;
|
|
1181
|
-
element.append(label);
|
|
1182
|
-
return element;
|
|
1183
|
-
};
|
|
1184
|
-
this.buildTokenClassName = (tokenKind, isSelected) => {
|
|
1185
|
-
if (tokenKind === "file") return [
|
|
1186
|
-
"mx-[2px]",
|
|
1187
|
-
"inline-flex",
|
|
1188
|
-
"h-7",
|
|
1189
|
-
"max-w-[min(100%,17rem)]",
|
|
1190
|
-
"items-center",
|
|
1191
|
-
"gap-1.5",
|
|
1192
|
-
"rounded-lg",
|
|
1193
|
-
"border",
|
|
1194
|
-
"px-2",
|
|
1195
|
-
"align-baseline",
|
|
1196
|
-
"transition-[border-color,background-color,box-shadow,color]",
|
|
1197
|
-
"duration-150",
|
|
1198
|
-
isSelected ? "border-slate-300 bg-slate-100 text-slate-800 shadow-[0_0_0_2px_rgba(148,163,184,0.14)]" : "border-slate-200/80 bg-slate-50 text-slate-700"
|
|
1199
|
-
].join(" ");
|
|
1200
|
-
return [
|
|
1201
|
-
"mx-[2px]",
|
|
1202
|
-
"inline-flex",
|
|
1203
|
-
"h-7",
|
|
1204
|
-
"max-w-full",
|
|
1205
|
-
"items-center",
|
|
1206
|
-
"gap-1.5",
|
|
1207
|
-
"rounded-lg",
|
|
1208
|
-
"border",
|
|
1209
|
-
"px-2",
|
|
1210
|
-
"align-baseline",
|
|
1211
|
-
"text-[11px]",
|
|
1212
|
-
"font-medium",
|
|
1213
|
-
"transition",
|
|
1214
|
-
isSelected ? "border-primary/30 bg-primary/18 text-primary" : "border-primary/12 bg-primary/8 text-primary"
|
|
1215
|
-
].join(" ");
|
|
1216
|
-
};
|
|
1217
|
-
this.createTokenIcon = (tokenKind) => {
|
|
1218
|
-
const wrapper = document.createElement("span");
|
|
1219
|
-
wrapper.className = tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-white text-slate-500 ring-1 ring-black/5" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70";
|
|
1220
|
-
wrapper.append(tokenKind === "file" ? this.createFileIcon() : this.createSkillIcon());
|
|
1221
|
-
return wrapper;
|
|
1374
|
+
var LexicalComposerHandleOwner = class {
|
|
1375
|
+
constructor(params) {
|
|
1376
|
+
this.params = params;
|
|
1377
|
+
this.insertSlashItem = (item) => {
|
|
1378
|
+
if (!item.value) return;
|
|
1379
|
+
this.params.onSlashItemSelect?.(item);
|
|
1380
|
+
this.params.publishSnapshot(insertSkillTokenIntoChatComposer({
|
|
1381
|
+
label: item.title,
|
|
1382
|
+
nodes: this.params.optionsReader().nodes,
|
|
1383
|
+
selection: this.params.optionsReader().selection,
|
|
1384
|
+
tokenKey: item.value
|
|
1385
|
+
}), { focusAfterSync: true });
|
|
1222
1386
|
};
|
|
1223
|
-
this.
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1387
|
+
this.insertFileToken = (tokenKey, label) => {
|
|
1388
|
+
this.params.publishSnapshot(insertFileTokenIntoChatComposer({
|
|
1389
|
+
label,
|
|
1390
|
+
nodes: this.params.optionsReader().nodes,
|
|
1391
|
+
selection: this.params.optionsReader().selection,
|
|
1392
|
+
tokenKey
|
|
1393
|
+
}), { focusAfterSync: true });
|
|
1394
|
+
};
|
|
1395
|
+
this.insertFileTokens = (tokens) => {
|
|
1396
|
+
let nextNodes = this.params.optionsReader().nodes;
|
|
1397
|
+
let nextSelection = this.params.optionsReader().selection;
|
|
1398
|
+
for (const token of tokens) {
|
|
1399
|
+
const snapshot = insertFileTokenIntoChatComposer({
|
|
1400
|
+
label: token.label,
|
|
1401
|
+
nodes: nextNodes,
|
|
1402
|
+
selection: nextSelection,
|
|
1403
|
+
tokenKey: token.tokenKey
|
|
1404
|
+
});
|
|
1405
|
+
nextNodes = snapshot.nodes;
|
|
1406
|
+
nextSelection = snapshot.selection;
|
|
1407
|
+
}
|
|
1408
|
+
this.params.publishSnapshot({
|
|
1409
|
+
nodes: nextNodes,
|
|
1410
|
+
selection: nextSelection
|
|
1411
|
+
}, { focusAfterSync: true });
|
|
1242
1412
|
};
|
|
1243
|
-
this.
|
|
1244
|
-
|
|
1245
|
-
{
|
|
1246
|
-
tag: "path",
|
|
1247
|
-
attrs: { d: "M3.25 4.25A1.5 1.5 0 0 1 4.75 2.75h6.5a1.5 1.5 0 0 1 1.5 1.5v7.5a1.5 1.5 0 0 1-1.5 1.5h-6.5a1.5 1.5 0 0 1-1.5-1.5v-7.5Z" }
|
|
1248
|
-
},
|
|
1249
|
-
{
|
|
1250
|
-
tag: "path",
|
|
1251
|
-
attrs: { d: "m4.75 10 2.25-2.5 1.75 1.75 1.25-1.25 2 2" }
|
|
1252
|
-
},
|
|
1253
|
-
{
|
|
1254
|
-
tag: "path",
|
|
1255
|
-
attrs: { d: "M9.75 6.25h.01" }
|
|
1256
|
-
}
|
|
1257
|
-
]);
|
|
1413
|
+
this.focusComposer = () => {
|
|
1414
|
+
this.params.focusComposer();
|
|
1258
1415
|
};
|
|
1259
|
-
this.
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
svg.setAttribute("stroke-linejoin", "round");
|
|
1267
|
-
svg.setAttribute("aria-hidden", "true");
|
|
1268
|
-
svg.setAttribute("class", "h-3 w-3");
|
|
1269
|
-
for (const child of children) {
|
|
1270
|
-
const element = document.createElementNS(SVG_NAMESPACE, child.tag);
|
|
1271
|
-
for (const [key, value] of Object.entries(child.attrs)) element.setAttribute(key, value);
|
|
1272
|
-
svg.append(element);
|
|
1273
|
-
}
|
|
1274
|
-
return svg;
|
|
1416
|
+
this.syncSelectedSkills = (nextKeys, options) => {
|
|
1417
|
+
this.params.publishSnapshot(syncSelectedSkillsIntoChatComposer({
|
|
1418
|
+
nextKeys,
|
|
1419
|
+
nodes: this.params.optionsReader().nodes,
|
|
1420
|
+
options,
|
|
1421
|
+
selection: this.params.optionsReader().selection
|
|
1422
|
+
}), { focusAfterSync: true });
|
|
1275
1423
|
};
|
|
1276
1424
|
}
|
|
1277
1425
|
};
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
isSlashMenuOpen: currentSnapshot.slashTrigger !== null,
|
|
1344
|
-
slashItemCount: slashItems.length,
|
|
1345
|
-
activeSlashIndex,
|
|
1346
|
-
isSending: actions.isSending,
|
|
1347
|
-
canStopGeneration: actions.canStopGeneration
|
|
1348
|
-
});
|
|
1349
|
-
if (action.type === "noop") return;
|
|
1350
|
-
event.preventDefault();
|
|
1351
|
-
this.applyKeyboardAction({
|
|
1352
|
-
action,
|
|
1353
|
-
activeSlashItem,
|
|
1354
|
-
actions,
|
|
1355
|
-
commitSnapshot,
|
|
1356
|
-
insertSkillToken,
|
|
1357
|
-
onSlashItemSelect,
|
|
1358
|
-
onSlashActiveIndexChange,
|
|
1359
|
-
onSlashQueryChange,
|
|
1360
|
-
onSlashOpenChange
|
|
1361
|
-
});
|
|
1362
|
-
};
|
|
1363
|
-
this.handlePaste = (params) => {
|
|
1364
|
-
const { event, onFilesAdd, commitSnapshot } = params;
|
|
1365
|
-
const files = Array.from(event.clipboardData.files ?? []);
|
|
1366
|
-
if (files.length > 0 && onFilesAdd) {
|
|
1367
|
-
event.preventDefault();
|
|
1368
|
-
onFilesAdd(files);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
const text = event.clipboardData.getData("text/plain");
|
|
1372
|
-
if (!text) return;
|
|
1373
|
-
event.preventDefault();
|
|
1374
|
-
commitSnapshot(this.controller.insertText(text));
|
|
1375
|
-
};
|
|
1376
|
-
this.handleBlur = (params) => {
|
|
1377
|
-
const { clearSelectedRange, onSlashQueryChange, onSlashOpenChange } = params;
|
|
1378
|
-
clearSelectedRange();
|
|
1426
|
+
function createLexicalComposerHandle(params) {
|
|
1427
|
+
return new LexicalComposerHandleOwner(params);
|
|
1428
|
+
}
|
|
1429
|
+
function getChatComposerContentSignature(nodes) {
|
|
1430
|
+
return JSON.stringify(nodes.map((node) => node.type === "text" ? {
|
|
1431
|
+
text: node.text,
|
|
1432
|
+
type: node.type
|
|
1433
|
+
} : {
|
|
1434
|
+
label: node.label,
|
|
1435
|
+
tokenKey: node.tokenKey,
|
|
1436
|
+
tokenKind: node.tokenKind,
|
|
1437
|
+
type: node.type
|
|
1438
|
+
}));
|
|
1439
|
+
}
|
|
1440
|
+
function handleLexicalComposerBeforeInput(params) {
|
|
1441
|
+
const { disabled, event, isComposing, publishSnapshot, snapshotReader } = params;
|
|
1442
|
+
const nativeEvent = event.nativeEvent;
|
|
1443
|
+
const shouldInsertText = nativeEvent.inputType === "insertText" || nativeEvent.inputType === "insertReplacementText";
|
|
1444
|
+
if (disabled || isComposing || nativeEvent.isComposing || !shouldInsertText || !nativeEvent.data) return;
|
|
1445
|
+
event.preventDefault();
|
|
1446
|
+
publishSnapshot(replaceChatComposerSelectionWithText({
|
|
1447
|
+
nodes: snapshotReader().nodes,
|
|
1448
|
+
selection: snapshotReader().selection,
|
|
1449
|
+
text: nativeEvent.data
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1452
|
+
function handleLexicalComposerCompositionEnd(params) {
|
|
1453
|
+
const { data, fallbackSnapshot, publishSnapshot, snapshotReader } = params;
|
|
1454
|
+
const currentSnapshot = snapshotReader();
|
|
1455
|
+
const editorSnapshot = fallbackSnapshot();
|
|
1456
|
+
publishSnapshot(getChatComposerContentSignature(editorSnapshot.nodes) !== getChatComposerContentSignature(currentSnapshot.nodes) ? editorSnapshot : data.length > 0 ? replaceChatComposerSelectionWithText({
|
|
1457
|
+
nodes: currentSnapshot.nodes,
|
|
1458
|
+
selection: currentSnapshot.selection,
|
|
1459
|
+
text: data
|
|
1460
|
+
}) : editorSnapshot, { forcePublish: true });
|
|
1461
|
+
}
|
|
1462
|
+
function handleLexicalComposerKeyboardCommand(params) {
|
|
1463
|
+
const { actions, activeSlashIndex, nativeEvent, onSlashActiveIndexChange, onSlashItemSelect, onSlashOpenChange, onSlashQueryChange, publishSnapshot, slashItems, snapshot } = params;
|
|
1464
|
+
const action = resolveLexicalComposerKeyboardAction({
|
|
1465
|
+
activeSlashIndex,
|
|
1466
|
+
canStopGeneration: actions.canStopGeneration,
|
|
1467
|
+
isComposing: nativeEvent.isComposing,
|
|
1468
|
+
isSending: actions.isSending,
|
|
1469
|
+
isSlashMenuOpen: resolveChatComposerSlashTrigger(snapshot.nodes, snapshot.selection) !== null,
|
|
1470
|
+
key: nativeEvent.key,
|
|
1471
|
+
shiftKey: nativeEvent.shiftKey,
|
|
1472
|
+
slashItemCount: slashItems.length
|
|
1473
|
+
});
|
|
1474
|
+
const activeSlashItem = slashItems[activeSlashIndex] ?? null;
|
|
1475
|
+
if (action.type !== "noop") nativeEvent.preventDefault();
|
|
1476
|
+
switch (action.type) {
|
|
1477
|
+
case "move-slash-index":
|
|
1478
|
+
onSlashActiveIndexChange(action.index);
|
|
1479
|
+
return true;
|
|
1480
|
+
case "insert-active-slash-item":
|
|
1481
|
+
if (!activeSlashItem) return true;
|
|
1482
|
+
onSlashItemSelect?.(activeSlashItem);
|
|
1483
|
+
publishSnapshot(insertSkillTokenIntoChatComposer({
|
|
1484
|
+
label: activeSlashItem.title,
|
|
1485
|
+
nodes: snapshot.nodes,
|
|
1486
|
+
selection: snapshot.selection,
|
|
1487
|
+
tokenKey: activeSlashItem.value ?? activeSlashItem.key
|
|
1488
|
+
}), { focusAfterSync: true });
|
|
1489
|
+
return true;
|
|
1490
|
+
case "close-slash":
|
|
1379
1491
|
onSlashQueryChange?.(null);
|
|
1380
1492
|
onSlashOpenChange(false);
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
case "send-message":
|
|
1405
|
-
actions.onSend();
|
|
1406
|
-
return;
|
|
1407
|
-
case "delete-content":
|
|
1408
|
-
commitSnapshot(this.controller.deleteContent(action.direction));
|
|
1409
|
-
return;
|
|
1410
|
-
case "noop": return;
|
|
1411
|
-
}
|
|
1412
|
-
};
|
|
1493
|
+
return true;
|
|
1494
|
+
case "consume": return true;
|
|
1495
|
+
case "stop-generation":
|
|
1496
|
+
actions.onStop();
|
|
1497
|
+
return true;
|
|
1498
|
+
case "insert-line-break":
|
|
1499
|
+
publishSnapshot(replaceChatComposerSelectionWithText({
|
|
1500
|
+
nodes: snapshot.nodes,
|
|
1501
|
+
selection: snapshot.selection,
|
|
1502
|
+
text: "\n"
|
|
1503
|
+
}));
|
|
1504
|
+
return true;
|
|
1505
|
+
case "send-message":
|
|
1506
|
+
actions.onSend();
|
|
1507
|
+
return true;
|
|
1508
|
+
case "delete-content":
|
|
1509
|
+
publishSnapshot(deleteChatComposerContent({
|
|
1510
|
+
direction: action.direction,
|
|
1511
|
+
nodes: snapshot.nodes,
|
|
1512
|
+
selection: snapshot.selection
|
|
1513
|
+
}));
|
|
1514
|
+
return true;
|
|
1515
|
+
case "noop": return false;
|
|
1413
1516
|
}
|
|
1414
|
-
}
|
|
1517
|
+
}
|
|
1415
1518
|
//#endregion
|
|
1416
|
-
//#region src/components/chat/ui/chat-input-bar/chat-composer-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
this.selectedRange = null;
|
|
1424
|
-
this.snapshot = this.controller.getSnapshot();
|
|
1425
|
-
this.config = null;
|
|
1426
|
-
this.isComposing = false;
|
|
1427
|
-
this.hasPendingImeComposition = false;
|
|
1428
|
-
this.bindRootElement = (node) => {
|
|
1429
|
-
this.rootElement = node;
|
|
1430
|
-
};
|
|
1431
|
-
this.update = (config) => {
|
|
1432
|
-
this.config = config;
|
|
1433
|
-
this.snapshot = this.viewController.sync(config.nodes, this.selectedRange);
|
|
1434
|
-
return {
|
|
1435
|
-
snapshot: this.snapshot,
|
|
1436
|
-
selectedRange: this.selectedRange,
|
|
1437
|
-
bindRootElement: this.bindRootElement,
|
|
1438
|
-
handleBeforeInput: this.handleBeforeInput,
|
|
1439
|
-
handleInput: this.handleInput,
|
|
1440
|
-
handleCompositionStart: this.handleCompositionStart,
|
|
1441
|
-
handleCompositionEnd: this.handleCompositionEnd,
|
|
1442
|
-
handleKeyDown: this.handleKeyDown,
|
|
1443
|
-
handlePaste: this.handlePaste,
|
|
1444
|
-
handleBlur: this.handleBlur,
|
|
1445
|
-
syncSelectionState: this.syncSelectionState,
|
|
1446
|
-
imperativeHandle: this.createHandle()
|
|
1447
|
-
};
|
|
1448
|
-
};
|
|
1449
|
-
this.createHandle = () => {
|
|
1450
|
-
return {
|
|
1451
|
-
insertSlashItem: (item) => {
|
|
1452
|
-
this.config?.onSlashItemSelect?.(item);
|
|
1453
|
-
this.viewController.insertSlashItem(item, this.commitSnapshot);
|
|
1454
|
-
},
|
|
1455
|
-
insertFileToken: (tokenKey, label) => {
|
|
1456
|
-
this.commitSnapshot(this.controller.insertFileToken(tokenKey, label));
|
|
1457
|
-
this.focusComposerSoon();
|
|
1458
|
-
},
|
|
1459
|
-
insertFileTokens: (tokens) => {
|
|
1460
|
-
let nextSnapshot = null;
|
|
1461
|
-
for (const token of tokens) nextSnapshot = this.controller.insertFileToken(token.tokenKey, token.label);
|
|
1462
|
-
if (nextSnapshot) {
|
|
1463
|
-
this.commitSnapshot(nextSnapshot);
|
|
1464
|
-
this.focusComposerSoon();
|
|
1465
|
-
}
|
|
1466
|
-
},
|
|
1467
|
-
focusComposer: () => {
|
|
1468
|
-
this.focusComposer();
|
|
1469
|
-
},
|
|
1470
|
-
syncSelectedSkills: (nextKeys, options) => {
|
|
1471
|
-
this.viewController.syncSelectedSkills(nextKeys, options, this.commitSnapshot);
|
|
1472
|
-
}
|
|
1473
|
-
};
|
|
1474
|
-
};
|
|
1475
|
-
this.restoreDomAfterCommit = () => {
|
|
1476
|
-
if (this.shouldSuspendDomSync()) return;
|
|
1477
|
-
this.viewController.restoreSelectionIfFocused(this.rootElement, this.selection);
|
|
1478
|
-
};
|
|
1479
|
-
this.renderSurface = () => {
|
|
1480
|
-
if (this.shouldSuspendDomSync()) return;
|
|
1481
|
-
this.viewController.renderSurface({
|
|
1482
|
-
root: this.rootElement,
|
|
1483
|
-
snapshot: this.snapshot,
|
|
1484
|
-
selectedRange: this.selectedRange
|
|
1485
|
-
});
|
|
1486
|
-
};
|
|
1487
|
-
this.syncViewport = () => {
|
|
1488
|
-
this.viewController.syncViewport(this.rootElement);
|
|
1489
|
-
};
|
|
1490
|
-
this.syncSelectionState = () => {
|
|
1491
|
-
if (!this.rootElement || this.shouldSuspendDomSync()) return;
|
|
1492
|
-
const nextSnapshot = this.viewController.syncSelectionFromRoot(this.rootElement);
|
|
1493
|
-
this.selection = nextSnapshot.selection;
|
|
1494
|
-
this.selectedRange = nextSnapshot.selection;
|
|
1495
|
-
this.syncSlashState(nextSnapshot);
|
|
1496
|
-
this.requestRender();
|
|
1497
|
-
};
|
|
1498
|
-
this.handleBeforeInput = (event) => {
|
|
1499
|
-
this.syncImeGuardFromInputEvent(event.nativeEvent);
|
|
1500
|
-
this.viewController.handleBeforeInput({
|
|
1501
|
-
event,
|
|
1502
|
-
disabled: this.requireConfig().disabled,
|
|
1503
|
-
isComposing: this.shouldSuspendDomSync(),
|
|
1504
|
-
commitSnapshot: this.commitSnapshot
|
|
1505
|
-
});
|
|
1506
|
-
};
|
|
1507
|
-
this.handleInput = (event) => {
|
|
1508
|
-
this.syncImeGuardFromInputEvent(event.nativeEvent);
|
|
1509
|
-
this.viewController.handleInput({
|
|
1510
|
-
event,
|
|
1511
|
-
isComposing: this.shouldSuspendDomSync(),
|
|
1512
|
-
commitSnapshot: this.commitSnapshot
|
|
1513
|
-
});
|
|
1514
|
-
};
|
|
1515
|
-
this.handleCompositionStart = () => {
|
|
1516
|
-
this.hasPendingImeComposition = false;
|
|
1517
|
-
this.isComposing = true;
|
|
1518
|
-
};
|
|
1519
|
-
this.handleCompositionEnd = (event) => {
|
|
1520
|
-
this.hasPendingImeComposition = false;
|
|
1521
|
-
this.isComposing = false;
|
|
1522
|
-
this.viewController.handleCompositionEnd({
|
|
1523
|
-
event,
|
|
1524
|
-
commitSnapshot: this.commitSnapshot
|
|
1525
|
-
});
|
|
1526
|
-
};
|
|
1527
|
-
this.handleKeyDown = (event) => {
|
|
1528
|
-
const config = this.requireConfig();
|
|
1529
|
-
this.syncImeGuardFromKeyboardEvent(event);
|
|
1530
|
-
if (this.rootElement && !this.shouldSuspendDomSync()) {
|
|
1531
|
-
const nextSnapshot = this.viewController.syncSelectionFromRoot(this.rootElement);
|
|
1532
|
-
this.selection = nextSnapshot.selection;
|
|
1533
|
-
this.selectedRange = nextSnapshot.selection;
|
|
1534
|
-
this.snapshot = nextSnapshot;
|
|
1535
|
-
}
|
|
1536
|
-
const activeSlashItem = config.slashItems[config.activeSlashIndex] ?? null;
|
|
1537
|
-
this.viewController.handleKeyDown({
|
|
1538
|
-
event,
|
|
1539
|
-
slashItems: config.slashItems,
|
|
1540
|
-
activeSlashIndex: config.activeSlashIndex,
|
|
1541
|
-
activeSlashItem,
|
|
1542
|
-
actions: config.actions,
|
|
1543
|
-
commitSnapshot: this.commitSnapshot,
|
|
1544
|
-
insertSkillToken: this.insertSkillToken,
|
|
1545
|
-
onSlashItemSelect: config.onSlashItemSelect,
|
|
1546
|
-
onSlashActiveIndexChange: config.onSlashActiveIndexChange,
|
|
1547
|
-
onSlashQueryChange: config.onSlashQueryChange,
|
|
1548
|
-
onSlashOpenChange: config.onSlashOpenChange
|
|
1549
|
-
});
|
|
1550
|
-
};
|
|
1551
|
-
this.handlePaste = (event) => {
|
|
1552
|
-
this.viewController.handlePaste({
|
|
1553
|
-
event,
|
|
1554
|
-
onFilesAdd: this.requireConfig().onFilesAdd,
|
|
1555
|
-
commitSnapshot: this.commitSnapshot
|
|
1556
|
-
});
|
|
1519
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-plugins.tsx
|
|
1520
|
+
function ChatComposerBindingsPlugin(props) {
|
|
1521
|
+
const [editor] = useLexicalComposerContext();
|
|
1522
|
+
useLayoutEffect(() => {
|
|
1523
|
+
props.editorRef.current = editor;
|
|
1524
|
+
return () => {
|
|
1525
|
+
if (props.editorRef.current === editor) props.editorRef.current = null;
|
|
1557
1526
|
};
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1527
|
+
}, [editor, props.editorRef]);
|
|
1528
|
+
useLayoutEffect(() => {
|
|
1529
|
+
editor.setEditable(!props.disabled);
|
|
1530
|
+
}, [editor, props.disabled]);
|
|
1531
|
+
useLayoutEffect(() => {
|
|
1532
|
+
const nextSignature = getChatComposerNodesSignature(props.nodes);
|
|
1533
|
+
const pendingSelection = props.pendingSelectionRef.current;
|
|
1534
|
+
const shouldSyncDocument = nextSignature !== props.editorSignatureRef.current;
|
|
1535
|
+
if (!shouldSyncDocument && !pendingSelection) return;
|
|
1536
|
+
props.isApplyingExternalUpdateRef.current = true;
|
|
1537
|
+
if (shouldSyncDocument) {
|
|
1538
|
+
syncLexicalEditorFromChatComposerState(editor, props.nodes, pendingSelection);
|
|
1539
|
+
props.editorSignatureRef.current = nextSignature;
|
|
1540
|
+
props.lastPublishedSignatureRef.current = nextSignature;
|
|
1541
|
+
} else if (pendingSelection) syncLexicalSelectionFromChatComposerSelection(editor, pendingSelection);
|
|
1542
|
+
if (pendingSelection) {
|
|
1543
|
+
props.selectionRef.current = pendingSelection;
|
|
1544
|
+
props.pendingSelectionRef.current = null;
|
|
1545
|
+
}
|
|
1546
|
+
if (props.shouldFocusAfterSyncRef.current) {
|
|
1547
|
+
props.shouldFocusAfterSyncRef.current = false;
|
|
1548
|
+
const targetSelection = props.selectionRef.current;
|
|
1549
|
+
editor.focus(() => {
|
|
1550
|
+
if (targetSelection) syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
1566
1551
|
});
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
};
|
|
1616
|
-
this.isCompositionInputType = (inputType) => {
|
|
1617
|
-
return typeof inputType === "string" && inputType.toLowerCase().includes("composition");
|
|
1618
|
-
};
|
|
1619
|
-
this.focusComposerSoon = () => {
|
|
1620
|
-
if (typeof requestAnimationFrame === "function") {
|
|
1621
|
-
requestAnimationFrame(() => this.focusComposer());
|
|
1622
|
-
return;
|
|
1623
|
-
}
|
|
1624
|
-
this.focusComposer();
|
|
1625
|
-
};
|
|
1626
|
-
this.focusComposer = () => {
|
|
1627
|
-
if (!this.rootElement) return;
|
|
1628
|
-
const targetSelection = this.selection;
|
|
1629
|
-
this.rootElement.focus();
|
|
1630
|
-
const restoreSelection = () => {
|
|
1631
|
-
if (!this.rootElement) return;
|
|
1632
|
-
this.selection = targetSelection;
|
|
1633
|
-
this.selectedRange = targetSelection;
|
|
1634
|
-
this.viewController.restoreSelectionIfFocused(this.rootElement, targetSelection);
|
|
1635
|
-
};
|
|
1636
|
-
if (typeof requestAnimationFrame === "function") {
|
|
1637
|
-
requestAnimationFrame(restoreSelection);
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
restoreSelection();
|
|
1641
|
-
};
|
|
1642
|
-
this.requireConfig = () => {
|
|
1643
|
-
if (!this.config) throw new Error("ChatComposerRuntime is not configured.");
|
|
1644
|
-
return this.config;
|
|
1645
|
-
};
|
|
1646
|
-
}
|
|
1647
|
-
};
|
|
1552
|
+
}
|
|
1553
|
+
requestAnimationFrame(() => {
|
|
1554
|
+
props.isApplyingExternalUpdateRef.current = false;
|
|
1555
|
+
});
|
|
1556
|
+
}, [
|
|
1557
|
+
editor,
|
|
1558
|
+
props.editorSignatureRef,
|
|
1559
|
+
props.isApplyingExternalUpdateRef,
|
|
1560
|
+
props.lastPublishedSignatureRef,
|
|
1561
|
+
props.nodes,
|
|
1562
|
+
props.pendingSelectionRef,
|
|
1563
|
+
props.selectionRef,
|
|
1564
|
+
props.shouldFocusAfterSyncRef
|
|
1565
|
+
]);
|
|
1566
|
+
useEffect(() => {
|
|
1567
|
+
return mergeRegister(editor.registerUpdateListener(({ editorState }) => {
|
|
1568
|
+
const snapshot = readChatComposerSnapshotFromEditorState(editorState);
|
|
1569
|
+
const signature = getChatComposerNodesSignature(snapshot.nodes);
|
|
1570
|
+
props.selectionRef.current = snapshot.selection;
|
|
1571
|
+
props.editorSignatureRef.current = signature;
|
|
1572
|
+
props.syncSlashState(snapshot.nodes, snapshot.selection);
|
|
1573
|
+
if (props.isApplyingExternalUpdateRef.current || props.isComposingRef.current) return;
|
|
1574
|
+
if (signature === props.lastPublishedSignatureRef.current) return;
|
|
1575
|
+
props.lastPublishedSignatureRef.current = signature;
|
|
1576
|
+
props.onNodesChange(snapshot.nodes);
|
|
1577
|
+
}), editor.registerCommand(SELECTION_CHANGE_COMMAND, () => {
|
|
1578
|
+
const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
|
|
1579
|
+
props.selectionRef.current = snapshot.selection;
|
|
1580
|
+
if (!props.isComposingRef.current) props.syncSlashState(snapshot.nodes, snapshot.selection);
|
|
1581
|
+
return false;
|
|
1582
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(BLUR_COMMAND, () => {
|
|
1583
|
+
props.onBlur();
|
|
1584
|
+
return false;
|
|
1585
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(KEY_DOWN_COMMAND, (event) => props.onKeyDown(event), COMMAND_PRIORITY_HIGH));
|
|
1586
|
+
}, [
|
|
1587
|
+
editor,
|
|
1588
|
+
props.editorSignatureRef,
|
|
1589
|
+
props.isApplyingExternalUpdateRef,
|
|
1590
|
+
props.isComposingRef,
|
|
1591
|
+
props.lastPublishedSignatureRef,
|
|
1592
|
+
props.onBlur,
|
|
1593
|
+
props.onKeyDown,
|
|
1594
|
+
props.onNodesChange,
|
|
1595
|
+
props.selectionRef,
|
|
1596
|
+
props.syncSlashState
|
|
1597
|
+
]);
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1648
1600
|
//#endregion
|
|
1649
|
-
//#region src/components/chat/ui/chat-input-bar/chat-input-bar-tokenized-composer.tsx
|
|
1601
|
+
//#region src/components/chat/ui/chat-input-bar/lexical/chat-input-bar-tokenized-composer.tsx
|
|
1650
1602
|
const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer(props, ref) {
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
onSlashQueryChange
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1603
|
+
const editorRef = useRef(null);
|
|
1604
|
+
const selectionRef = useRef(null);
|
|
1605
|
+
const pendingSelectionRef = useRef(null);
|
|
1606
|
+
const shouldFocusAfterSyncRef = useRef(false);
|
|
1607
|
+
const isComposingRef = useRef(false);
|
|
1608
|
+
const isApplyingExternalUpdateRef = useRef(false);
|
|
1609
|
+
const editorSignatureRef = useRef("");
|
|
1610
|
+
const lastPublishedSignatureRef = useRef("");
|
|
1611
|
+
const syncSlashState = (nodes, selection) => {
|
|
1612
|
+
const trigger = resolveChatComposerSlashTrigger(nodes, selection);
|
|
1613
|
+
props.onSlashTriggerChange?.(trigger);
|
|
1614
|
+
props.onSlashQueryChange?.(trigger?.query ?? null);
|
|
1615
|
+
props.onSlashOpenChange(trigger !== null);
|
|
1616
|
+
};
|
|
1617
|
+
const readCurrentNodes = () => {
|
|
1618
|
+
return props.nodes;
|
|
1619
|
+
};
|
|
1620
|
+
const readCurrentSelection = () => {
|
|
1621
|
+
if (selectionRef.current) return selectionRef.current;
|
|
1622
|
+
if (!editorRef.current) return null;
|
|
1623
|
+
const snapshot = readChatComposerSnapshotFromEditorState(editorRef.current.getEditorState());
|
|
1624
|
+
selectionRef.current = snapshot.selection;
|
|
1625
|
+
return snapshot.selection;
|
|
1626
|
+
};
|
|
1627
|
+
const publishSnapshot = (snapshot, options) => {
|
|
1628
|
+
selectionRef.current = snapshot.selection;
|
|
1629
|
+
pendingSelectionRef.current = snapshot.selection;
|
|
1630
|
+
if (options?.focusAfterSync) shouldFocusAfterSyncRef.current = true;
|
|
1631
|
+
const signature = getChatComposerNodesSignature(snapshot.nodes);
|
|
1632
|
+
syncSlashState(snapshot.nodes, snapshot.selection);
|
|
1633
|
+
if (options?.forcePublish || signature !== lastPublishedSignatureRef.current) {
|
|
1634
|
+
lastPublishedSignatureRef.current = signature;
|
|
1635
|
+
props.onNodesChange(snapshot.nodes);
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
const focusComposer = () => {
|
|
1639
|
+
const editor = editorRef.current;
|
|
1640
|
+
if (!editor) return;
|
|
1641
|
+
const targetSelection = selectionRef.current;
|
|
1642
|
+
editor.focus(() => {
|
|
1643
|
+
if (targetSelection) syncLexicalSelectionFromChatComposerSelection(editor, targetSelection);
|
|
1644
|
+
});
|
|
1645
|
+
};
|
|
1646
|
+
const readComposerSnapshot = () => ({
|
|
1647
|
+
nodes: readCurrentNodes(),
|
|
1648
|
+
selection: readCurrentSelection()
|
|
1668
1649
|
});
|
|
1669
|
-
useImperativeHandle(ref, () =>
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}, [
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
"
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1650
|
+
useImperativeHandle(ref, () => createLexicalComposerHandle({
|
|
1651
|
+
focusComposer,
|
|
1652
|
+
onSlashItemSelect: props.onSlashItemSelect,
|
|
1653
|
+
optionsReader: readComposerSnapshot,
|
|
1654
|
+
publishSnapshot
|
|
1655
|
+
}), [props.onSlashItemSelect]);
|
|
1656
|
+
return /* @__PURE__ */ jsxs(LexicalComposer, {
|
|
1657
|
+
initialConfig: useMemo(() => ({
|
|
1658
|
+
editable: !props.disabled,
|
|
1659
|
+
editorState: () => {
|
|
1660
|
+
writeChatComposerStateToLexicalRoot(props.nodes, null);
|
|
1661
|
+
},
|
|
1662
|
+
namespace: "NextClawChatComposerLexical",
|
|
1663
|
+
nodes: [ChatComposerTokenNode],
|
|
1664
|
+
onError: (error) => {
|
|
1665
|
+
throw error;
|
|
1666
|
+
},
|
|
1667
|
+
theme: {}
|
|
1668
|
+
}), [props.disabled, props.nodes]),
|
|
1669
|
+
children: [
|
|
1670
|
+
/* @__PURE__ */ jsx("div", {
|
|
1671
|
+
className: "px-4 py-2.5",
|
|
1672
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1673
|
+
className: "min-h-[60px]",
|
|
1674
|
+
children: /* @__PURE__ */ jsx(PlainTextPlugin, {
|
|
1675
|
+
contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
|
|
1676
|
+
className: "min-h-7 max-h-[188px] w-full overflow-y-auto whitespace-pre-wrap break-words bg-transparent py-0.5 text-sm leading-6 text-gray-800 outline-none",
|
|
1677
|
+
onBeforeInput: (event) => {
|
|
1678
|
+
handleLexicalComposerBeforeInput({
|
|
1679
|
+
disabled: props.disabled,
|
|
1680
|
+
event,
|
|
1681
|
+
isComposing: isComposingRef.current,
|
|
1682
|
+
publishSnapshot,
|
|
1683
|
+
snapshotReader: readComposerSnapshot
|
|
1684
|
+
});
|
|
1685
|
+
},
|
|
1686
|
+
onCompositionEnd: (event) => {
|
|
1687
|
+
isComposingRef.current = false;
|
|
1688
|
+
const nativeEvent = event.nativeEvent;
|
|
1689
|
+
handleLexicalComposerCompositionEnd({
|
|
1690
|
+
data: typeof nativeEvent.data === "string" ? nativeEvent.data : "",
|
|
1691
|
+
fallbackSnapshot: () => {
|
|
1692
|
+
const editor = editorRef.current;
|
|
1693
|
+
return editor ? readChatComposerSnapshotFromEditorState(editor.getEditorState()) : readComposerSnapshot();
|
|
1694
|
+
},
|
|
1695
|
+
publishSnapshot,
|
|
1696
|
+
snapshotReader: readComposerSnapshot
|
|
1697
|
+
});
|
|
1698
|
+
},
|
|
1699
|
+
onCompositionStart: () => {
|
|
1700
|
+
isComposingRef.current = true;
|
|
1701
|
+
},
|
|
1702
|
+
onPaste: (event) => {
|
|
1703
|
+
const files = Array.from(event.clipboardData.files ?? []);
|
|
1704
|
+
if (files.length > 0 && props.onFilesAdd) {
|
|
1705
|
+
event.preventDefault();
|
|
1706
|
+
props.onFilesAdd(files);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
}),
|
|
1710
|
+
placeholder: /* @__PURE__ */ jsx("div", {
|
|
1711
|
+
className: "pointer-events-none absolute left-4 top-2.5 select-none text-sm leading-6 text-gray-400",
|
|
1712
|
+
children: props.placeholder
|
|
1713
|
+
}),
|
|
1714
|
+
ErrorBoundary: LexicalErrorBoundary
|
|
1715
|
+
})
|
|
1716
|
+
})
|
|
1717
|
+
}),
|
|
1718
|
+
/* @__PURE__ */ jsx(EditorRefPlugin, { editorRef }),
|
|
1719
|
+
/* @__PURE__ */ jsx(ChatComposerBindingsPlugin, {
|
|
1720
|
+
disabled: props.disabled,
|
|
1721
|
+
editorRef,
|
|
1722
|
+
editorSignatureRef,
|
|
1723
|
+
isApplyingExternalUpdateRef,
|
|
1724
|
+
isComposingRef,
|
|
1725
|
+
lastPublishedSignatureRef,
|
|
1726
|
+
nodes: props.nodes,
|
|
1727
|
+
onBlur: () => {
|
|
1728
|
+
props.onSlashQueryChange?.(null);
|
|
1729
|
+
props.onSlashOpenChange(false);
|
|
1730
|
+
},
|
|
1731
|
+
onKeyDown: (event) => {
|
|
1732
|
+
const editor = editorRef.current;
|
|
1733
|
+
if (!editor) return false;
|
|
1734
|
+
const snapshot = readChatComposerSnapshotFromEditorState(editor.getEditorState());
|
|
1735
|
+
selectionRef.current = snapshot.selection;
|
|
1736
|
+
return handleLexicalComposerKeyboardCommand({
|
|
1737
|
+
actions: props.actions,
|
|
1738
|
+
activeSlashIndex: props.activeSlashIndex,
|
|
1739
|
+
nativeEvent: event,
|
|
1740
|
+
onSlashActiveIndexChange: props.onSlashActiveIndexChange,
|
|
1741
|
+
onSlashItemSelect: props.onSlashItemSelect,
|
|
1742
|
+
onSlashOpenChange: props.onSlashOpenChange,
|
|
1743
|
+
onSlashQueryChange: props.onSlashQueryChange,
|
|
1744
|
+
publishSnapshot,
|
|
1745
|
+
slashItems: props.slashItems,
|
|
1746
|
+
snapshot
|
|
1747
|
+
});
|
|
1748
|
+
},
|
|
1749
|
+
onNodesChange: props.onNodesChange,
|
|
1750
|
+
pendingSelectionRef,
|
|
1751
|
+
selectionRef,
|
|
1752
|
+
shouldFocusAfterSyncRef,
|
|
1753
|
+
syncSlashState
|
|
1702
1754
|
})
|
|
1703
|
-
|
|
1755
|
+
]
|
|
1704
1756
|
});
|
|
1705
1757
|
});
|
|
1758
|
+
ChatInputBarTokenizedComposer.displayName = "LexicalChatInputBarTokenizedComposer";
|
|
1706
1759
|
//#endregion
|
|
1707
1760
|
//#region src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
|
|
1708
1761
|
function InputBarHint({ hint }) {
|
|
@@ -1755,6 +1808,7 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
|
1755
1808
|
...props.toolbar.skillPicker,
|
|
1756
1809
|
onSelectedKeysChange: (nextKeys) => {
|
|
1757
1810
|
composerRef.current?.syncSelectedSkills(nextKeys, props.toolbar.skillPicker?.options ?? []);
|
|
1811
|
+
props.toolbar.skillPicker?.onSelectedKeysChange(nextKeys);
|
|
1758
1812
|
}
|
|
1759
1813
|
}
|
|
1760
1814
|
};
|
|
@@ -2129,29 +2183,17 @@ function ChatMessageInlineContent({ segments, role, texts }) {
|
|
|
2129
2183
|
}
|
|
2130
2184
|
//#endregion
|
|
2131
2185
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
|
|
2132
|
-
const FILE_CATEGORY_LABELS = {
|
|
2133
|
-
archive: "Archive",
|
|
2134
|
-
audio: "Audio",
|
|
2135
|
-
code: "Code",
|
|
2136
|
-
data: "Data",
|
|
2137
|
-
document: "Document",
|
|
2138
|
-
generic: "File",
|
|
2139
|
-
image: "Image",
|
|
2140
|
-
pdf: "PDF",
|
|
2141
|
-
sheet: "Sheet",
|
|
2142
|
-
video: "Video"
|
|
2143
|
-
};
|
|
2144
2186
|
const FILE_CATEGORY_TILE_CLASSES = {
|
|
2145
|
-
archive: "border-amber-200/80 bg-amber-
|
|
2146
|
-
audio: "border-fuchsia-200/80 bg-fuchsia-
|
|
2147
|
-
code: "border-cyan-200/80 bg-cyan-
|
|
2148
|
-
data: "border-slate-200/80 bg-slate-
|
|
2149
|
-
document: "border-blue-200/80 bg-blue-
|
|
2150
|
-
generic: "border-slate-200/80 bg-slate-
|
|
2151
|
-
image: "border-emerald-200/80 bg-emerald-
|
|
2152
|
-
pdf: "border-rose-200/80 bg-rose-
|
|
2153
|
-
sheet: "border-lime-200/80 bg-lime-
|
|
2154
|
-
video: "border-violet-200/80 bg-violet-
|
|
2187
|
+
archive: "border-amber-200/80 bg-amber-50 text-amber-700",
|
|
2188
|
+
audio: "border-fuchsia-200/80 bg-fuchsia-50 text-fuchsia-700",
|
|
2189
|
+
code: "border-cyan-200/80 bg-cyan-50 text-cyan-700",
|
|
2190
|
+
data: "border-slate-200/80 bg-slate-50 text-slate-700",
|
|
2191
|
+
document: "border-blue-200/80 bg-blue-50 text-blue-700",
|
|
2192
|
+
generic: "border-slate-200/80 bg-slate-50 text-slate-700",
|
|
2193
|
+
image: "border-emerald-200/80 bg-emerald-50 text-emerald-700",
|
|
2194
|
+
pdf: "border-rose-200/80 bg-rose-50 text-rose-700",
|
|
2195
|
+
sheet: "border-lime-200/80 bg-lime-50 text-lime-700",
|
|
2196
|
+
video: "border-violet-200/80 bg-violet-50 text-violet-700"
|
|
2155
2197
|
};
|
|
2156
2198
|
const CODE_EXTENSIONS = new Set([
|
|
2157
2199
|
"c",
|
|
@@ -2278,22 +2320,30 @@ function buildChatMessageFileMeta(file) {
|
|
|
2278
2320
|
return {
|
|
2279
2321
|
category,
|
|
2280
2322
|
extension: getFileExtension(file.label, file.mimeType),
|
|
2281
|
-
sizeLabel
|
|
2282
|
-
metaBadges: [FILE_CATEGORY_LABELS[category], sizeLabel].filter((value) => Boolean(value))
|
|
2323
|
+
sizeLabel
|
|
2283
2324
|
};
|
|
2284
2325
|
}
|
|
2285
2326
|
//#endregion
|
|
2286
2327
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/index.tsx
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2328
|
+
const DEFAULT_FILE_CATEGORY_LABELS = {
|
|
2329
|
+
archive: "Archive",
|
|
2330
|
+
audio: "Audio",
|
|
2331
|
+
code: "Code file",
|
|
2332
|
+
data: "Data file",
|
|
2333
|
+
document: "Document",
|
|
2334
|
+
generic: "File",
|
|
2335
|
+
image: "Image",
|
|
2336
|
+
pdf: "PDF document",
|
|
2337
|
+
sheet: "Spreadsheet",
|
|
2338
|
+
video: "Video"
|
|
2339
|
+
};
|
|
2340
|
+
function readFileCategoryLabel(category, texts) {
|
|
2341
|
+
return texts?.attachmentCategoryLabels?.[category] ?? DEFAULT_FILE_CATEGORY_LABELS[category];
|
|
2292
2342
|
}
|
|
2293
|
-
function
|
|
2343
|
+
function renderMetaLine(categoryLabel, sizeLabel, isUser) {
|
|
2294
2344
|
return /* @__PURE__ */ jsx("div", {
|
|
2295
|
-
className: cn("
|
|
2296
|
-
children:
|
|
2345
|
+
className: cn("mt-1 text-xs leading-5", isUser ? "text-white/70" : "text-slate-500"),
|
|
2346
|
+
children: sizeLabel ? `${categoryLabel} · ${sizeLabel}` : categoryLabel
|
|
2297
2347
|
});
|
|
2298
2348
|
}
|
|
2299
2349
|
function renderActionPill(label, isUser, isInteractive) {
|
|
@@ -2302,18 +2352,39 @@ function renderActionPill(label, isUser, isInteractive) {
|
|
|
2302
2352
|
children: label
|
|
2303
2353
|
});
|
|
2304
2354
|
}
|
|
2305
|
-
function
|
|
2306
|
-
|
|
2355
|
+
function readFileCategoryIcon(category) {
|
|
2356
|
+
if (category === "archive") return FileArchive;
|
|
2357
|
+
if (category === "audio") return FileAudio2;
|
|
2358
|
+
if (category === "code") return FileCode2;
|
|
2359
|
+
if (category === "data") return FileJson2;
|
|
2360
|
+
if (category === "pdf") return FileText;
|
|
2361
|
+
if (category === "sheet") return FileSpreadsheet;
|
|
2362
|
+
if (category === "image") return FileImage;
|
|
2363
|
+
if (category === "video") return FileVideo2;
|
|
2364
|
+
if (category === "document") return FileText;
|
|
2365
|
+
return File;
|
|
2366
|
+
}
|
|
2367
|
+
function FileCategoryGlyph({ category, isUser }) {
|
|
2368
|
+
const Icon = readFileCategoryIcon(category);
|
|
2369
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2370
|
+
className: cn("flex h-14 w-14 shrink-0 items-center justify-center rounded-[1rem] border", isUser ? "border-white/12 bg-white/10 text-white" : FILE_CATEGORY_TILE_CLASSES[category]),
|
|
2371
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
2372
|
+
className: cn("h-7 w-7", isUser ? "text-white/92" : "text-current"),
|
|
2373
|
+
strokeWidth: 2.2
|
|
2374
|
+
})
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
function ChatMessageFile({ file, isUser = false, texts }) {
|
|
2378
|
+
const { category, extension, sizeLabel } = buildChatMessageFileMeta(file);
|
|
2307
2379
|
const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
|
|
2308
2380
|
const isInteractive = Boolean(file.dataUrl);
|
|
2309
|
-
const actionLabel = isInteractive ?
|
|
2310
|
-
const
|
|
2311
|
-
const shellClasses = cn("block overflow-hidden rounded-[1.25rem] border transition duration-200", isUser ? "border-white/12 bg-white/10 text-white
|
|
2381
|
+
const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
|
|
2382
|
+
const categoryLabel = readFileCategoryLabel(category, texts);
|
|
2383
|
+
const shellClasses = cn("block overflow-hidden rounded-[1.25rem] border transition duration-200", isUser ? "border-white/12 bg-white/10 text-white" : "border-slate-200/80 bg-white/95 text-slate-900", isInteractive && (isUser ? "hover:border-white/18 hover:bg-white/13" : "hover:border-slate-300 hover:bg-white"));
|
|
2312
2384
|
if (renderAsImage && file.dataUrl) return /* @__PURE__ */ jsx("a", {
|
|
2313
2385
|
href: file.dataUrl,
|
|
2314
2386
|
target: "_blank",
|
|
2315
2387
|
rel: "noreferrer",
|
|
2316
|
-
"aria-label": `Open preview: ${file.label}`,
|
|
2317
2388
|
className: "group block",
|
|
2318
2389
|
children: /* @__PURE__ */ jsxs("div", {
|
|
2319
2390
|
className: cn("relative overflow-hidden rounded-[1rem]", isUser ? "ring-1 ring-white/10" : "bg-slate-100/80 ring-1 ring-slate-200/80"),
|
|
@@ -2325,7 +2396,7 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2325
2396
|
className: "pointer-events-none absolute right-3 top-3 flex items-center gap-2",
|
|
2326
2397
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2327
2398
|
className: cn("inline-flex items-center rounded-full px-2 py-1 text-[10px] font-semibold tracking-[0.18em] text-white backdrop-blur-sm", isUser ? "bg-slate-950/36" : "bg-slate-950/58"),
|
|
2328
|
-
children:
|
|
2399
|
+
children: categoryLabel
|
|
2329
2400
|
}), sizeLabel ? /* @__PURE__ */ jsx("span", {
|
|
2330
2401
|
className: cn("inline-flex items-center rounded-full px-2 py-1 text-[10px] font-medium text-white/92 backdrop-blur-sm", isUser ? "bg-slate-950/28" : "bg-slate-950/46"),
|
|
2331
2402
|
children: sizeLabel
|
|
@@ -2334,35 +2405,22 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2334
2405
|
})
|
|
2335
2406
|
});
|
|
2336
2407
|
const content = /* @__PURE__ */ jsxs("div", {
|
|
2337
|
-
className: "flex items-
|
|
2338
|
-
children: [/* @__PURE__ */ jsx(
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}), /* @__PURE__ */
|
|
2408
|
+
className: "flex items-center gap-3 p-3.5",
|
|
2409
|
+
children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
|
|
2410
|
+
category,
|
|
2411
|
+
isUser
|
|
2412
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2342
2413
|
className: "min-w-0 flex-1",
|
|
2343
|
-
children:
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
children: [FILE_CATEGORY_LABELS[category], " attachment"]
|
|
2354
|
-
})]
|
|
2355
|
-
}), renderActionPill(actionLabel, isUser, isInteractive)]
|
|
2356
|
-
}),
|
|
2357
|
-
/* @__PURE__ */ jsx("div", {
|
|
2358
|
-
className: "mt-3 flex flex-wrap gap-2",
|
|
2359
|
-
children: metaBadges.map((label) => renderMetaBadge(label, isUser))
|
|
2360
|
-
}),
|
|
2361
|
-
/* @__PURE__ */ jsx("div", {
|
|
2362
|
-
className: "mt-2",
|
|
2363
|
-
children: renderMimeType(file.mimeType, isUser)
|
|
2364
|
-
})
|
|
2365
|
-
]
|
|
2414
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2415
|
+
className: "flex items-center gap-3",
|
|
2416
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
2417
|
+
className: "min-w-0 flex-1",
|
|
2418
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2419
|
+
className: "truncate text-[15px] font-semibold leading-5",
|
|
2420
|
+
children: file.label
|
|
2421
|
+
}), renderMetaLine(categoryLabel, sizeLabel, isUser)]
|
|
2422
|
+
}), renderActionPill(actionLabel, isUser, isInteractive)]
|
|
2423
|
+
})
|
|
2366
2424
|
})]
|
|
2367
2425
|
});
|
|
2368
2426
|
if (!isInteractive) return /* @__PURE__ */ jsx("div", {
|
|
@@ -2373,7 +2431,6 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2373
2431
|
href: file.dataUrl,
|
|
2374
2432
|
target: "_blank",
|
|
2375
2433
|
rel: "noreferrer",
|
|
2376
|
-
"aria-label": `Open file: ${file.label}`,
|
|
2377
2434
|
className: cn(shellClasses, "group"),
|
|
2378
2435
|
children: content
|
|
2379
2436
|
});
|
|
@@ -2409,19 +2466,113 @@ function useReasoningBlockOpenState(params) {
|
|
|
2409
2466
|
};
|
|
2410
2467
|
}
|
|
2411
2468
|
//#endregion
|
|
2469
|
+
//#region src/components/chat/hooks/use-sticky-bottom-scroll.ts
|
|
2470
|
+
const DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
2471
|
+
function scrollElementToBottom(element) {
|
|
2472
|
+
element.scrollTop = element.scrollHeight;
|
|
2473
|
+
}
|
|
2474
|
+
function queueScrollToBottom(params) {
|
|
2475
|
+
if (!params.scrollRef.current) return;
|
|
2476
|
+
if (params.scheduledScrollFrameRef.current !== null) cancelAnimationFrame(params.scheduledScrollFrameRef.current);
|
|
2477
|
+
params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
|
|
2478
|
+
params.scheduledScrollFrameRef.current = null;
|
|
2479
|
+
const currentElement = params.scrollRef.current;
|
|
2480
|
+
if (!currentElement) return;
|
|
2481
|
+
params.isProgrammaticScrollRef.current = true;
|
|
2482
|
+
scrollElementToBottom(currentElement);
|
|
2483
|
+
});
|
|
2484
|
+
}
|
|
2485
|
+
function useStickyBottomScroll(params) {
|
|
2486
|
+
const isStickyRef = useRef(true);
|
|
2487
|
+
const isProgrammaticScrollRef = useRef(false);
|
|
2488
|
+
const previousResetKeyRef = useRef(null);
|
|
2489
|
+
const pendingInitialScrollRef = useRef(false);
|
|
2490
|
+
const scheduledScrollFrameRef = useRef(null);
|
|
2491
|
+
const onScroll = () => {
|
|
2492
|
+
if (isProgrammaticScrollRef.current) {
|
|
2493
|
+
isProgrammaticScrollRef.current = false;
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
const element = params.scrollRef.current;
|
|
2497
|
+
if (!element) return;
|
|
2498
|
+
isStickyRef.current = element.scrollHeight - element.scrollTop - element.clientHeight <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
|
|
2499
|
+
};
|
|
2500
|
+
useEffect(() => {
|
|
2501
|
+
if (previousResetKeyRef.current === params.resetKey) return;
|
|
2502
|
+
previousResetKeyRef.current = params.resetKey;
|
|
2503
|
+
isStickyRef.current = true;
|
|
2504
|
+
pendingInitialScrollRef.current = true;
|
|
2505
|
+
}, [params.resetKey]);
|
|
2506
|
+
useEffect(() => {
|
|
2507
|
+
const scheduledScrollFrame = scheduledScrollFrameRef.current;
|
|
2508
|
+
return () => {
|
|
2509
|
+
if (scheduledScrollFrame !== null) cancelAnimationFrame(scheduledScrollFrame);
|
|
2510
|
+
};
|
|
2511
|
+
}, []);
|
|
2512
|
+
useLayoutEffect(() => {
|
|
2513
|
+
if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) return;
|
|
2514
|
+
if (!params.scrollRef.current) return;
|
|
2515
|
+
pendingInitialScrollRef.current = false;
|
|
2516
|
+
queueScrollToBottom({
|
|
2517
|
+
scrollRef: params.scrollRef,
|
|
2518
|
+
scheduledScrollFrameRef,
|
|
2519
|
+
isProgrammaticScrollRef
|
|
2520
|
+
});
|
|
2521
|
+
}, [
|
|
2522
|
+
params.hasContent,
|
|
2523
|
+
params.isLoading,
|
|
2524
|
+
params.scrollRef
|
|
2525
|
+
]);
|
|
2526
|
+
useLayoutEffect(() => {
|
|
2527
|
+
if (!isStickyRef.current || !params.hasContent) return;
|
|
2528
|
+
if (!params.scrollRef.current) return;
|
|
2529
|
+
queueScrollToBottom({
|
|
2530
|
+
scrollRef: params.scrollRef,
|
|
2531
|
+
scheduledScrollFrameRef,
|
|
2532
|
+
isProgrammaticScrollRef
|
|
2533
|
+
});
|
|
2534
|
+
}, [
|
|
2535
|
+
params.contentVersion,
|
|
2536
|
+
params.hasContent,
|
|
2537
|
+
params.scrollRef
|
|
2538
|
+
]);
|
|
2539
|
+
return { onScroll };
|
|
2540
|
+
}
|
|
2541
|
+
//#endregion
|
|
2412
2542
|
//#region src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
|
|
2543
|
+
function normalizeReasoningLabel(label) {
|
|
2544
|
+
const normalizedLabel = label.trim().toLowerCase();
|
|
2545
|
+
if (normalizedLabel === "reasoning" || normalizedLabel === "thinking" || normalizedLabel === "推理" || normalizedLabel === "推理过程" || normalizedLabel === "思考过程" || normalizedLabel === "思考") return "思考";
|
|
2546
|
+
return label;
|
|
2547
|
+
}
|
|
2413
2548
|
function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
2414
2549
|
const { isOpen, onSummaryClick } = useReasoningBlockOpenState({ isInProgress });
|
|
2550
|
+
const scrollRef = useRef(null);
|
|
2551
|
+
const displayLabel = normalizeReasoningLabel(label);
|
|
2552
|
+
const { onScroll } = useStickyBottomScroll({
|
|
2553
|
+
scrollRef,
|
|
2554
|
+
resetKey: `${displayLabel}:${isInProgress ? "streaming" : "idle"}`,
|
|
2555
|
+
isLoading: false,
|
|
2556
|
+
hasContent: text.length > 0,
|
|
2557
|
+
contentVersion: text,
|
|
2558
|
+
stickyThresholdPx: 20
|
|
2559
|
+
});
|
|
2415
2560
|
return /* @__PURE__ */ jsxs("details", {
|
|
2416
2561
|
className: "mt-2",
|
|
2417
2562
|
open: isOpen,
|
|
2418
2563
|
children: [/* @__PURE__ */ jsx("summary", {
|
|
2419
2564
|
className: cn("cursor-pointer text-xs", isUser ? "text-primary-100" : "text-gray-500"),
|
|
2420
2565
|
onClick: onSummaryClick,
|
|
2421
|
-
children:
|
|
2422
|
-
}), /* @__PURE__ */ jsx("
|
|
2423
|
-
|
|
2424
|
-
|
|
2566
|
+
children: displayLabel
|
|
2567
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2568
|
+
ref: scrollRef,
|
|
2569
|
+
onScroll,
|
|
2570
|
+
"data-reasoning-scroll": "true",
|
|
2571
|
+
className: cn("mt-2 w-fit max-w-[500px] max-h-56 overflow-y-auto overscroll-contain rounded-lg custom-scrollbar-amber", isUser ? "bg-primary-700/60" : "bg-gray-100"),
|
|
2572
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
2573
|
+
className: "min-w-0 whitespace-pre-wrap break-all p-2 text-[11px]",
|
|
2574
|
+
children: text
|
|
2575
|
+
})
|
|
2425
2576
|
})]
|
|
2426
2577
|
});
|
|
2427
2578
|
}
|
|
@@ -2541,79 +2692,6 @@ function ToolCardHeaderSessionAction({ action, onAction }) {
|
|
|
2541
2692
|
});
|
|
2542
2693
|
}
|
|
2543
2694
|
//#endregion
|
|
2544
|
-
//#region src/components/chat/hooks/use-sticky-bottom-scroll.ts
|
|
2545
|
-
const DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
2546
|
-
function scrollElementToBottom(element) {
|
|
2547
|
-
element.scrollTop = element.scrollHeight;
|
|
2548
|
-
}
|
|
2549
|
-
function queueScrollToBottom(params) {
|
|
2550
|
-
if (!params.scrollRef.current) return;
|
|
2551
|
-
if (params.scheduledScrollFrameRef.current !== null) cancelAnimationFrame(params.scheduledScrollFrameRef.current);
|
|
2552
|
-
params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
|
|
2553
|
-
params.scheduledScrollFrameRef.current = null;
|
|
2554
|
-
const currentElement = params.scrollRef.current;
|
|
2555
|
-
if (!currentElement) return;
|
|
2556
|
-
params.isProgrammaticScrollRef.current = true;
|
|
2557
|
-
scrollElementToBottom(currentElement);
|
|
2558
|
-
});
|
|
2559
|
-
}
|
|
2560
|
-
function useStickyBottomScroll(params) {
|
|
2561
|
-
const isStickyRef = useRef(true);
|
|
2562
|
-
const isProgrammaticScrollRef = useRef(false);
|
|
2563
|
-
const previousResetKeyRef = useRef(null);
|
|
2564
|
-
const pendingInitialScrollRef = useRef(false);
|
|
2565
|
-
const scheduledScrollFrameRef = useRef(null);
|
|
2566
|
-
const onScroll = () => {
|
|
2567
|
-
if (isProgrammaticScrollRef.current) {
|
|
2568
|
-
isProgrammaticScrollRef.current = false;
|
|
2569
|
-
return;
|
|
2570
|
-
}
|
|
2571
|
-
const element = params.scrollRef.current;
|
|
2572
|
-
if (!element) return;
|
|
2573
|
-
isStickyRef.current = element.scrollHeight - element.scrollTop - element.clientHeight <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
|
|
2574
|
-
};
|
|
2575
|
-
useEffect(() => {
|
|
2576
|
-
if (previousResetKeyRef.current === params.resetKey) return;
|
|
2577
|
-
previousResetKeyRef.current = params.resetKey;
|
|
2578
|
-
isStickyRef.current = true;
|
|
2579
|
-
pendingInitialScrollRef.current = true;
|
|
2580
|
-
}, [params.resetKey]);
|
|
2581
|
-
useEffect(() => {
|
|
2582
|
-
const scheduledScrollFrame = scheduledScrollFrameRef.current;
|
|
2583
|
-
return () => {
|
|
2584
|
-
if (scheduledScrollFrame !== null) cancelAnimationFrame(scheduledScrollFrame);
|
|
2585
|
-
};
|
|
2586
|
-
}, []);
|
|
2587
|
-
useLayoutEffect(() => {
|
|
2588
|
-
if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) return;
|
|
2589
|
-
if (!params.scrollRef.current) return;
|
|
2590
|
-
pendingInitialScrollRef.current = false;
|
|
2591
|
-
queueScrollToBottom({
|
|
2592
|
-
scrollRef: params.scrollRef,
|
|
2593
|
-
scheduledScrollFrameRef,
|
|
2594
|
-
isProgrammaticScrollRef
|
|
2595
|
-
});
|
|
2596
|
-
}, [
|
|
2597
|
-
params.hasContent,
|
|
2598
|
-
params.isLoading,
|
|
2599
|
-
params.scrollRef
|
|
2600
|
-
]);
|
|
2601
|
-
useLayoutEffect(() => {
|
|
2602
|
-
if (!isStickyRef.current || !params.hasContent) return;
|
|
2603
|
-
if (!params.scrollRef.current) return;
|
|
2604
|
-
queueScrollToBottom({
|
|
2605
|
-
scrollRef: params.scrollRef,
|
|
2606
|
-
scheduledScrollFrameRef,
|
|
2607
|
-
isProgrammaticScrollRef
|
|
2608
|
-
});
|
|
2609
|
-
}, [
|
|
2610
|
-
params.contentVersion,
|
|
2611
|
-
params.hasContent,
|
|
2612
|
-
params.scrollRef
|
|
2613
|
-
]);
|
|
2614
|
-
return { onScroll };
|
|
2615
|
-
}
|
|
2616
|
-
//#endregion
|
|
2617
2695
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
|
|
2618
2696
|
const FILE_ROW_CLASS_NAME = "h-5 font-mono text-[11px] leading-5";
|
|
2619
2697
|
const FILE_GUTTER_NUMBER_CELL_CLASS_NAME = "sticky left-0 z-[1] flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
|
|
@@ -2862,6 +2940,9 @@ function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
|
2862
2940
|
return stripAnsi(rawOutput);
|
|
2863
2941
|
}
|
|
2864
2942
|
}
|
|
2943
|
+
function shouldAutoExpandRunningFileOperation(toolName) {
|
|
2944
|
+
return toolName === "write_file" || toolName === "edit_file";
|
|
2945
|
+
}
|
|
2865
2946
|
function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning = true, expandOnError = false, statusTone }) {
|
|
2866
2947
|
const [expanded, setExpanded] = useState(false);
|
|
2867
2948
|
const [hasUserToggled, setHasUserToggled] = useState(false);
|
|
@@ -2962,6 +3043,7 @@ function TerminalExecutionView({ card }) {
|
|
|
2962
3043
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
2963
3044
|
canExpand: output.trim().length > 0 || isRunning,
|
|
2964
3045
|
isRunning,
|
|
3046
|
+
autoExpandWhileRunning: false,
|
|
2965
3047
|
expandOnError: hasContent,
|
|
2966
3048
|
statusTone: card.statusTone
|
|
2967
3049
|
});
|
|
@@ -3003,7 +3085,7 @@ function FileOperationView({ card }) {
|
|
|
3003
3085
|
if (block.rawText) return count + block.rawText.length;
|
|
3004
3086
|
return count + block.lines.reduce((lineCount, line) => lineCount + line.text.length + 1, 0);
|
|
3005
3087
|
}, 0);
|
|
3006
|
-
const shouldAutoExpandWhileRunning = !(isRunning && card.toolName === "write_file" && (previewLineCount > 24 || previewCharCount > 1200));
|
|
3088
|
+
const shouldAutoExpandWhileRunning = shouldAutoExpandRunningFileOperation(card.toolName) && !(isRunning && card.toolName === "write_file" && (previewLineCount > 24 || previewCharCount > 1200));
|
|
3007
3089
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3008
3090
|
canExpand: hasContent || isRunning,
|
|
3009
3091
|
isRunning,
|
|
@@ -3029,6 +3111,7 @@ function SearchSnippetView({ card }) {
|
|
|
3029
3111
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3030
3112
|
canExpand: !!output || isRunning,
|
|
3031
3113
|
isRunning,
|
|
3114
|
+
autoExpandWhileRunning: false,
|
|
3032
3115
|
statusTone: card.statusTone
|
|
3033
3116
|
});
|
|
3034
3117
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
@@ -3054,6 +3137,7 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
|
|
|
3054
3137
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3055
3138
|
canExpand: hasContent || isRunning,
|
|
3056
3139
|
isRunning,
|
|
3140
|
+
autoExpandWhileRunning: false,
|
|
3057
3141
|
statusTone: card.statusTone
|
|
3058
3142
|
});
|
|
3059
3143
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
@@ -3164,7 +3248,8 @@ const ChatMessage = memo(function ChatMessage(props) {
|
|
|
3164
3248
|
}, `tool-${index}`);
|
|
3165
3249
|
if (type === "file") return /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
3166
3250
|
file: part.file,
|
|
3167
|
-
isUser
|
|
3251
|
+
isUser,
|
|
3252
|
+
texts
|
|
3168
3253
|
}, `file-${index}`);
|
|
3169
3254
|
if (type === "unknown") return /* @__PURE__ */ jsx(ChatUnknownPart, {
|
|
3170
3255
|
label: part.label,
|