@copilotkit/vue 1.72.1-canary.sp-longthread-perf → 1.73.1
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/PARITY.md +15 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +42 -41
- package/dist/index.mjs.map +1 -1
- package/dist/use-render-activity-message-6LwUTCNm.cjs +85 -0
- package/dist/use-render-activity-message-6LwUTCNm.cjs.map +1 -0
- package/dist/{use-render-activity-message-Ciee03Rx.js → use-render-activity-message-Di_L8HCv.js} +4501 -4400
- package/dist/use-render-activity-message-Di_L8HCv.js.map +1 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts +3 -5
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +4 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +7 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts +2 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +8 -2
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts +7 -4
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts +8 -5
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/index.d.ts +50 -26
- package/dist/v2/components/chat/index.d.ts.map +1 -1
- package/dist/v2/components/chat/types.d.ts +44 -17
- package/dist/v2/components/chat/types.d.ts.map +1 -1
- package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +25 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/v2/components/LicenseWarningBanner.vue +5 -17
- package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +23 -0
- package/src/v2/components/chat/CopilotChat.vue +23 -22
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +12 -21
- package/src/v2/components/chat/CopilotChatInput.vue +32 -26
- package/src/v2/components/chat/CopilotChatMessageView.vue +29 -3
- package/src/v2/components/chat/CopilotChatUserMessage.vue +19 -19
- package/src/v2/components/chat/CopilotChatView.vue +31 -39
- package/src/v2/components/chat/CopilotPopup.vue +8 -10
- package/src/v2/components/chat/CopilotPopupView.vue +8 -18
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +5 -19
- package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +36 -6
- package/src/v2/components/chat/CopilotSidebar.vue +8 -10
- package/src/v2/components/chat/CopilotSidebarView.vue +8 -18
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +5 -19
- package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +36 -6
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +156 -5
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +34 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.rowKeys.test.ts +140 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +46 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +118 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +79 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +93 -0
- package/src/v2/components/chat/types.ts +73 -18
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +32 -6
- package/src/v2/hooks/use-human-in-the-loop.ts +1 -0
- package/src/v2/providers/CopilotKitProvider.types.ts +28 -1
- package/src/v2/providers/CopilotKitProvider.vue +86 -6
- package/src/v2/providers/__tests__/CopilotKitProvider.hitl.e2e.test.ts +192 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.messageFilter.test.ts +159 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +275 -21
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.ts +19 -1
- package/dist/use-render-activity-message-Ciee03Rx.js.map +0 -1
- package/dist/use-render-activity-message-DXJDZGkW.cjs +0 -85
- package/dist/use-render-activity-message-DXJDZGkW.cjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cleanup, fireEvent, screen, waitFor } from "@testing-library/vue";
|
|
2
2
|
import { computed, defineComponent, onMounted } from "vue";
|
|
3
3
|
import type { PropType } from "vue";
|
|
4
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { EventType } from "@ag-ui/client";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { useConfigureSuggestions } from "../../../hooks/use-configure-suggestions";
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
toolCallResultEvent,
|
|
24
24
|
} from "../../../__tests__/utils/test-helpers";
|
|
25
25
|
import CopilotChat from "../CopilotChat.vue";
|
|
26
|
+
import CopilotPopup from "../CopilotPopup.vue";
|
|
26
27
|
|
|
27
28
|
afterEach(() => {
|
|
28
29
|
cleanup();
|
|
@@ -138,11 +139,33 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
138
139
|
components: { CopilotChat },
|
|
139
140
|
template: `
|
|
140
141
|
<CopilotChat :welcome-screen="false">
|
|
141
|
-
<template #chat-view="{
|
|
142
|
+
<template #chat-view="{
|
|
143
|
+
isRunning,
|
|
144
|
+
onStop,
|
|
145
|
+
onSubmitMessage,
|
|
146
|
+
onAddFile,
|
|
147
|
+
onStartTranscribe,
|
|
148
|
+
onCancelTranscribe,
|
|
149
|
+
onFinishTranscribe,
|
|
150
|
+
onFinishTranscribeWithAudio,
|
|
151
|
+
}">
|
|
142
152
|
<div>
|
|
143
153
|
<button data-testid="submit" @click="onSubmitMessage('trigger run')">submit</button>
|
|
144
154
|
<span data-testid="running">{{ isRunning ? "running" : "idle" }}</span>
|
|
145
|
-
<span data-testid="stop-availability">
|
|
155
|
+
<span data-testid="stop-availability">
|
|
156
|
+
{{
|
|
157
|
+
[
|
|
158
|
+
onStop,
|
|
159
|
+
onAddFile,
|
|
160
|
+
onStartTranscribe,
|
|
161
|
+
onCancelTranscribe,
|
|
162
|
+
onFinishTranscribe,
|
|
163
|
+
onFinishTranscribeWithAudio,
|
|
164
|
+
].every((command) => typeof command === "function")
|
|
165
|
+
? "available"
|
|
166
|
+
: "missing"
|
|
167
|
+
}}
|
|
168
|
+
</span>
|
|
146
169
|
</div>
|
|
147
170
|
</template>
|
|
148
171
|
</CopilotChat>
|
|
@@ -153,7 +176,7 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
153
176
|
|
|
154
177
|
expect(screen.getByTestId("running").textContent).toBe("idle");
|
|
155
178
|
expect(screen.getByTestId("stop-availability").textContent).toBe(
|
|
156
|
-
"
|
|
179
|
+
"available",
|
|
157
180
|
);
|
|
158
181
|
|
|
159
182
|
await fireEvent.click(screen.getByTestId("submit"));
|
|
@@ -172,7 +195,7 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
172
195
|
await waitFor(() => {
|
|
173
196
|
expect(screen.getByTestId("running").textContent).toBe("idle");
|
|
174
197
|
expect(screen.getByTestId("stop-availability").textContent).toBe(
|
|
175
|
-
"
|
|
198
|
+
"available",
|
|
176
199
|
);
|
|
177
200
|
});
|
|
178
201
|
});
|
|
@@ -1237,4 +1260,132 @@ describe("CopilotChat E2E - Chat Basics and Streaming Patterns", () => {
|
|
|
1237
1260
|
});
|
|
1238
1261
|
});
|
|
1239
1262
|
});
|
|
1263
|
+
|
|
1264
|
+
describe("Vue-specific semantics", () => {
|
|
1265
|
+
it("forwards a static stop listener through a custom input slot after a run starts", async () => {
|
|
1266
|
+
const agent = new MockStepwiseAgent();
|
|
1267
|
+
const onStop = vi.fn();
|
|
1268
|
+
const CustomInputChat = defineComponent({
|
|
1269
|
+
components: { CopilotChat },
|
|
1270
|
+
setup() {
|
|
1271
|
+
return { onStop };
|
|
1272
|
+
},
|
|
1273
|
+
template: `
|
|
1274
|
+
<CopilotChat :welcome-screen="false" @stop="onStop">
|
|
1275
|
+
<template #input="{ isRunning, onSubmitMessage, onStop }">
|
|
1276
|
+
<button data-testid="custom-input-submit" @click="onSubmitMessage('Hello')">
|
|
1277
|
+
submit
|
|
1278
|
+
</button>
|
|
1279
|
+
<button v-if="isRunning" data-testid="custom-input-stop" @click="onStop">
|
|
1280
|
+
stop
|
|
1281
|
+
</button>
|
|
1282
|
+
</template>
|
|
1283
|
+
</CopilotChat>
|
|
1284
|
+
`,
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
renderWithCopilotKit({ agent, children: CustomInputChat });
|
|
1288
|
+
|
|
1289
|
+
expect(screen.queryByTestId("custom-input-stop")).toBeNull();
|
|
1290
|
+
|
|
1291
|
+
await fireEvent.click(screen.getByTestId("custom-input-submit"));
|
|
1292
|
+
await waitFor(() => expect(agent.messages.length).toBeGreaterThan(0));
|
|
1293
|
+
|
|
1294
|
+
await agent.emit(runStartedEvent());
|
|
1295
|
+
await waitFor(() => {
|
|
1296
|
+
expect(screen.getByTestId("custom-input-stop")).toBeDefined();
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
await fireEvent.click(screen.getByTestId("custom-input-stop"));
|
|
1300
|
+
expect(onStop).toHaveBeenCalledTimes(1);
|
|
1301
|
+
|
|
1302
|
+
await agent.complete();
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
it("does not emit unavailable transcription commands from a custom chat-view slot", async () => {
|
|
1306
|
+
const agent = new MockStepwiseAgent();
|
|
1307
|
+
const onCancelTranscribe = vi.fn();
|
|
1308
|
+
const onFinishTranscribe = vi.fn();
|
|
1309
|
+
const CustomChatView = defineComponent({
|
|
1310
|
+
components: { CopilotChat },
|
|
1311
|
+
setup() {
|
|
1312
|
+
return { onCancelTranscribe, onFinishTranscribe };
|
|
1313
|
+
},
|
|
1314
|
+
template: `
|
|
1315
|
+
<CopilotChat
|
|
1316
|
+
:welcome-screen="false"
|
|
1317
|
+
@cancel-transcribe="onCancelTranscribe"
|
|
1318
|
+
@finish-transcribe="onFinishTranscribe"
|
|
1319
|
+
>
|
|
1320
|
+
<template #chat-view="{ onCancelTranscribe, onFinishTranscribe }">
|
|
1321
|
+
<button data-testid="custom-cancel-transcribe" @click="onCancelTranscribe">
|
|
1322
|
+
cancel
|
|
1323
|
+
</button>
|
|
1324
|
+
<button data-testid="custom-finish-transcribe" @click="onFinishTranscribe">
|
|
1325
|
+
finish
|
|
1326
|
+
</button>
|
|
1327
|
+
</template>
|
|
1328
|
+
</CopilotChat>
|
|
1329
|
+
`,
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
renderWithCopilotKit({ agent, children: CustomChatView });
|
|
1333
|
+
|
|
1334
|
+
await fireEvent.click(screen.getByTestId("custom-cancel-transcribe"));
|
|
1335
|
+
await fireEvent.click(screen.getByTestId("custom-finish-transcribe"));
|
|
1336
|
+
|
|
1337
|
+
expect(onCancelTranscribe).not.toHaveBeenCalled();
|
|
1338
|
+
expect(onFinishTranscribe).not.toHaveBeenCalled();
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
it("preserves stop behavior through a popup close and reopen", async () => {
|
|
1342
|
+
const agent = new MockStepwiseAgent();
|
|
1343
|
+
const onStop = vi.fn();
|
|
1344
|
+
const CustomPopup = defineComponent({
|
|
1345
|
+
components: { CopilotPopup },
|
|
1346
|
+
setup() {
|
|
1347
|
+
return { onStop };
|
|
1348
|
+
},
|
|
1349
|
+
template: `
|
|
1350
|
+
<CopilotPopup :welcome-screen="false" @stop="onStop">
|
|
1351
|
+
<template #toggle-button="{ toggle }">
|
|
1352
|
+
<button data-testid="popup-toggle" @click="toggle">Toggle</button>
|
|
1353
|
+
</template>
|
|
1354
|
+
<template #input="{ isRunning, onSubmitMessage, onStop }">
|
|
1355
|
+
<button data-testid="popup-submit" @click="onSubmitMessage('Hello')">
|
|
1356
|
+
submit
|
|
1357
|
+
</button>
|
|
1358
|
+
<button v-if="isRunning" data-testid="popup-stop" @click="onStop">
|
|
1359
|
+
stop
|
|
1360
|
+
</button>
|
|
1361
|
+
</template>
|
|
1362
|
+
</CopilotPopup>
|
|
1363
|
+
`,
|
|
1364
|
+
});
|
|
1365
|
+
|
|
1366
|
+
renderWithCopilotKit({ agent, children: CustomPopup });
|
|
1367
|
+
|
|
1368
|
+
await fireEvent.click(screen.getByTestId("popup-submit"));
|
|
1369
|
+
await waitFor(() => expect(agent.messages.length).toBeGreaterThan(0));
|
|
1370
|
+
await agent.emit(runStartedEvent());
|
|
1371
|
+
await waitFor(() => {
|
|
1372
|
+
expect(screen.getByTestId("popup-stop")).toBeDefined();
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
await fireEvent.click(screen.getByTestId("popup-toggle"));
|
|
1376
|
+
await waitFor(() => {
|
|
1377
|
+
expect(screen.queryByTestId("popup-stop")).toBeNull();
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
await fireEvent.click(screen.getByTestId("popup-toggle"));
|
|
1381
|
+
await waitFor(() => {
|
|
1382
|
+
expect(screen.getByTestId("popup-stop")).toBeDefined();
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
await fireEvent.click(screen.getByTestId("popup-stop"));
|
|
1386
|
+
expect(onStop).toHaveBeenCalledTimes(1);
|
|
1387
|
+
|
|
1388
|
+
await agent.complete();
|
|
1389
|
+
});
|
|
1390
|
+
});
|
|
1240
1391
|
});
|
|
@@ -562,6 +562,40 @@ describe("CopilotChatInput", () => {
|
|
|
562
562
|
expect(screen.queryByTestId("copilot-chat-input-send")).toBeNull();
|
|
563
563
|
});
|
|
564
564
|
|
|
565
|
+
it("shows finish control when only onFinishTranscribeWithAudio is provided", () => {
|
|
566
|
+
renderWithProvider({
|
|
567
|
+
props: {
|
|
568
|
+
mode: "transcribe",
|
|
569
|
+
},
|
|
570
|
+
listeners: {
|
|
571
|
+
onCancelTranscribe: () => {},
|
|
572
|
+
onFinishTranscribeWithAudio: () => {},
|
|
573
|
+
},
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
expect(
|
|
577
|
+
screen.getByTestId("copilot-chat-input-finish-transcribe"),
|
|
578
|
+
).toBeDefined();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it("hides finish control when neither finish handler is provided", () => {
|
|
582
|
+
renderWithProvider({
|
|
583
|
+
props: {
|
|
584
|
+
mode: "transcribe",
|
|
585
|
+
},
|
|
586
|
+
listeners: {
|
|
587
|
+
onCancelTranscribe: () => {},
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
expect(
|
|
592
|
+
screen.queryByTestId("copilot-chat-input-finish-transcribe"),
|
|
593
|
+
).toBeNull();
|
|
594
|
+
expect(
|
|
595
|
+
screen.getByTestId("copilot-chat-input-cancel-transcribe"),
|
|
596
|
+
).toBeDefined();
|
|
597
|
+
});
|
|
598
|
+
|
|
565
599
|
it("disables add menu button in transcribe mode", () => {
|
|
566
600
|
renderWithProvider({
|
|
567
601
|
props: {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { defineComponent, ref } from "vue";
|
|
2
|
+
import { render, screen } from "@testing-library/vue";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import type { Message, ToolCall } from "@ag-ui/core";
|
|
5
|
+
import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
|
|
6
|
+
import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
|
|
7
|
+
import CopilotChatMessageView from "../CopilotChatMessageView.vue";
|
|
8
|
+
|
|
9
|
+
const AGENT_ID = "default";
|
|
10
|
+
const THREAD_ID = "thread-test";
|
|
11
|
+
|
|
12
|
+
function toolCall(id: string, name = "approve"): ToolCall {
|
|
13
|
+
return { id, type: "function", function: { name, arguments: "{}" } };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function assistantMsg(id: string, content?: string, toolCalls?: ToolCall[]) {
|
|
17
|
+
return { id, role: "assistant" as const, content, toolCalls } as Message;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders the view over a reactive message list and returns a setter, so a test
|
|
22
|
+
* can swap the list on the SAME mounted instance. Row teardown-vs-reuse is only
|
|
23
|
+
* observable that way — comparing DOM node identity across an update.
|
|
24
|
+
*/
|
|
25
|
+
function renderWithSwappableMessages(initial: Message[]) {
|
|
26
|
+
const messages = ref<Message[]>(initial);
|
|
27
|
+
|
|
28
|
+
const Host = defineComponent({
|
|
29
|
+
components: {
|
|
30
|
+
CopilotKitProvider,
|
|
31
|
+
CopilotChatConfigurationProvider,
|
|
32
|
+
CopilotChatMessageView,
|
|
33
|
+
},
|
|
34
|
+
setup() {
|
|
35
|
+
return { messages, agentId: AGENT_ID, threadId: THREAD_ID };
|
|
36
|
+
},
|
|
37
|
+
template: `
|
|
38
|
+
<CopilotKitProvider runtime-url="/api/copilotkit">
|
|
39
|
+
<CopilotChatConfigurationProvider :agent-id="agentId" :thread-id="threadId">
|
|
40
|
+
<CopilotChatMessageView :messages="messages" />
|
|
41
|
+
</CopilotChatConfigurationProvider>
|
|
42
|
+
</CopilotKitProvider>
|
|
43
|
+
`,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
render(Host);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
async setMessages(next: Message[]) {
|
|
50
|
+
messages.value = next;
|
|
51
|
+
// Let Vue flush the patch (and the post-flush prune watcher).
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assistantRow() {
|
|
58
|
+
return screen.getByTestId("copilot-assistant-message");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("CopilotChatMessageView stable row keys", () => {
|
|
62
|
+
it("reuses the row when the message id changes but a tool call is stable", async () => {
|
|
63
|
+
const { setMessages } = renderWithSwappableMessages([
|
|
64
|
+
assistantMsg("lc_run--1", "Working...", [toolCall("call_A")]),
|
|
65
|
+
]);
|
|
66
|
+
const node = assistantRow();
|
|
67
|
+
|
|
68
|
+
await setMessages([assistantMsg("resp_1", "Done", [toolCall("call_A")])]);
|
|
69
|
+
|
|
70
|
+
expect(assistantRow()).toBe(node);
|
|
71
|
+
expect(assistantRow().textContent).toContain("Done");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("does not tear down the row when a tool call arrives mid-stream", async () => {
|
|
75
|
+
// Regression guard: keying by the tool-call id instead of recording an
|
|
76
|
+
// override tears the row down here, for every provider.
|
|
77
|
+
const { setMessages } = renderWithSwappableMessages([
|
|
78
|
+
assistantMsg("lc_run--1", "Let me check that..."),
|
|
79
|
+
]);
|
|
80
|
+
const node = assistantRow();
|
|
81
|
+
|
|
82
|
+
await setMessages([
|
|
83
|
+
assistantMsg("lc_run--1", "Let me check that...", [toolCall("call_A")]),
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
expect(assistantRow()).toBe(node);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("survives the whole sequence: text, then tool call, then id re-key", async () => {
|
|
90
|
+
const { setMessages } = renderWithSwappableMessages([
|
|
91
|
+
assistantMsg("lc_run--1", "Let me check..."),
|
|
92
|
+
]);
|
|
93
|
+
const node = assistantRow();
|
|
94
|
+
|
|
95
|
+
await setMessages([
|
|
96
|
+
assistantMsg("lc_run--1", "Let me check...", [toolCall("call_A")]),
|
|
97
|
+
]);
|
|
98
|
+
expect(assistantRow()).toBe(node);
|
|
99
|
+
|
|
100
|
+
await setMessages([assistantMsg("resp_1", "Done", [toolCall("call_A")])]);
|
|
101
|
+
expect(assistantRow()).toBe(node);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("tears down a text-only row on an id change (documented limitation)", async () => {
|
|
105
|
+
const { setMessages } = renderWithSwappableMessages([
|
|
106
|
+
assistantMsg("lc_run--1", "Working..."),
|
|
107
|
+
]);
|
|
108
|
+
const node = assistantRow();
|
|
109
|
+
|
|
110
|
+
await setMessages([assistantMsg("resp_1", "Done")]);
|
|
111
|
+
|
|
112
|
+
expect(assistantRow()).not.toBe(node);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders both rows when a tool-call id is shared", async () => {
|
|
116
|
+
renderWithSwappableMessages([
|
|
117
|
+
assistantMsg("a-1", "First", [toolCall("call_X")]),
|
|
118
|
+
assistantMsg("a-2", "Second", [toolCall("call_X")]),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
const rows = screen.getAllByTestId("copilot-assistant-message");
|
|
122
|
+
expect(rows).toHaveLength(2);
|
|
123
|
+
expect(rows[0]!.textContent).toContain("First");
|
|
124
|
+
expect(rows[1]!.textContent).toContain("Second");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("keeps the first row when the list grows around it", async () => {
|
|
128
|
+
const { setMessages } = renderWithSwappableMessages([
|
|
129
|
+
assistantMsg("lc_run--1", "First", [toolCall("call_A")]),
|
|
130
|
+
]);
|
|
131
|
+
const node = assistantRow();
|
|
132
|
+
|
|
133
|
+
await setMessages([
|
|
134
|
+
assistantMsg("resp_1", "First", [toolCall("call_A")]),
|
|
135
|
+
assistantMsg("lc_run--2", "Second", [toolCall("call_B", "b")]),
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
expect(screen.getAllByTestId("copilot-assistant-message")[0]).toBe(node);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -254,6 +254,52 @@ describe("CopilotChatUserMessage", () => {
|
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
|
|
257
|
+
it("disables custom branch commands when no switch callback is available", async () => {
|
|
258
|
+
const message: UserMessage = {
|
|
259
|
+
id: "user-no-branch-handler",
|
|
260
|
+
role: "user",
|
|
261
|
+
timestamp: new Date(),
|
|
262
|
+
content: "Custom branch controls without a handler",
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const wrapper = mount(CopilotChatUserMessage, {
|
|
266
|
+
props: {
|
|
267
|
+
message,
|
|
268
|
+
branchIndex: 0,
|
|
269
|
+
numberOfBranches: 2,
|
|
270
|
+
},
|
|
271
|
+
slots: {
|
|
272
|
+
"branch-navigation": ({
|
|
273
|
+
canGoPrev,
|
|
274
|
+
canGoNext,
|
|
275
|
+
goNext,
|
|
276
|
+
}: {
|
|
277
|
+
canGoPrev: boolean;
|
|
278
|
+
canGoNext: boolean;
|
|
279
|
+
goNext: () => void;
|
|
280
|
+
}) =>
|
|
281
|
+
h("div", [
|
|
282
|
+
h(
|
|
283
|
+
"span",
|
|
284
|
+
{ "data-testid": "branch-capabilities" },
|
|
285
|
+
`${canGoPrev}:${canGoNext}`,
|
|
286
|
+
),
|
|
287
|
+
h(
|
|
288
|
+
"button",
|
|
289
|
+
{ "data-testid": "branch-next", onClick: goNext },
|
|
290
|
+
"next",
|
|
291
|
+
),
|
|
292
|
+
]),
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(wrapper.get("[data-testid='branch-capabilities']").text()).toBe(
|
|
297
|
+
"false:false",
|
|
298
|
+
);
|
|
299
|
+
await wrapper.get("[data-testid='branch-next']").trigger("click");
|
|
300
|
+
expect(wrapper.emitted("switch-to-branch")).toBeUndefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
257
303
|
it("supports custom layout slot with all control callbacks", async () => {
|
|
258
304
|
const onEditMessage = vi.fn();
|
|
259
305
|
const onSwitchToBranch = vi.fn();
|
|
@@ -121,6 +121,124 @@ describe("CopilotChatView", () => {
|
|
|
121
121
|
]);
|
|
122
122
|
});
|
|
123
123
|
|
|
124
|
+
it("forwards a static stop listener through the input slot", async () => {
|
|
125
|
+
const onStop = vi.fn();
|
|
126
|
+
const StaticStopHost = defineComponent({
|
|
127
|
+
components: {
|
|
128
|
+
CopilotKitProvider,
|
|
129
|
+
CopilotChatConfigurationProvider,
|
|
130
|
+
CopilotChatView,
|
|
131
|
+
},
|
|
132
|
+
setup() {
|
|
133
|
+
return { chatMessages, onStop };
|
|
134
|
+
},
|
|
135
|
+
template: `
|
|
136
|
+
<CopilotKitProvider runtime-url="/api/copilotkit">
|
|
137
|
+
<CopilotChatConfigurationProvider
|
|
138
|
+
thread-id="thread-1"
|
|
139
|
+
agent-id="default"
|
|
140
|
+
>
|
|
141
|
+
<CopilotChatView :messages="chatMessages" @stop="onStop">
|
|
142
|
+
<template #input="{ onStop: slotOnStop }">
|
|
143
|
+
<button data-testid="direct-stop" @click="slotOnStop">
|
|
144
|
+
stop
|
|
145
|
+
</button>
|
|
146
|
+
</template>
|
|
147
|
+
</CopilotChatView>
|
|
148
|
+
</CopilotChatConfigurationProvider>
|
|
149
|
+
</CopilotKitProvider>
|
|
150
|
+
`,
|
|
151
|
+
});
|
|
152
|
+
const wrapper = mount(StaticStopHost);
|
|
153
|
+
|
|
154
|
+
await wrapper.get("[data-testid='direct-stop']").trigger("click");
|
|
155
|
+
|
|
156
|
+
expect(onStop).toHaveBeenCalledTimes(1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("exposes canStop / canAddFile / canTranscribe on the input slot", () => {
|
|
160
|
+
const CapabilityHost = defineComponent({
|
|
161
|
+
components: {
|
|
162
|
+
CopilotKitProvider,
|
|
163
|
+
CopilotChatConfigurationProvider,
|
|
164
|
+
CopilotChatView,
|
|
165
|
+
},
|
|
166
|
+
setup() {
|
|
167
|
+
return { chatMessages };
|
|
168
|
+
},
|
|
169
|
+
template: `
|
|
170
|
+
<CopilotKitProvider runtime-url="/api/copilotkit">
|
|
171
|
+
<CopilotChatConfigurationProvider
|
|
172
|
+
thread-id="thread-1"
|
|
173
|
+
agent-id="default"
|
|
174
|
+
>
|
|
175
|
+
<CopilotChatView
|
|
176
|
+
:messages="chatMessages"
|
|
177
|
+
:on-stop="() => {}"
|
|
178
|
+
:on-add-file="() => {}"
|
|
179
|
+
>
|
|
180
|
+
<template
|
|
181
|
+
#input="{ canStop, canAddFile, canTranscribe }"
|
|
182
|
+
>
|
|
183
|
+
<span data-testid="can-stop">{{ canStop }}</span>
|
|
184
|
+
<span data-testid="can-add-file">{{ canAddFile }}</span>
|
|
185
|
+
<span data-testid="can-transcribe">{{ canTranscribe }}</span>
|
|
186
|
+
</template>
|
|
187
|
+
</CopilotChatView>
|
|
188
|
+
</CopilotChatConfigurationProvider>
|
|
189
|
+
</CopilotKitProvider>
|
|
190
|
+
`,
|
|
191
|
+
});
|
|
192
|
+
const wrapper = mount(CapabilityHost);
|
|
193
|
+
|
|
194
|
+
expect(wrapper.get("[data-testid='can-stop']").text()).toBe("true");
|
|
195
|
+
expect(wrapper.get("[data-testid='can-add-file']").text()).toBe("true");
|
|
196
|
+
expect(wrapper.get("[data-testid='can-transcribe']").text()).toBe("false");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("exposes canStop / canAddFile / canTranscribe on the welcome-screen slot", () => {
|
|
200
|
+
const WelcomeCapabilityHost = defineComponent({
|
|
201
|
+
components: {
|
|
202
|
+
CopilotKitProvider,
|
|
203
|
+
CopilotChatConfigurationProvider,
|
|
204
|
+
CopilotChatView,
|
|
205
|
+
},
|
|
206
|
+
template: `
|
|
207
|
+
<CopilotKitProvider runtime-url="/api/copilotkit">
|
|
208
|
+
<CopilotChatConfigurationProvider
|
|
209
|
+
thread-id="thread-1"
|
|
210
|
+
agent-id="default"
|
|
211
|
+
>
|
|
212
|
+
<CopilotChatView
|
|
213
|
+
:messages="[]"
|
|
214
|
+
:on-stop="() => {}"
|
|
215
|
+
:on-start-transcribe="() => {}"
|
|
216
|
+
>
|
|
217
|
+
<template
|
|
218
|
+
#welcome-screen="{ canStop, canAddFile, canTranscribe }"
|
|
219
|
+
>
|
|
220
|
+
<span data-testid="welcome-can-stop">{{ canStop }}</span>
|
|
221
|
+
<span data-testid="welcome-can-add-file">{{ canAddFile }}</span>
|
|
222
|
+
<span data-testid="welcome-can-transcribe">{{
|
|
223
|
+
canTranscribe
|
|
224
|
+
}}</span>
|
|
225
|
+
</template>
|
|
226
|
+
</CopilotChatView>
|
|
227
|
+
</CopilotChatConfigurationProvider>
|
|
228
|
+
</CopilotKitProvider>
|
|
229
|
+
`,
|
|
230
|
+
});
|
|
231
|
+
const wrapper = mount(WelcomeCapabilityHost);
|
|
232
|
+
|
|
233
|
+
expect(wrapper.get("[data-testid='welcome-can-stop']").text()).toBe("true");
|
|
234
|
+
expect(wrapper.get("[data-testid='welcome-can-add-file']").text()).toBe(
|
|
235
|
+
"false",
|
|
236
|
+
);
|
|
237
|
+
expect(wrapper.get("[data-testid='welcome-can-transcribe']").text()).toBe(
|
|
238
|
+
"true",
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
124
242
|
it("renders suggestions and forwards selection callback and event", async () => {
|
|
125
243
|
const onSelectSuggestion = vi.fn();
|
|
126
244
|
const wrapper = mountChatView({
|
|
@@ -55,6 +55,43 @@ describe("CopilotPopupView", () => {
|
|
|
55
55
|
);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it("only exposes stop capability when an onStop callback is available", async () => {
|
|
59
|
+
const withoutStop = mountPopupView(
|
|
60
|
+
{},
|
|
61
|
+
{
|
|
62
|
+
"welcome-screen": ({ canStop }: { canStop: boolean }) =>
|
|
63
|
+
h("div", { "data-testid": "popup-can-stop" }, String(canStop)),
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(withoutStop.get("[data-testid='popup-can-stop']").text()).toBe(
|
|
68
|
+
"false",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const onStop = vi.fn();
|
|
72
|
+
const withStop = mountPopupView(
|
|
73
|
+
{ onStop },
|
|
74
|
+
{
|
|
75
|
+
"welcome-screen": ({
|
|
76
|
+
canStop,
|
|
77
|
+
onStop: stop,
|
|
78
|
+
}: {
|
|
79
|
+
canStop: boolean;
|
|
80
|
+
onStop: () => void;
|
|
81
|
+
}) =>
|
|
82
|
+
h(
|
|
83
|
+
"button",
|
|
84
|
+
{ "data-testid": "popup-stop", onClick: stop },
|
|
85
|
+
String(canStop),
|
|
86
|
+
),
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
expect(withStop.get("[data-testid='popup-stop']").text()).toBe("true");
|
|
91
|
+
await withStop.get("[data-testid='popup-stop']").trigger("click");
|
|
92
|
+
expect(onStop).toHaveBeenCalledTimes(1);
|
|
93
|
+
});
|
|
94
|
+
|
|
58
95
|
it("closes on escape and outside pointerdown when enabled", async () => {
|
|
59
96
|
const wrapper = mountPopupView({
|
|
60
97
|
clickOutsideToClose: true,
|
|
@@ -109,4 +146,46 @@ describe("CopilotPopupView", () => {
|
|
|
109
146
|
it("exposes the WelcomeScreen namespaced export", () => {
|
|
110
147
|
expect(NamespacedPopupView.WelcomeScreen).toBeDefined();
|
|
111
148
|
});
|
|
149
|
+
|
|
150
|
+
it("passes capability flags through the namespaced WelcomeScreen input slot", () => {
|
|
151
|
+
const wrapper = mount(NamespacedPopupView.WelcomeScreen, {
|
|
152
|
+
props: {
|
|
153
|
+
suggestions: [],
|
|
154
|
+
loadingIndexes: [],
|
|
155
|
+
modelValue: "",
|
|
156
|
+
isRunning: false,
|
|
157
|
+
inputMode: "input",
|
|
158
|
+
inputToolsMenu: [],
|
|
159
|
+
canStop: false,
|
|
160
|
+
canAddFile: false,
|
|
161
|
+
canTranscribe: false,
|
|
162
|
+
onUpdateModelValue: vi.fn(),
|
|
163
|
+
onSubmitMessage: vi.fn(),
|
|
164
|
+
onStop: vi.fn(),
|
|
165
|
+
onAddFile: vi.fn(),
|
|
166
|
+
onStartTranscribe: vi.fn(),
|
|
167
|
+
onSelectSuggestion: vi.fn(),
|
|
168
|
+
},
|
|
169
|
+
slots: {
|
|
170
|
+
input: ({
|
|
171
|
+
canStop,
|
|
172
|
+
canAddFile,
|
|
173
|
+
canTranscribe,
|
|
174
|
+
}: {
|
|
175
|
+
canStop: boolean;
|
|
176
|
+
canAddFile: boolean;
|
|
177
|
+
canTranscribe: boolean;
|
|
178
|
+
}) =>
|
|
179
|
+
h(
|
|
180
|
+
"div",
|
|
181
|
+
{ "data-testid": "popup-welcome-capabilities" },
|
|
182
|
+
`${canStop}:${canAddFile}:${canTranscribe}`,
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(
|
|
188
|
+
wrapper.get("[data-testid='popup-welcome-capabilities']").text(),
|
|
189
|
+
).toBe("false:false:false");
|
|
190
|
+
});
|
|
112
191
|
});
|