@copilotkit/react-core 1.54.1 → 1.55.0-next.8
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/CHANGELOG.md +127 -116
- package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
- package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
- package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
- package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
- package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
- package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
- package/dist/index.cjs +27 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -5
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1941 -35
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +77 -7
- package/dist/v2/index.css +1 -2
- package/dist/v2/index.d.cts +6 -4
- package/dist/v2/index.d.mts +6 -4
- package/dist/v2/index.mjs +7 -4
- package/dist/v2/index.umd.js +5725 -24
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +37 -9
- package/scripts/scope-preflight.mjs +101 -0
- package/src/components/CopilotListeners.tsx +2 -6
- package/src/components/copilot-provider/copilot-messages.tsx +1 -1
- package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
- package/src/components/copilot-provider/copilotkit.tsx +4 -4
- package/src/context/copilot-messages-context.tsx +1 -1
- package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
- package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
- package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
- package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
- package/src/hooks/use-agent-nodename.ts +1 -1
- package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
- package/src/hooks/use-coagent.ts +1 -1
- package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat_internal.ts +2 -2
- package/src/hooks/use-copilot-readable.ts +1 -1
- package/src/hooks/use-frontend-tool.ts +2 -2
- package/src/hooks/use-human-in-the-loop.ts +2 -2
- package/src/hooks/use-langgraph-interrupt.ts +2 -5
- package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
- package/src/hooks/use-render-tool-call.ts +1 -1
- package/src/lib/copilot-task.ts +1 -1
- package/src/setupTests.ts +18 -14
- package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
- package/src/v2/__tests__/globalSetup.ts +14 -0
- package/src/v2/__tests__/setup.ts +93 -0
- package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
- package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
- package/src/v2/components/CopilotKitInspector.tsx +50 -0
- package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
- package/src/v2/components/WildcardToolCallRender.tsx +86 -0
- package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
- package/src/v2/components/chat/CopilotChat.tsx +431 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
- package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
- package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
- package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
- package/src/v2/components/chat/CopilotChatView.tsx +598 -0
- package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
- package/src/v2/components/chat/CopilotPopup.tsx +81 -0
- package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
- package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
- package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
- package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
- package/src/v2/components/chat/__tests__/setup.ts +1 -0
- package/src/v2/components/chat/index.ts +79 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/components/license-warning-banner.tsx +198 -0
- package/src/v2/components/ui/button.tsx +123 -0
- package/src/v2/components/ui/dropdown-menu.tsx +258 -0
- package/src/v2/components/ui/tooltip.tsx +60 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
- package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
- package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
- package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
- package/src/v2/hooks/index.ts +18 -0
- package/src/v2/hooks/use-agent-context.tsx +45 -0
- package/src/v2/hooks/use-agent.tsx +155 -0
- package/src/v2/hooks/use-component.tsx +89 -0
- package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
- package/src/v2/hooks/use-default-render-tool.tsx +254 -0
- package/src/v2/hooks/use-frontend-tool.tsx +43 -0
- package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
- package/src/v2/hooks/use-interrupt.tsx +305 -0
- package/src/v2/hooks/use-keyboard-height.tsx +67 -0
- package/src/v2/hooks/use-render-activity-message.tsx +73 -0
- package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
- package/src/v2/hooks/use-render-tool-call.tsx +175 -0
- package/src/v2/hooks/use-render-tool.tsx +181 -0
- package/src/v2/hooks/use-suggestions.tsx +91 -0
- package/src/v2/hooks/use-threads.tsx +256 -0
- package/src/v2/hooks/useKatexStyles.ts +27 -0
- package/src/v2/index.css +1 -1
- package/src/v2/index.ts +18 -2
- package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
- package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
- package/src/v2/lib/react-core.ts +156 -0
- package/src/v2/lib/slots.tsx +143 -0
- package/src/v2/lib/transcription-client.ts +184 -0
- package/src/v2/lib/utils.ts +8 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
- package/src/v2/providers/CopilotKitProvider.tsx +600 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
- package/src/v2/providers/index.ts +14 -0
- package/src/v2/styles/globals.css +230 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
- package/src/v2/types/defineToolCallRenderer.ts +65 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +33 -0
- package/src/v2/types/index.ts +7 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/react-activity-message-renderer.ts +27 -0
- package/src/v2/types/react-custom-message-renderer.ts +17 -0
- package/src/v2/types/react-tool-call-renderer.ts +32 -0
- package/tsdown.config.ts +34 -10
- package/vitest.config.mjs +4 -3
- package/LICENSE +0 -21
- package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
- package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
- package/dist/copilotkit-C94ayZbs.cjs +0 -2161
- package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
- package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
- package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
- package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
- package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
- package/dist/v2/index.css.map +0 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { defineToolCallRenderer } from "../types/defineToolCallRenderer";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
export const WildcardToolCallRender = defineToolCallRenderer({
|
|
5
|
+
name: "*",
|
|
6
|
+
render: ({ args, result, name, status }) => {
|
|
7
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
8
|
+
|
|
9
|
+
const statusString = String(status) as
|
|
10
|
+
| "inProgress"
|
|
11
|
+
| "executing"
|
|
12
|
+
| "complete";
|
|
13
|
+
const isActive =
|
|
14
|
+
statusString === "inProgress" || statusString === "executing";
|
|
15
|
+
const isComplete = statusString === "complete";
|
|
16
|
+
const statusStyles = isActive
|
|
17
|
+
? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400"
|
|
18
|
+
: isComplete
|
|
19
|
+
? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400"
|
|
20
|
+
: "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300";
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="cpk:mt-2 cpk:pb-2">
|
|
24
|
+
<div 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">
|
|
25
|
+
<div
|
|
26
|
+
className="cpk:flex cpk:items-center cpk:justify-between cpk:gap-3 cpk:cursor-pointer"
|
|
27
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
28
|
+
>
|
|
29
|
+
<div className="cpk:flex cpk:items-center cpk:gap-2 cpk:min-w-0">
|
|
30
|
+
<svg
|
|
31
|
+
className={`cpk:h-4 cpk:w-4 cpk:text-zinc-500 cpk:dark:text-zinc-400 cpk:transition-transform ${
|
|
32
|
+
isExpanded ? "cpk:rotate-90" : ""
|
|
33
|
+
}`}
|
|
34
|
+
fill="none"
|
|
35
|
+
viewBox="0 0 24 24"
|
|
36
|
+
strokeWidth={2}
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
>
|
|
39
|
+
<path
|
|
40
|
+
strokeLinecap="round"
|
|
41
|
+
strokeLinejoin="round"
|
|
42
|
+
d="M8.25 4.5l7.5 7.5-7.5 7.5"
|
|
43
|
+
/>
|
|
44
|
+
</svg>
|
|
45
|
+
<span className="cpk:inline-block cpk:h-2 cpk:w-2 cpk:rounded-full cpk:bg-blue-500" />
|
|
46
|
+
<span className="cpk:truncate cpk:text-sm cpk:font-medium cpk:text-zinc-900 cpk:dark:text-zinc-100">
|
|
47
|
+
{name}
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
<span
|
|
51
|
+
className={`cpk:inline-flex cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-1 cpk:text-xs cpk:font-medium ${statusStyles}`}
|
|
52
|
+
>
|
|
53
|
+
{String(status)}
|
|
54
|
+
</span>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
{isExpanded && (
|
|
58
|
+
<div className="cpk:mt-3 cpk:grid cpk:gap-4">
|
|
59
|
+
<div>
|
|
60
|
+
<div className="cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400">
|
|
61
|
+
Arguments
|
|
62
|
+
</div>
|
|
63
|
+
<pre 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">
|
|
64
|
+
{JSON.stringify(args ?? {}, null, 2)}
|
|
65
|
+
</pre>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{result !== undefined && (
|
|
69
|
+
<div>
|
|
70
|
+
<div className="cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400">
|
|
71
|
+
Result
|
|
72
|
+
</div>
|
|
73
|
+
<pre 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">
|
|
74
|
+
{typeof result === "string"
|
|
75
|
+
? result
|
|
76
|
+
: JSON.stringify(result, null, 2)}
|
|
77
|
+
</pre>
|
|
78
|
+
</div>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {
|
|
5
|
+
LicenseWarningBanner,
|
|
6
|
+
InlineFeatureWarning,
|
|
7
|
+
} from "../license-warning-banner";
|
|
8
|
+
|
|
9
|
+
describe("LicenseWarningBanner", () => {
|
|
10
|
+
it("renders 'Powered by CopilotKit' for no_license type", () => {
|
|
11
|
+
render(<LicenseWarningBanner type="no_license" />);
|
|
12
|
+
expect(screen.getByText("Powered by CopilotKit")).toBeTruthy();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("renders feature name for feature_unlicensed type", () => {
|
|
16
|
+
render(
|
|
17
|
+
<LicenseWarningBanner type="feature_unlicensed" featureName="Chat" />,
|
|
18
|
+
);
|
|
19
|
+
expect(
|
|
20
|
+
screen.getByText(/Chat.*requires a CopilotKit license/),
|
|
21
|
+
).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("renders expiry warning with days remaining", () => {
|
|
25
|
+
render(<LicenseWarningBanner type="expiring" graceRemaining={3} />);
|
|
26
|
+
expect(screen.getByText(/expires in 3 days/)).toBeTruthy();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("renders critical expired banner", () => {
|
|
30
|
+
render(<LicenseWarningBanner type="expired" expiryDate="2026-03-01" />);
|
|
31
|
+
expect(screen.getByText(/expired/i)).toBeTruthy();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("renders invalid license banner", () => {
|
|
35
|
+
render(<LicenseWarningBanner type="invalid" />);
|
|
36
|
+
expect(screen.getByText(/Invalid CopilotKit license/)).toBeTruthy();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("InlineFeatureWarning", () => {
|
|
41
|
+
it("renders with feature name and pricing link", () => {
|
|
42
|
+
render(<InlineFeatureWarning featureName="Agents" />);
|
|
43
|
+
expect(screen.getByText(/requires a CopilotKit license/)).toBeTruthy();
|
|
44
|
+
expect(screen.getByRole("link")).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { useAgent } from "../../hooks/use-agent";
|
|
2
|
+
import { useSuggestions } from "../../hooks/use-suggestions";
|
|
3
|
+
import { CopilotChatView, CopilotChatViewProps } from "./CopilotChatView";
|
|
4
|
+
import { CopilotChatInputMode } from "./CopilotChatInput";
|
|
5
|
+
import {
|
|
6
|
+
CopilotChatConfigurationProvider,
|
|
7
|
+
CopilotChatLabels,
|
|
8
|
+
useCopilotChatConfiguration,
|
|
9
|
+
} from "../../providers/CopilotChatConfigurationProvider";
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_AGENT_ID,
|
|
12
|
+
randomUUID,
|
|
13
|
+
TranscriptionErrorCode,
|
|
14
|
+
} from "@copilotkit/shared";
|
|
15
|
+
import { Suggestion, CopilotKitCoreErrorCode } from "@copilotkit/core";
|
|
16
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
17
|
+
import { merge } from "ts-deepmerge";
|
|
18
|
+
import {
|
|
19
|
+
useCopilotKit,
|
|
20
|
+
useLicenseContext,
|
|
21
|
+
} from "../../providers/CopilotKitProvider";
|
|
22
|
+
import { InlineFeatureWarning } from "../../components/license-warning-banner";
|
|
23
|
+
import { AbstractAgent, HttpAgent } from "@ag-ui/client";
|
|
24
|
+
import { renderSlot, SlotValue } from "../../lib/slots";
|
|
25
|
+
import {
|
|
26
|
+
transcribeAudio,
|
|
27
|
+
TranscriptionError,
|
|
28
|
+
} from "../../lib/transcription-client";
|
|
29
|
+
|
|
30
|
+
export type CopilotChatProps = Omit<
|
|
31
|
+
CopilotChatViewProps,
|
|
32
|
+
| "messages"
|
|
33
|
+
| "isRunning"
|
|
34
|
+
| "suggestions"
|
|
35
|
+
| "suggestionLoadingIndexes"
|
|
36
|
+
| "onSelectSuggestion"
|
|
37
|
+
> & {
|
|
38
|
+
agentId?: string;
|
|
39
|
+
threadId?: string;
|
|
40
|
+
labels?: Partial<CopilotChatLabels>;
|
|
41
|
+
chatView?: SlotValue<typeof CopilotChatView>;
|
|
42
|
+
isModalDefaultOpen?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Error handler scoped to this chat's agent. Fires in addition to the
|
|
45
|
+
* provider-level onError (does not suppress it). Receives only errors
|
|
46
|
+
* whose context.agentId matches this chat's agent.
|
|
47
|
+
*/
|
|
48
|
+
onError?: (event: {
|
|
49
|
+
error: Error;
|
|
50
|
+
code: CopilotKitCoreErrorCode;
|
|
51
|
+
context: Record<string, any>;
|
|
52
|
+
}) => void | Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
export function CopilotChat({
|
|
55
|
+
agentId,
|
|
56
|
+
threadId,
|
|
57
|
+
labels,
|
|
58
|
+
chatView,
|
|
59
|
+
isModalDefaultOpen,
|
|
60
|
+
onError,
|
|
61
|
+
...props
|
|
62
|
+
}: CopilotChatProps) {
|
|
63
|
+
// Check for existing configuration provider
|
|
64
|
+
const existingConfig = useCopilotChatConfiguration();
|
|
65
|
+
|
|
66
|
+
// Apply priority: props > existing config > defaults
|
|
67
|
+
const resolvedAgentId =
|
|
68
|
+
agentId ?? existingConfig?.agentId ?? DEFAULT_AGENT_ID;
|
|
69
|
+
const resolvedThreadId = useMemo(
|
|
70
|
+
() => threadId ?? existingConfig?.threadId ?? randomUUID(),
|
|
71
|
+
[threadId, existingConfig?.threadId],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const { agent } = useAgent({ agentId: resolvedAgentId });
|
|
75
|
+
const { copilotkit } = useCopilotKit();
|
|
76
|
+
const { suggestions: autoSuggestions } = useSuggestions({
|
|
77
|
+
agentId: resolvedAgentId,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const { checkFeature } = useLicenseContext();
|
|
81
|
+
const isChatLicensed = checkFeature("chat");
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!isChatLicensed) {
|
|
85
|
+
console.warn(
|
|
86
|
+
'[CopilotKit] Warning: "chat" feature is not licensed. Visit copilotkit.ai/pricing',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}, [isChatLicensed]);
|
|
90
|
+
|
|
91
|
+
// onError subscription — forward core errors scoped to this chat's agent
|
|
92
|
+
const onErrorRef = useRef(onError);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
onErrorRef.current = onError;
|
|
95
|
+
}, [onError]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!onErrorRef.current) return;
|
|
99
|
+
|
|
100
|
+
const subscription = copilotkit.subscribe({
|
|
101
|
+
onError: (event) => {
|
|
102
|
+
// Only forward errors that match this chat's agent
|
|
103
|
+
if (
|
|
104
|
+
event.context?.agentId === resolvedAgentId ||
|
|
105
|
+
!event.context?.agentId
|
|
106
|
+
) {
|
|
107
|
+
onErrorRef.current?.({
|
|
108
|
+
error: event.error,
|
|
109
|
+
code: event.code,
|
|
110
|
+
context: event.context,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
subscription.unsubscribe();
|
|
118
|
+
};
|
|
119
|
+
}, [copilotkit, resolvedAgentId]);
|
|
120
|
+
|
|
121
|
+
// Transcription state
|
|
122
|
+
const [transcribeMode, setTranscribeMode] =
|
|
123
|
+
useState<CopilotChatInputMode>("input");
|
|
124
|
+
const [inputValue, setInputValue] = useState("");
|
|
125
|
+
const [transcriptionError, setTranscriptionError] = useState<string | null>(
|
|
126
|
+
null,
|
|
127
|
+
);
|
|
128
|
+
const [isTranscribing, setIsTranscribing] = useState(false);
|
|
129
|
+
|
|
130
|
+
// Check if transcription is enabled
|
|
131
|
+
const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
|
|
132
|
+
|
|
133
|
+
// Check if browser supports MediaRecorder
|
|
134
|
+
const isMediaRecorderSupported =
|
|
135
|
+
typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
|
|
136
|
+
|
|
137
|
+
const {
|
|
138
|
+
messageView: providedMessageView,
|
|
139
|
+
suggestionView: providedSuggestionView,
|
|
140
|
+
onStop: providedStopHandler,
|
|
141
|
+
...restProps
|
|
142
|
+
} = props;
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
let detached = false;
|
|
146
|
+
|
|
147
|
+
// Create a fresh AbortController so we can cancel the HTTP request on cleanup.
|
|
148
|
+
// HttpAgent (parent of ProxiedCopilotRuntimeAgent) uses this.abortController.signal
|
|
149
|
+
// in its fetch config. Unlike runAgent(), connectAgent() does NOT create a new
|
|
150
|
+
// AbortController automatically, so we must set one before connecting.
|
|
151
|
+
const connectAbortController = new AbortController();
|
|
152
|
+
if (agent instanceof HttpAgent) {
|
|
153
|
+
agent.abortController = connectAbortController;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const connect = async (agent: AbstractAgent) => {
|
|
157
|
+
try {
|
|
158
|
+
await copilotkit.connectAgent({ agent });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
// Ignore errors from aborted connections (e.g., React StrictMode cleanup)
|
|
161
|
+
if (detached) return;
|
|
162
|
+
// connectAgent already emits via the subscriber system, but catch
|
|
163
|
+
// here to prevent unhandled rejections from unexpected errors.
|
|
164
|
+
console.error("CopilotChat: connectAgent failed", error);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
agent.threadId = resolvedThreadId;
|
|
168
|
+
connect(agent);
|
|
169
|
+
return () => {
|
|
170
|
+
// Abort the HTTP request and detach the active run.
|
|
171
|
+
// This is critical for React StrictMode which unmounts+remounts in dev,
|
|
172
|
+
// preventing duplicate /connect requests from reaching the server.
|
|
173
|
+
detached = true;
|
|
174
|
+
connectAbortController.abort();
|
|
175
|
+
// The .catch() is required to prevent a false-positive "Uncaught (in promise)
|
|
176
|
+
// AbortError" in browser devtools. detachActiveRun() itself does not reject,
|
|
177
|
+
// but without an attached handler V8 flags the promise chain as unhandled
|
|
178
|
+
// when the abort signal propagates through connected promises internally.
|
|
179
|
+
void agent.detachActiveRun().catch(() => {});
|
|
180
|
+
};
|
|
181
|
+
// copilotkit is intentionally excluded — it is a stable ref that never changes.
|
|
182
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
183
|
+
}, [resolvedThreadId, agent, resolvedAgentId]);
|
|
184
|
+
|
|
185
|
+
const onSubmitInput = useCallback(
|
|
186
|
+
async (value: string) => {
|
|
187
|
+
agent.addMessage({
|
|
188
|
+
id: randomUUID(),
|
|
189
|
+
role: "user",
|
|
190
|
+
content: value,
|
|
191
|
+
});
|
|
192
|
+
// Clear input after submitting
|
|
193
|
+
setInputValue("");
|
|
194
|
+
try {
|
|
195
|
+
await copilotkit.runAgent({ agent });
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error("CopilotChat: runAgent failed", error);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
// copilotkit is intentionally excluded — it is a stable ref that never changes.
|
|
201
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
202
|
+
[agent],
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const handleSelectSuggestion = useCallback(
|
|
206
|
+
async (suggestion: Suggestion) => {
|
|
207
|
+
agent.addMessage({
|
|
208
|
+
id: randomUUID(),
|
|
209
|
+
role: "user",
|
|
210
|
+
content: suggestion.message,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await copilotkit.runAgent({ agent });
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error(
|
|
217
|
+
"CopilotChat: runAgent failed after selecting suggestion",
|
|
218
|
+
error,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
223
|
+
[agent],
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const stopCurrentRun = useCallback(() => {
|
|
227
|
+
try {
|
|
228
|
+
copilotkit.stopAgent({ agent });
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error("CopilotChat: stopAgent failed", error);
|
|
231
|
+
try {
|
|
232
|
+
agent.abortRun();
|
|
233
|
+
} catch (abortError) {
|
|
234
|
+
console.error("CopilotChat: abortRun fallback failed", abortError);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
238
|
+
}, [agent]);
|
|
239
|
+
|
|
240
|
+
// Transcription handlers
|
|
241
|
+
const handleStartTranscribe = useCallback(() => {
|
|
242
|
+
setTranscriptionError(null);
|
|
243
|
+
setTranscribeMode("transcribe");
|
|
244
|
+
}, []);
|
|
245
|
+
|
|
246
|
+
const handleCancelTranscribe = useCallback(() => {
|
|
247
|
+
setTranscriptionError(null);
|
|
248
|
+
setTranscribeMode("input");
|
|
249
|
+
}, []);
|
|
250
|
+
|
|
251
|
+
const handleFinishTranscribe = useCallback(() => {
|
|
252
|
+
setTranscribeMode("input");
|
|
253
|
+
}, []);
|
|
254
|
+
|
|
255
|
+
// Handle audio blob from CopilotChatInput and transcribe it
|
|
256
|
+
const handleFinishTranscribeWithAudio = useCallback(
|
|
257
|
+
async (audioBlob: Blob) => {
|
|
258
|
+
setIsTranscribing(true);
|
|
259
|
+
try {
|
|
260
|
+
setTranscriptionError(null);
|
|
261
|
+
|
|
262
|
+
// Send to transcription endpoint
|
|
263
|
+
const result = await transcribeAudio(copilotkit, audioBlob);
|
|
264
|
+
|
|
265
|
+
// Insert transcribed text into input
|
|
266
|
+
setInputValue((prev) => {
|
|
267
|
+
const trimmedPrev = prev.trim();
|
|
268
|
+
if (trimmedPrev) {
|
|
269
|
+
return `${trimmedPrev} ${result.text}`;
|
|
270
|
+
}
|
|
271
|
+
return result.text;
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error("CopilotChat: Transcription failed", error);
|
|
275
|
+
|
|
276
|
+
// Show contextual error message based on error type
|
|
277
|
+
if (error instanceof TranscriptionError) {
|
|
278
|
+
const { code, retryable, message } = error.info;
|
|
279
|
+
switch (code) {
|
|
280
|
+
case TranscriptionErrorCode.RATE_LIMITED:
|
|
281
|
+
setTranscriptionError("Too many requests. Please wait a moment.");
|
|
282
|
+
break;
|
|
283
|
+
case TranscriptionErrorCode.AUTH_FAILED:
|
|
284
|
+
setTranscriptionError(
|
|
285
|
+
"Authentication error. Please check your configuration.",
|
|
286
|
+
);
|
|
287
|
+
break;
|
|
288
|
+
case TranscriptionErrorCode.AUDIO_TOO_LONG:
|
|
289
|
+
setTranscriptionError(
|
|
290
|
+
"Recording is too long. Please try a shorter recording.",
|
|
291
|
+
);
|
|
292
|
+
break;
|
|
293
|
+
case TranscriptionErrorCode.AUDIO_TOO_SHORT:
|
|
294
|
+
setTranscriptionError(
|
|
295
|
+
"Recording is too short. Please try again.",
|
|
296
|
+
);
|
|
297
|
+
break;
|
|
298
|
+
case TranscriptionErrorCode.INVALID_AUDIO_FORMAT:
|
|
299
|
+
setTranscriptionError("Audio format not supported.");
|
|
300
|
+
break;
|
|
301
|
+
case TranscriptionErrorCode.SERVICE_NOT_CONFIGURED:
|
|
302
|
+
setTranscriptionError("Transcription service is not available.");
|
|
303
|
+
break;
|
|
304
|
+
case TranscriptionErrorCode.NETWORK_ERROR:
|
|
305
|
+
setTranscriptionError(
|
|
306
|
+
"Network error. Please check your connection.",
|
|
307
|
+
);
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
// For retryable errors, show more helpful message
|
|
311
|
+
setTranscriptionError(
|
|
312
|
+
retryable ? "Transcription failed. Please try again." : message,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// Fallback for unexpected errors
|
|
317
|
+
setTranscriptionError("Transcription failed. Please try again.");
|
|
318
|
+
}
|
|
319
|
+
} finally {
|
|
320
|
+
setIsTranscribing(false);
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
324
|
+
[],
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Clear transcription error after a delay
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (transcriptionError) {
|
|
330
|
+
const timer = setTimeout(() => {
|
|
331
|
+
setTranscriptionError(null);
|
|
332
|
+
}, 5000);
|
|
333
|
+
return () => clearTimeout(timer);
|
|
334
|
+
}
|
|
335
|
+
}, [transcriptionError]);
|
|
336
|
+
|
|
337
|
+
const mergedProps = merge(
|
|
338
|
+
{
|
|
339
|
+
isRunning: agent.isRunning,
|
|
340
|
+
suggestions: autoSuggestions,
|
|
341
|
+
onSelectSuggestion: handleSelectSuggestion,
|
|
342
|
+
suggestionView: providedSuggestionView,
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
...restProps,
|
|
346
|
+
...(typeof providedMessageView === "string"
|
|
347
|
+
? { messageView: { className: providedMessageView } }
|
|
348
|
+
: providedMessageView !== undefined
|
|
349
|
+
? { messageView: providedMessageView }
|
|
350
|
+
: {}),
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const hasMessages = agent.messages.length > 0;
|
|
355
|
+
const shouldAllowStop = agent.isRunning && hasMessages;
|
|
356
|
+
const effectiveStopHandler = shouldAllowStop
|
|
357
|
+
? (providedStopHandler ?? stopCurrentRun)
|
|
358
|
+
: providedStopHandler;
|
|
359
|
+
|
|
360
|
+
// Determine if transcription feature should be available
|
|
361
|
+
const showTranscription = isTranscriptionEnabled && isMediaRecorderSupported;
|
|
362
|
+
|
|
363
|
+
// Determine mode: transcribing takes priority, then transcribe mode, then default to input
|
|
364
|
+
const effectiveMode: CopilotChatInputMode = isTranscribing
|
|
365
|
+
? "processing"
|
|
366
|
+
: transcribeMode;
|
|
367
|
+
|
|
368
|
+
// Memoize messages array - only create new reference when content actually changes
|
|
369
|
+
// (agent.messages is mutated in place, so we need a new reference for React to detect changes)
|
|
370
|
+
|
|
371
|
+
const messages = useMemo(
|
|
372
|
+
() => [...agent.messages],
|
|
373
|
+
[JSON.stringify(agent.messages)],
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const finalProps = merge(mergedProps, {
|
|
377
|
+
messages,
|
|
378
|
+
// Input behavior props
|
|
379
|
+
onSubmitMessage: onSubmitInput,
|
|
380
|
+
onStop: effectiveStopHandler,
|
|
381
|
+
inputMode: effectiveMode,
|
|
382
|
+
inputValue,
|
|
383
|
+
onInputChange: setInputValue,
|
|
384
|
+
// Only provide transcription handlers if feature is available
|
|
385
|
+
onStartTranscribe: showTranscription ? handleStartTranscribe : undefined,
|
|
386
|
+
onCancelTranscribe: showTranscription ? handleCancelTranscribe : undefined,
|
|
387
|
+
onFinishTranscribe: showTranscription ? handleFinishTranscribe : undefined,
|
|
388
|
+
onFinishTranscribeWithAudio: showTranscription
|
|
389
|
+
? handleFinishTranscribeWithAudio
|
|
390
|
+
: undefined,
|
|
391
|
+
}) as CopilotChatViewProps;
|
|
392
|
+
|
|
393
|
+
// Always create a provider with merged values
|
|
394
|
+
// This ensures priority: props > existing config > defaults
|
|
395
|
+
const RenderedChatView = renderSlot(chatView, CopilotChatView, finalProps);
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<CopilotChatConfigurationProvider
|
|
399
|
+
agentId={resolvedAgentId}
|
|
400
|
+
threadId={resolvedThreadId}
|
|
401
|
+
labels={labels}
|
|
402
|
+
isModalDefaultOpen={isModalDefaultOpen}
|
|
403
|
+
>
|
|
404
|
+
{!isChatLicensed && <InlineFeatureWarning featureName="Chat" />}
|
|
405
|
+
{transcriptionError && (
|
|
406
|
+
<div
|
|
407
|
+
style={{
|
|
408
|
+
position: "absolute",
|
|
409
|
+
bottom: "100px",
|
|
410
|
+
left: "50%",
|
|
411
|
+
transform: "translateX(-50%)",
|
|
412
|
+
backgroundColor: "#ef4444",
|
|
413
|
+
color: "white",
|
|
414
|
+
padding: "8px 16px",
|
|
415
|
+
borderRadius: "8px",
|
|
416
|
+
fontSize: "14px",
|
|
417
|
+
zIndex: 50,
|
|
418
|
+
}}
|
|
419
|
+
>
|
|
420
|
+
{transcriptionError}
|
|
421
|
+
</div>
|
|
422
|
+
)}
|
|
423
|
+
{RenderedChatView}
|
|
424
|
+
</CopilotChatConfigurationProvider>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
429
|
+
export namespace CopilotChat {
|
|
430
|
+
export const View = CopilotChatView;
|
|
431
|
+
}
|