@copilotkit/react-core 1.79.1 → 1.80.0
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/{copilotkit-CD3uPUN3.mjs → copilotkit-BNLtgOyD.mjs} +1855 -221
- package/dist/copilotkit-BNLtgOyD.mjs.map +1 -0
- package/dist/{copilotkit-Yz8qXx00.d.mts → copilotkit-CWOSXhRO.d.mts} +277 -40
- package/dist/copilotkit-CWOSXhRO.d.mts.map +1 -0
- package/dist/{copilotkit-DEaA0Cat.cjs → copilotkit-DKICi3nC.cjs} +1843 -196
- package/dist/copilotkit-DKICi3nC.cjs.map +1 -0
- package/dist/{copilotkit-k7IYk2AN.d.cts → copilotkit-Dfw9HzO9.d.cts} +277 -40
- package/dist/copilotkit-Dfw9HzO9.d.cts.map +1 -0
- package/dist/index.cjs +15 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -3
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +127 -16
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/context.d.cts +7 -0
- package/dist/v2/context.d.cts.map +1 -1
- package/dist/v2/context.d.mts +7 -0
- package/dist/v2/context.d.mts.map +1 -1
- package/dist/v2/headless.cjs +106 -16
- package/dist/v2/headless.cjs.map +1 -1
- package/dist/v2/headless.d.cts +20 -2
- package/dist/v2/headless.d.cts.map +1 -1
- package/dist/v2/headless.d.mts +20 -2
- package/dist/v2/headless.d.mts.map +1 -1
- package/dist/v2/headless.mjs +106 -16
- package/dist/v2/headless.mjs.map +1 -1
- package/dist/v2/index.cjs +3 -1
- package/dist/v2/index.css +1 -1
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/index.umd.js +1858 -222
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +11 -8
- package/dist/copilotkit-CD3uPUN3.mjs.map +0 -1
- package/dist/copilotkit-DEaA0Cat.cjs.map +0 -1
- package/dist/copilotkit-Yz8qXx00.d.mts.map +0 -1
- package/dist/copilotkit-k7IYk2AN.d.cts.map +0 -1
|
@@ -47,6 +47,8 @@ let zod = require("zod");
|
|
|
47
47
|
let _copilotkit_mcp_apps_renderer_activity = require("@copilotkit/mcp-apps-renderer/activity");
|
|
48
48
|
let _copilotkit_a2ui_renderer = require("@copilotkit/a2ui-renderer");
|
|
49
49
|
let zod_to_json_schema = require("zod-to-json-schema");
|
|
50
|
+
let _radix_ui_react_popover = require("@radix-ui/react-popover");
|
|
51
|
+
_radix_ui_react_popover = __toESM(_radix_ui_react_popover);
|
|
50
52
|
let react_dom = require("react-dom");
|
|
51
53
|
let _tanstack_react_virtual = require("@tanstack/react-virtual");
|
|
52
54
|
let use_stick_to_bottom = require("use-stick-to-bottom");
|
|
@@ -69,7 +71,7 @@ function shallowEqual(obj1, obj2) {
|
|
|
69
71
|
* Returns true only for plain JS objects (`{}`), excluding arrays, Dates,
|
|
70
72
|
* class instances, and other exotic objects that happen to have typeof "object".
|
|
71
73
|
*/
|
|
72
|
-
function isPlainObject(obj) {
|
|
74
|
+
function isPlainObject$1(obj) {
|
|
73
75
|
return obj !== null && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]";
|
|
74
76
|
}
|
|
75
77
|
/**
|
|
@@ -88,7 +90,7 @@ function isPlainObject(obj) {
|
|
|
88
90
|
function useShallowStableRef(value) {
|
|
89
91
|
const ref = (0, react.useRef)(value);
|
|
90
92
|
if (ref.current === value) return ref.current;
|
|
91
|
-
if (isPlainObject(ref.current) && isPlainObject(value)) {
|
|
93
|
+
if (isPlainObject$1(ref.current) && isPlainObject$1(value)) {
|
|
92
94
|
if (shallowEqual(ref.current, value)) return ref.current;
|
|
93
95
|
}
|
|
94
96
|
ref.current = value;
|
|
@@ -104,6 +106,8 @@ const CopilotChatDefaultLabels = {
|
|
|
104
106
|
chatInputToolbarFinishTranscribeButtonLabel: "Finish",
|
|
105
107
|
chatInputToolbarAddButtonLabel: "Add attachments",
|
|
106
108
|
chatInputToolbarToolsButtonLabel: "Tools",
|
|
109
|
+
chatInputToolbarSendButtonLabel: "Send message",
|
|
110
|
+
chatInputToolbarStopButtonLabel: "Stop",
|
|
107
111
|
assistantMessageToolbarCopyCodeLabel: "Copy",
|
|
108
112
|
assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
|
|
109
113
|
assistantMessageToolbarCopyMessageLabel: "Copy",
|
|
@@ -116,13 +120,18 @@ const CopilotChatDefaultLabels = {
|
|
|
116
120
|
assistantMessageToolbarInspectorHideDescription: "Until you reload this page",
|
|
117
121
|
assistantMessageToolbarThumbsUpLabel: "Good response",
|
|
118
122
|
assistantMessageToolbarThumbsDownLabel: "Bad response",
|
|
123
|
+
assistantMessageFeedbackCommentPlaceholder: "Add a comment (optional)",
|
|
124
|
+
assistantMessageFeedbackCommentSubmitLabel: "Send comment",
|
|
119
125
|
assistantMessageToolbarReadAloudLabel: "Read aloud",
|
|
120
126
|
assistantMessageToolbarRegenerateLabel: "Regenerate",
|
|
121
127
|
userMessageToolbarCopyMessageLabel: "Copy",
|
|
122
128
|
userMessageToolbarEditMessageLabel: "Edit",
|
|
129
|
+
userMessageToolbarPreviousBranchLabel: "Previous branch",
|
|
130
|
+
userMessageToolbarNextBranchLabel: "Next branch",
|
|
123
131
|
chatDisclaimerText: "AI can make mistakes. Please verify important information.",
|
|
124
132
|
chatToggleOpenLabel: "Open chat",
|
|
125
133
|
chatToggleCloseLabel: "Close chat",
|
|
134
|
+
chatScrollToBottomLabel: "Scroll to bottom",
|
|
126
135
|
modalHeaderTitle: "CopilotKit Chat",
|
|
127
136
|
welcomeMessageText: "How can I help you today?"
|
|
128
137
|
};
|
|
@@ -384,9 +393,9 @@ ControlledModalOpenScope.displayName = "ControlledModalOpenScope";
|
|
|
384
393
|
|
|
385
394
|
//#endregion
|
|
386
395
|
//#region src/v2/lib/utils.ts
|
|
387
|
-
const twMerge$
|
|
396
|
+
const twMerge$12 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
|
|
388
397
|
function cn(...inputs) {
|
|
389
|
-
return twMerge$
|
|
398
|
+
return twMerge$12((0, clsx.clsx)(inputs));
|
|
390
399
|
}
|
|
391
400
|
|
|
392
401
|
//#endregion
|
|
@@ -834,6 +843,11 @@ function renderSlot(slot, DefaultComponent, props) {
|
|
|
834
843
|
//#region src/v2/components/chat/CopilotChatInput.tsx
|
|
835
844
|
const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
|
|
836
845
|
const SLASH_MENU_ITEM_HEIGHT_PX = 40;
|
|
846
|
+
/**
|
|
847
|
+
* The chat composer: a text area with the add-menu, voice and send/stop
|
|
848
|
+
* buttons, and a slash-command menu that lists `toolsMenu` items once the text
|
|
849
|
+
* starts with "/".
|
|
850
|
+
*/
|
|
837
851
|
function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
|
|
838
852
|
const isControlled = value !== void 0;
|
|
839
853
|
const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
|
|
@@ -1053,10 +1067,12 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
|
|
|
1053
1067
|
send();
|
|
1054
1068
|
};
|
|
1055
1069
|
const BoundAudioRecorder = renderSlot(audioRecorder, CopilotChatAudioRecorder, { ref: audioRecorderRef });
|
|
1070
|
+
const showsStop = isProcessing && canStop;
|
|
1056
1071
|
const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, {
|
|
1057
1072
|
onClick: handleSendButtonClick,
|
|
1058
1073
|
disabled: isProcessing ? !canStop : !canSend,
|
|
1059
|
-
|
|
1074
|
+
"aria-label": showsStop ? labels.chatInputToolbarStopButtonLabel : labels.chatInputToolbarSendButtonLabel,
|
|
1075
|
+
children: showsStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0
|
|
1060
1076
|
});
|
|
1061
1077
|
const BoundStartTranscribeButton = renderSlot(startTranscribeButton, CopilotChatInput.StartTranscribeButton, { onClick: onStartTranscribe });
|
|
1062
1078
|
const BoundCancelTranscribeButton = renderSlot(cancelTranscribeButton, CopilotChatInput.CancelTranscribeButton, { onClick: onCancelTranscribe });
|
|
@@ -1368,18 +1384,22 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
|
|
|
1368
1384
|
});
|
|
1369
1385
|
}
|
|
1370
1386
|
(function(_CopilotChatInput) {
|
|
1371
|
-
_CopilotChatInput.SendButton = ({ className, children, ...props }) =>
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1387
|
+
_CopilotChatInput.SendButton = ({ className, children, ...props }) => {
|
|
1388
|
+
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
1389
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1390
|
+
className: "cpk:mr-[10px]",
|
|
1391
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1392
|
+
type: "button",
|
|
1393
|
+
"data-testid": "copilot-send-button",
|
|
1394
|
+
variant: "chatInputToolbarPrimary",
|
|
1395
|
+
size: "chatInputToolbarIcon",
|
|
1396
|
+
className,
|
|
1397
|
+
"aria-label": labels.chatInputToolbarSendButtonLabel,
|
|
1398
|
+
...props,
|
|
1399
|
+
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
|
|
1400
|
+
})
|
|
1401
|
+
});
|
|
1402
|
+
};
|
|
1383
1403
|
const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
|
|
1384
1404
|
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
1385
1405
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
@@ -1389,6 +1409,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
|
|
|
1389
1409
|
variant: "chatInputToolbarSecondary",
|
|
1390
1410
|
size: "chatInputToolbarIcon",
|
|
1391
1411
|
className: (0, tailwind_merge.twMerge)(defaultClassName, className),
|
|
1412
|
+
"aria-label": labels[labelKey],
|
|
1392
1413
|
...props,
|
|
1393
1414
|
children: icon
|
|
1394
1415
|
})
|
|
@@ -1459,6 +1480,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
|
|
|
1459
1480
|
size: "chatInputToolbarIcon",
|
|
1460
1481
|
className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
|
|
1461
1482
|
disabled: isDisabled,
|
|
1483
|
+
"aria-label": labels.chatInputToolbarAddButtonLabel,
|
|
1462
1484
|
...props,
|
|
1463
1485
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
|
|
1464
1486
|
});
|
|
@@ -1608,6 +1630,32 @@ function warnAboutUnrenderedToolCalls(toolNames, registered, alreadyWarned) {
|
|
|
1608
1630
|
}
|
|
1609
1631
|
}
|
|
1610
1632
|
/**
|
|
1633
|
+
* The tool-call renderers registered on the provider, kept current.
|
|
1634
|
+
* @internal
|
|
1635
|
+
*/
|
|
1636
|
+
function useToolCallRenderers() {
|
|
1637
|
+
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
1638
|
+
return (0, react.useSyncExternalStore)((callback) => copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Which tool names have a renderer of their own, whether a wildcard (`"*"`)
|
|
1642
|
+
* renderer is registered, and which of them opted into grouping.
|
|
1643
|
+
* @internal
|
|
1644
|
+
*/
|
|
1645
|
+
function useToolCallRendererNames() {
|
|
1646
|
+
const renderers = useToolCallRenderers();
|
|
1647
|
+
return (0, react.useMemo)(() => {
|
|
1648
|
+
const named = renderers.filter((rc) => rc.name !== "*");
|
|
1649
|
+
const wildcards = renderers.filter((rc) => rc.name === "*");
|
|
1650
|
+
return {
|
|
1651
|
+
named: new Set(named.map((rc) => rc.name)),
|
|
1652
|
+
groupedNames: new Set(named.filter((rc) => rc.group).map((rc) => rc.name)),
|
|
1653
|
+
wildcard: wildcards.length > 0,
|
|
1654
|
+
wildcardGroups: wildcards.some((rc) => rc.group)
|
|
1655
|
+
};
|
|
1656
|
+
}, [renderers]);
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1611
1659
|
* Hook that returns a function to render tool calls based on the render functions
|
|
1612
1660
|
* defined in CopilotKitProvider.
|
|
1613
1661
|
*
|
|
@@ -1618,9 +1666,7 @@ function useRenderToolCall() {
|
|
|
1618
1666
|
const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
|
|
1619
1667
|
const unrenderedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1620
1668
|
const warnedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1621
|
-
const renderToolCalls = (
|
|
1622
|
-
return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
|
|
1623
|
-
}, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
|
|
1669
|
+
const renderToolCalls = useToolCallRenderers();
|
|
1624
1670
|
const renderToolCall = (0, react.useCallback)(({ toolCall, toolMessage }) => {
|
|
1625
1671
|
const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
|
|
1626
1672
|
const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
|
|
@@ -1655,7 +1701,7 @@ function useRenderToolCall() {
|
|
|
1655
1701
|
|
|
1656
1702
|
//#endregion
|
|
1657
1703
|
//#region package.json
|
|
1658
|
-
var version = "1.
|
|
1704
|
+
var version = "1.80.0";
|
|
1659
1705
|
|
|
1660
1706
|
//#endregion
|
|
1661
1707
|
//#region src/v2/components/CopilotKitInspector.tsx
|
|
@@ -1891,7 +1937,7 @@ function InlineFeatureWarning({ featureName }) {
|
|
|
1891
1937
|
* Fetches resource content on-demand via proxied MCP requests. The React shell
|
|
1892
1938
|
* owns the iframe; `bindMcpApp` owns the protocol.
|
|
1893
1939
|
*/
|
|
1894
|
-
const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent }) {
|
|
1940
|
+
const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent, allowSameOriginFallback }) {
|
|
1895
1941
|
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
1896
1942
|
const messageId = message?.id;
|
|
1897
1943
|
const containerRef = (0, react.useRef)(null);
|
|
@@ -1941,6 +1987,7 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, mess
|
|
|
1941
1987
|
getAgent: () => agentRef.current,
|
|
1942
1988
|
host: copilotkit,
|
|
1943
1989
|
messageId,
|
|
1990
|
+
options: { allowSameOriginFallback },
|
|
1944
1991
|
hooks: {
|
|
1945
1992
|
onResource: (resource) => {
|
|
1946
1993
|
if (!mounted) return;
|
|
@@ -1982,6 +2029,7 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, mess
|
|
|
1982
2029
|
agent,
|
|
1983
2030
|
copilotkit,
|
|
1984
2031
|
messageId,
|
|
2032
|
+
allowSameOriginFallback,
|
|
1985
2033
|
content.resourceUri,
|
|
1986
2034
|
content.serverHash,
|
|
1987
2035
|
content.serverId
|
|
@@ -3196,7 +3244,8 @@ function defineToolCallRenderer(def) {
|
|
|
3196
3244
|
name: def.name,
|
|
3197
3245
|
args: argsSchema,
|
|
3198
3246
|
render: def.render,
|
|
3199
|
-
...def.agentId ? { agentId: def.agentId } : {}
|
|
3247
|
+
...def.agentId ? { agentId: def.agentId } : {},
|
|
3248
|
+
...def.group ? { group: true } : {}
|
|
3200
3249
|
};
|
|
3201
3250
|
}
|
|
3202
3251
|
|
|
@@ -3412,6 +3461,11 @@ const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follo
|
|
|
3412
3461
|
- Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
|
|
3413
3462
|
- Minimal transitions (150ms) for hover/focus states only. No decorative animations.
|
|
3414
3463
|
- Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
|
|
3464
|
+
/** The MCP Apps renderer with `mcpApps.allowSameOriginFallback` applied. */
|
|
3465
|
+
const SameOriginMCPAppsActivityRenderer = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MCPAppsActivityRenderer, {
|
|
3466
|
+
...props,
|
|
3467
|
+
allowSameOriginFallback: true
|
|
3468
|
+
});
|
|
3415
3469
|
const DEFAULT_LEARNING = Object.freeze({});
|
|
3416
3470
|
function toLearningConfig(learning) {
|
|
3417
3471
|
if (learning === true) return DEFAULT_LEARNING;
|
|
@@ -3428,7 +3482,8 @@ function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
|
|
|
3428
3482
|
}, [value, warningMessage]);
|
|
3429
3483
|
return value;
|
|
3430
3484
|
}
|
|
3431
|
-
|
|
3485
|
+
/** Provide a shared core instance and synchronize runtime, tool, and UI configuration. */
|
|
3486
|
+
const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, messageFilter, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, showIntelligenceIndicator = true, useSingleEndpoint, onError, a2ui, mcpApps, defaultThrottleMs, debug, learning }) => {
|
|
3432
3487
|
const learningConfig = (0, react.useMemo)(() => toLearningConfig(learning), [learning]);
|
|
3433
3488
|
const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
|
|
3434
3489
|
const [inspectorVisible, setInspectorVisible] = (0, react.useState)(false);
|
|
@@ -3485,11 +3540,12 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
|
|
|
3485
3540
|
const core = copilotkitRef.current;
|
|
3486
3541
|
return (0, _copilotkit_a2ui_renderer.filterCatalog)(rawCatalog, (name) => core ? core.isCatalogComponentEnabled(name) : true);
|
|
3487
3542
|
}, [rawCatalog, catalogToggleVersion]);
|
|
3543
|
+
const mcpAppsAllowSameOrigin = mcpApps?.allowSameOriginFallback === true;
|
|
3488
3544
|
const builtInActivityRenderers = (0, react.useMemo)(() => {
|
|
3489
3545
|
const renderers = [{
|
|
3490
3546
|
activityType: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityType,
|
|
3491
3547
|
content: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityContentSchema,
|
|
3492
|
-
render: MCPAppsActivityRenderer
|
|
3548
|
+
render: mcpAppsAllowSameOrigin ? SameOriginMCPAppsActivityRenderer : MCPAppsActivityRenderer
|
|
3493
3549
|
}];
|
|
3494
3550
|
if (openGenUIActive) renderers.push({
|
|
3495
3551
|
activityType: OpenGenerativeUIActivityType,
|
|
@@ -3507,7 +3563,8 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
|
|
|
3507
3563
|
a2uiActive,
|
|
3508
3564
|
openGenUIActive,
|
|
3509
3565
|
a2ui,
|
|
3510
|
-
filteredCatalog
|
|
3566
|
+
filteredCatalog,
|
|
3567
|
+
mcpAppsAllowSameOrigin
|
|
3511
3568
|
]);
|
|
3512
3569
|
const allActivityRenderers = (0, react.useMemo)(() => {
|
|
3513
3570
|
return [...renderActivityMessagesList, ...builtInActivityRenderers];
|
|
@@ -3615,6 +3672,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
|
|
|
3615
3672
|
description: _copilotkit_shared.GENERATE_SANDBOXED_UI_DESCRIPTION,
|
|
3616
3673
|
parameters: GenerateSandboxedUiArgsSchema,
|
|
3617
3674
|
handler: async () => "UI generated",
|
|
3675
|
+
reconnectBehavior: "resume-pending",
|
|
3618
3676
|
followUp: true,
|
|
3619
3677
|
render: OpenGenerativeUIToolRenderer
|
|
3620
3678
|
}];
|
|
@@ -4020,12 +4078,86 @@ function useRenderActivityMessage() {
|
|
|
4020
4078
|
}), [renderActivityMessage, findRenderer]);
|
|
4021
4079
|
}
|
|
4022
4080
|
|
|
4081
|
+
//#endregion
|
|
4082
|
+
//#region src/v2/hooks/tool-registration.ts
|
|
4083
|
+
/**
|
|
4084
|
+
* Deps comparison shared by `useFrontendTool` (and through it
|
|
4085
|
+
* `useHumanInTheLoop` and `useComponent`) and `useRenderTool`. Not part of the
|
|
4086
|
+
* public API.
|
|
4087
|
+
*/
|
|
4088
|
+
const EMPTY_DEPS$1 = [];
|
|
4089
|
+
function isPlainObject(value) {
|
|
4090
|
+
const proto = Object.getPrototypeOf(value);
|
|
4091
|
+
return proto === Object.prototype || proto === null;
|
|
4092
|
+
}
|
|
4093
|
+
/**
|
|
4094
|
+
* Plain data (primitives, plain objects, arrays, dates) compares by value.
|
|
4095
|
+
* Functions always compare equal: these hooks used to compare
|
|
4096
|
+
* `JSON.stringify(deps)`, which turns every function into `null`, and a
|
|
4097
|
+
* function that counted as changed would re-register on every render when it
|
|
4098
|
+
* is written inline (and loop in a component that also reads
|
|
4099
|
+
* `useRenderToolCall`). Everything else (class instances, maps, sets) compares
|
|
4100
|
+
* by identity. A cycle inside plain data is safe: a pair already being
|
|
4101
|
+
* compared counts as equal for that branch.
|
|
4102
|
+
*/
|
|
4103
|
+
function depEqual(a, b, seen) {
|
|
4104
|
+
if (Object.is(a, b)) return true;
|
|
4105
|
+
if (typeof a === "function" && typeof b === "function") return true;
|
|
4106
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
4107
|
+
if (a instanceof Date && b instanceof Date) return Object.is(a.getTime(), b.getTime());
|
|
4108
|
+
const aIsArray = Array.isArray(a);
|
|
4109
|
+
if (aIsArray !== Array.isArray(b)) return false;
|
|
4110
|
+
if (!aIsArray && !(isPlainObject(a) && isPlainObject(b))) return false;
|
|
4111
|
+
if (seen.get(a) === b) return true;
|
|
4112
|
+
seen.set(a, b);
|
|
4113
|
+
if (aIsArray) {
|
|
4114
|
+
const left = a;
|
|
4115
|
+
const right = b;
|
|
4116
|
+
if (left.length !== right.length) return false;
|
|
4117
|
+
for (let i = 0; i < left.length; i++) if (!depEqual(left[i], right[i], seen)) return false;
|
|
4118
|
+
return true;
|
|
4119
|
+
}
|
|
4120
|
+
const left = a;
|
|
4121
|
+
const right = b;
|
|
4122
|
+
const leftKeys = Object.keys(left);
|
|
4123
|
+
if (leftKeys.length !== Object.keys(right).length) return false;
|
|
4124
|
+
for (const key of leftKeys) {
|
|
4125
|
+
if (!Object.prototype.hasOwnProperty.call(right, key)) return false;
|
|
4126
|
+
if (!depEqual(left[key], right[key], seen)) return false;
|
|
4127
|
+
}
|
|
4128
|
+
return true;
|
|
4129
|
+
}
|
|
4130
|
+
/** Whether two deps arrays are equal. A change in length is always a change. */
|
|
4131
|
+
function depsEqual(prev, next) {
|
|
4132
|
+
if (prev.length !== next.length) return false;
|
|
4133
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4134
|
+
for (let i = 0; i < prev.length; i++) if (!depEqual(prev[i], next[i], seen)) return false;
|
|
4135
|
+
return true;
|
|
4136
|
+
}
|
|
4137
|
+
/**
|
|
4138
|
+
* Turns a caller's `deps` array into a number that changes exactly when the
|
|
4139
|
+
* deps change, by the rules of {@link depsEqual}. Put the number, not the
|
|
4140
|
+
* array, in the registration effect's dependency list: that keeps the list a
|
|
4141
|
+
* constant length and never serializes a value.
|
|
4142
|
+
*/
|
|
4143
|
+
function useDepsVersion(deps) {
|
|
4144
|
+
const next = deps ?? EMPTY_DEPS$1;
|
|
4145
|
+
const ref = (0, react.useRef)({
|
|
4146
|
+
deps: next,
|
|
4147
|
+
version: 0
|
|
4148
|
+
});
|
|
4149
|
+
if (ref.current.deps !== next && !depsEqual(ref.current.deps, next)) ref.current = {
|
|
4150
|
+
deps: next,
|
|
4151
|
+
version: ref.current.version + 1
|
|
4152
|
+
};
|
|
4153
|
+
return ref.current.version;
|
|
4154
|
+
}
|
|
4155
|
+
|
|
4023
4156
|
//#endregion
|
|
4024
4157
|
//#region src/v2/hooks/use-frontend-tool.tsx
|
|
4025
|
-
const EMPTY_DEPS$2 = [];
|
|
4026
4158
|
function useFrontendTool(tool, deps) {
|
|
4027
4159
|
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
4028
|
-
const
|
|
4160
|
+
const depsVersion = useDepsVersion(deps);
|
|
4029
4161
|
(0, react.useLayoutEffect)(() => {
|
|
4030
4162
|
const name = tool.name;
|
|
4031
4163
|
if (copilotkit.getTool({
|
|
@@ -4049,14 +4181,14 @@ function useFrontendTool(tool, deps) {
|
|
|
4049
4181
|
tool.name,
|
|
4050
4182
|
tool.available,
|
|
4051
4183
|
copilotkit,
|
|
4052
|
-
|
|
4184
|
+
depsVersion,
|
|
4053
4185
|
JSON.stringify(tool.webmcp ?? null)
|
|
4054
4186
|
]);
|
|
4055
4187
|
}
|
|
4056
4188
|
|
|
4057
4189
|
//#endregion
|
|
4058
4190
|
//#region src/v2/hooks/use-frontend-tools.tsx
|
|
4059
|
-
const EMPTY_DEPS
|
|
4191
|
+
const EMPTY_DEPS = [];
|
|
4060
4192
|
const EMPTY_TOOLS = [];
|
|
4061
4193
|
/**
|
|
4062
4194
|
* Register a list of frontend tools whose length can change between renders.
|
|
@@ -4110,7 +4242,7 @@ const EMPTY_TOOLS = [];
|
|
|
4110
4242
|
function useFrontendTools(tools, deps) {
|
|
4111
4243
|
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
4112
4244
|
const toolList = tools ?? EMPTY_TOOLS;
|
|
4113
|
-
const extraDeps = deps ?? EMPTY_DEPS
|
|
4245
|
+
const extraDeps = deps ?? EMPTY_DEPS;
|
|
4114
4246
|
(0, react.useLayoutEffect)(() => {
|
|
4115
4247
|
const deduped = /* @__PURE__ */ new Map();
|
|
4116
4248
|
for (const tool of toolList) {
|
|
@@ -4221,7 +4353,6 @@ function useComponent(config, deps) {
|
|
|
4221
4353
|
|
|
4222
4354
|
//#endregion
|
|
4223
4355
|
//#region src/v2/hooks/use-render-tool.tsx
|
|
4224
|
-
const EMPTY_DEPS = [];
|
|
4225
4356
|
/**
|
|
4226
4357
|
* Registers a renderer entry in CopilotKit's `renderToolCalls` registry.
|
|
4227
4358
|
*
|
|
@@ -4265,7 +4396,7 @@ const EMPTY_DEPS = [];
|
|
|
4265
4396
|
*/
|
|
4266
4397
|
function useRenderTool(config, deps) {
|
|
4267
4398
|
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
4268
|
-
const
|
|
4399
|
+
const depsVersion = useDepsVersion(deps);
|
|
4269
4400
|
(0, react.useEffect)(() => {
|
|
4270
4401
|
const renderer = config.name === "*" && !config.parameters ? defineToolCallRenderer({
|
|
4271
4402
|
name: "*",
|
|
@@ -4273,7 +4404,8 @@ function useRenderTool(config, deps) {
|
|
|
4273
4404
|
...props,
|
|
4274
4405
|
parameters: props.args
|
|
4275
4406
|
}),
|
|
4276
|
-
...config.agentId ? { agentId: config.agentId } : {}
|
|
4407
|
+
...config.agentId ? { agentId: config.agentId } : {},
|
|
4408
|
+
group: config.group
|
|
4277
4409
|
}) : defineToolCallRenderer({
|
|
4278
4410
|
name: config.name,
|
|
4279
4411
|
args: config.parameters,
|
|
@@ -4291,16 +4423,395 @@ function useRenderTool(config, deps) {
|
|
|
4291
4423
|
parameters: props.args
|
|
4292
4424
|
});
|
|
4293
4425
|
},
|
|
4294
|
-
...config.agentId ? { agentId: config.agentId } : {}
|
|
4426
|
+
...config.agentId ? { agentId: config.agentId } : {},
|
|
4427
|
+
group: config.group
|
|
4295
4428
|
});
|
|
4296
4429
|
copilotkit.addHookRenderToolCall(renderer);
|
|
4297
4430
|
}, [
|
|
4298
4431
|
config.name,
|
|
4432
|
+
config.group,
|
|
4299
4433
|
copilotkit,
|
|
4300
|
-
|
|
4434
|
+
depsVersion
|
|
4301
4435
|
]);
|
|
4302
4436
|
}
|
|
4303
4437
|
|
|
4438
|
+
//#endregion
|
|
4439
|
+
//#region src/v2/components/chat/CopilotChatToolCallGroup.tsx
|
|
4440
|
+
/**
|
|
4441
|
+
* Set by a group around the calls it shows, so CopilotKit's built-in tool card
|
|
4442
|
+
* draws itself as a compact row that fits the group instead of a card.
|
|
4443
|
+
* @internal
|
|
4444
|
+
*/
|
|
4445
|
+
const ToolCallGroupContext = (0, react.createContext)(null);
|
|
4446
|
+
/**
|
|
4447
|
+
* A call without a result is running only while the group is active: once
|
|
4448
|
+
* the run has moved on or ended, its result is never coming.
|
|
4449
|
+
*/
|
|
4450
|
+
const entryStatus = ({ toolMessage }, active) => toolMessage ? toolMessage.error ? "failed" : "done" : active ? "running" : "stopped";
|
|
4451
|
+
/** @internal */
|
|
4452
|
+
const statusLabels = {
|
|
4453
|
+
running: "Running",
|
|
4454
|
+
done: "Done",
|
|
4455
|
+
failed: "Failed",
|
|
4456
|
+
stopped: "Stopped"
|
|
4457
|
+
};
|
|
4458
|
+
/** The first non-empty string argument: usually a command, path or query. */
|
|
4459
|
+
function argumentPreview(parameters) {
|
|
4460
|
+
if (!parameters || typeof parameters !== "object") return void 0;
|
|
4461
|
+
return Object.values(parameters).find((v) => typeof v === "string" && v.trim().length > 0)?.replace(/\s+/g, " ");
|
|
4462
|
+
}
|
|
4463
|
+
const entryPreview = (entry) => argumentPreview((0, _copilotkit_shared.partialJSONParse)(entry.toolCall.function.arguments));
|
|
4464
|
+
/** "run_command ×2, read_file" */
|
|
4465
|
+
function toolNames(calls) {
|
|
4466
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4467
|
+
for (const { toolCall } of calls) {
|
|
4468
|
+
const name = toolCall.function.name;
|
|
4469
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
4470
|
+
}
|
|
4471
|
+
return [...counts].map(([name, count]) => count > 1 ? `${name} ×${count}` : name).join(", ");
|
|
4472
|
+
}
|
|
4473
|
+
const pluralize = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
4474
|
+
/**
|
|
4475
|
+
* Height transition between closed and its content's height. Closed content
|
|
4476
|
+
* stays mounted (so its state survives) but is inert.
|
|
4477
|
+
* @internal
|
|
4478
|
+
*/
|
|
4479
|
+
function Collapsible({ open, id, className, children }) {
|
|
4480
|
+
const ref = (0, react.useRef)(null);
|
|
4481
|
+
(0, react.useLayoutEffect)(() => {
|
|
4482
|
+
ref.current?.toggleAttribute("inert", !open);
|
|
4483
|
+
}, [open]);
|
|
4484
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4485
|
+
ref,
|
|
4486
|
+
id,
|
|
4487
|
+
className: (0, tailwind_merge.twMerge)("cpk:grid cpk:transition-[grid-template-rows] cpk:duration-300 cpk:ease-[cubic-bezier(0.16,1,0.3,1)] cpk:motion-reduce:transition-none", className),
|
|
4488
|
+
style: { gridTemplateRows: open ? "1fr" : "0fr" },
|
|
4489
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4490
|
+
className: "cpk:min-h-0 cpk:overflow-hidden",
|
|
4491
|
+
children
|
|
4492
|
+
})
|
|
4493
|
+
});
|
|
4494
|
+
}
|
|
4495
|
+
function StatusIcon({ status, className }) {
|
|
4496
|
+
const Icon = {
|
|
4497
|
+
running: lucide_react.Loader2,
|
|
4498
|
+
done: lucide_react.Check,
|
|
4499
|
+
failed: lucide_react.X,
|
|
4500
|
+
stopped: lucide_react.Circle
|
|
4501
|
+
}[status];
|
|
4502
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
4503
|
+
"aria-hidden": "true",
|
|
4504
|
+
className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:text-muted-foreground", status === "running" && "cpk:animate-spin", status === "failed" && "cpk:text-destructive", className)
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
function Chevron({ open, className }) {
|
|
4508
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
|
|
4509
|
+
"aria-hidden": "true",
|
|
4510
|
+
className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", open && "cpk:rotate-90", className)
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
function FailedCount({ calls }) {
|
|
4514
|
+
const failed = calls.filter((call) => call.toolMessage?.error).length;
|
|
4515
|
+
if (!failed) return null;
|
|
4516
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4517
|
+
className: "cpk:shrink-0 cpk:text-destructive",
|
|
4518
|
+
children: [failed, " failed"]
|
|
4519
|
+
});
|
|
4520
|
+
}
|
|
4521
|
+
/**
|
|
4522
|
+
* Guarded JSON.stringify for the payload blocks. A circular reference would
|
|
4523
|
+
* otherwise crash the entire React tree on render.
|
|
4524
|
+
*/
|
|
4525
|
+
function stringifyPayload(value) {
|
|
4526
|
+
if (typeof value === "string") return value;
|
|
4527
|
+
try {
|
|
4528
|
+
return JSON.stringify(value, null, 2);
|
|
4529
|
+
} catch (err) {
|
|
4530
|
+
console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload; falling back to String():", err);
|
|
4531
|
+
try {
|
|
4532
|
+
return String(value);
|
|
4533
|
+
} catch {
|
|
4534
|
+
return "[unserializable]";
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
function Payload({ label, value }) {
|
|
4539
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4540
|
+
className: "cpk:text-[11px] cpk:font-medium cpk:uppercase cpk:tracking-wide cpk:text-muted-foreground",
|
|
4541
|
+
children: label
|
|
4542
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
4543
|
+
className: "cpk:m-0 cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-lg cpk:bg-muted cpk:p-2.5 cpk:font-mono cpk:text-xs cpk:leading-relaxed cpk:text-foreground cpk:whitespace-pre-wrap cpk:break-words",
|
|
4544
|
+
children: stringifyPayload(value)
|
|
4545
|
+
})] });
|
|
4546
|
+
}
|
|
4547
|
+
/**
|
|
4548
|
+
* A tool call's arguments and, once it has one, its result.
|
|
4549
|
+
* @internal
|
|
4550
|
+
*/
|
|
4551
|
+
function ToolCallPayloads({ parameters, result, className }) {
|
|
4552
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4553
|
+
className: (0, tailwind_merge.twMerge)("cpk:grid cpk:gap-3", className),
|
|
4554
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
|
|
4555
|
+
label: "Arguments",
|
|
4556
|
+
value: parameters ?? {}
|
|
4557
|
+
}), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
|
|
4558
|
+
label: "Result",
|
|
4559
|
+
value: result
|
|
4560
|
+
})]
|
|
4561
|
+
});
|
|
4562
|
+
}
|
|
4563
|
+
/**
|
|
4564
|
+
* The built-in tool card drawn as a row inside a group: status, name and main
|
|
4565
|
+
* argument, opening to the payloads.
|
|
4566
|
+
* @internal
|
|
4567
|
+
*/
|
|
4568
|
+
function ToolCallRow({ name, parameters, result, status, appearance, className, ...props }) {
|
|
4569
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
4570
|
+
const detailsId = (0, react.useId)();
|
|
4571
|
+
const preview = argumentPreview(parameters);
|
|
4572
|
+
const line = appearance === "line";
|
|
4573
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4574
|
+
"data-copilotkit": true,
|
|
4575
|
+
className: (0, tailwind_merge.twMerge)(!line && "cpk:border-t cpk:border-border", className),
|
|
4576
|
+
...props,
|
|
4577
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4578
|
+
type: "button",
|
|
4579
|
+
"aria-expanded": open,
|
|
4580
|
+
"aria-controls": detailsId,
|
|
4581
|
+
onClick: () => setOpen((value) => !value),
|
|
4582
|
+
className: (0, tailwind_merge.twMerge)("cpk:group/row cpk:m-0 cpk:flex cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-2 cpk:border-none cpk:bg-transparent cpk:text-left cpk:text-[13px] cpk:text-inherit cpk:transition-colors cpk:hover:bg-accent/60 cpk:focus-visible:outline-2 cpk:focus-visible:-outline-offset-2 cpk:focus-visible:outline-ring", line ? "cpk:-mx-1.5 cpk:w-[calc(100%+0.75rem)] cpk:rounded-md cpk:px-1.5 cpk:py-1" : "cpk:w-full cpk:px-3.5 cpk:py-2"),
|
|
4583
|
+
children: [
|
|
4584
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
|
|
4585
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4586
|
+
"data-testid": "copilot-tool-render-name",
|
|
4587
|
+
className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:text-[13px] cpk:font-medium cpk:text-foreground", status === "running" && "cpk-shimmer"),
|
|
4588
|
+
children: name
|
|
4589
|
+
}),
|
|
4590
|
+
preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4591
|
+
className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-[13px] cpk:text-muted-foreground",
|
|
4592
|
+
children: preview
|
|
4593
|
+
}),
|
|
4594
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4595
|
+
"data-testid": "copilot-tool-render-status",
|
|
4596
|
+
className: "cpk:sr-only",
|
|
4597
|
+
children: statusLabels[status]
|
|
4598
|
+
}),
|
|
4599
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
|
|
4600
|
+
open,
|
|
4601
|
+
className: (0, tailwind_merge.twMerge)("cpk:ml-auto cpk:text-muted-foreground", !open && "cpk:opacity-0 cpk:group-hover/row:opacity-100 cpk:group-focus-visible/row:opacity-100")
|
|
4602
|
+
})
|
|
4603
|
+
]
|
|
4604
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
4605
|
+
open,
|
|
4606
|
+
id: detailsId,
|
|
4607
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallPayloads, {
|
|
4608
|
+
parameters,
|
|
4609
|
+
result,
|
|
4610
|
+
className: line ? "cpk:mt-1 cpk:mb-2 cpk:ml-5.5" : "cpk:px-3.5 cpk:pt-1 cpk:pb-3"
|
|
4611
|
+
})
|
|
4612
|
+
})]
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
/**
|
|
4616
|
+
* Tool calls made in a row, collapsed into one summary. While a call runs the
|
|
4617
|
+
* summary names it ("Running read_file src/app.ts"); once they're all done it
|
|
4618
|
+
* reads "Used 3 tools" and opens to the calls.
|
|
4619
|
+
*
|
|
4620
|
+
* Each call is drawn by its registered renderer. CopilotKit's built-in card
|
|
4621
|
+
* (`useDefaultRenderTool`) draws a compact row inside the card and line
|
|
4622
|
+
* appearances; a custom wildcard renderer draws whatever it draws.
|
|
4623
|
+
*/
|
|
4624
|
+
function CopilotChatToolCallGroup({ calls, renderCall, appearance = "card", isActive = false, className, ...props }) {
|
|
4625
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
4626
|
+
const bodyId = (0, react.useId)();
|
|
4627
|
+
const latest = calls[calls.length - 1];
|
|
4628
|
+
if (!latest) return null;
|
|
4629
|
+
const current = isActive ? calls.find((call) => !call.toolMessage) ?? latest : void 0;
|
|
4630
|
+
const preview = current ? entryPreview(current) : void 0;
|
|
4631
|
+
const done = calls.filter((call) => call.toolMessage).length;
|
|
4632
|
+
const statuses = calls.map((call) => entryStatus(call, isActive));
|
|
4633
|
+
const failedIds = new Set(calls.filter((_, index) => statuses[index] === "failed").map((call) => call.toolCall.id));
|
|
4634
|
+
const status = current ? "running" : failedIds.size > 0 ? "failed" : statuses.includes("stopped") ? "stopped" : "done";
|
|
4635
|
+
const rows = (rowAppearance) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
|
|
4636
|
+
value: {
|
|
4637
|
+
appearance: rowAppearance,
|
|
4638
|
+
failedIds,
|
|
4639
|
+
active: isActive
|
|
4640
|
+
},
|
|
4641
|
+
children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
|
|
4642
|
+
});
|
|
4643
|
+
const groupProps = {
|
|
4644
|
+
"data-copilotkit": true,
|
|
4645
|
+
"data-testid": "copilot-tool-call-group",
|
|
4646
|
+
"data-appearance": appearance,
|
|
4647
|
+
"data-state": status,
|
|
4648
|
+
...props
|
|
4649
|
+
};
|
|
4650
|
+
const runningLabel = current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4651
|
+
className: "cpk:shrink-0 cpk:font-medium cpk-shimmer",
|
|
4652
|
+
children: ["Running ", current.toolCall.function.name]
|
|
4653
|
+
}), preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4654
|
+
className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
|
|
4655
|
+
children: preview
|
|
4656
|
+
})] });
|
|
4657
|
+
if (appearance === "line") {
|
|
4658
|
+
if (calls.length === 1 && !current) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4659
|
+
...groupProps,
|
|
4660
|
+
className: (0, tailwind_merge.twMerge)("cpk:my-2", className),
|
|
4661
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
|
|
4662
|
+
value: {
|
|
4663
|
+
appearance: "line",
|
|
4664
|
+
failedIds,
|
|
4665
|
+
active: isActive
|
|
4666
|
+
},
|
|
4667
|
+
children: renderCall(latest)
|
|
4668
|
+
})
|
|
4669
|
+
});
|
|
4670
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4671
|
+
...groupProps,
|
|
4672
|
+
className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
|
|
4673
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4674
|
+
type: "button",
|
|
4675
|
+
"aria-expanded": open,
|
|
4676
|
+
"aria-controls": bodyId,
|
|
4677
|
+
onClick: () => setOpen((value) => !value),
|
|
4678
|
+
className: "cpk:m-0 cpk:inline-flex cpk:max-w-full cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:hover:text-foreground cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring",
|
|
4679
|
+
children: [runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4680
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4681
|
+
className: "cpk:shrink-0 cpk:font-medium",
|
|
4682
|
+
children: ["Used ", pluralize(calls.length, "tool")]
|
|
4683
|
+
}),
|
|
4684
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4685
|
+
className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
|
|
4686
|
+
children: toolNames(calls)
|
|
4687
|
+
}),
|
|
4688
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls })
|
|
4689
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
|
|
4690
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
4691
|
+
open,
|
|
4692
|
+
id: bodyId,
|
|
4693
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4694
|
+
className: "cpk:mt-1 cpk:ml-1 cpk:border-l-2 cpk:border-border cpk:py-0.5 cpk:pl-3",
|
|
4695
|
+
children: rows("line")
|
|
4696
|
+
})
|
|
4697
|
+
})]
|
|
4698
|
+
});
|
|
4699
|
+
}
|
|
4700
|
+
if (appearance === "stack") {
|
|
4701
|
+
if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4702
|
+
...groupProps,
|
|
4703
|
+
className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
|
|
4704
|
+
children: renderCall(latest)
|
|
4705
|
+
});
|
|
4706
|
+
const behind = open ? 0 : Math.min(calls.length - 1, 2);
|
|
4707
|
+
const layer = "cpk:rounded-xl cpk:border cpk:border-border cpk:bg-card cpk:text-card-foreground";
|
|
4708
|
+
const latestStatus = entryStatus(latest, isActive);
|
|
4709
|
+
const latestPreview = entryPreview(latest);
|
|
4710
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4711
|
+
...groupProps,
|
|
4712
|
+
className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
|
|
4713
|
+
style: {
|
|
4714
|
+
paddingBottom: behind * 6,
|
|
4715
|
+
...props.style
|
|
4716
|
+
},
|
|
4717
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4718
|
+
className: "cpk:relative cpk:isolate",
|
|
4719
|
+
children: [Array.from({ length: behind }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4720
|
+
"aria-hidden": "true",
|
|
4721
|
+
className: (0, tailwind_merge.twMerge)(layer, "cpk:absolute cpk:inset-0"),
|
|
4722
|
+
style: {
|
|
4723
|
+
transform: `translateY(${(index + 1) * 6}px) scaleX(${1 - (index + 1) * .045})`,
|
|
4724
|
+
zIndex: -1 - index,
|
|
4725
|
+
opacity: 1 - (index + 1) * .28
|
|
4726
|
+
}
|
|
4727
|
+
}, index)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4728
|
+
type: "button",
|
|
4729
|
+
"aria-expanded": open,
|
|
4730
|
+
"aria-controls": open ? bodyId : void 0,
|
|
4731
|
+
onClick: () => setOpen((value) => !value),
|
|
4732
|
+
className: (0, tailwind_merge.twMerge)("cpk:relative cpk:m-0 cpk:flex cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:text-left cpk:transition-colors cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring", open ? "cpk:mb-1 cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-xs cpk:text-muted-foreground cpk:hover:text-foreground" : (0, tailwind_merge.twMerge)(layer, "cpk:w-full cpk:gap-2 cpk:px-3.5 cpk:py-2.5 cpk:text-[13px] cpk:shadow-[0_1px_2px_rgb(0_0_0/0.04)] cpk:hover:bg-accent")),
|
|
4733
|
+
children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4734
|
+
pluralize(calls.length, "tool call"),
|
|
4735
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
|
|
4736
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
|
|
4737
|
+
open: true,
|
|
4738
|
+
className: "cpk:-rotate-90"
|
|
4739
|
+
}),
|
|
4740
|
+
"Collapse"
|
|
4741
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4742
|
+
className: "cpk:flex cpk:min-w-0 cpk:flex-1 cpk:items-center cpk:gap-2 cpk:animate-[cpk-stack-drop_260ms_cubic-bezier(0.16,1,0.3,1)] cpk:motion-reduce:animate-none",
|
|
4743
|
+
children: [
|
|
4744
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status: latestStatus }),
|
|
4745
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4746
|
+
className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:font-medium cpk:text-foreground", latestStatus === "running" && "cpk-shimmer"),
|
|
4747
|
+
children: latest.toolCall.function.name
|
|
4748
|
+
}),
|
|
4749
|
+
latestPreview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4750
|
+
className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-muted-foreground",
|
|
4751
|
+
children: latestPreview
|
|
4752
|
+
})
|
|
4753
|
+
]
|
|
4754
|
+
}, latest.toolCall.id), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4755
|
+
className: "cpk:ml-2 cpk:inline-flex cpk:shrink-0 cpk:items-center cpk:gap-2 cpk:text-xs cpk:text-muted-foreground",
|
|
4756
|
+
children: [
|
|
4757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
|
|
4758
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4759
|
+
className: "cpk:tabular-nums",
|
|
4760
|
+
children: pluralize(calls.length, "call")
|
|
4761
|
+
}),
|
|
4762
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
|
|
4763
|
+
open: false,
|
|
4764
|
+
className: "cpk:rotate-90"
|
|
4765
|
+
})
|
|
4766
|
+
]
|
|
4767
|
+
})] })
|
|
4768
|
+
})]
|
|
4769
|
+
}), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4770
|
+
id: bodyId,
|
|
4771
|
+
className: "cpk:[&>*]:animate-[cpk-stack-fan_300ms_cubic-bezier(0.16,1,0.3,1)_both] cpk:motion-reduce:[&>*]:animate-none",
|
|
4772
|
+
children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
|
|
4773
|
+
})]
|
|
4774
|
+
});
|
|
4775
|
+
}
|
|
4776
|
+
if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4777
|
+
...groupProps,
|
|
4778
|
+
className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
|
|
4779
|
+
children: renderCall(latest)
|
|
4780
|
+
});
|
|
4781
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4782
|
+
...groupProps,
|
|
4783
|
+
className: (0, tailwind_merge.twMerge)("cpk:my-3 cpk:overflow-hidden cpk:rounded-xl cpk:border cpk:border-border cpk:bg-card cpk:text-card-foreground", className),
|
|
4784
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4785
|
+
type: "button",
|
|
4786
|
+
"aria-expanded": open,
|
|
4787
|
+
"aria-controls": bodyId,
|
|
4788
|
+
onClick: () => setOpen((value) => !value),
|
|
4789
|
+
className: "cpk:m-0 cpk:flex cpk:w-full cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-2.5 cpk:border-none cpk:bg-transparent cpk:px-3.5 cpk:py-2.5 cpk:text-left cpk:text-[13px] cpk:text-inherit cpk:transition-colors cpk:hover:bg-accent/60 cpk:focus-visible:outline-2 cpk:focus-visible:-outline-offset-2 cpk:focus-visible:outline-ring",
|
|
4790
|
+
children: [
|
|
4791
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
|
|
4792
|
+
runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4793
|
+
className: "cpk:shrink-0 cpk:font-medium cpk:text-foreground",
|
|
4794
|
+
children: ["Used ", pluralize(calls.length, "tool")]
|
|
4795
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4796
|
+
className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:text-muted-foreground",
|
|
4797
|
+
children: toolNames(calls)
|
|
4798
|
+
})] }),
|
|
4799
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4800
|
+
className: "cpk:ml-auto cpk:flex cpk:shrink-0 cpk:items-center cpk:gap-2 cpk:pl-2 cpk:text-xs cpk:text-muted-foreground",
|
|
4801
|
+
children: [status === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4802
|
+
className: "cpk:tabular-nums",
|
|
4803
|
+
children: status === "running" ? `${done} done` : statusLabels[status]
|
|
4804
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
|
|
4805
|
+
})
|
|
4806
|
+
]
|
|
4807
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
4808
|
+
open,
|
|
4809
|
+
id: bodyId,
|
|
4810
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: rows("card") })
|
|
4811
|
+
})]
|
|
4812
|
+
});
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4304
4815
|
//#endregion
|
|
4305
4816
|
//#region src/v2/hooks/use-default-render-tool.tsx
|
|
4306
4817
|
/**
|
|
@@ -4384,7 +4895,8 @@ function useDefaultRenderTool(config, deps) {
|
|
|
4384
4895
|
const userRender = config?.render;
|
|
4385
4896
|
useRenderTool({
|
|
4386
4897
|
name: "*",
|
|
4387
|
-
render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
|
|
4898
|
+
render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) }),
|
|
4899
|
+
group: userRender ? config?.group === true : true
|
|
4388
4900
|
}, deps);
|
|
4389
4901
|
}
|
|
4390
4902
|
/**
|
|
@@ -4405,9 +4917,23 @@ function safeStringifyForPre(value) {
|
|
|
4405
4917
|
}
|
|
4406
4918
|
}
|
|
4407
4919
|
function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
|
|
4920
|
+
const group = (0, react.useContext)(ToolCallGroupContext);
|
|
4408
4921
|
const [isExpanded, setIsExpanded] = (0, react.useState)(false);
|
|
4409
4922
|
const isActive = status === "inProgress" || status === "executing";
|
|
4410
4923
|
const isComplete = status === "complete";
|
|
4924
|
+
if (group) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRow, {
|
|
4925
|
+
"data-testid": "copilot-tool-render",
|
|
4926
|
+
"data-tool-name": name,
|
|
4927
|
+
"data-tool-call-id": toolCallId,
|
|
4928
|
+
"data-status": status,
|
|
4929
|
+
"data-args": safeStringifyForAttr(parameters),
|
|
4930
|
+
"data-result": safeStringifyForAttr(result),
|
|
4931
|
+
name,
|
|
4932
|
+
parameters,
|
|
4933
|
+
result,
|
|
4934
|
+
status: isComplete ? group.failedIds.has(toolCallId) ? "failed" : "done" : group.active ? "running" : "stopped",
|
|
4935
|
+
appearance: group.appearance
|
|
4936
|
+
});
|
|
4411
4937
|
const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
|
|
4412
4938
|
const dotClassName = isActive ? "cpk:bg-amber-500" : isComplete ? "cpk:bg-emerald-500" : "cpk:bg-zinc-400";
|
|
4413
4939
|
const badgeClassName = isActive ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : isComplete ? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400" : "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300";
|
|
@@ -6104,38 +6630,227 @@ function useLearningContainersInCurrentThread({ learningContainers }) {
|
|
|
6104
6630
|
});
|
|
6105
6631
|
}
|
|
6106
6632
|
|
|
6633
|
+
//#endregion
|
|
6634
|
+
//#region src/v2/components/chat/tool-call-display-context.ts
|
|
6635
|
+
/** @internal */
|
|
6636
|
+
const ToolCallDisplayContext = (0, react.createContext)(null);
|
|
6637
|
+
|
|
6638
|
+
//#endregion
|
|
6639
|
+
//#region src/v2/components/chat/tool-call-layout.ts
|
|
6640
|
+
function contentLength(content) {
|
|
6641
|
+
return typeof content === "string" ? content.length : JSON.stringify(content ?? "").length;
|
|
6642
|
+
}
|
|
6643
|
+
const entrySignature = ({ toolCall, toolMessage }) => [
|
|
6644
|
+
toolCall.id,
|
|
6645
|
+
toolCall.function.name,
|
|
6646
|
+
toolCall.function.arguments.length,
|
|
6647
|
+
toolMessage ? `${contentLength(toolMessage.content)}${toolMessage.error ? "!" : ""}` : "-"
|
|
6648
|
+
].join(":");
|
|
6649
|
+
function signatureOf(segments) {
|
|
6650
|
+
return segments.map((segment) => segment.kind === "call" ? `c(${entrySignature(segment.call)})` : `g${segment.trailing ? "*" : ""}(${segment.calls.map(entrySignature).join(",")})`).join("|");
|
|
6651
|
+
}
|
|
6652
|
+
/** Index of tool results by the call they answer. */
|
|
6653
|
+
function getToolResults(messages) {
|
|
6654
|
+
const results = /* @__PURE__ */ new Map();
|
|
6655
|
+
for (const message of messages) if (message.role === "tool") results.set(message.toolCallId, message);
|
|
6656
|
+
return results;
|
|
6657
|
+
}
|
|
6658
|
+
/**
|
|
6659
|
+
* Lays out the tool calls of every assistant message in `rendered`.
|
|
6660
|
+
*
|
|
6661
|
+
* Calls drawn by a renderer that opted into grouping join the group that's
|
|
6662
|
+
* open; anything else that shows between them closes it: assistant text, a
|
|
6663
|
+
* user, reasoning or activity message, or a call drawn by any other renderer. A group belongs to
|
|
6664
|
+
* the message where it starts, so later messages whose calls joined it render
|
|
6665
|
+
* no tool calls themselves.
|
|
6666
|
+
*
|
|
6667
|
+
* @param rendered - The messages as the chat renders them, in order.
|
|
6668
|
+
* @param all - Every message, for finding tool results (a transform may hide them).
|
|
6669
|
+
* @param renderers - The registered renderers.
|
|
6670
|
+
*/
|
|
6671
|
+
function getToolCallLayouts(rendered, all, renderers) {
|
|
6672
|
+
const results = getToolResults(all);
|
|
6673
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
6674
|
+
let open;
|
|
6675
|
+
for (const message of rendered) {
|
|
6676
|
+
if (message.role === "tool") continue;
|
|
6677
|
+
if (message.role !== "assistant") {
|
|
6678
|
+
open = void 0;
|
|
6679
|
+
continue;
|
|
6680
|
+
}
|
|
6681
|
+
if (typeof message.content === "string" && message.content.trim()) open = void 0;
|
|
6682
|
+
const segments = [];
|
|
6683
|
+
layouts.set(message.id, segments);
|
|
6684
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
6685
|
+
for (const toolCall of message.toolCalls ?? []) toolCalls.set(toolCall.id, toolCall);
|
|
6686
|
+
for (const toolCall of toolCalls.values()) {
|
|
6687
|
+
const entry = {
|
|
6688
|
+
toolCall,
|
|
6689
|
+
toolMessage: results.get(toolCall.id)
|
|
6690
|
+
};
|
|
6691
|
+
const name = toolCall.function.name;
|
|
6692
|
+
const named = renderers.named.has(name);
|
|
6693
|
+
const rendered = named || renderers.wildcard;
|
|
6694
|
+
const groups = named ? renderers.groupedNames.has(name) : renderers.wildcardGroups;
|
|
6695
|
+
if (!rendered || !groups) {
|
|
6696
|
+
if (rendered) open = void 0;
|
|
6697
|
+
segments.push({
|
|
6698
|
+
kind: "call",
|
|
6699
|
+
id: toolCall.id,
|
|
6700
|
+
call: entry,
|
|
6701
|
+
rendered
|
|
6702
|
+
});
|
|
6703
|
+
} else if (open) open.calls.push(entry);
|
|
6704
|
+
else {
|
|
6705
|
+
open = {
|
|
6706
|
+
kind: "group",
|
|
6707
|
+
id: toolCall.id,
|
|
6708
|
+
calls: [entry],
|
|
6709
|
+
trailing: false
|
|
6710
|
+
};
|
|
6711
|
+
segments.push(open);
|
|
6712
|
+
}
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
if (open) open.trailing = true;
|
|
6716
|
+
const layout = /* @__PURE__ */ new Map();
|
|
6717
|
+
for (const [id, segments] of layouts) layout.set(id, {
|
|
6718
|
+
segments,
|
|
6719
|
+
signature: signatureOf(segments)
|
|
6720
|
+
});
|
|
6721
|
+
return layout;
|
|
6722
|
+
}
|
|
6723
|
+
/**
|
|
6724
|
+
* Finds the working phase of every reply in `rendered`: its messages up to the
|
|
6725
|
+
* last one that calls a tool. Text in there was narration; what follows is the
|
|
6726
|
+
* reply itself. Replies that called no tools are omitted, except the latest,
|
|
6727
|
+
* which may be about to.
|
|
6728
|
+
*/
|
|
6729
|
+
function getTurnWork(rendered, layouts) {
|
|
6730
|
+
const turns = /* @__PURE__ */ new Map();
|
|
6731
|
+
let start = 0;
|
|
6732
|
+
while (start < rendered.length) {
|
|
6733
|
+
let end = start;
|
|
6734
|
+
while (end < rendered.length && rendered[end].role !== "user") end++;
|
|
6735
|
+
const key = start > 0 ? rendered[start - 1].id : "start";
|
|
6736
|
+
const visible = rendered.slice(start, end).filter((message) => message.role !== "tool");
|
|
6737
|
+
const isLatest = end === rendered.length;
|
|
6738
|
+
const calls = [];
|
|
6739
|
+
for (const message of visible) for (const segment of layouts.get(message.id)?.segments ?? []) if (segment.kind === "group") calls.push(...segment.calls);
|
|
6740
|
+
else if (segment.rendered) calls.push(segment.call);
|
|
6741
|
+
const shown = new Set(calls.map((entry) => entry.toolCall.id));
|
|
6742
|
+
let lastWork = -1;
|
|
6743
|
+
visible.forEach((message, index) => {
|
|
6744
|
+
if (message.role === "assistant" && message.toolCalls?.some((call) => shown.has(call.id))) lastWork = index;
|
|
6745
|
+
});
|
|
6746
|
+
const first = visible[0];
|
|
6747
|
+
if (first && (calls.length > 0 || isLatest)) {
|
|
6748
|
+
let lastAssistant = -1;
|
|
6749
|
+
visible.forEach((message, index) => {
|
|
6750
|
+
if (message.role === "assistant") lastAssistant = index;
|
|
6751
|
+
});
|
|
6752
|
+
const replyText = visible[lastAssistant]?.content;
|
|
6753
|
+
const endsWithWork = lastAssistant >= 0 && lastAssistant <= lastWork && typeof replyText === "string" && replyText.trim().length > 0;
|
|
6754
|
+
turns.set(key, {
|
|
6755
|
+
key,
|
|
6756
|
+
firstMessageId: first.id,
|
|
6757
|
+
workMessageIds: new Set(visible.slice(0, lastWork + 1).map((message) => message.id)),
|
|
6758
|
+
replyTextMessageId: endsWithWork ? visible[lastAssistant].id : void 0,
|
|
6759
|
+
calls,
|
|
6760
|
+
isLatest
|
|
6761
|
+
});
|
|
6762
|
+
}
|
|
6763
|
+
start = end + 1;
|
|
6764
|
+
}
|
|
6765
|
+
return turns;
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6107
6768
|
//#endregion
|
|
6108
6769
|
//#region src/v2/components/chat/CopilotChatToolCallsView.tsx
|
|
6109
|
-
|
|
6770
|
+
const appearances = {
|
|
6771
|
+
grouped: "card",
|
|
6772
|
+
line: "line",
|
|
6773
|
+
stacked: "stack"
|
|
6774
|
+
};
|
|
6775
|
+
function CopilotChatToolCallsView({ message, messages = [], display: displayProp, group }) {
|
|
6110
6776
|
const renderToolCall = useRenderToolCall();
|
|
6777
|
+
const rendererNames = useToolCallRendererNames();
|
|
6111
6778
|
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
|
|
6112
6779
|
const subscribe = (0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]);
|
|
6113
6780
|
const getSnapshot = () => copilotkit.ɵlearningConfigured;
|
|
6114
6781
|
const learningConfigured = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
6782
|
+
const context = (0, react.useContext)(ToolCallDisplayContext);
|
|
6783
|
+
const display = displayProp ?? context?.display ?? "grouped";
|
|
6784
|
+
const viewLayout = context?.layouts.get(message.id);
|
|
6785
|
+
const layout = (0, react.useMemo)(() => {
|
|
6786
|
+
if (display === "separate") return void 0;
|
|
6787
|
+
return viewLayout ?? getToolCallLayouts([message], messages, rendererNames).get(message.id);
|
|
6788
|
+
}, [
|
|
6789
|
+
display,
|
|
6790
|
+
viewLayout,
|
|
6791
|
+
message,
|
|
6792
|
+
messages,
|
|
6793
|
+
rendererNames
|
|
6794
|
+
]);
|
|
6115
6795
|
if (!message.toolCalls || message.toolCalls.length === 0) return null;
|
|
6116
|
-
|
|
6796
|
+
const renderCall = (entry) => {
|
|
6117
6797
|
const rendered = renderToolCall({
|
|
6118
|
-
toolCall,
|
|
6119
|
-
toolMessage:
|
|
6798
|
+
toolCall: entry.toolCall,
|
|
6799
|
+
toolMessage: entry.toolMessage
|
|
6120
6800
|
});
|
|
6121
|
-
if (rendered === null) return
|
|
6122
|
-
if (!learningConfigured) return rendered;
|
|
6801
|
+
if (rendered === null || !learningConfigured) return rendered;
|
|
6123
6802
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6124
|
-
"data-tool-call-id": toolCall.id,
|
|
6803
|
+
"data-tool-call-id": entry.toolCall.id,
|
|
6125
6804
|
style: { display: "contents" },
|
|
6126
6805
|
children: rendered
|
|
6127
|
-
}
|
|
6806
|
+
});
|
|
6807
|
+
};
|
|
6808
|
+
if (!layout) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
|
|
6809
|
+
const toolMessage = messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id);
|
|
6810
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall({
|
|
6811
|
+
toolCall,
|
|
6812
|
+
toolMessage
|
|
6813
|
+
}) }, toolCall.id);
|
|
6814
|
+
}) });
|
|
6815
|
+
const workOpen = context?.workOpen.get(message.id);
|
|
6816
|
+
const groupActive = (trailing) => trailing && (context ? context.isRunning : true);
|
|
6817
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: layout.segments.map((segment) => {
|
|
6818
|
+
if (segment.kind === "call") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(segment.call) }, segment.id);
|
|
6819
|
+
if (display === "turn") {
|
|
6820
|
+
const failedIds = new Set(segment.calls.filter((call) => call.toolMessage?.error).map((call) => call.toolCall.id));
|
|
6821
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
6822
|
+
open: workOpen ?? true,
|
|
6823
|
+
className: "cpk:-mx-2",
|
|
6824
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
6825
|
+
className: "cpk:px-2 cpk:py-1.5",
|
|
6826
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
|
|
6827
|
+
value: {
|
|
6828
|
+
appearance: "line",
|
|
6829
|
+
failedIds,
|
|
6830
|
+
active: groupActive(segment.trailing)
|
|
6831
|
+
},
|
|
6832
|
+
children: segment.calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
|
|
6833
|
+
})
|
|
6834
|
+
})
|
|
6835
|
+
}, segment.id);
|
|
6836
|
+
}
|
|
6837
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderSlot(group, CopilotChatToolCallGroup, {
|
|
6838
|
+
calls: segment.calls,
|
|
6839
|
+
renderCall,
|
|
6840
|
+
appearance: appearances[display] ?? "card",
|
|
6841
|
+
isActive: groupActive(segment.trailing)
|
|
6842
|
+
}) }, segment.id);
|
|
6128
6843
|
}) });
|
|
6129
6844
|
}
|
|
6130
6845
|
|
|
6131
6846
|
//#endregion
|
|
6132
6847
|
//#region src/v2/components/chat/CopilotChatInspectorButton.tsx
|
|
6133
6848
|
let shortcutsHidden = false;
|
|
6134
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
6849
|
+
const listeners$1 = /* @__PURE__ */ new Set();
|
|
6135
6850
|
const subscribe = (listener) => {
|
|
6136
|
-
listeners.add(listener);
|
|
6851
|
+
listeners$1.add(listener);
|
|
6137
6852
|
return () => {
|
|
6138
|
-
listeners.delete(listener);
|
|
6853
|
+
listeners$1.delete(listener);
|
|
6139
6854
|
};
|
|
6140
6855
|
};
|
|
6141
6856
|
const getSnapshot = () => shortcutsHidden;
|
|
@@ -6145,7 +6860,7 @@ function useInspectorShortcutsHidden() {
|
|
|
6145
6860
|
}
|
|
6146
6861
|
function hideShortcuts() {
|
|
6147
6862
|
shortcutsHidden = true;
|
|
6148
|
-
listeners.forEach((listener) => listener());
|
|
6863
|
+
listeners$1.forEach((listener) => listener());
|
|
6149
6864
|
}
|
|
6150
6865
|
function InspectorKiteIcon({ className, ...props }) {
|
|
6151
6866
|
const id = (0, react.useId)();
|
|
@@ -6411,9 +7126,321 @@ function CopilotChatInspectorButton({ title, className, onClick, onPointerDown,
|
|
|
6411
7126
|
});
|
|
6412
7127
|
}
|
|
6413
7128
|
|
|
7129
|
+
//#endregion
|
|
7130
|
+
//#region src/v2/components/ui/popover.tsx
|
|
7131
|
+
function Popover({ ...props }) {
|
|
7132
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
|
|
7133
|
+
"data-slot": "popover",
|
|
7134
|
+
...props
|
|
7135
|
+
});
|
|
7136
|
+
}
|
|
7137
|
+
function PopoverAnchor({ ...props }) {
|
|
7138
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Anchor, {
|
|
7139
|
+
"data-slot": "popover-anchor",
|
|
7140
|
+
...props
|
|
7141
|
+
});
|
|
7142
|
+
}
|
|
7143
|
+
/**
|
|
7144
|
+
* Popover content in a portal, so it floats over the page instead of taking
|
|
7145
|
+
* room in the layout, with the CopilotKit style scope on it.
|
|
7146
|
+
*/
|
|
7147
|
+
const PopoverContent = react.forwardRef(({ className, align = "start", sideOffset = 6, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Content, {
|
|
7148
|
+
ref,
|
|
7149
|
+
"data-copilotkit": true,
|
|
7150
|
+
"data-copilotkit-popover": true,
|
|
7151
|
+
"data-slot": "popover-content",
|
|
7152
|
+
align,
|
|
7153
|
+
sideOffset,
|
|
7154
|
+
className: cn("cpk:z-50 cpk:outline-none cpk:data-[state=open]:animate-in cpk:data-[state=open]:fade-in-0 cpk:data-[state=open]:zoom-in-95 cpk:data-[side=bottom]:slide-in-from-top-1 cpk:data-[side=top]:slide-in-from-bottom-1", className),
|
|
7155
|
+
...props
|
|
7156
|
+
}) }));
|
|
7157
|
+
PopoverContent.displayName = "PopoverContent";
|
|
7158
|
+
|
|
7159
|
+
//#endregion
|
|
7160
|
+
//#region src/v2/lib/message-feedback.ts
|
|
7161
|
+
/** Trajectory event name for a rating of one assistant message. */
|
|
7162
|
+
const MESSAGE_FEEDBACK_EVENT_NAME = "message.feedback";
|
|
7163
|
+
/** Longest comment kept, in characters; the rest is cut so the event stays small. */
|
|
7164
|
+
const MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH = 1e3;
|
|
7165
|
+
/** Longest excerpt of the rated response sent with the rating, in characters. */
|
|
7166
|
+
const RESPONSE_EXCERPT_LENGTH = 500;
|
|
7167
|
+
const ratings = /* @__PURE__ */ new Map();
|
|
7168
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
7169
|
+
const threadKey = (agentId, threadId) => JSON.stringify([agentId, threadId]);
|
|
7170
|
+
const messageKey = (agentId, threadId, messageId) => JSON.stringify([
|
|
7171
|
+
agentId,
|
|
7172
|
+
threadId,
|
|
7173
|
+
messageId
|
|
7174
|
+
]);
|
|
7175
|
+
/** Cuts `text` to `max` characters without splitting a surrogate pair. */
|
|
7176
|
+
const clip = (text, max) => {
|
|
7177
|
+
const characters = Array.from(text);
|
|
7178
|
+
return characters.length > max ? characters.slice(0, max).join("") : text;
|
|
7179
|
+
};
|
|
7180
|
+
/** The comment as kept and sent: trimmed and capped, or none. */
|
|
7181
|
+
const normalizeComment = (comment) => {
|
|
7182
|
+
if (typeof comment !== "string") return void 0;
|
|
7183
|
+
const text = clip(comment.trim(), MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH);
|
|
7184
|
+
return text === "" ? void 0 : text;
|
|
7185
|
+
};
|
|
7186
|
+
/** The rating this page gave one message, if any. */
|
|
7187
|
+
function getMessageFeedback(agentId, threadId, messageId) {
|
|
7188
|
+
return ratings.get(messageKey(agentId, threadId, messageId));
|
|
7189
|
+
}
|
|
7190
|
+
/** Calls `listener` whenever a rating in this Thread changes. */
|
|
7191
|
+
function subscribeToMessageFeedback(agentId, threadId, listener) {
|
|
7192
|
+
const key = threadKey(agentId, threadId);
|
|
7193
|
+
let set = listeners.get(key);
|
|
7194
|
+
if (set === void 0) {
|
|
7195
|
+
set = /* @__PURE__ */ new Set();
|
|
7196
|
+
listeners.set(key, set);
|
|
7197
|
+
}
|
|
7198
|
+
set.add(listener);
|
|
7199
|
+
return () => {
|
|
7200
|
+
set.delete(listener);
|
|
7201
|
+
if (set.size === 0) listeners.delete(key);
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
/**
|
|
7205
|
+
* Sets (or, with `null`, clears) this page's rating of one message and sends
|
|
7206
|
+
* it to the active Trajectory. Every change is a new event; the newest event
|
|
7207
|
+
* for a `feedbackId` is the current one. Like any Trajectory event, a change
|
|
7208
|
+
* made while no Trajectory captures, for example during a reconnect, is not
|
|
7209
|
+
* sent; the page still shows it.
|
|
7210
|
+
*/
|
|
7211
|
+
function setMessageFeedback(params) {
|
|
7212
|
+
const { copilotkit, agentId, threadId, message } = params;
|
|
7213
|
+
const comment = normalizeComment(params.feedback?.comment);
|
|
7214
|
+
const feedback = params.feedback && {
|
|
7215
|
+
rating: params.feedback.rating,
|
|
7216
|
+
...comment ? { comment } : {}
|
|
7217
|
+
};
|
|
7218
|
+
const key = messageKey(agentId, threadId, message.id);
|
|
7219
|
+
if (feedback) ratings.set(key, feedback);
|
|
7220
|
+
else ratings.delete(key);
|
|
7221
|
+
for (const listener of listeners.get(threadKey(agentId, threadId)) ?? []) listener();
|
|
7222
|
+
const runId = copilotkit.getRunIdForMessage(agentId, threadId, message.id);
|
|
7223
|
+
copilotkit.emitTrajectoryEvent(MESSAGE_FEEDBACK_EVENT_NAME, {
|
|
7224
|
+
feedbackId: `${threadId}:${message.id}`,
|
|
7225
|
+
threadId,
|
|
7226
|
+
messageId: message.id,
|
|
7227
|
+
agentId,
|
|
7228
|
+
...runId ? { runId } : {},
|
|
7229
|
+
rating: feedback?.rating ?? null,
|
|
7230
|
+
...feedback?.comment ? { comment: feedback.comment } : {},
|
|
7231
|
+
...copilotkit.ɵlearningCapturesAgentText ? { response: clip(message.content ?? "", RESPONSE_EXCERPT_LENGTH) } : {}
|
|
7232
|
+
});
|
|
7233
|
+
}
|
|
7234
|
+
|
|
7235
|
+
//#endregion
|
|
7236
|
+
//#region src/v2/components/chat/message-feedback.tsx
|
|
7237
|
+
/**
|
|
7238
|
+
* Set by the message view while Learning is on for this chat. Assistant
|
|
7239
|
+
* messages without it show thumbs only when the app passes its own handlers
|
|
7240
|
+
* or thumb slots.
|
|
7241
|
+
*/
|
|
7242
|
+
const MessageFeedbackScopeContext = (0, react.createContext)(null);
|
|
7243
|
+
const capturedCores = /* @__PURE__ */ new WeakSet();
|
|
7244
|
+
const rememberCapture = (copilotkit) => {
|
|
7245
|
+
if (capturedCores.has(copilotkit)) return;
|
|
7246
|
+
capturedCores.add(copilotkit);
|
|
7247
|
+
const unsubscribe = copilotkit.ɵsubscribeToLearningConfigured(() => {
|
|
7248
|
+
if (copilotkit.ɵlearningConfigured) return;
|
|
7249
|
+
capturedCores.delete(copilotkit);
|
|
7250
|
+
unsubscribe();
|
|
7251
|
+
});
|
|
7252
|
+
};
|
|
7253
|
+
/**
|
|
7254
|
+
* Whether the thumbs record feedback for Learning. True once a Trajectory of
|
|
7255
|
+
* this Core has started and as long as Learning stays configured, also while
|
|
7256
|
+
* capture reconnects or is stopped, so the buttons do not flicker. Ratings
|
|
7257
|
+
* given meanwhile still show but, like any Trajectory event, are not
|
|
7258
|
+
* sent. A capture that never starts (for example without a license) leaves it
|
|
7259
|
+
* false.
|
|
7260
|
+
*/
|
|
7261
|
+
function useLearningFeedbackEnabled(copilotkit) {
|
|
7262
|
+
const configured = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]), () => copilotkit.ɵlearningConfigured, () => false);
|
|
7263
|
+
const capturing = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.subscribe({ onTrajectoryChanged: onChange }).unsubscribe, [copilotkit]), () => copilotkit.trajectoryId !== null, () => false);
|
|
7264
|
+
(0, react.useEffect)(() => {
|
|
7265
|
+
if (capturing) rememberCapture(copilotkit);
|
|
7266
|
+
}, [capturing, copilotkit]);
|
|
7267
|
+
return configured && (capturing || capturedCores.has(copilotkit));
|
|
7268
|
+
}
|
|
7269
|
+
const noSubscription = () => () => {};
|
|
7270
|
+
/** Feedback controls for `message`, or `null` when this chat collects none. */
|
|
7271
|
+
function useMessageFeedback(message) {
|
|
7272
|
+
const scope = (0, react.useContext)(MessageFeedbackScopeContext);
|
|
7273
|
+
const subscribe = (0, react.useCallback)((onChange) => scope ? subscribeToMessageFeedback(scope.agentId, scope.threadId, onChange) : noSubscription(), [scope]);
|
|
7274
|
+
const getSnapshot = () => scope ? getMessageFeedback(scope.agentId, scope.threadId, message.id) : void 0;
|
|
7275
|
+
const feedback = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
7276
|
+
if (!scope) return null;
|
|
7277
|
+
const save = (next) => setMessageFeedback({
|
|
7278
|
+
...scope,
|
|
7279
|
+
message,
|
|
7280
|
+
feedback: next
|
|
7281
|
+
});
|
|
7282
|
+
return {
|
|
7283
|
+
feedback,
|
|
7284
|
+
rate: (rating) => {
|
|
7285
|
+
if (feedback?.rating !== rating) save({ rating });
|
|
7286
|
+
},
|
|
7287
|
+
clear: () => {
|
|
7288
|
+
if (feedback) save(null);
|
|
7289
|
+
},
|
|
7290
|
+
comment: (comment) => {
|
|
7291
|
+
if (feedback) save({
|
|
7292
|
+
rating: feedback.rating,
|
|
7293
|
+
comment
|
|
7294
|
+
});
|
|
7295
|
+
}
|
|
7296
|
+
};
|
|
7297
|
+
}
|
|
7298
|
+
/**
|
|
7299
|
+
* How long the pointer may be away from the rating and its comment before the
|
|
7300
|
+
* comment closes: enough to cross the gap between them, short enough that
|
|
7301
|
+
* passing over a rating does not leave it open.
|
|
7302
|
+
*/
|
|
7303
|
+
const FEEDBACK_COMMENT_CLOSE_DELAY_MS = 150;
|
|
7304
|
+
/**
|
|
7305
|
+
* Drives the comment popover of a rated message.
|
|
7306
|
+
*
|
|
7307
|
+
* It opens when a rating is given and whenever the pointer or keyboard focus
|
|
7308
|
+
* reaches the chosen thumb, with the saved comment in the field. It closes by
|
|
7309
|
+
* itself shortly after the pointer leaves the thumb and the popover, unless
|
|
7310
|
+
* focus is in the field: then it stays until focus moves away, Enter, or
|
|
7311
|
+
* Escape. Closing saves a changed comment; Escape, a click on a thumb, and
|
|
7312
|
+
* removing the rating discard it.
|
|
7313
|
+
*/
|
|
7314
|
+
function useFeedbackComment(controls) {
|
|
7315
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
7316
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
7317
|
+
const [focusRequest, setFocusRequest] = (0, react.useState)(0);
|
|
7318
|
+
const inputRef = (0, react.useRef)(null);
|
|
7319
|
+
const timer = (0, react.useRef)(void 0);
|
|
7320
|
+
const editing = (0, react.useRef)(false);
|
|
7321
|
+
const focusWaiting = (0, react.useRef)(false);
|
|
7322
|
+
const pointerOver = (0, react.useRef)(false);
|
|
7323
|
+
const returning = (0, react.useRef)(false);
|
|
7324
|
+
const latest = (0, react.useRef)({
|
|
7325
|
+
controls,
|
|
7326
|
+
draft,
|
|
7327
|
+
open
|
|
7328
|
+
});
|
|
7329
|
+
latest.current = {
|
|
7330
|
+
...latest.current,
|
|
7331
|
+
controls,
|
|
7332
|
+
draft,
|
|
7333
|
+
open
|
|
7334
|
+
};
|
|
7335
|
+
const takeFocusRequest = (0, react.useCallback)(() => {
|
|
7336
|
+
const waiting = focusWaiting.current;
|
|
7337
|
+
focusWaiting.current = false;
|
|
7338
|
+
return waiting;
|
|
7339
|
+
}, []);
|
|
7340
|
+
const cancelClose = (0, react.useCallback)(() => {
|
|
7341
|
+
if (timer.current === void 0) return;
|
|
7342
|
+
clearTimeout(timer.current);
|
|
7343
|
+
timer.current = void 0;
|
|
7344
|
+
}, []);
|
|
7345
|
+
(0, react.useEffect)(() => cancelClose, [cancelClose]);
|
|
7346
|
+
const savedComment = (0, react.useCallback)(() => latest.current.controls?.feedback?.comment ?? "", []);
|
|
7347
|
+
(0, react.useEffect)(() => () => {
|
|
7348
|
+
const { open: wasOpen, controls: current, draft: text } = latest.current;
|
|
7349
|
+
if (wasOpen && current?.feedback && text.trim() !== savedComment()) current.comment(text);
|
|
7350
|
+
}, []);
|
|
7351
|
+
const close = (0, react.useCallback)(({ save = true } = {}) => {
|
|
7352
|
+
cancelClose();
|
|
7353
|
+
focusWaiting.current = false;
|
|
7354
|
+
pointerOver.current = false;
|
|
7355
|
+
if (!latest.current.open) return;
|
|
7356
|
+
const current = latest.current.controls;
|
|
7357
|
+
if (save && current?.feedback && latest.current.draft.trim() !== savedComment()) current.comment(latest.current.draft);
|
|
7358
|
+
editing.current = false;
|
|
7359
|
+
latest.current = {
|
|
7360
|
+
...latest.current,
|
|
7361
|
+
open: false
|
|
7362
|
+
};
|
|
7363
|
+
setOpen(false);
|
|
7364
|
+
}, [cancelClose, savedComment]);
|
|
7365
|
+
const show = ({ comment, focus }) => {
|
|
7366
|
+
cancelClose();
|
|
7367
|
+
latest.current = {
|
|
7368
|
+
...latest.current,
|
|
7369
|
+
open: true,
|
|
7370
|
+
draft: comment
|
|
7371
|
+
};
|
|
7372
|
+
setDraft(comment);
|
|
7373
|
+
if (focus) {
|
|
7374
|
+
focusWaiting.current = true;
|
|
7375
|
+
setFocusRequest((request) => request + 1);
|
|
7376
|
+
}
|
|
7377
|
+
setOpen(true);
|
|
7378
|
+
};
|
|
7379
|
+
return {
|
|
7380
|
+
open,
|
|
7381
|
+
draft,
|
|
7382
|
+
setDraft,
|
|
7383
|
+
focusRequest,
|
|
7384
|
+
takeFocusRequest,
|
|
7385
|
+
inputRef,
|
|
7386
|
+
show,
|
|
7387
|
+
enter: (via = "focus") => {
|
|
7388
|
+
cancelClose();
|
|
7389
|
+
if (via === "pointer") pointerOver.current = true;
|
|
7390
|
+
if (returning.current) return;
|
|
7391
|
+
if (!latest.current.open && latest.current.controls?.feedback) show({
|
|
7392
|
+
comment: savedComment(),
|
|
7393
|
+
focus: false
|
|
7394
|
+
});
|
|
7395
|
+
},
|
|
7396
|
+
leave: (via = "focus") => {
|
|
7397
|
+
if (via === "pointer") pointerOver.current = false;
|
|
7398
|
+
else if (pointerOver.current) return;
|
|
7399
|
+
cancelClose();
|
|
7400
|
+
timer.current = setTimeout(() => {
|
|
7401
|
+
timer.current = void 0;
|
|
7402
|
+
if (!editing.current) close();
|
|
7403
|
+
}, FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
7404
|
+
},
|
|
7405
|
+
setEditing: (value) => {
|
|
7406
|
+
editing.current = value;
|
|
7407
|
+
},
|
|
7408
|
+
close,
|
|
7409
|
+
returnFocus: (target) => {
|
|
7410
|
+
returning.current = true;
|
|
7411
|
+
try {
|
|
7412
|
+
target?.focus();
|
|
7413
|
+
} finally {
|
|
7414
|
+
returning.current = false;
|
|
7415
|
+
}
|
|
7416
|
+
}
|
|
7417
|
+
};
|
|
7418
|
+
}
|
|
7419
|
+
|
|
6414
7420
|
//#endregion
|
|
6415
7421
|
//#region src/v2/components/chat/CopilotChatAssistantMessage.tsx
|
|
6416
7422
|
/**
|
|
7423
|
+
* In the `"turn"` tool-call display, text in a reply's working phase was
|
|
7424
|
+
* narration: it fades to muted once a call follows it, and folds away with the
|
|
7425
|
+
* rest of the work when the reply is done. The wrapper is there for every
|
|
7426
|
+
* message in that display, so text that turns into narration keeps its node
|
|
7427
|
+
* (and its place) rather than remounting.
|
|
7428
|
+
*/
|
|
7429
|
+
function WorkText({ messageId, children }) {
|
|
7430
|
+
const context = (0, react.useContext)(ToolCallDisplayContext);
|
|
7431
|
+
if (context?.display !== "turn") return children;
|
|
7432
|
+
const open = context.workTextOpen.get(messageId);
|
|
7433
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
7434
|
+
open: open ?? true,
|
|
7435
|
+
className: "cpk:-mx-2",
|
|
7436
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7437
|
+
className: "cpk:px-2",
|
|
7438
|
+
"data-work-text": open === void 0 ? "reply" : "narration",
|
|
7439
|
+
children
|
|
7440
|
+
})
|
|
7441
|
+
});
|
|
7442
|
+
}
|
|
7443
|
+
/**
|
|
6417
7444
|
* Binds the Inspector action to the active chat without making the full
|
|
6418
7445
|
* assistant message subscribe to chat configuration changes.
|
|
6419
7446
|
*/
|
|
@@ -6428,31 +7455,216 @@ function BoundInspectorButton({ inspectorButton, messageId, openInspector, ...pr
|
|
|
6428
7455
|
})
|
|
6429
7456
|
});
|
|
6430
7457
|
}
|
|
6431
|
-
|
|
7458
|
+
/** Whether focus came from the keyboard; a pointer click also focuses buttons. */
|
|
7459
|
+
function isFocusVisible(element) {
|
|
7460
|
+
try {
|
|
7461
|
+
return element.matches(":focus-visible");
|
|
7462
|
+
} catch {
|
|
7463
|
+
return false;
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7466
|
+
const HIDDEN_TOO = {
|
|
7467
|
+
checkVisibilityCSS: true,
|
|
7468
|
+
visibilityProperty: true
|
|
7469
|
+
};
|
|
7470
|
+
const FOCUSABLE = [
|
|
7471
|
+
"a[href]",
|
|
7472
|
+
"button:not([disabled])",
|
|
7473
|
+
"input:not([disabled])",
|
|
7474
|
+
"select:not([disabled])",
|
|
7475
|
+
"textarea:not([disabled])",
|
|
7476
|
+
"[tabindex]"
|
|
7477
|
+
].map((selector) => `${selector}:not([tabindex="-1"]):not([data-radix-focus-guard])`).join(", ");
|
|
7478
|
+
/** Approximately the element Tab reaches after `element`, outside `skip`. */
|
|
7479
|
+
function nextFocusable(element, skip) {
|
|
7480
|
+
const all = Array.from(element.ownerDocument.querySelectorAll(FOCUSABLE)).filter((candidate) => candidate === element || !skip?.contains(candidate) && !candidate.closest("[hidden], [inert]"));
|
|
7481
|
+
const index = all.indexOf(element);
|
|
7482
|
+
return index === -1 ? null : all[index + 1] ?? null;
|
|
7483
|
+
}
|
|
7484
|
+
/**
|
|
7485
|
+
* The comment popover of a rated message, pointing at the chosen thumb. Kept
|
|
7486
|
+
* apart so only it, not the whole message, follows chat configuration.
|
|
7487
|
+
*
|
|
7488
|
+
* The popover lives in a portal, so it keeps keyboard order by hand: Shift+Tab
|
|
7489
|
+
* from its start returns to the thumb, Tab from its end moves on to the
|
|
7490
|
+
* control after the thumb, and Enter or Escape inside it returns to the thumb.
|
|
7491
|
+
*/
|
|
7492
|
+
function FeedbackCommentPopover({ feedbackComment, comment, savedComment, rating, anchor, messageId }) {
|
|
7493
|
+
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
7494
|
+
const contentRef = (0, react.useRef)(null);
|
|
7495
|
+
const focusInside = () => contentRef.current?.contains(document.activeElement) ?? false;
|
|
7496
|
+
const { focusRequest, takeFocusRequest, inputRef } = comment;
|
|
7497
|
+
const focusField = (0, react.useCallback)(() => {
|
|
7498
|
+
if (inputRef.current && takeFocusRequest()) inputRef.current.focus();
|
|
7499
|
+
}, [inputRef, takeFocusRequest]);
|
|
7500
|
+
(0, react.useEffect)(() => focusField(), [focusRequest, focusField]);
|
|
7501
|
+
/** Whether `target` is a thumb of this message, whose click decides itself. */
|
|
7502
|
+
const isOwnThumb = (target) => {
|
|
7503
|
+
return (target instanceof Element ? target.closest("[data-copilotkit-rating]") : null)?.closest("[data-message-id]")?.getAttribute("data-message-id") === messageId;
|
|
7504
|
+
};
|
|
7505
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Popover, {
|
|
7506
|
+
open: comment.open,
|
|
7507
|
+
onOpenChange: (open) => {
|
|
7508
|
+
if (!open) comment.close();
|
|
7509
|
+
},
|
|
7510
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverAnchor, { virtualRef: anchor }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverContent, {
|
|
7511
|
+
ref: contentRef,
|
|
7512
|
+
side: "bottom",
|
|
7513
|
+
collisionPadding: 8,
|
|
7514
|
+
hideWhenDetached: true,
|
|
7515
|
+
"aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
|
|
7516
|
+
"data-copilotkit-ignore": true,
|
|
7517
|
+
onOpenAutoFocus: (event) => {
|
|
7518
|
+
event.preventDefault();
|
|
7519
|
+
focusField();
|
|
7520
|
+
},
|
|
7521
|
+
onCloseAutoFocus: (event) => event.preventDefault(),
|
|
7522
|
+
onInteractOutside: (event) => {
|
|
7523
|
+
if (isOwnThumb(event.target)) event.preventDefault();
|
|
7524
|
+
},
|
|
7525
|
+
onEscapeKeyDown: (event) => {
|
|
7526
|
+
const returnFocus = focusInside();
|
|
7527
|
+
if (returnFocus) event.stopPropagation();
|
|
7528
|
+
comment.close({ save: false });
|
|
7529
|
+
if (returnFocus) comment.returnFocus(anchor.current);
|
|
7530
|
+
},
|
|
7531
|
+
onKeyDown: (event) => {
|
|
7532
|
+
if (event.key !== "Tab") return;
|
|
7533
|
+
const focusables = Array.from(contentRef.current?.querySelectorAll(FOCUSABLE) ?? []);
|
|
7534
|
+
if (!(event.shiftKey ? document.activeElement === focusables[0] : document.activeElement === focusables[focusables.length - 1]) || !anchor.current?.isConnected) return;
|
|
7535
|
+
event.preventDefault();
|
|
7536
|
+
const target = event.shiftKey ? anchor.current : nextFocusable(anchor.current, contentRef.current) ?? anchor.current;
|
|
7537
|
+
comment.close();
|
|
7538
|
+
if (target === anchor.current) comment.returnFocus(target);
|
|
7539
|
+
else target.focus();
|
|
7540
|
+
},
|
|
7541
|
+
onPointerEnter: () => comment.enter("pointer"),
|
|
7542
|
+
onPointerLeave: () => comment.leave("pointer"),
|
|
7543
|
+
className: "cpk:z-[1300]",
|
|
7544
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderSlot(feedbackComment, CopilotChatAssistantMessage.FeedbackComment, {
|
|
7545
|
+
value: comment.draft,
|
|
7546
|
+
savedComment,
|
|
7547
|
+
inputRef: comment.inputRef,
|
|
7548
|
+
onChange: comment.setDraft,
|
|
7549
|
+
onSubmit: () => {
|
|
7550
|
+
comment.close();
|
|
7551
|
+
comment.returnFocus(anchor.current);
|
|
7552
|
+
},
|
|
7553
|
+
onEditingChange: comment.setEditing,
|
|
7554
|
+
onLeave: (next) => {
|
|
7555
|
+
if (next && (contentRef.current?.contains(next) || isOwnThumb(next))) return;
|
|
7556
|
+
comment.leave();
|
|
7557
|
+
}
|
|
7558
|
+
}) }, rating)
|
|
7559
|
+
})]
|
|
7560
|
+
});
|
|
7561
|
+
}
|
|
7562
|
+
function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, onThumbsUp, onThumbsDown, onReadAloud, onRegenerate, additionalToolbarItems, toolbarVisible = true, markdownRenderer, toolbar, copyButton, inspectorButton, thumbsUpButton, thumbsDownButton, readAloudButton, regenerateButton, feedbackComment: feedbackCommentSlot, toolCallsView, children, className, ...props }) {
|
|
6432
7563
|
useKatexStyles();
|
|
6433
7564
|
const { isInspectorEnabled, openInspector } = useCopilotKitInspector();
|
|
6434
7565
|
const shortcutsHidden = useInspectorShortcutsHidden();
|
|
6435
7566
|
const showInspectorShortcut = isInspectorEnabled && !shortcutsHidden;
|
|
7567
|
+
const feedback = useMessageFeedback(message);
|
|
7568
|
+
const comment = useFeedbackComment(feedback);
|
|
7569
|
+
const rating = feedback?.feedback?.rating;
|
|
7570
|
+
const root = (0, react.useRef)(null);
|
|
7571
|
+
const commentAnchor = (0, react.useRef)(null);
|
|
7572
|
+
const commentRating = (0, react.useRef)(rating);
|
|
7573
|
+
(0, react.useLayoutEffect)(() => {
|
|
7574
|
+
if (comment.open && !commentAnchor.current?.isConnected && rating) commentAnchor.current = root.current?.querySelector(`[data-copilotkit-rating="${rating}"]`) ?? root.current?.querySelector("button[aria-pressed=\"true\"]") ?? null;
|
|
7575
|
+
});
|
|
7576
|
+
/**
|
|
7577
|
+
* Thumbs are toggles: a new rating is saved and opens its comment, and
|
|
7578
|
+
* clicking the chosen thumb again removes the rating. A keyboard click
|
|
7579
|
+
* (`detail` 0) moves focus into the comment, as there is no pointer.
|
|
7580
|
+
* Custom thumb slots may call `onClick` without an event.
|
|
7581
|
+
*
|
|
7582
|
+
* @returns Whether the app's handler should run: true for a new rating and
|
|
7583
|
+
* whenever this chat does not record ratings, false when one is removed.
|
|
7584
|
+
*/
|
|
7585
|
+
const rate = (value, event) => {
|
|
7586
|
+
if (!feedback) return true;
|
|
7587
|
+
if (rating === value) {
|
|
7588
|
+
comment.close({ save: false });
|
|
7589
|
+
feedback.clear();
|
|
7590
|
+
return false;
|
|
7591
|
+
}
|
|
7592
|
+
feedback.rate(value);
|
|
7593
|
+
commentRating.current = value;
|
|
7594
|
+
commentAnchor.current = event?.currentTarget ?? null;
|
|
7595
|
+
comment.show({
|
|
7596
|
+
comment: "",
|
|
7597
|
+
focus: event?.detail === 0
|
|
7598
|
+
});
|
|
7599
|
+
return true;
|
|
7600
|
+
};
|
|
7601
|
+
/** Props that let the chosen thumb open and point at its comment. */
|
|
7602
|
+
const thumbFeedbackProps = (value) => {
|
|
7603
|
+
if (!feedback) return {};
|
|
7604
|
+
const thumb = { "data-copilotkit-rating": value };
|
|
7605
|
+
if (rating !== value) return {
|
|
7606
|
+
...thumb,
|
|
7607
|
+
"aria-pressed": false
|
|
7608
|
+
};
|
|
7609
|
+
const enter = (target, via) => {
|
|
7610
|
+
if (!comment.open) commentRating.current = value;
|
|
7611
|
+
commentAnchor.current = target;
|
|
7612
|
+
comment.enter(via);
|
|
7613
|
+
};
|
|
7614
|
+
return {
|
|
7615
|
+
...thumb,
|
|
7616
|
+
"aria-pressed": true,
|
|
7617
|
+
"aria-haspopup": "dialog",
|
|
7618
|
+
"aria-expanded": comment.open,
|
|
7619
|
+
onPointerEnter: (event) => {
|
|
7620
|
+
if (event.pointerType !== "touch") enter(event.currentTarget, "pointer");
|
|
7621
|
+
},
|
|
7622
|
+
onPointerLeave: (event) => {
|
|
7623
|
+
if (event.pointerType !== "touch") comment.leave("pointer");
|
|
7624
|
+
},
|
|
7625
|
+
onFocus: (event) => {
|
|
7626
|
+
if (isFocusVisible(event.currentTarget)) enter(event.currentTarget, "focus");
|
|
7627
|
+
},
|
|
7628
|
+
onBlur: () => comment.leave(),
|
|
7629
|
+
onKeyDown: (event) => {
|
|
7630
|
+
const input = comment.inputRef.current;
|
|
7631
|
+
if (event.key === "Tab" && !event.shiftKey && comment.open && input && input.checkVisibility?.(HIDDEN_TOO) !== false) {
|
|
7632
|
+
event.preventDefault();
|
|
7633
|
+
input.focus();
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
};
|
|
7637
|
+
};
|
|
6436
7638
|
const boundMarkdownRenderer = renderSlot(markdownRenderer, CopilotChatAssistantMessage.MarkdownRenderer, { content: message.content || "" });
|
|
6437
7639
|
const boundCopyButton = renderSlot(copyButton, CopilotChatAssistantMessage.CopyButton, { onClick: async () => {
|
|
6438
7640
|
if (message.content) return await (0, _copilotkit_shared.copyToClipboard)(message.content);
|
|
6439
7641
|
return false;
|
|
6440
7642
|
} });
|
|
6441
|
-
const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, {
|
|
7643
|
+
const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, {
|
|
7644
|
+
onClick: onThumbsUp || feedback ? (event) => {
|
|
7645
|
+
if (rate("up", event)) onThumbsUp?.(message);
|
|
7646
|
+
} : void 0,
|
|
7647
|
+
...thumbFeedbackProps("up")
|
|
7648
|
+
});
|
|
6442
7649
|
const boundInspectorButton = showInspectorShortcut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundInspectorButton, {
|
|
6443
7650
|
inspectorButton,
|
|
6444
7651
|
messageId: message.id,
|
|
6445
7652
|
openInspector
|
|
6446
7653
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
|
|
6447
|
-
const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, {
|
|
7654
|
+
const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, {
|
|
7655
|
+
onClick: onThumbsDown || feedback ? (event) => {
|
|
7656
|
+
if (rate("down", event)) onThumbsDown?.(message);
|
|
7657
|
+
} : void 0,
|
|
7658
|
+
...thumbFeedbackProps("down")
|
|
7659
|
+
});
|
|
6448
7660
|
const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud ? () => onReadAloud(message) : void 0 });
|
|
6449
7661
|
const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate ? () => onRegenerate(message) : void 0 });
|
|
6450
7662
|
const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6451
7663
|
className: "cpk:flex cpk:w-full cpk:items-center cpk:gap-1",
|
|
6452
7664
|
children: [
|
|
6453
7665
|
boundCopyButton,
|
|
6454
|
-
(onThumbsUp || thumbsUpButton) && boundThumbsUpButton,
|
|
6455
|
-
(onThumbsDown || thumbsDownButton) && boundThumbsDownButton,
|
|
7666
|
+
(onThumbsUp || thumbsUpButton || feedback) && boundThumbsUpButton,
|
|
7667
|
+
(onThumbsDown || thumbsDownButton || feedback) && boundThumbsDownButton,
|
|
6456
7668
|
(onReadAloud || readAloudButton) && boundReadAloudButton,
|
|
6457
7669
|
(onRegenerate || regenerateButton) && boundRegenerateButton,
|
|
6458
7670
|
additionalToolbarItems,
|
|
@@ -6466,11 +7678,31 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
|
|
|
6466
7678
|
const hasContent = !!(message.content && message.content.trim().length > 0);
|
|
6467
7679
|
const isLatestAssistantMessage = message.role === "assistant" && (isLatest ?? messages?.[messages.length - 1]?.id === message.id);
|
|
6468
7680
|
const shouldShowToolbar = toolbarVisible && (hasContent || showInspectorShortcut) && !(isRunning && isLatestAssistantMessage);
|
|
6469
|
-
|
|
7681
|
+
const { open: commentOpen, close: closeComment } = comment;
|
|
7682
|
+
(0, react.useEffect)(() => {
|
|
7683
|
+
if (!commentOpen) return;
|
|
7684
|
+
if (rating !== commentRating.current) closeComment({ save: false });
|
|
7685
|
+
else if (!shouldShowToolbar) closeComment();
|
|
7686
|
+
}, [
|
|
7687
|
+
commentOpen,
|
|
7688
|
+
shouldShowToolbar,
|
|
7689
|
+
rating,
|
|
7690
|
+
closeComment
|
|
7691
|
+
]);
|
|
7692
|
+
const feedbackComment = feedback?.feedback && shouldShowToolbar && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FeedbackCommentPopover, {
|
|
7693
|
+
feedbackComment: feedbackCommentSlot,
|
|
7694
|
+
comment,
|
|
7695
|
+
savedComment: feedback.feedback.comment ?? "",
|
|
7696
|
+
rating: feedback.feedback.rating,
|
|
7697
|
+
anchor: commentAnchor,
|
|
7698
|
+
messageId: message.id
|
|
7699
|
+
});
|
|
7700
|
+
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7701
|
+
ref: root,
|
|
6470
7702
|
"data-copilotkit": true,
|
|
6471
7703
|
"data-message-id": message.id,
|
|
6472
7704
|
style: { display: "contents" },
|
|
6473
|
-
children: children({
|
|
7705
|
+
children: [children({
|
|
6474
7706
|
markdownRenderer: boundMarkdownRenderer,
|
|
6475
7707
|
toolbar: boundToolbar,
|
|
6476
7708
|
toolCallsView: boundToolCallsView,
|
|
@@ -6489,21 +7721,26 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
|
|
|
6489
7721
|
onRegenerate,
|
|
6490
7722
|
additionalToolbarItems,
|
|
6491
7723
|
toolbarVisible: shouldShowToolbar
|
|
6492
|
-
})
|
|
7724
|
+
}), feedbackComment]
|
|
6493
7725
|
});
|
|
6494
7726
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7727
|
+
ref: root,
|
|
6495
7728
|
"data-copilotkit": true,
|
|
6496
7729
|
"data-testid": "copilot-assistant-message",
|
|
6497
7730
|
className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitAssistantMessage", className),
|
|
6498
7731
|
...props,
|
|
6499
7732
|
"data-message-id": message.id,
|
|
6500
7733
|
children: [
|
|
6501
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
6502
|
-
|
|
6503
|
-
children:
|
|
7734
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkText, {
|
|
7735
|
+
messageId: message.id,
|
|
7736
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7737
|
+
className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
|
|
7738
|
+
children: boundMarkdownRenderer
|
|
7739
|
+
})
|
|
6504
7740
|
}),
|
|
6505
7741
|
boundToolCallsView,
|
|
6506
|
-
shouldShowToolbar && boundToolbar
|
|
7742
|
+
shouldShowToolbar && boundToolbar,
|
|
7743
|
+
feedbackComment
|
|
6507
7744
|
]
|
|
6508
7745
|
});
|
|
6509
7746
|
}
|
|
@@ -6519,20 +7756,27 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
|
|
|
6519
7756
|
...props
|
|
6520
7757
|
});
|
|
6521
7758
|
const ToolbarButton = _CopilotChatAssistantMessage.ToolbarButton = ({ title, tooltip, tooltipClassName, children, ...props }) => {
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
7759
|
+
const [tooltipOpen, setTooltipOpen] = (0, react.useState)(false);
|
|
7760
|
+
const popupOpen = props["aria-expanded"] === true;
|
|
7761
|
+
if (popupOpen && tooltipOpen) setTooltipOpen(false);
|
|
7762
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, {
|
|
7763
|
+
open: tooltipOpen && !popupOpen,
|
|
7764
|
+
onOpenChange: (open) => setTooltipOpen(open && !popupOpen),
|
|
7765
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
|
|
7766
|
+
asChild: true,
|
|
7767
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7768
|
+
type: "button",
|
|
7769
|
+
variant: "assistantMessageToolbarButton",
|
|
7770
|
+
"aria-label": title,
|
|
7771
|
+
...props,
|
|
7772
|
+
children
|
|
7773
|
+
})
|
|
7774
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
|
|
7775
|
+
side: "bottom",
|
|
7776
|
+
className: tooltipClassName,
|
|
7777
|
+
children: tooltip ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
|
|
7778
|
+
})]
|
|
7779
|
+
});
|
|
6536
7780
|
};
|
|
6537
7781
|
_CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
|
|
6538
7782
|
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
@@ -6571,7 +7815,10 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
|
|
|
6571
7815
|
"data-testid": "copilot-thumbs-up-button",
|
|
6572
7816
|
title: title || labels.assistantMessageToolbarThumbsUpLabel,
|
|
6573
7817
|
...props,
|
|
6574
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, {
|
|
7818
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, {
|
|
7819
|
+
className: "cpk:size-[18px]",
|
|
7820
|
+
fill: props["aria-pressed"] === true ? "currentColor" : "none"
|
|
7821
|
+
})
|
|
6575
7822
|
});
|
|
6576
7823
|
};
|
|
6577
7824
|
_CopilotChatAssistantMessage.ThumbsDownButton = ({ title, ...props }) => {
|
|
@@ -6580,7 +7827,45 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
|
|
|
6580
7827
|
"data-testid": "copilot-thumbs-down-button",
|
|
6581
7828
|
title: title || labels.assistantMessageToolbarThumbsDownLabel,
|
|
6582
7829
|
...props,
|
|
6583
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, {
|
|
7830
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, {
|
|
7831
|
+
className: "cpk:size-[18px]",
|
|
7832
|
+
fill: props["aria-pressed"] === true ? "currentColor" : "none"
|
|
7833
|
+
})
|
|
7834
|
+
});
|
|
7835
|
+
};
|
|
7836
|
+
_CopilotChatAssistantMessage.FeedbackComment = ({ value, savedComment, inputRef, onChange, onSubmit, onEditingChange, onLeave, className }) => {
|
|
7837
|
+
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
7838
|
+
const changed = value.trim() !== savedComment;
|
|
7839
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
7840
|
+
"data-testid": "copilot-feedback-comment",
|
|
7841
|
+
className: (0, tailwind_merge.twMerge)("cpk:flex cpk:w-80 cpk:max-w-[calc(100vw-1rem)] cpk:items-center cpk:gap-1 cpk:rounded-full cpk:border cpk:border-border cpk:bg-background cpk:py-0.5 cpk:pl-3 cpk:pr-0.5 cpk:text-sm cpk:shadow-md", className),
|
|
7842
|
+
onSubmit: (event) => {
|
|
7843
|
+
event.preventDefault();
|
|
7844
|
+
onSubmit();
|
|
7845
|
+
},
|
|
7846
|
+
onFocus: () => onEditingChange?.(true),
|
|
7847
|
+
onBlur: (event) => {
|
|
7848
|
+
const next = event.relatedTarget instanceof Element ? event.relatedTarget : null;
|
|
7849
|
+
if (event.currentTarget.contains(next)) return;
|
|
7850
|
+
if (typeof document !== "undefined" && !document.hasFocus()) return;
|
|
7851
|
+
onEditingChange?.(false);
|
|
7852
|
+
onLeave?.(next);
|
|
7853
|
+
},
|
|
7854
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7855
|
+
ref: inputRef,
|
|
7856
|
+
value,
|
|
7857
|
+
maxLength: MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH,
|
|
7858
|
+
onChange: (event) => onChange(event.target.value),
|
|
7859
|
+
placeholder: labels.assistantMessageFeedbackCommentPlaceholder,
|
|
7860
|
+
"aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
|
|
7861
|
+
className: "cpk:min-w-0 cpk:flex-1 cpk:bg-transparent cpk:py-1 cpk:text-foreground cpk:outline-none cpk:placeholder:text-muted-foreground"
|
|
7862
|
+
}), changed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7863
|
+
type: "submit",
|
|
7864
|
+
variant: "assistantMessageToolbarButton",
|
|
7865
|
+
"aria-label": labels.assistantMessageFeedbackCommentSubmitLabel,
|
|
7866
|
+
title: labels.assistantMessageFeedbackCommentSubmitLabel,
|
|
7867
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[16px]" })
|
|
7868
|
+
})]
|
|
6584
7869
|
});
|
|
6585
7870
|
};
|
|
6586
7871
|
_CopilotChatAssistantMessage.ReadAloudButton = ({ title, ...props }) => {
|
|
@@ -6608,6 +7893,7 @@ CopilotChatAssistantMessage.CopyButton.displayName = "CopilotChatAssistantMessag
|
|
|
6608
7893
|
CopilotChatAssistantMessage.InspectorButton.displayName = "CopilotChatAssistantMessage.InspectorButton";
|
|
6609
7894
|
CopilotChatAssistantMessage.ThumbsUpButton.displayName = "CopilotChatAssistantMessage.ThumbsUpButton";
|
|
6610
7895
|
CopilotChatAssistantMessage.ThumbsDownButton.displayName = "CopilotChatAssistantMessage.ThumbsDownButton";
|
|
7896
|
+
CopilotChatAssistantMessage.FeedbackComment.displayName = "CopilotChatAssistantMessage.FeedbackComment";
|
|
6611
7897
|
CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantMessage.ReadAloudButton";
|
|
6612
7898
|
CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
|
|
6613
7899
|
var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
|
|
@@ -6913,6 +8199,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
|
|
|
6913
8199
|
});
|
|
6914
8200
|
};
|
|
6915
8201
|
_CopilotChatUserMessage.BranchNavigation = ({ className, currentBranch = 0, numberOfBranches = 1, onSwitchToBranch, message, ...props }) => {
|
|
8202
|
+
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
6916
8203
|
if (!numberOfBranches || numberOfBranches <= 1 || !onSwitchToBranch) return null;
|
|
6917
8204
|
const canGoPrev = currentBranch > 0;
|
|
6918
8205
|
const canGoNext = currentBranch < numberOfBranches - 1;
|
|
@@ -6931,6 +8218,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
|
|
|
6931
8218
|
}),
|
|
6932
8219
|
disabled: !canGoPrev,
|
|
6933
8220
|
className: "cpk:h-6 cpk:w-6 cpk:p-0",
|
|
8221
|
+
"aria-label": labels.userMessageToolbarPreviousBranchLabel,
|
|
6934
8222
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "cpk:size-[20px]" })
|
|
6935
8223
|
}),
|
|
6936
8224
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -6951,6 +8239,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
|
|
|
6951
8239
|
}),
|
|
6952
8240
|
disabled: !canGoNext,
|
|
6953
8241
|
className: "cpk:h-6 cpk:w-6 cpk:p-0",
|
|
8242
|
+
"aria-label": labels.userMessageToolbarNextBranchLabel,
|
|
6954
8243
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "cpk:size-[20px]" })
|
|
6955
8244
|
})
|
|
6956
8245
|
]
|
|
@@ -6971,7 +8260,7 @@ var CopilotChatUserMessage_default = CopilotChatUserMessage;
|
|
|
6971
8260
|
/**
|
|
6972
8261
|
* Formats an elapsed duration (in seconds) to a human-readable string.
|
|
6973
8262
|
*/
|
|
6974
|
-
function formatDuration(seconds) {
|
|
8263
|
+
function formatDuration$1(seconds) {
|
|
6975
8264
|
if (seconds < 1) return "a few seconds";
|
|
6976
8265
|
if (seconds < 60) return `${Math.round(seconds)} seconds`;
|
|
6977
8266
|
const mins = Math.floor(seconds / 60);
|
|
@@ -7009,7 +8298,7 @@ function CopilotChatReasoningMessage({ message, messages, isRunning, isLatest: i
|
|
|
7009
8298
|
userToggledRef.current = true;
|
|
7010
8299
|
setIsOpen((prev) => !prev);
|
|
7011
8300
|
} : void 0;
|
|
7012
|
-
const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration(elapsed)}`;
|
|
8301
|
+
const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration$1(elapsed)}`;
|
|
7013
8302
|
const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {
|
|
7014
8303
|
isOpen,
|
|
7015
8304
|
label,
|
|
@@ -7231,6 +8520,71 @@ const ScrollElementContext = react.default.createContext(null);
|
|
|
7231
8520
|
*/
|
|
7232
8521
|
const ScrollPinnedContext = react.default.createContext(false);
|
|
7233
8522
|
|
|
8523
|
+
//#endregion
|
|
8524
|
+
//#region src/v2/components/chat/CopilotChatWorkSummary.tsx
|
|
8525
|
+
/** "45s", "1m 5s", "2m". Used for the live timer and the final duration. */
|
|
8526
|
+
function formatDuration(ms) {
|
|
8527
|
+
const seconds = Math.max(0, Math.floor(ms / 1e3));
|
|
8528
|
+
if (seconds < 60) return `${seconds}s`;
|
|
8529
|
+
const minutes = Math.floor(seconds / 60);
|
|
8530
|
+
const rest = seconds % 60;
|
|
8531
|
+
return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
|
|
8532
|
+
}
|
|
8533
|
+
/** Re-renders every second while `active`, returning the current time. */
|
|
8534
|
+
function useNow(active) {
|
|
8535
|
+
const [now, setNow] = (0, react.useState)(() => Date.now());
|
|
8536
|
+
(0, react.useEffect)(() => {
|
|
8537
|
+
if (!active) return;
|
|
8538
|
+
setNow(Date.now());
|
|
8539
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
8540
|
+
return () => clearInterval(timer);
|
|
8541
|
+
}, [active]);
|
|
8542
|
+
return now;
|
|
8543
|
+
}
|
|
8544
|
+
/**
|
|
8545
|
+
* The line at the top of a reply in the `"turn"` tool-call display. While the
|
|
8546
|
+
* agent works it reads "Working 4s"; once the reply is done it reads "Worked
|
|
8547
|
+
* for 9s · 7 tools" and toggles the work it folded away.
|
|
8548
|
+
*/
|
|
8549
|
+
function CopilotChatWorkSummary({ isWorking, isOpen, calls, elapsedMs, workingSince, className, ...props }) {
|
|
8550
|
+
const now = useNow(isWorking);
|
|
8551
|
+
const failed = calls.filter((call) => call.toolMessage?.error).length;
|
|
8552
|
+
const liveMs = elapsedMs !== void 0 && workingSince !== void 0 ? elapsedMs + Math.max(0, now - workingSince) : void 0;
|
|
8553
|
+
const duration = elapsedMs !== void 0 ? formatDuration(Math.max(1e3, elapsedMs)) : void 0;
|
|
8554
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8555
|
+
type: "button",
|
|
8556
|
+
"data-copilotkit": true,
|
|
8557
|
+
"data-testid": "copilot-work-summary",
|
|
8558
|
+
"data-state": isWorking ? "working" : "done",
|
|
8559
|
+
"aria-expanded": isOpen,
|
|
8560
|
+
className: (0, tailwind_merge.twMerge)("cpk:m-0 cpk:inline-flex cpk:max-w-full cpk:cursor-pointer cpk:items-center cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:hover:text-foreground cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring", className),
|
|
8561
|
+
...props,
|
|
8562
|
+
children: [isWorking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8563
|
+
className: "cpk:font-medium cpk-shimmer",
|
|
8564
|
+
children: "Working"
|
|
8565
|
+
}), liveMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8566
|
+
className: "cpk:text-xs cpk:tabular-nums cpk:opacity-70",
|
|
8567
|
+
children: formatDuration(liveMs)
|
|
8568
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
8569
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8570
|
+
className: "cpk:font-medium",
|
|
8571
|
+
children: duration ? `Worked for ${duration}` : `Used ${pluralize(calls.length, "tool")}`
|
|
8572
|
+
}),
|
|
8573
|
+
duration && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8574
|
+
className: "cpk:text-xs cpk:opacity-70",
|
|
8575
|
+
children: pluralize(calls.length, "tool")
|
|
8576
|
+
}),
|
|
8577
|
+
failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8578
|
+
className: "cpk:text-destructive",
|
|
8579
|
+
children: [failed, " failed"]
|
|
8580
|
+
})
|
|
8581
|
+
] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
|
|
8582
|
+
"aria-hidden": "true",
|
|
8583
|
+
className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", isOpen && "cpk:rotate-90")
|
|
8584
|
+
})]
|
|
8585
|
+
});
|
|
8586
|
+
}
|
|
8587
|
+
|
|
7234
8588
|
//#endregion
|
|
7235
8589
|
//#region src/v2/components/intelligence-indicator/IntelligenceIndicatorView.tsx
|
|
7236
8590
|
/**
|
|
@@ -7717,7 +9071,7 @@ function messageGroup(group) {
|
|
|
7717
9071
|
}
|
|
7718
9072
|
const GROUP_ROW_KEY_PREFIX = "copilotkit-group:";
|
|
7719
9073
|
const VIRTUALIZE_THRESHOLD = 50;
|
|
7720
|
-
function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, isRunning = false, transformMessages, groupMessages, children, className, ...props }) {
|
|
9074
|
+
function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, workSummary, isRunning = false, transformMessages, groupMessages, messageFeedback = true, toolCallDisplay = "grouped", children, className, ...props }) {
|
|
7721
9075
|
const isPinnedToBottom = (0, react.useContext)(ScrollPinnedContext);
|
|
7722
9076
|
const renderCustomMessage = useRenderCustomMessages();
|
|
7723
9077
|
const { renderActivityMessage } = useRenderActivityMessage();
|
|
@@ -7758,6 +9112,100 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7758
9112
|
transformedMessages
|
|
7759
9113
|
]);
|
|
7760
9114
|
const latestRenderedId = renderedMessages[renderedMessages.length - 1]?.id;
|
|
9115
|
+
const rendererNames = useToolCallRendererNames();
|
|
9116
|
+
const toolCallLayouts = (0, react.useMemo)(() => toolCallDisplay === "separate" ? /* @__PURE__ */ new Map() : getToolCallLayouts(renderedMessages, messages, rendererNames), [
|
|
9117
|
+
toolCallDisplay,
|
|
9118
|
+
renderedMessages,
|
|
9119
|
+
messages,
|
|
9120
|
+
rendererNames
|
|
9121
|
+
]);
|
|
9122
|
+
const turnWork = (0, react.useMemo)(() => toolCallDisplay === "turn" ? getTurnWork(renderedMessages, toolCallLayouts) : void 0, [
|
|
9123
|
+
toolCallDisplay,
|
|
9124
|
+
renderedMessages,
|
|
9125
|
+
toolCallLayouts
|
|
9126
|
+
]);
|
|
9127
|
+
const turnByFirstMessage = (0, react.useMemo)(() => {
|
|
9128
|
+
const byFirst = /* @__PURE__ */ new Map();
|
|
9129
|
+
for (const turn of turnWork?.values() ?? []) byFirst.set(turn.firstMessageId, turn);
|
|
9130
|
+
return byFirst;
|
|
9131
|
+
}, [turnWork]);
|
|
9132
|
+
let latestTurnKey = "start";
|
|
9133
|
+
for (let i = renderedMessages.length - 1; i >= 0; i--) if (renderedMessages[i].role === "user") {
|
|
9134
|
+
latestTurnKey = renderedMessages[i].id;
|
|
9135
|
+
break;
|
|
9136
|
+
}
|
|
9137
|
+
const isWorkingOn = (turnKey) => isRunning && turnKey === latestTurnKey;
|
|
9138
|
+
const [toggledTurns, setToggledTurns] = (0, react.useState)(() => /* @__PURE__ */ new Map());
|
|
9139
|
+
const isWorkOpen = (turnKey) => toggledTurns.get(turnKey) ?? isWorkingOn(turnKey);
|
|
9140
|
+
const workTimesRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
9141
|
+
if (toolCallDisplay === "turn") {
|
|
9142
|
+
const now = Date.now();
|
|
9143
|
+
const workTimes = workTimesRef.current;
|
|
9144
|
+
if (isRunning) {
|
|
9145
|
+
const times = workTimes.get(latestTurnKey);
|
|
9146
|
+
if (times) times.workingSince ??= now;
|
|
9147
|
+
else if (renderedMessages[renderedMessages.length - 1]?.role === "user") workTimes.set(latestTurnKey, {
|
|
9148
|
+
elapsedMs: 0,
|
|
9149
|
+
workingSince: now
|
|
9150
|
+
});
|
|
9151
|
+
} else for (const times of workTimes.values()) {
|
|
9152
|
+
if (times.workingSince === void 0) continue;
|
|
9153
|
+
times.elapsedMs += now - times.workingSince;
|
|
9154
|
+
times.workingSince = void 0;
|
|
9155
|
+
}
|
|
9156
|
+
}
|
|
9157
|
+
const { workOpen, workTextOpen } = (0, react.useMemo)(() => {
|
|
9158
|
+
const open = /* @__PURE__ */ new Map();
|
|
9159
|
+
const textOpen = /* @__PURE__ */ new Map();
|
|
9160
|
+
for (const turn of turnWork?.values() ?? []) {
|
|
9161
|
+
const turnOpen = toggledTurns.get(turn.key) ?? (isRunning && turn.key === latestTurnKey);
|
|
9162
|
+
for (const id of turn.workMessageIds) {
|
|
9163
|
+
open.set(id, turnOpen);
|
|
9164
|
+
if (id !== turn.replyTextMessageId) textOpen.set(id, turnOpen);
|
|
9165
|
+
}
|
|
9166
|
+
}
|
|
9167
|
+
return {
|
|
9168
|
+
workOpen: open,
|
|
9169
|
+
workTextOpen: textOpen
|
|
9170
|
+
};
|
|
9171
|
+
}, [
|
|
9172
|
+
turnWork,
|
|
9173
|
+
toggledTurns,
|
|
9174
|
+
isRunning,
|
|
9175
|
+
latestTurnKey
|
|
9176
|
+
]);
|
|
9177
|
+
const toolCallDisplayContext = (0, react.useMemo)(() => ({
|
|
9178
|
+
display: toolCallDisplay,
|
|
9179
|
+
isRunning,
|
|
9180
|
+
layouts: toolCallLayouts,
|
|
9181
|
+
workOpen,
|
|
9182
|
+
workTextOpen
|
|
9183
|
+
}), [
|
|
9184
|
+
toolCallDisplay,
|
|
9185
|
+
isRunning,
|
|
9186
|
+
toolCallLayouts,
|
|
9187
|
+
workOpen,
|
|
9188
|
+
workTextOpen
|
|
9189
|
+
]);
|
|
9190
|
+
const renderWorkSummary = (turnKey, calls) => {
|
|
9191
|
+
const working = isWorkingOn(turnKey);
|
|
9192
|
+
const open = isWorkOpen(turnKey);
|
|
9193
|
+
const times = workTimesRef.current.get(turnKey);
|
|
9194
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
9195
|
+
open: working || calls.length > 0,
|
|
9196
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9197
|
+
className: "cpk:pt-1 cpk:pb-2",
|
|
9198
|
+
children: renderSlot(workSummary, CopilotChatWorkSummary, {
|
|
9199
|
+
isWorking: working,
|
|
9200
|
+
isOpen: open,
|
|
9201
|
+
calls,
|
|
9202
|
+
elapsedMs: times?.elapsedMs,
|
|
9203
|
+
workingSince: times?.workingSince,
|
|
9204
|
+
onClick: () => setToggledTurns((toggled) => new Map(toggled).set(turnKey, !open))
|
|
9205
|
+
})
|
|
9206
|
+
})
|
|
9207
|
+
}, `work-summary-${turnKey}`);
|
|
9208
|
+
};
|
|
7761
9209
|
const transformDuplicateId = (0, react.useMemo)(() => {
|
|
7762
9210
|
if (process.env.NODE_ENV === "production" || !transformMessages && !groupMessages) return;
|
|
7763
9211
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7823,6 +9271,18 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7823
9271
|
const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
|
|
7824
9272
|
const agentId = config?.agentId;
|
|
7825
9273
|
const threadId = config?.threadId;
|
|
9274
|
+
const learningFeedbackEnabled = useLearningFeedbackEnabled(copilotkit);
|
|
9275
|
+
const feedbackScope = (0, react.useMemo)(() => messageFeedback && learningFeedbackEnabled && agentId !== void 0 && threadId !== void 0 ? {
|
|
9276
|
+
copilotkit,
|
|
9277
|
+
agentId,
|
|
9278
|
+
threadId
|
|
9279
|
+
} : null, [
|
|
9280
|
+
messageFeedback,
|
|
9281
|
+
learningFeedbackEnabled,
|
|
9282
|
+
copilotkit,
|
|
9283
|
+
agentId,
|
|
9284
|
+
threadId
|
|
9285
|
+
]);
|
|
7826
9286
|
const assistantSlotPropsWithFeedback = (0, react.useMemo)(() => {
|
|
7827
9287
|
const onThumbsUp = assistantSlotProps?.onThumbsUp;
|
|
7828
9288
|
const onThumbsDown = assistantSlotProps?.onThumbsDown;
|
|
@@ -7889,7 +9349,7 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7889
9349
|
measuredRef.current.total = total - (sizes.get(index) ?? 0) + height;
|
|
7890
9350
|
sizes.set(index, height);
|
|
7891
9351
|
}
|
|
7892
|
-
return height;
|
|
9352
|
+
return height > 0 ? height : measuredRef.current.sizes.get(index) ?? 100;
|
|
7893
9353
|
}, []);
|
|
7894
9354
|
const isPinnedToBottomRef = react.default.useRef(isPinnedToBottom);
|
|
7895
9355
|
const shouldAdjustScrollOnResize = react.default.useCallback((item, _delta, instance) => {
|
|
@@ -7899,7 +9359,7 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7899
9359
|
}, []);
|
|
7900
9360
|
const virtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
|
|
7901
9361
|
count: shouldVirtualize ? rows.length : 0,
|
|
7902
|
-
getScrollElement: () => scrollElement,
|
|
9362
|
+
getScrollElement: () => shouldVirtualize ? scrollElement : null,
|
|
7903
9363
|
estimateSize: estimateRowSize,
|
|
7904
9364
|
overscan: 5,
|
|
7905
9365
|
measureElement: measureRowElement,
|
|
@@ -7910,8 +9370,11 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7910
9370
|
});
|
|
7911
9371
|
isPinnedToBottomRef.current = isPinnedToBottom;
|
|
7912
9372
|
virtualizer.shouldAdjustScrollPositionOnItemSizeChange = shouldAdjustScrollOnResize;
|
|
9373
|
+
const lastEndAlignedFirstMessageRef = react.default.useRef(void 0);
|
|
7913
9374
|
(0, react.useLayoutEffect)(() => {
|
|
7914
9375
|
if (!shouldVirtualize || !rows.length) return;
|
|
9376
|
+
if (lastEndAlignedFirstMessageRef.current === firstMessageId) return;
|
|
9377
|
+
lastEndAlignedFirstMessageRef.current = firstMessageId;
|
|
7915
9378
|
virtualizer.scrollToIndex(rows.length - 1, { align: "end" });
|
|
7916
9379
|
}, [shouldVirtualize, firstMessageId]);
|
|
7917
9380
|
const intelligenceTurnAnchors = (0, react.useMemo)(() => getIntelligenceTurnAnchors(deduplicatedMessages, renderedMessages), [deduplicatedMessages, renderedMessages]);
|
|
@@ -7919,6 +9382,8 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7919
9382
|
const elements = [];
|
|
7920
9383
|
const stateSnapshot = renderCustomMessage ? getStateSnapshotForMessage(message.id) : void 0;
|
|
7921
9384
|
const rowKey = rowRenderKeys.get(message.id) ?? message.id;
|
|
9385
|
+
const turn = turnByFirstMessage.get(message.id);
|
|
9386
|
+
if (turn) elements.push(renderWorkSummary(turn.key, turn.calls));
|
|
7922
9387
|
if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
|
|
7923
9388
|
message,
|
|
7924
9389
|
position: "before",
|
|
@@ -7942,14 +9407,20 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7942
9407
|
message,
|
|
7943
9408
|
renderActivityMessage
|
|
7944
9409
|
}, rowKey));
|
|
7945
|
-
else if (message.role === "reasoning")
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
9410
|
+
else if (message.role === "reasoning") {
|
|
9411
|
+
const reasoning = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
|
|
9412
|
+
message,
|
|
9413
|
+
messages,
|
|
9414
|
+
isRunning,
|
|
9415
|
+
isLatest: message.id === latestRenderedId,
|
|
9416
|
+
ReasoningMessageComponent: ReasoningComponent,
|
|
9417
|
+
slotProps: reasoningSlotProps
|
|
9418
|
+
}, rowKey);
|
|
9419
|
+
elements.push(toolCallDisplay === "turn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
|
|
9420
|
+
open: workOpen.get(message.id) ?? true,
|
|
9421
|
+
children: reasoning
|
|
9422
|
+
}, rowKey) : reasoning);
|
|
9423
|
+
}
|
|
7953
9424
|
if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
|
|
7954
9425
|
message,
|
|
7955
9426
|
position: "after",
|
|
@@ -7977,52 +9448,157 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
|
|
|
7977
9448
|
children: row.messages.flatMap(renderMessageBlock)
|
|
7978
9449
|
}, rowKey(row))];
|
|
7979
9450
|
};
|
|
9451
|
+
const rowKeysRef = (0, react.useRef)([]);
|
|
9452
|
+
rowKeysRef.current = rows.map(rowKey);
|
|
9453
|
+
const virtualModeRef = (0, react.useRef)(shouldVirtualize);
|
|
9454
|
+
virtualModeRef.current = shouldVirtualize;
|
|
9455
|
+
(0, react.useEffect)(() => {
|
|
9456
|
+
const element = scrollElementFromCtx;
|
|
9457
|
+
if (!element) return;
|
|
9458
|
+
let anchor = null;
|
|
9459
|
+
let suspended = false;
|
|
9460
|
+
let frame = 0;
|
|
9461
|
+
const visibleRows = () => Array.from(element.querySelectorAll("[data-scroll-anchor]"));
|
|
9462
|
+
const remember = () => {
|
|
9463
|
+
if (suspended || element.clientHeight === 0 || !virtualModeRef.current) return;
|
|
9464
|
+
if (isPinnedToBottomRef.current) {
|
|
9465
|
+
anchor = null;
|
|
9466
|
+
return;
|
|
9467
|
+
}
|
|
9468
|
+
const viewport = element.getBoundingClientRect();
|
|
9469
|
+
const row = visibleRows().find((row) => {
|
|
9470
|
+
const bounds = row.getBoundingClientRect();
|
|
9471
|
+
return bounds.bottom > viewport.top && bounds.top < viewport.bottom;
|
|
9472
|
+
});
|
|
9473
|
+
if (row) anchor = {
|
|
9474
|
+
key: row.dataset.scrollAnchor,
|
|
9475
|
+
offset: row.getBoundingClientRect().top - viewport.top
|
|
9476
|
+
};
|
|
9477
|
+
};
|
|
9478
|
+
const observer = new ResizeObserver(() => {
|
|
9479
|
+
if (element.clientHeight === 0) {
|
|
9480
|
+
suspended = true;
|
|
9481
|
+
cancelAnimationFrame(frame);
|
|
9482
|
+
return;
|
|
9483
|
+
}
|
|
9484
|
+
if (!suspended || !anchor) {
|
|
9485
|
+
suspended = false;
|
|
9486
|
+
remember();
|
|
9487
|
+
return;
|
|
9488
|
+
}
|
|
9489
|
+
let attempts = 0;
|
|
9490
|
+
let stableFrames = 0;
|
|
9491
|
+
const restore = () => {
|
|
9492
|
+
if (!anchor || element.clientHeight === 0) return;
|
|
9493
|
+
const index = rowKeysRef.current.indexOf(anchor.key);
|
|
9494
|
+
if (index < 0) {
|
|
9495
|
+
anchor = null;
|
|
9496
|
+
suspended = false;
|
|
9497
|
+
return;
|
|
9498
|
+
}
|
|
9499
|
+
const row = visibleRows().find((row) => row.dataset.scrollAnchor === anchor.key);
|
|
9500
|
+
if (row) {
|
|
9501
|
+
const delta = row.getBoundingClientRect().top - element.getBoundingClientRect().top - anchor.offset;
|
|
9502
|
+
element.scrollTop += delta;
|
|
9503
|
+
stableFrames = Math.abs(delta) < 1 ? stableFrames + 1 : 0;
|
|
9504
|
+
} else {
|
|
9505
|
+
const offset = virtualizer.getOffsetForIndex(index, "start")?.[0];
|
|
9506
|
+
if (offset !== void 0) virtualizer.scrollToOffset(offset - anchor.offset);
|
|
9507
|
+
stableFrames = 0;
|
|
9508
|
+
}
|
|
9509
|
+
if (++attempts < 60 && stableFrames < 3) frame = requestAnimationFrame(restore);
|
|
9510
|
+
else {
|
|
9511
|
+
suspended = false;
|
|
9512
|
+
remember();
|
|
9513
|
+
}
|
|
9514
|
+
};
|
|
9515
|
+
frame = requestAnimationFrame(restore);
|
|
9516
|
+
});
|
|
9517
|
+
const interrupt = () => {
|
|
9518
|
+
cancelAnimationFrame(frame);
|
|
9519
|
+
anchor = null;
|
|
9520
|
+
suspended = false;
|
|
9521
|
+
};
|
|
9522
|
+
element.addEventListener("scroll", remember, { passive: true });
|
|
9523
|
+
element.addEventListener("wheel", interrupt, { passive: true });
|
|
9524
|
+
element.addEventListener("touchstart", interrupt, { passive: true });
|
|
9525
|
+
element.addEventListener("keydown", interrupt);
|
|
9526
|
+
observer.observe(element);
|
|
9527
|
+
remember();
|
|
9528
|
+
return () => {
|
|
9529
|
+
cancelAnimationFrame(frame);
|
|
9530
|
+
observer.disconnect();
|
|
9531
|
+
element.removeEventListener("scroll", remember);
|
|
9532
|
+
element.removeEventListener("wheel", interrupt);
|
|
9533
|
+
element.removeEventListener("touchstart", interrupt);
|
|
9534
|
+
element.removeEventListener("keydown", interrupt);
|
|
9535
|
+
};
|
|
9536
|
+
}, [
|
|
9537
|
+
scrollElementFromCtx,
|
|
9538
|
+
virtualizer,
|
|
9539
|
+
firstMessageId
|
|
9540
|
+
]);
|
|
7980
9541
|
const messageElements = shouldVirtualize ? [] : rows.flatMap(renderRow);
|
|
7981
|
-
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
9542
|
+
if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
|
|
9543
|
+
value: feedbackScope,
|
|
9544
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
|
|
9545
|
+
value: toolCallDisplayContext,
|
|
9546
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9547
|
+
"data-copilotkit": true,
|
|
9548
|
+
style: { display: "contents" },
|
|
9549
|
+
children: children({
|
|
9550
|
+
messageElements,
|
|
9551
|
+
messages,
|
|
9552
|
+
isRunning,
|
|
9553
|
+
interruptElement
|
|
9554
|
+
})
|
|
9555
|
+
})
|
|
7989
9556
|
})
|
|
7990
9557
|
});
|
|
7991
9558
|
const lastMessage = renderedMessages[renderedMessages.length - 1];
|
|
7992
|
-
const showCursor = isRunning && lastMessage?.role !== "reasoning";
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
const row = rows[virtualItem.index];
|
|
8006
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8007
|
-
"data-index": virtualItem.index,
|
|
8008
|
-
ref: virtualizer.measureElement,
|
|
9559
|
+
const showCursor = isRunning && lastMessage?.role !== "reasoning" && toolCallDisplay !== "turn";
|
|
9560
|
+
const pendingWorkSummary = toolCallDisplay === "turn" && isRunning && lastMessage?.role === "user";
|
|
9561
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
|
|
9562
|
+
value: feedbackScope,
|
|
9563
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
|
|
9564
|
+
value: toolCallDisplayContext,
|
|
9565
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
9566
|
+
"data-copilotkit": true,
|
|
9567
|
+
"data-testid": "copilot-message-list",
|
|
9568
|
+
className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
|
|
9569
|
+
...props,
|
|
9570
|
+
children: [
|
|
9571
|
+
shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8009
9572
|
style: {
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
left: 0,
|
|
8013
|
-
width: "100%",
|
|
8014
|
-
transform: `translateY(${virtualItem.start}px)`
|
|
9573
|
+
height: virtualizer.getTotalSize(),
|
|
9574
|
+
position: "relative"
|
|
8015
9575
|
},
|
|
8016
|
-
children:
|
|
8017
|
-
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
|
|
9576
|
+
children: virtualizer.getVirtualItems().map((virtualItem) => {
|
|
9577
|
+
const row = rows[virtualItem.index];
|
|
9578
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9579
|
+
"data-index": virtualItem.index,
|
|
9580
|
+
"data-scroll-anchor": rowKey(row),
|
|
9581
|
+
ref: virtualizer.measureElement,
|
|
9582
|
+
style: {
|
|
9583
|
+
position: "absolute",
|
|
9584
|
+
top: 0,
|
|
9585
|
+
left: 0,
|
|
9586
|
+
width: "100%",
|
|
9587
|
+
transform: `translateY(${virtualItem.start}px)`
|
|
9588
|
+
},
|
|
9589
|
+
children: renderRow(row)
|
|
9590
|
+
}, rowKey(row));
|
|
9591
|
+
})
|
|
9592
|
+
}) : messageElements,
|
|
9593
|
+
interruptElement,
|
|
9594
|
+
pendingWorkSummary && renderWorkSummary(latestTurnKey, []),
|
|
9595
|
+
showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9596
|
+
className: "cpk:mt-2",
|
|
9597
|
+
children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
|
|
9598
|
+
})
|
|
9599
|
+
]
|
|
8024
9600
|
})
|
|
8025
|
-
|
|
9601
|
+
})
|
|
8026
9602
|
});
|
|
8027
9603
|
}
|
|
8028
9604
|
CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
|
|
@@ -8407,6 +9983,16 @@ function DropOverlay() {
|
|
|
8407
9983
|
});
|
|
8408
9984
|
}
|
|
8409
9985
|
function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, intelligenceIndicator, children, className, ...props }) {
|
|
9986
|
+
const configuredThreadId = useCopilotChatConfiguration()?.threadId;
|
|
9987
|
+
const [fallbackThreadId, setFallbackThreadId] = (0, react.useState)(messages[0]?.id);
|
|
9988
|
+
const previousMessageIds = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
9989
|
+
(0, react.useEffect)(() => {
|
|
9990
|
+
if (messages.length === 0) return;
|
|
9991
|
+
const overlaps = messages.some((message) => previousMessageIds.current.has(message.id));
|
|
9992
|
+
previousMessageIds.current = new Set(messages.map((message) => message.id));
|
|
9993
|
+
if (!overlaps) setFallbackThreadId(messages[0].id);
|
|
9994
|
+
}, [messages]);
|
|
9995
|
+
const scrollThreadId = configuredThreadId ?? fallbackThreadId;
|
|
8410
9996
|
const [inputContainerEl, setInputContainerEl] = (0, react.useState)(null);
|
|
8411
9997
|
const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
|
|
8412
9998
|
const [isResizing, setIsResizing] = (0, react.useState)(false);
|
|
@@ -8473,6 +10059,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8473
10059
|
}) : null;
|
|
8474
10060
|
const BoundScrollView = renderSlot(scrollView, CopilotChatView.ScrollView, {
|
|
8475
10061
|
autoScroll,
|
|
10062
|
+
scrollThreadId,
|
|
8476
10063
|
inputContainerHeight,
|
|
8477
10064
|
isResizing,
|
|
8478
10065
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
@@ -8568,12 +10155,102 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8568
10155
|
});
|
|
8569
10156
|
}
|
|
8570
10157
|
(function(_CopilotChatView) {
|
|
8571
|
-
const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
|
|
8572
|
-
const { isAtBottom, scrollToBottom, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
|
|
10158
|
+
const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing, scrollThreadId }) => {
|
|
10159
|
+
const { isAtBottom, scrollToBottom, stopScroll, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
|
|
8573
10160
|
const [scrollEl, setScrollEl] = (0, react.useState)(null);
|
|
10161
|
+
const readingOffsetRef = (0, react.useRef)(null);
|
|
10162
|
+
const pinIntentRef = (0, react.useRef)(false);
|
|
10163
|
+
const stateRef = (0, react.useRef)(state);
|
|
10164
|
+
stateRef.current = state;
|
|
8574
10165
|
(0, react.useLayoutEffect)(() => {
|
|
8575
10166
|
setScrollEl(scrollRef.current ?? null);
|
|
8576
10167
|
}, []);
|
|
10168
|
+
(0, react.useEffect)(() => {
|
|
10169
|
+
if (!scrollEl) return;
|
|
10170
|
+
let hidden = false;
|
|
10171
|
+
let resumeBottom = false;
|
|
10172
|
+
let pendingFrame = 0;
|
|
10173
|
+
let resumeFrame = 0;
|
|
10174
|
+
let settleFrame = 0;
|
|
10175
|
+
readingOffsetRef.current = null;
|
|
10176
|
+
const rememberScroll = () => {
|
|
10177
|
+
if (hidden || scrollEl.clientHeight === 0) return;
|
|
10178
|
+
if (resumeBottom) {
|
|
10179
|
+
readingOffsetRef.current = null;
|
|
10180
|
+
return;
|
|
10181
|
+
}
|
|
10182
|
+
if (pinIntentRef.current) if (stateRef.current.escapedFromLock) pinIntentRef.current = false;
|
|
10183
|
+
else {
|
|
10184
|
+
readingOffsetRef.current = null;
|
|
10185
|
+
if (!stateRef.current.isNearBottom) return;
|
|
10186
|
+
pinIntentRef.current = false;
|
|
10187
|
+
}
|
|
10188
|
+
readingOffsetRef.current = !stateRef.current.isAtBottom && (stateRef.current.escapedFromLock || !stateRef.current.isNearBottom) ? scrollEl.scrollTop : null;
|
|
10189
|
+
};
|
|
10190
|
+
const onScroll = () => {
|
|
10191
|
+
rememberScroll();
|
|
10192
|
+
cancelAnimationFrame(pendingFrame);
|
|
10193
|
+
pendingFrame = requestAnimationFrame(rememberScroll);
|
|
10194
|
+
};
|
|
10195
|
+
const onWheel = (event) => {
|
|
10196
|
+
if (event.deltaY < 0) {
|
|
10197
|
+
pinIntentRef.current = false;
|
|
10198
|
+
resumeBottom = false;
|
|
10199
|
+
}
|
|
10200
|
+
};
|
|
10201
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
10202
|
+
if (scrollEl.clientHeight === 0) {
|
|
10203
|
+
hidden = true;
|
|
10204
|
+
resumeBottom = pinIntentRef.current || readingOffsetRef.current === null;
|
|
10205
|
+
if (!resumeBottom) stopScroll();
|
|
10206
|
+
} else if (hidden) {
|
|
10207
|
+
hidden = false;
|
|
10208
|
+
if (resumeBottom) {
|
|
10209
|
+
stopScroll();
|
|
10210
|
+
resumeFrame = requestAnimationFrame(() => {
|
|
10211
|
+
settleFrame = requestAnimationFrame(() => {
|
|
10212
|
+
if (scrollEl.clientHeight > 0 && resumeBottom) {
|
|
10213
|
+
resumeBottom = false;
|
|
10214
|
+
pinIntentRef.current = true;
|
|
10215
|
+
scrollToBottom({ animation: "instant" });
|
|
10216
|
+
}
|
|
10217
|
+
});
|
|
10218
|
+
});
|
|
10219
|
+
} else if (readingOffsetRef.current !== null) {
|
|
10220
|
+
stopScroll();
|
|
10221
|
+
scrollEl.scrollTop = readingOffsetRef.current;
|
|
10222
|
+
}
|
|
10223
|
+
}
|
|
10224
|
+
});
|
|
10225
|
+
rememberScroll();
|
|
10226
|
+
scrollEl.addEventListener("scroll", onScroll);
|
|
10227
|
+
scrollEl.addEventListener("wheel", onWheel, { passive: true });
|
|
10228
|
+
resizeObserver.observe(scrollEl);
|
|
10229
|
+
return () => {
|
|
10230
|
+
scrollEl.removeEventListener("scroll", onScroll);
|
|
10231
|
+
scrollEl.removeEventListener("wheel", onWheel);
|
|
10232
|
+
cancelAnimationFrame(pendingFrame);
|
|
10233
|
+
cancelAnimationFrame(resumeFrame);
|
|
10234
|
+
cancelAnimationFrame(settleFrame);
|
|
10235
|
+
resizeObserver.disconnect();
|
|
10236
|
+
};
|
|
10237
|
+
}, [
|
|
10238
|
+
scrollEl,
|
|
10239
|
+
scrollToBottom,
|
|
10240
|
+
stopScroll,
|
|
10241
|
+
scrollThreadId
|
|
10242
|
+
]);
|
|
10243
|
+
const startBottomPin = (0, react.useCallback)((instant = false) => {
|
|
10244
|
+
readingOffsetRef.current = null;
|
|
10245
|
+
pinIntentRef.current = true;
|
|
10246
|
+
scrollToBottom(instant ? { animation: "instant" } : void 0);
|
|
10247
|
+
}, [scrollToBottom]);
|
|
10248
|
+
const previousScrollThreadId = (0, react.useRef)(scrollThreadId);
|
|
10249
|
+
(0, react.useEffect)(() => {
|
|
10250
|
+
if (previousScrollThreadId.current === scrollThreadId) return;
|
|
10251
|
+
previousScrollThreadId.current = scrollThreadId;
|
|
10252
|
+
startBottomPin(true);
|
|
10253
|
+
}, [scrollThreadId, startBottomPin]);
|
|
8577
10254
|
const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
|
|
8578
10255
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
|
|
8579
10256
|
value: scrollEl,
|
|
@@ -8595,7 +10272,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8595
10272
|
!isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8596
10273
|
className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
|
|
8597
10274
|
style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
|
|
8598
|
-
children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () =>
|
|
10275
|
+
children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => startBottomPin() })
|
|
8599
10276
|
})
|
|
8600
10277
|
] })
|
|
8601
10278
|
})
|
|
@@ -8643,7 +10320,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8643
10320
|
})
|
|
8644
10321
|
});
|
|
8645
10322
|
};
|
|
8646
|
-
_CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
|
|
10323
|
+
_CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollThreadId, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
|
|
8647
10324
|
const mode = normalizeAutoScroll(autoScroll);
|
|
8648
10325
|
const [hasMounted, setHasMounted] = (0, react.useState)(false);
|
|
8649
10326
|
const scrollRef = (0, react.useRef)(null);
|
|
@@ -8732,6 +10409,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8732
10409
|
initial: "smooth",
|
|
8733
10410
|
...props,
|
|
8734
10411
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollContent, {
|
|
10412
|
+
scrollThreadId,
|
|
8735
10413
|
scrollToBottomButton,
|
|
8736
10414
|
feather,
|
|
8737
10415
|
inputContainerHeight,
|
|
@@ -8740,14 +10418,18 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
|
|
|
8740
10418
|
})
|
|
8741
10419
|
});
|
|
8742
10420
|
};
|
|
8743
|
-
_CopilotChatView.ScrollToBottomButton = ({ className, ...props }) =>
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
10421
|
+
_CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => {
|
|
10422
|
+
const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
|
|
10423
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
10424
|
+
"data-testid": "copilot-scroll-to-bottom",
|
|
10425
|
+
variant: "outline",
|
|
10426
|
+
size: "sm",
|
|
10427
|
+
className: (0, tailwind_merge.twMerge)("cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto", "cpk:bg-white cpk:dark:bg-gray-900", "cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700", "cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800", "cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer", className),
|
|
10428
|
+
"aria-label": labels.chatScrollToBottomLabel,
|
|
10429
|
+
...props,
|
|
10430
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
|
|
10431
|
+
});
|
|
10432
|
+
};
|
|
8751
10433
|
_CopilotChatView.Feather = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8752
10434
|
className,
|
|
8753
10435
|
...props
|
|
@@ -10130,6 +11812,7 @@ function CopilotPopupViewInternal({ header, toggleButton, width, height, clickOu
|
|
|
10130
11812
|
if (containerRef.current?.contains(target)) return;
|
|
10131
11813
|
const toggleButton = document.querySelector("[data-slot='chat-toggle-button']");
|
|
10132
11814
|
if (toggleButton && toggleButton.contains(target)) return;
|
|
11815
|
+
if (target instanceof Element && target.closest("[data-copilotkit-popover]")) return;
|
|
10133
11816
|
setModalOpen?.(false);
|
|
10134
11817
|
};
|
|
10135
11818
|
document.addEventListener("pointerdown", handlePointerDown);
|
|
@@ -10640,62 +12323,14 @@ CopilotThreadsDrawer.displayName = "CopilotThreadsDrawer";
|
|
|
10640
12323
|
|
|
10641
12324
|
//#endregion
|
|
10642
12325
|
//#region src/v2/components/WildcardToolCallRender.tsx
|
|
12326
|
+
/**
|
|
12327
|
+
* Catch-all (`"*"`) tool-call renderer that shows CopilotKit's default
|
|
12328
|
+
* tool-call card for any tool without a dedicated renderer.
|
|
12329
|
+
*/
|
|
10643
12330
|
const WildcardToolCallRender = defineToolCallRenderer({
|
|
10644
12331
|
name: "*",
|
|
10645
|
-
|
|
10646
|
-
|
|
10647
|
-
const statusString = String(status);
|
|
10648
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10649
|
-
className: "cpk:mt-2 cpk:pb-2",
|
|
10650
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10651
|
-
className: "cpk:rounded-xl cpk:border cpk:border-zinc-200/60 cpk:dark:border-zinc-800/60 cpk:bg-white/70 cpk:dark:bg-zinc-900/50 cpk:shadow-sm cpk:backdrop-blur cpk:p-4",
|
|
10652
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10653
|
-
className: "cpk:flex cpk:items-center cpk:justify-between cpk:gap-3 cpk:cursor-pointer",
|
|
10654
|
-
onClick: () => setIsExpanded(!isExpanded),
|
|
10655
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10656
|
-
className: "cpk:flex cpk:items-center cpk:gap-2 cpk:min-w-0",
|
|
10657
|
-
children: [
|
|
10658
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
10659
|
-
className: `cpk:h-4 cpk:w-4 cpk:text-zinc-500 cpk:dark:text-zinc-400 cpk:transition-transform ${isExpanded ? "cpk:rotate-90" : ""}`,
|
|
10660
|
-
fill: "none",
|
|
10661
|
-
viewBox: "0 0 24 24",
|
|
10662
|
-
strokeWidth: 2,
|
|
10663
|
-
stroke: "currentColor",
|
|
10664
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
10665
|
-
strokeLinecap: "round",
|
|
10666
|
-
strokeLinejoin: "round",
|
|
10667
|
-
d: "M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
10668
|
-
})
|
|
10669
|
-
}),
|
|
10670
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:inline-block cpk:h-2 cpk:w-2 cpk:rounded-full cpk:bg-blue-500" }),
|
|
10671
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10672
|
-
className: "cpk:truncate cpk:text-sm cpk:font-medium cpk:text-zinc-900 cpk:dark:text-zinc-100",
|
|
10673
|
-
children: name
|
|
10674
|
-
})
|
|
10675
|
-
]
|
|
10676
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10677
|
-
className: `cpk:inline-flex cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-1 cpk:text-xs cpk:font-medium ${statusString === "inProgress" || statusString === "executing" ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : statusString === "complete" ? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400" : "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300"}`,
|
|
10678
|
-
children: String(status)
|
|
10679
|
-
})]
|
|
10680
|
-
}), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10681
|
-
className: "cpk:mt-3 cpk:grid cpk:gap-4",
|
|
10682
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10683
|
-
className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
|
|
10684
|
-
children: "Arguments"
|
|
10685
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
10686
|
-
className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
|
|
10687
|
-
children: JSON.stringify(args ?? {}, null, 2)
|
|
10688
|
-
})] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10689
|
-
className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
|
|
10690
|
-
children: "Result"
|
|
10691
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
10692
|
-
className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
|
|
10693
|
-
children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
10694
|
-
})] })]
|
|
10695
|
-
})]
|
|
10696
|
-
})
|
|
10697
|
-
});
|
|
10698
|
-
}
|
|
12332
|
+
group: true,
|
|
12333
|
+
render: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(props) })
|
|
10699
12334
|
});
|
|
10700
12335
|
|
|
10701
12336
|
//#endregion
|
|
@@ -12974,6 +14609,12 @@ Object.defineProperty(exports, 'CopilotChatToggleButton', {
|
|
|
12974
14609
|
return CopilotChatToggleButton;
|
|
12975
14610
|
}
|
|
12976
14611
|
});
|
|
14612
|
+
Object.defineProperty(exports, 'CopilotChatToolCallGroup', {
|
|
14613
|
+
enumerable: true,
|
|
14614
|
+
get: function () {
|
|
14615
|
+
return CopilotChatToolCallGroup;
|
|
14616
|
+
}
|
|
14617
|
+
});
|
|
12977
14618
|
Object.defineProperty(exports, 'CopilotChatToolCallsView', {
|
|
12978
14619
|
enumerable: true,
|
|
12979
14620
|
get: function () {
|
|
@@ -12992,6 +14633,12 @@ Object.defineProperty(exports, 'CopilotChatView_default', {
|
|
|
12992
14633
|
return CopilotChatView_default;
|
|
12993
14634
|
}
|
|
12994
14635
|
});
|
|
14636
|
+
Object.defineProperty(exports, 'CopilotChatWorkSummary', {
|
|
14637
|
+
enumerable: true,
|
|
14638
|
+
get: function () {
|
|
14639
|
+
return CopilotChatWorkSummary;
|
|
14640
|
+
}
|
|
14641
|
+
});
|
|
12995
14642
|
Object.defineProperty(exports, 'CopilotContext', {
|
|
12996
14643
|
enumerable: true,
|
|
12997
14644
|
get: function () {
|
|
@@ -13394,4 +15041,4 @@ Object.defineProperty(exports, 'useToast', {
|
|
|
13394
15041
|
return useToast;
|
|
13395
15042
|
}
|
|
13396
15043
|
});
|
|
13397
|
-
//# sourceMappingURL=copilotkit-
|
|
15044
|
+
//# sourceMappingURL=copilotkit-DKICi3nC.cjs.map
|