@nextclaw/agent-chat-ui 0.3.1 → 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 -921
- 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
|
+
}
|
|
1114
1237
|
//#endregion
|
|
1115
|
-
//#region src/components/chat/ui/chat-input-bar/chat-composer-
|
|
1116
|
-
function
|
|
1117
|
-
|
|
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
|
+
}
|
|
1345
|
+
//#endregion
|
|
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,540 +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.bindRootElement = (node) => {
|
|
1428
|
-
this.rootElement = node;
|
|
1429
|
-
};
|
|
1430
|
-
this.update = (config) => {
|
|
1431
|
-
this.config = config;
|
|
1432
|
-
this.snapshot = this.viewController.sync(config.nodes, this.selectedRange);
|
|
1433
|
-
return {
|
|
1434
|
-
snapshot: this.snapshot,
|
|
1435
|
-
selectedRange: this.selectedRange,
|
|
1436
|
-
bindRootElement: this.bindRootElement,
|
|
1437
|
-
handleBeforeInput: this.handleBeforeInput,
|
|
1438
|
-
handleInput: this.handleInput,
|
|
1439
|
-
handleCompositionStart: this.handleCompositionStart,
|
|
1440
|
-
handleCompositionEnd: this.handleCompositionEnd,
|
|
1441
|
-
handleKeyDown: this.handleKeyDown,
|
|
1442
|
-
handlePaste: this.handlePaste,
|
|
1443
|
-
handleBlur: this.handleBlur,
|
|
1444
|
-
syncSelectionState: this.syncSelectionState,
|
|
1445
|
-
imperativeHandle: this.createHandle()
|
|
1446
|
-
};
|
|
1447
|
-
};
|
|
1448
|
-
this.createHandle = () => {
|
|
1449
|
-
return {
|
|
1450
|
-
insertSlashItem: (item) => {
|
|
1451
|
-
this.config?.onSlashItemSelect?.(item);
|
|
1452
|
-
this.viewController.insertSlashItem(item, this.commitSnapshot);
|
|
1453
|
-
},
|
|
1454
|
-
insertFileToken: (tokenKey, label) => {
|
|
1455
|
-
this.commitSnapshot(this.controller.insertFileToken(tokenKey, label));
|
|
1456
|
-
this.focusComposerSoon();
|
|
1457
|
-
},
|
|
1458
|
-
insertFileTokens: (tokens) => {
|
|
1459
|
-
let nextSnapshot = null;
|
|
1460
|
-
for (const token of tokens) nextSnapshot = this.controller.insertFileToken(token.tokenKey, token.label);
|
|
1461
|
-
if (nextSnapshot) {
|
|
1462
|
-
this.commitSnapshot(nextSnapshot);
|
|
1463
|
-
this.focusComposerSoon();
|
|
1464
|
-
}
|
|
1465
|
-
},
|
|
1466
|
-
focusComposer: () => {
|
|
1467
|
-
this.focusComposer();
|
|
1468
|
-
},
|
|
1469
|
-
syncSelectedSkills: (nextKeys, options) => {
|
|
1470
|
-
this.viewController.syncSelectedSkills(nextKeys, options, this.commitSnapshot);
|
|
1471
|
-
}
|
|
1472
|
-
};
|
|
1473
|
-
};
|
|
1474
|
-
this.restoreDomAfterCommit = () => {
|
|
1475
|
-
if (this.isComposing) return;
|
|
1476
|
-
this.viewController.restoreSelectionIfFocused(this.rootElement, this.selection);
|
|
1477
|
-
};
|
|
1478
|
-
this.renderSurface = () => {
|
|
1479
|
-
if (this.isComposing) return;
|
|
1480
|
-
this.viewController.renderSurface({
|
|
1481
|
-
root: this.rootElement,
|
|
1482
|
-
snapshot: this.snapshot,
|
|
1483
|
-
selectedRange: this.selectedRange
|
|
1484
|
-
});
|
|
1485
|
-
};
|
|
1486
|
-
this.syncViewport = () => {
|
|
1487
|
-
this.viewController.syncViewport(this.rootElement);
|
|
1488
|
-
};
|
|
1489
|
-
this.syncSelectionState = () => {
|
|
1490
|
-
if (!this.rootElement || this.isComposing) return;
|
|
1491
|
-
const nextSnapshot = this.viewController.syncSelectionFromRoot(this.rootElement);
|
|
1492
|
-
this.selection = nextSnapshot.selection;
|
|
1493
|
-
this.selectedRange = nextSnapshot.selection;
|
|
1494
|
-
this.syncSlashState(nextSnapshot);
|
|
1495
|
-
this.requestRender();
|
|
1496
|
-
};
|
|
1497
|
-
this.handleBeforeInput = (event) => {
|
|
1498
|
-
this.viewController.handleBeforeInput({
|
|
1499
|
-
event,
|
|
1500
|
-
disabled: this.requireConfig().disabled,
|
|
1501
|
-
isComposing: this.isComposing,
|
|
1502
|
-
commitSnapshot: this.commitSnapshot
|
|
1503
|
-
});
|
|
1504
|
-
};
|
|
1505
|
-
this.handleInput = (event) => {
|
|
1506
|
-
this.viewController.handleInput({
|
|
1507
|
-
event,
|
|
1508
|
-
isComposing: this.isComposing,
|
|
1509
|
-
commitSnapshot: this.commitSnapshot
|
|
1510
|
-
});
|
|
1511
|
-
};
|
|
1512
|
-
this.handleCompositionStart = () => {
|
|
1513
|
-
this.isComposing = true;
|
|
1514
|
-
};
|
|
1515
|
-
this.handleCompositionEnd = (event) => {
|
|
1516
|
-
this.isComposing = false;
|
|
1517
|
-
this.viewController.handleCompositionEnd({
|
|
1518
|
-
event,
|
|
1519
|
-
commitSnapshot: this.commitSnapshot
|
|
1520
|
-
});
|
|
1521
|
-
};
|
|
1522
|
-
this.handleKeyDown = (event) => {
|
|
1523
|
-
const config = this.requireConfig();
|
|
1524
|
-
if (this.rootElement && !this.isComposing) {
|
|
1525
|
-
const nextSnapshot = this.viewController.syncSelectionFromRoot(this.rootElement);
|
|
1526
|
-
this.selection = nextSnapshot.selection;
|
|
1527
|
-
this.selectedRange = nextSnapshot.selection;
|
|
1528
|
-
this.snapshot = nextSnapshot;
|
|
1529
|
-
}
|
|
1530
|
-
const activeSlashItem = config.slashItems[config.activeSlashIndex] ?? null;
|
|
1531
|
-
this.viewController.handleKeyDown({
|
|
1532
|
-
event,
|
|
1533
|
-
slashItems: config.slashItems,
|
|
1534
|
-
activeSlashIndex: config.activeSlashIndex,
|
|
1535
|
-
activeSlashItem,
|
|
1536
|
-
actions: config.actions,
|
|
1537
|
-
commitSnapshot: this.commitSnapshot,
|
|
1538
|
-
insertSkillToken: this.insertSkillToken,
|
|
1539
|
-
onSlashItemSelect: config.onSlashItemSelect,
|
|
1540
|
-
onSlashActiveIndexChange: config.onSlashActiveIndexChange,
|
|
1541
|
-
onSlashQueryChange: config.onSlashQueryChange,
|
|
1542
|
-
onSlashOpenChange: config.onSlashOpenChange
|
|
1543
|
-
});
|
|
1544
|
-
};
|
|
1545
|
-
this.handlePaste = (event) => {
|
|
1546
|
-
this.viewController.handlePaste({
|
|
1547
|
-
event,
|
|
1548
|
-
onFilesAdd: this.requireConfig().onFilesAdd,
|
|
1549
|
-
commitSnapshot: this.commitSnapshot
|
|
1550
|
-
});
|
|
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;
|
|
1551
1526
|
};
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
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);
|
|
1559
1551
|
});
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
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
|
-
requestAnimationFrame(restoreSelection);
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
restoreSelection();
|
|
1612
|
-
};
|
|
1613
|
-
this.requireConfig = () => {
|
|
1614
|
-
if (!this.config) throw new Error("ChatComposerRuntime is not configured.");
|
|
1615
|
-
return this.config;
|
|
1616
|
-
};
|
|
1617
|
-
}
|
|
1618
|
-
};
|
|
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
|
+
}
|
|
1619
1600
|
//#endregion
|
|
1620
|
-
//#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
|
|
1621
1602
|
const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedComposer(props, ref) {
|
|
1622
|
-
const
|
|
1623
|
-
const
|
|
1624
|
-
const
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
onSlashQueryChange
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
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()
|
|
1639
1649
|
});
|
|
1640
|
-
useImperativeHandle(ref, () =>
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}, [
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
"
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
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
|
|
1673
1754
|
})
|
|
1674
|
-
|
|
1755
|
+
]
|
|
1675
1756
|
});
|
|
1676
1757
|
});
|
|
1758
|
+
ChatInputBarTokenizedComposer.displayName = "LexicalChatInputBarTokenizedComposer";
|
|
1677
1759
|
//#endregion
|
|
1678
1760
|
//#region src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
|
|
1679
1761
|
function InputBarHint({ hint }) {
|
|
@@ -1726,6 +1808,7 @@ const ChatInputBar = forwardRef(function ChatInputBar(props, ref) {
|
|
|
1726
1808
|
...props.toolbar.skillPicker,
|
|
1727
1809
|
onSelectedKeysChange: (nextKeys) => {
|
|
1728
1810
|
composerRef.current?.syncSelectedSkills(nextKeys, props.toolbar.skillPicker?.options ?? []);
|
|
1811
|
+
props.toolbar.skillPicker?.onSelectedKeysChange(nextKeys);
|
|
1729
1812
|
}
|
|
1730
1813
|
}
|
|
1731
1814
|
};
|
|
@@ -2100,29 +2183,17 @@ function ChatMessageInlineContent({ segments, role, texts }) {
|
|
|
2100
2183
|
}
|
|
2101
2184
|
//#endregion
|
|
2102
2185
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
|
|
2103
|
-
const FILE_CATEGORY_LABELS = {
|
|
2104
|
-
archive: "Archive",
|
|
2105
|
-
audio: "Audio",
|
|
2106
|
-
code: "Code",
|
|
2107
|
-
data: "Data",
|
|
2108
|
-
document: "Document",
|
|
2109
|
-
generic: "File",
|
|
2110
|
-
image: "Image",
|
|
2111
|
-
pdf: "PDF",
|
|
2112
|
-
sheet: "Sheet",
|
|
2113
|
-
video: "Video"
|
|
2114
|
-
};
|
|
2115
2186
|
const FILE_CATEGORY_TILE_CLASSES = {
|
|
2116
|
-
archive: "border-amber-200/80 bg-amber-
|
|
2117
|
-
audio: "border-fuchsia-200/80 bg-fuchsia-
|
|
2118
|
-
code: "border-cyan-200/80 bg-cyan-
|
|
2119
|
-
data: "border-slate-200/80 bg-slate-
|
|
2120
|
-
document: "border-blue-200/80 bg-blue-
|
|
2121
|
-
generic: "border-slate-200/80 bg-slate-
|
|
2122
|
-
image: "border-emerald-200/80 bg-emerald-
|
|
2123
|
-
pdf: "border-rose-200/80 bg-rose-
|
|
2124
|
-
sheet: "border-lime-200/80 bg-lime-
|
|
2125
|
-
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"
|
|
2126
2197
|
};
|
|
2127
2198
|
const CODE_EXTENSIONS = new Set([
|
|
2128
2199
|
"c",
|
|
@@ -2249,22 +2320,30 @@ function buildChatMessageFileMeta(file) {
|
|
|
2249
2320
|
return {
|
|
2250
2321
|
category,
|
|
2251
2322
|
extension: getFileExtension(file.label, file.mimeType),
|
|
2252
|
-
sizeLabel
|
|
2253
|
-
metaBadges: [FILE_CATEGORY_LABELS[category], sizeLabel].filter((value) => Boolean(value))
|
|
2323
|
+
sizeLabel
|
|
2254
2324
|
};
|
|
2255
2325
|
}
|
|
2256
2326
|
//#endregion
|
|
2257
2327
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/index.tsx
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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];
|
|
2263
2342
|
}
|
|
2264
|
-
function
|
|
2343
|
+
function renderMetaLine(categoryLabel, sizeLabel, isUser) {
|
|
2265
2344
|
return /* @__PURE__ */ jsx("div", {
|
|
2266
|
-
className: cn("
|
|
2267
|
-
children:
|
|
2345
|
+
className: cn("mt-1 text-xs leading-5", isUser ? "text-white/70" : "text-slate-500"),
|
|
2346
|
+
children: sizeLabel ? `${categoryLabel} · ${sizeLabel}` : categoryLabel
|
|
2268
2347
|
});
|
|
2269
2348
|
}
|
|
2270
2349
|
function renderActionPill(label, isUser, isInteractive) {
|
|
@@ -2273,18 +2352,39 @@ function renderActionPill(label, isUser, isInteractive) {
|
|
|
2273
2352
|
children: label
|
|
2274
2353
|
});
|
|
2275
2354
|
}
|
|
2276
|
-
function
|
|
2277
|
-
|
|
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);
|
|
2278
2379
|
const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
|
|
2279
2380
|
const isInteractive = Boolean(file.dataUrl);
|
|
2280
|
-
const actionLabel = isInteractive ?
|
|
2281
|
-
const
|
|
2282
|
-
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"));
|
|
2283
2384
|
if (renderAsImage && file.dataUrl) return /* @__PURE__ */ jsx("a", {
|
|
2284
2385
|
href: file.dataUrl,
|
|
2285
2386
|
target: "_blank",
|
|
2286
2387
|
rel: "noreferrer",
|
|
2287
|
-
"aria-label": `Open preview: ${file.label}`,
|
|
2288
2388
|
className: "group block",
|
|
2289
2389
|
children: /* @__PURE__ */ jsxs("div", {
|
|
2290
2390
|
className: cn("relative overflow-hidden rounded-[1rem]", isUser ? "ring-1 ring-white/10" : "bg-slate-100/80 ring-1 ring-slate-200/80"),
|
|
@@ -2296,7 +2396,7 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2296
2396
|
className: "pointer-events-none absolute right-3 top-3 flex items-center gap-2",
|
|
2297
2397
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2298
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"),
|
|
2299
|
-
children:
|
|
2399
|
+
children: categoryLabel
|
|
2300
2400
|
}), sizeLabel ? /* @__PURE__ */ jsx("span", {
|
|
2301
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"),
|
|
2302
2402
|
children: sizeLabel
|
|
@@ -2305,35 +2405,22 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2305
2405
|
})
|
|
2306
2406
|
});
|
|
2307
2407
|
const content = /* @__PURE__ */ jsxs("div", {
|
|
2308
|
-
className: "flex items-
|
|
2309
|
-
children: [/* @__PURE__ */ jsx(
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
}), /* @__PURE__ */
|
|
2408
|
+
className: "flex items-center gap-3 p-3.5",
|
|
2409
|
+
children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
|
|
2410
|
+
category,
|
|
2411
|
+
isUser
|
|
2412
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
2313
2413
|
className: "min-w-0 flex-1",
|
|
2314
|
-
children:
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
children: [FILE_CATEGORY_LABELS[category], " attachment"]
|
|
2325
|
-
})]
|
|
2326
|
-
}), renderActionPill(actionLabel, isUser, isInteractive)]
|
|
2327
|
-
}),
|
|
2328
|
-
/* @__PURE__ */ jsx("div", {
|
|
2329
|
-
className: "mt-3 flex flex-wrap gap-2",
|
|
2330
|
-
children: metaBadges.map((label) => renderMetaBadge(label, isUser))
|
|
2331
|
-
}),
|
|
2332
|
-
/* @__PURE__ */ jsx("div", {
|
|
2333
|
-
className: "mt-2",
|
|
2334
|
-
children: renderMimeType(file.mimeType, isUser)
|
|
2335
|
-
})
|
|
2336
|
-
]
|
|
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
|
+
})
|
|
2337
2424
|
})]
|
|
2338
2425
|
});
|
|
2339
2426
|
if (!isInteractive) return /* @__PURE__ */ jsx("div", {
|
|
@@ -2344,7 +2431,6 @@ function ChatMessageFile({ file, isUser = false }) {
|
|
|
2344
2431
|
href: file.dataUrl,
|
|
2345
2432
|
target: "_blank",
|
|
2346
2433
|
rel: "noreferrer",
|
|
2347
|
-
"aria-label": `Open file: ${file.label}`,
|
|
2348
2434
|
className: cn(shellClasses, "group"),
|
|
2349
2435
|
children: content
|
|
2350
2436
|
});
|
|
@@ -2380,19 +2466,113 @@ function useReasoningBlockOpenState(params) {
|
|
|
2380
2466
|
};
|
|
2381
2467
|
}
|
|
2382
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
|
|
2383
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
|
+
}
|
|
2384
2548
|
function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
2385
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
|
+
});
|
|
2386
2560
|
return /* @__PURE__ */ jsxs("details", {
|
|
2387
2561
|
className: "mt-2",
|
|
2388
2562
|
open: isOpen,
|
|
2389
2563
|
children: [/* @__PURE__ */ jsx("summary", {
|
|
2390
2564
|
className: cn("cursor-pointer text-xs", isUser ? "text-primary-100" : "text-gray-500"),
|
|
2391
2565
|
onClick: onSummaryClick,
|
|
2392
|
-
children:
|
|
2393
|
-
}), /* @__PURE__ */ jsx("
|
|
2394
|
-
|
|
2395
|
-
|
|
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
|
+
})
|
|
2396
2576
|
})]
|
|
2397
2577
|
});
|
|
2398
2578
|
}
|
|
@@ -2512,79 +2692,6 @@ function ToolCardHeaderSessionAction({ action, onAction }) {
|
|
|
2512
2692
|
});
|
|
2513
2693
|
}
|
|
2514
2694
|
//#endregion
|
|
2515
|
-
//#region src/components/chat/hooks/use-sticky-bottom-scroll.ts
|
|
2516
|
-
const DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
2517
|
-
function scrollElementToBottom(element) {
|
|
2518
|
-
element.scrollTop = element.scrollHeight;
|
|
2519
|
-
}
|
|
2520
|
-
function queueScrollToBottom(params) {
|
|
2521
|
-
if (!params.scrollRef.current) return;
|
|
2522
|
-
if (params.scheduledScrollFrameRef.current !== null) cancelAnimationFrame(params.scheduledScrollFrameRef.current);
|
|
2523
|
-
params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
|
|
2524
|
-
params.scheduledScrollFrameRef.current = null;
|
|
2525
|
-
const currentElement = params.scrollRef.current;
|
|
2526
|
-
if (!currentElement) return;
|
|
2527
|
-
params.isProgrammaticScrollRef.current = true;
|
|
2528
|
-
scrollElementToBottom(currentElement);
|
|
2529
|
-
});
|
|
2530
|
-
}
|
|
2531
|
-
function useStickyBottomScroll(params) {
|
|
2532
|
-
const isStickyRef = useRef(true);
|
|
2533
|
-
const isProgrammaticScrollRef = useRef(false);
|
|
2534
|
-
const previousResetKeyRef = useRef(null);
|
|
2535
|
-
const pendingInitialScrollRef = useRef(false);
|
|
2536
|
-
const scheduledScrollFrameRef = useRef(null);
|
|
2537
|
-
const onScroll = () => {
|
|
2538
|
-
if (isProgrammaticScrollRef.current) {
|
|
2539
|
-
isProgrammaticScrollRef.current = false;
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
const element = params.scrollRef.current;
|
|
2543
|
-
if (!element) return;
|
|
2544
|
-
isStickyRef.current = element.scrollHeight - element.scrollTop - element.clientHeight <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
|
|
2545
|
-
};
|
|
2546
|
-
useEffect(() => {
|
|
2547
|
-
if (previousResetKeyRef.current === params.resetKey) return;
|
|
2548
|
-
previousResetKeyRef.current = params.resetKey;
|
|
2549
|
-
isStickyRef.current = true;
|
|
2550
|
-
pendingInitialScrollRef.current = true;
|
|
2551
|
-
}, [params.resetKey]);
|
|
2552
|
-
useEffect(() => {
|
|
2553
|
-
const scheduledScrollFrame = scheduledScrollFrameRef.current;
|
|
2554
|
-
return () => {
|
|
2555
|
-
if (scheduledScrollFrame !== null) cancelAnimationFrame(scheduledScrollFrame);
|
|
2556
|
-
};
|
|
2557
|
-
}, []);
|
|
2558
|
-
useLayoutEffect(() => {
|
|
2559
|
-
if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) return;
|
|
2560
|
-
if (!params.scrollRef.current) return;
|
|
2561
|
-
pendingInitialScrollRef.current = false;
|
|
2562
|
-
queueScrollToBottom({
|
|
2563
|
-
scrollRef: params.scrollRef,
|
|
2564
|
-
scheduledScrollFrameRef,
|
|
2565
|
-
isProgrammaticScrollRef
|
|
2566
|
-
});
|
|
2567
|
-
}, [
|
|
2568
|
-
params.hasContent,
|
|
2569
|
-
params.isLoading,
|
|
2570
|
-
params.scrollRef
|
|
2571
|
-
]);
|
|
2572
|
-
useLayoutEffect(() => {
|
|
2573
|
-
if (!isStickyRef.current || !params.hasContent) return;
|
|
2574
|
-
if (!params.scrollRef.current) return;
|
|
2575
|
-
queueScrollToBottom({
|
|
2576
|
-
scrollRef: params.scrollRef,
|
|
2577
|
-
scheduledScrollFrameRef,
|
|
2578
|
-
isProgrammaticScrollRef
|
|
2579
|
-
});
|
|
2580
|
-
}, [
|
|
2581
|
-
params.contentVersion,
|
|
2582
|
-
params.hasContent,
|
|
2583
|
-
params.scrollRef
|
|
2584
|
-
]);
|
|
2585
|
-
return { onScroll };
|
|
2586
|
-
}
|
|
2587
|
-
//#endregion
|
|
2588
2695
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
|
|
2589
2696
|
const FILE_ROW_CLASS_NAME = "h-5 font-mono text-[11px] leading-5";
|
|
2590
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";
|
|
@@ -2833,6 +2940,9 @@ function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
|
2833
2940
|
return stripAnsi(rawOutput);
|
|
2834
2941
|
}
|
|
2835
2942
|
}
|
|
2943
|
+
function shouldAutoExpandRunningFileOperation(toolName) {
|
|
2944
|
+
return toolName === "write_file" || toolName === "edit_file";
|
|
2945
|
+
}
|
|
2836
2946
|
function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning = true, expandOnError = false, statusTone }) {
|
|
2837
2947
|
const [expanded, setExpanded] = useState(false);
|
|
2838
2948
|
const [hasUserToggled, setHasUserToggled] = useState(false);
|
|
@@ -2933,6 +3043,7 @@ function TerminalExecutionView({ card }) {
|
|
|
2933
3043
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
2934
3044
|
canExpand: output.trim().length > 0 || isRunning,
|
|
2935
3045
|
isRunning,
|
|
3046
|
+
autoExpandWhileRunning: false,
|
|
2936
3047
|
expandOnError: hasContent,
|
|
2937
3048
|
statusTone: card.statusTone
|
|
2938
3049
|
});
|
|
@@ -2974,7 +3085,7 @@ function FileOperationView({ card }) {
|
|
|
2974
3085
|
if (block.rawText) return count + block.rawText.length;
|
|
2975
3086
|
return count + block.lines.reduce((lineCount, line) => lineCount + line.text.length + 1, 0);
|
|
2976
3087
|
}, 0);
|
|
2977
|
-
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));
|
|
2978
3089
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
2979
3090
|
canExpand: hasContent || isRunning,
|
|
2980
3091
|
isRunning,
|
|
@@ -3000,6 +3111,7 @@ function SearchSnippetView({ card }) {
|
|
|
3000
3111
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3001
3112
|
canExpand: !!output || isRunning,
|
|
3002
3113
|
isRunning,
|
|
3114
|
+
autoExpandWhileRunning: false,
|
|
3003
3115
|
statusTone: card.statusTone
|
|
3004
3116
|
});
|
|
3005
3117
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
@@ -3025,6 +3137,7 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
|
|
|
3025
3137
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3026
3138
|
canExpand: hasContent || isRunning,
|
|
3027
3139
|
isRunning,
|
|
3140
|
+
autoExpandWhileRunning: false,
|
|
3028
3141
|
statusTone: card.statusTone
|
|
3029
3142
|
});
|
|
3030
3143
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
@@ -3135,7 +3248,8 @@ const ChatMessage = memo(function ChatMessage(props) {
|
|
|
3135
3248
|
}, `tool-${index}`);
|
|
3136
3249
|
if (type === "file") return /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
3137
3250
|
file: part.file,
|
|
3138
|
-
isUser
|
|
3251
|
+
isUser,
|
|
3252
|
+
texts
|
|
3139
3253
|
}, `file-${index}`);
|
|
3140
3254
|
if (type === "unknown") return /* @__PURE__ */ jsx(ChatUnknownPart, {
|
|
3141
3255
|
label: part.label,
|