@copilotkit/vue 2.0.0-rc.20 → 2.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +13 -13
- package/dist/use-render-activity-message-BAhbpSDh.js +16200 -0
- package/dist/use-render-activity-message-BAhbpSDh.js.map +1 -0
- package/dist/use-render-activity-message-DJ6q1qHV.cjs +114 -0
- package/dist/use-render-activity-message-DJ6q1qHV.cjs.map +1 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts +14 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +10 -3
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
- package/dist/v2/components/CopilotKitInspector.vue.d.ts +5 -1
- package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -1
- package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -1
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +7 -21
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +0 -5
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +10 -15
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +14 -138
- package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -1
- package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
- package/dist/v2/components/a2ui/utils.d.ts +3 -15
- package/dist/v2/components/a2ui/utils.d.ts.map +1 -1
- package/dist/v2/components/a2ui.d.ts +6 -1
- package/dist/v2/components/a2ui.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +4 -8
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +1 -0
- package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/audioRecorder.d.ts +3 -4
- package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -1
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +3 -2
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
- package/dist/v2/components/chat/index.d.ts +6 -6
- package/dist/v2/components/chat/tool-call-display-context.d.ts +3 -1
- package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -1
- package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -1
- package/dist/v2/components/chat/types.d.ts +1 -1
- package/dist/v2/components/chat/types.d.ts.map +1 -1
- package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -1
- package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +1 -1
- package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -1
- package/dist/v2/hooks/use-agent-context.d.ts +2 -3
- package/dist/v2/hooks/use-agent-context.d.ts.map +1 -1
- package/dist/v2/hooks/use-agent.d.ts +4 -1
- package/dist/v2/hooks/use-agent.d.ts.map +1 -1
- package/dist/v2/hooks/use-attachments.d.ts +7 -0
- package/dist/v2/hooks/use-attachments.d.ts.map +1 -1
- package/dist/v2/hooks/use-capabilities.d.ts.map +1 -1
- package/dist/v2/hooks/use-component.d.ts +1 -0
- package/dist/v2/hooks/use-component.d.ts.map +1 -1
- package/dist/v2/hooks/use-configure-suggestions.d.ts +4 -9
- package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
- package/dist/v2/hooks/use-human-in-the-loop.d.ts +2 -1
- package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
- package/dist/v2/hooks/use-interrupt.d.ts +0 -1
- package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
- package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
- package/dist/v2/hooks/use-threads.d.ts +0 -8
- package/dist/v2/hooks/use-threads.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/lib/vue-core.d.ts +12 -11
- package/dist/v2/lib/vue-core.d.ts.map +1 -1
- package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +9 -124
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts +9 -10
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/chat-configuration-store-key.d.ts +8 -0
- package/dist/v2/providers/chat-configuration-store-key.d.ts.map +1 -0
- package/dist/v2/providers/markdown-renderer.d.ts +3 -1
- package/dist/v2/providers/markdown-renderer.d.ts.map +1 -1
- package/dist/v2/providers/use-inspector-thread-override.d.ts +5 -0
- package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/v2/__tests__/exports.test.ts +10 -0
- package/src/v2/__tests__/mocks/web-inspector.ts +6 -0
- package/src/v2/__tests__/utils/test-helpers.ts +34 -4
- package/src/v2/components/A2UIMessageRenderer.ts +98 -68
- package/src/v2/components/A2UISurfaceActivityRenderer.vue +68 -103
- package/src/v2/components/CopilotKitInspector.vue +104 -51
- package/src/v2/components/MCPAppsActivityRenderer.ts +75 -201
- package/src/v2/components/OpenGenerativeUIRenderer.ts +78 -80
- package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +171 -8
- package/src/v2/components/__tests__/CopilotKitInspector.test.ts +42 -0
- package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +9 -2
- package/src/v2/components/__tests__/OpenGenerativeUIToolRenderer.test.ts +38 -0
- package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +1 -6
- package/src/v2/components/a2ui/A2UICatalogContext.ts +27 -93
- package/src/v2/components/a2ui/A2UIRecoveryStates.ts +127 -327
- package/src/v2/components/a2ui/catalog.ts +2 -3
- package/src/v2/components/a2ui/utils.ts +13 -67
- package/src/v2/components/a2ui.ts +14 -21
- package/src/v2/components/chat/CopilotChat.vue +136 -324
- package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +22 -45
- package/src/v2/components/chat/CopilotChatAudioRecorder.vue +19 -246
- package/src/v2/components/chat/CopilotChatInput.vue +69 -407
- package/src/v2/components/chat/CopilotChatLightbox.vue +10 -31
- package/src/v2/components/chat/CopilotChatMessageView.vue +52 -112
- package/src/v2/components/chat/CopilotChatToolCallGroup.vue +14 -45
- package/src/v2/components/chat/CopilotChatToolCallItem.vue +6 -4
- package/src/v2/components/chat/CopilotChatToolCallsView.vue +16 -19
- package/src/v2/components/chat/CopilotChatView.vue +28 -23
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +21 -97
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +24 -25
- package/src/v2/components/chat/CopilotThreadsDrawer.vue +37 -102
- package/src/v2/components/chat/StreamingMarkdownDefault.vue +13 -26
- package/src/v2/components/chat/__tests__/CopilotChat.test.ts +12 -10
- package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +28 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +56 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +19 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +32 -8
- package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +2 -1
- package/src/v2/components/chat/__tests__/StreamingMarkdownDefault.test.ts +54 -0
- package/src/v2/components/chat/audioRecorder.ts +4 -7
- package/src/v2/components/chat/copilotChatToolCallItemRender.ts +22 -71
- package/src/v2/components/chat/tool-call-display-context.ts +6 -1
- package/src/v2/components/chat/tool-call-group-parts.ts +13 -33
- package/src/v2/components/chat/types.ts +1 -1
- package/src/v2/components/chat/use-lightbox.ts +15 -33
- package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +5 -3
- package/src/v2/hooks/__tests__/use-agent-stability.test.ts +25 -4
- package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +5 -1
- package/src/v2/hooks/__tests__/use-agent.test.ts +6 -3
- package/src/v2/hooks/__tests__/use-component.test.ts +21 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +30 -1
- package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +10 -10
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +4 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +6 -1
- package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +187 -12
- package/src/v2/hooks/__tests__/use-interrupt.test.ts +97 -0
- package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +2 -1
- package/src/v2/hooks/__tests__/use-threads.test.ts +48 -6
- package/src/v2/hooks/use-agent-context.ts +4 -10
- package/src/v2/hooks/use-agent.ts +86 -290
- package/src/v2/hooks/use-attachments.ts +42 -166
- package/src/v2/hooks/use-capabilities.ts +2 -7
- package/src/v2/hooks/use-component.ts +7 -4
- package/src/v2/hooks/use-configure-suggestions.ts +28 -218
- package/src/v2/hooks/use-default-render-tool.ts +34 -94
- package/src/v2/hooks/use-human-in-the-loop.ts +11 -80
- package/src/v2/hooks/use-interrupt.ts +29 -308
- package/src/v2/hooks/use-render-activity-message.ts +17 -20
- package/src/v2/hooks/use-render-custom-messages.ts +20 -63
- package/src/v2/hooks/use-suggestions.ts +9 -33
- package/src/v2/hooks/use-threads.ts +43 -273
- package/src/v2/lib/__tests__/vue-core-renderers.test.ts +42 -0
- package/src/v2/lib/vue-core.ts +67 -72
- package/src/v2/providers/CopilotChatConfigurationProvider.vue +48 -186
- package/src/v2/providers/CopilotKitProvider.types.ts +12 -127
- package/src/v2/providers/CopilotKitProvider.vue +188 -402
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +22 -2
- package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +19 -1
- package/src/v2/providers/__tests__/CopilotKitProvider.config.test.ts +124 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +4 -1
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +45 -2
- package/src/v2/providers/chat-configuration-store-key.ts +9 -0
- package/src/v2/providers/markdown-renderer.ts +7 -1
- package/src/v2/providers/use-inspector-thread-override.ts +22 -80
- package/vite.config.ts +5 -1
- package/vitest.config.ts +12 -7
- package/dist/use-render-activity-message-CsWYyd6r.cjs +0 -91
- package/dist/use-render-activity-message-CsWYyd6r.cjs.map +0 -1
- package/dist/use-render-activity-message-DntKKNdd.js +0 -17418
- package/dist/use-render-activity-message-DntKKNdd.js.map +0 -1
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts +0 -3
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +0 -1
- package/dist/v2/lib/is-mobile-viewport.d.ts +0 -2
- package/dist/v2/lib/is-mobile-viewport.d.ts.map +0 -1
- package/src/v2/components/__tests__/A2UICatalogContext.test.ts +0 -63
- package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +0 -175
- package/src/v2/components/chat/normalize-auto-scroll.ts +0 -3
- package/src/v2/lib/is-mobile-viewport.ts +0 -5
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
useAttrs,
|
|
9
9
|
watch,
|
|
10
10
|
} from "vue";
|
|
11
|
+
import {
|
|
12
|
+
ɵPOPUP_ANIMATION_MS,
|
|
13
|
+
ɵbindPopupDismiss,
|
|
14
|
+
ɵdimensionToCss,
|
|
15
|
+
} from "@copilotkit/core";
|
|
11
16
|
import { CopilotChatDefaultLabels } from "../../providers/types";
|
|
12
17
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
13
18
|
import CopilotChatToggleButton from "./CopilotChatToggleButton.vue";
|
|
@@ -32,7 +37,7 @@ defineOptions({ inheritAttrs: false });
|
|
|
32
37
|
|
|
33
38
|
const DEFAULT_POPUP_WIDTH = 420;
|
|
34
39
|
const DEFAULT_POPUP_HEIGHT = 560;
|
|
35
|
-
const POPUP_ANIMATION_MS =
|
|
40
|
+
const POPUP_ANIMATION_MS = ɵPOPUP_ANIMATION_MS;
|
|
36
41
|
|
|
37
42
|
const props = withDefaults(
|
|
38
43
|
defineProps<Omit<CopilotPopupViewProps, "defaultOpen">>(),
|
|
@@ -100,7 +105,6 @@ const containerRef = ref<HTMLElement | null>(null);
|
|
|
100
105
|
const isRendered = ref(config.value?.isModalOpen ?? false);
|
|
101
106
|
const isAnimatingOut = ref(false);
|
|
102
107
|
let animationTimer: ReturnType<typeof setTimeout> | undefined;
|
|
103
|
-
let focusTimer: ReturnType<typeof setTimeout> | undefined;
|
|
104
108
|
|
|
105
109
|
const isPopupOpen = computed(() => config.value?.isModalOpen ?? false);
|
|
106
110
|
const labels = computed(() => config.value?.labels ?? CopilotChatDefaultLabels);
|
|
@@ -113,8 +117,11 @@ const popupAnimationClass = computed(() =>
|
|
|
113
117
|
const popupStyle = computed(
|
|
114
118
|
() =>
|
|
115
119
|
({
|
|
116
|
-
"--copilot-popup-width":
|
|
117
|
-
|
|
120
|
+
"--copilot-popup-width": ɵdimensionToCss(
|
|
121
|
+
props.width,
|
|
122
|
+
DEFAULT_POPUP_WIDTH,
|
|
123
|
+
),
|
|
124
|
+
"--copilot-popup-height": ɵdimensionToCss(
|
|
118
125
|
props.height,
|
|
119
126
|
DEFAULT_POPUP_HEIGHT,
|
|
120
127
|
),
|
|
@@ -127,19 +134,6 @@ const popupStyle = computed(
|
|
|
127
134
|
}) as Record<string, string>,
|
|
128
135
|
);
|
|
129
136
|
|
|
130
|
-
function dimensionToCss(
|
|
131
|
-
value: number | string | undefined,
|
|
132
|
-
fallback: number,
|
|
133
|
-
): string {
|
|
134
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
135
|
-
return `${value}px`;
|
|
136
|
-
}
|
|
137
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
138
|
-
return value;
|
|
139
|
-
}
|
|
140
|
-
return `${fallback}px`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
137
|
function closePopup() {
|
|
144
138
|
config.value?.setModalOpen?.(false);
|
|
145
139
|
}
|
|
@@ -246,87 +240,20 @@ watch(
|
|
|
246
240
|
{ immediate: true },
|
|
247
241
|
);
|
|
248
242
|
|
|
249
|
-
|
|
250
|
-
isPopupOpen,
|
|
251
|
-
(open, _old, onCleanup) => {
|
|
252
|
-
if (!open || typeof window === "undefined") {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
257
|
-
if (event.key === "Escape") {
|
|
258
|
-
event.preventDefault();
|
|
259
|
-
closePopup();
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
264
|
-
onCleanup(() => {
|
|
265
|
-
window.removeEventListener("keydown", handleKeyDown);
|
|
266
|
-
});
|
|
267
|
-
},
|
|
268
|
-
{ immediate: true },
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
watch(
|
|
272
|
-
isPopupOpen,
|
|
273
|
-
(open, _old, onCleanup) => {
|
|
274
|
-
if (!open) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
focusTimer = setTimeout(() => {
|
|
279
|
-
if (typeof document === "undefined") {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const container = containerRef.value;
|
|
283
|
-
if (container && !container.contains(document.activeElement)) {
|
|
284
|
-
container.focus({ preventScroll: true });
|
|
285
|
-
}
|
|
286
|
-
}, POPUP_ANIMATION_MS);
|
|
287
|
-
|
|
288
|
-
onCleanup(() => {
|
|
289
|
-
if (focusTimer) {
|
|
290
|
-
clearTimeout(focusTimer);
|
|
291
|
-
focusTimer = undefined;
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
},
|
|
295
|
-
{ immediate: true },
|
|
296
|
-
);
|
|
297
|
-
|
|
243
|
+
// Escape, a click outside (when enabled), and focus once the popup opens.
|
|
298
244
|
watch(
|
|
299
245
|
[isPopupOpen, () => props.clickOutsideToClose],
|
|
300
|
-
([open,
|
|
301
|
-
if (!open
|
|
246
|
+
([open, clickOutsideToClose], _old, onCleanup) => {
|
|
247
|
+
if (!open) {
|
|
302
248
|
return;
|
|
303
249
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const container = containerRef.value;
|
|
312
|
-
if (container?.contains(target)) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const toggleButton = document.querySelector(
|
|
317
|
-
"[data-slot='chat-toggle-button']",
|
|
318
|
-
);
|
|
319
|
-
if (toggleButton?.contains(target)) {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
closePopup();
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
document.addEventListener("pointerdown", handlePointerDown);
|
|
327
|
-
onCleanup(() => {
|
|
328
|
-
document.removeEventListener("pointerdown", handlePointerDown);
|
|
329
|
-
});
|
|
250
|
+
onCleanup(
|
|
251
|
+
ɵbindPopupDismiss({
|
|
252
|
+
getContainer: () => containerRef.value,
|
|
253
|
+
onDismiss: closePopup,
|
|
254
|
+
clickOutsideToClose: clickOutsideToClose === true,
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
330
257
|
},
|
|
331
258
|
{ immediate: true },
|
|
332
259
|
);
|
|
@@ -339,9 +266,6 @@ onBeforeUnmount(() => {
|
|
|
339
266
|
if (animationTimer) {
|
|
340
267
|
clearTimeout(animationTimer);
|
|
341
268
|
}
|
|
342
|
-
if (focusTimer) {
|
|
343
|
-
clearTimeout(focusTimer);
|
|
344
|
-
}
|
|
345
269
|
});
|
|
346
270
|
</script>
|
|
347
271
|
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
useAttrs,
|
|
9
9
|
watch,
|
|
10
10
|
} from "vue";
|
|
11
|
+
import { ɵcreateDockedSidebar, ɵdimensionToCss } from "@copilotkit/core";
|
|
12
|
+
import type { ɵDockedSidebar } from "@copilotkit/core";
|
|
11
13
|
import { CopilotChatDefaultLabels } from "../../providers/types";
|
|
12
14
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
13
15
|
import CopilotChatToggleButton from "./CopilotChatToggleButton.vue";
|
|
@@ -122,32 +124,23 @@ const asideAttrs = computed(() => {
|
|
|
122
124
|
const asideStyle = computed(
|
|
123
125
|
() =>
|
|
124
126
|
({
|
|
125
|
-
"--sidebar-width":
|
|
127
|
+
"--sidebar-width": widthCss.value,
|
|
126
128
|
paddingTop: "env(safe-area-inset-top)",
|
|
127
129
|
paddingBottom: "env(safe-area-inset-bottom)",
|
|
128
130
|
}) as Record<string, string>,
|
|
129
131
|
);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
props.position === "left" ? "margin-inline-start" : "margin-inline-end";
|
|
134
|
-
return `
|
|
135
|
-
@media (min-width: 768px) {
|
|
136
|
-
body {
|
|
137
|
-
${margin}: ${widthToMargin(resolvedSidebarWidth.value)};
|
|
138
|
-
transition: ${margin} ${SIDEBAR_TRANSITION_MS}ms ease;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
`;
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
function widthToCss(width: number | string): string {
|
|
145
|
-
return typeof width === "number" ? `${width}px` : width;
|
|
146
|
-
}
|
|
132
|
+
const widthCss = computed(() =>
|
|
133
|
+
ɵdimensionToCss(resolvedSidebarWidth.value, DEFAULT_SIDEBAR_WIDTH),
|
|
134
|
+
);
|
|
147
135
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
136
|
+
// The page makes room on the docked edge (desktop only).
|
|
137
|
+
let dock: ɵDockedSidebar | undefined;
|
|
138
|
+
watch(
|
|
139
|
+
[isSidebarOpen, () => props.position, widthCss],
|
|
140
|
+
([open, position, width]) => {
|
|
141
|
+
dock?.set({ open, position, width });
|
|
142
|
+
},
|
|
143
|
+
);
|
|
151
144
|
|
|
152
145
|
function updateMeasuredWidth() {
|
|
153
146
|
if (props.width !== undefined || !sidebarRef.value) {
|
|
@@ -245,6 +238,14 @@ watch(
|
|
|
245
238
|
);
|
|
246
239
|
|
|
247
240
|
onMounted(async () => {
|
|
241
|
+
dock = ɵcreateDockedSidebar(document, {
|
|
242
|
+
transitionMs: SIDEBAR_TRANSITION_MS,
|
|
243
|
+
});
|
|
244
|
+
dock.set({
|
|
245
|
+
open: isSidebarOpen.value,
|
|
246
|
+
position: props.position,
|
|
247
|
+
width: widthCss.value,
|
|
248
|
+
});
|
|
248
249
|
await nextTick();
|
|
249
250
|
updateMeasuredWidth();
|
|
250
251
|
|
|
@@ -263,16 +264,14 @@ onMounted(async () => {
|
|
|
263
264
|
});
|
|
264
265
|
|
|
265
266
|
onBeforeUnmount(() => {
|
|
267
|
+
dock?.dispose();
|
|
268
|
+
dock = undefined;
|
|
266
269
|
resizeObserver?.disconnect();
|
|
267
270
|
resizeObserver = null;
|
|
268
271
|
});
|
|
269
272
|
</script>
|
|
270
273
|
|
|
271
274
|
<template>
|
|
272
|
-
<component :is="'style'" v-if="isSidebarOpen">
|
|
273
|
-
{{ bodyMarginStyle }}
|
|
274
|
-
</component>
|
|
275
|
-
|
|
276
275
|
<slot
|
|
277
276
|
name="toggle-button"
|
|
278
277
|
:is-open="isSidebarOpen"
|
|
@@ -8,7 +8,6 @@ import { computed, onMounted, onScopeDispose, ref, watchEffect } from "vue";
|
|
|
8
8
|
// below is kept at module scope.
|
|
9
9
|
import type {
|
|
10
10
|
CopilotKitThreadsDrawer as CopilotKitThreadsDrawerElement,
|
|
11
|
-
DrawerThread,
|
|
12
11
|
ThreadSelectedDetail,
|
|
13
12
|
ArchiveDetail,
|
|
14
13
|
UnarchiveDetail,
|
|
@@ -23,6 +22,12 @@ import type {
|
|
|
23
22
|
// in this worktree predate it.
|
|
24
23
|
type CollapseChangeDetail = { collapsed: boolean };
|
|
25
24
|
import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
|
|
25
|
+
import {
|
|
26
|
+
ɵcreateThreadsDrawerActions,
|
|
27
|
+
ɵgetThreadsDrawerElementProps,
|
|
28
|
+
ɵresolveThreadsLicense,
|
|
29
|
+
ɵtoDrawerThread,
|
|
30
|
+
} from "@copilotkit/core";
|
|
26
31
|
import { useThreads } from "../../hooks/use-threads";
|
|
27
32
|
import type { Thread } from "../../hooks/use-threads";
|
|
28
33
|
import { useCopilotChatConfiguration } from "../../providers/useCopilotChatConfiguration";
|
|
@@ -71,15 +76,11 @@ const emit = defineEmits<{
|
|
|
71
76
|
const config = useCopilotChatConfiguration();
|
|
72
77
|
const license = useLicenseContext();
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const present = status === "valid" || status === "expiring";
|
|
80
|
-
return present && license.value.checkFeature("threads");
|
|
81
|
-
});
|
|
82
|
-
const licensePending = computed(() => license.value.status === null);
|
|
79
|
+
// `unknown` (an unrecovered entitlement lookup) counts as licensed so the
|
|
80
|
+
// threads endpoint decides; `null` (not reported yet) is pending.
|
|
81
|
+
const threadsLicense = computed(() => ɵresolveThreadsLicense(license.value));
|
|
82
|
+
const licensed = computed(() => threadsLicense.value.licensed);
|
|
83
|
+
const licensePending = computed(() => threadsLicense.value.licensePending);
|
|
83
84
|
|
|
84
85
|
const resolvedAgentId = computed(
|
|
85
86
|
() => props.agentId ?? config.value?.agentId ?? DEFAULT_AGENT_ID,
|
|
@@ -107,18 +108,8 @@ const threadsApi = useThreads({
|
|
|
107
108
|
limit: () => props.limit,
|
|
108
109
|
});
|
|
109
110
|
|
|
110
|
-
function toDrawerThread(t: Thread): DrawerThread {
|
|
111
|
-
return {
|
|
112
|
-
id: t.id,
|
|
113
|
-
name: t.name,
|
|
114
|
-
archived: t.archived,
|
|
115
|
-
createdAt: t.createdAt,
|
|
116
|
-
updatedAt: t.updatedAt,
|
|
117
|
-
...(t.lastRunAt !== undefined ? { lastRunAt: t.lastRunAt } : {}),
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
111
|
const drawerThreads = computed(() =>
|
|
121
|
-
threadsApi.threads.value.map(
|
|
112
|
+
threadsApi.threads.value.map(ɵtoDrawerThread),
|
|
122
113
|
);
|
|
123
114
|
|
|
124
115
|
// Client-only element registration (SSR-safe). The element module is imported
|
|
@@ -144,10 +135,15 @@ watchEffect(
|
|
|
144
135
|
const el = elRef.value;
|
|
145
136
|
if (!el) return;
|
|
146
137
|
el.threads = drawerThreads.value;
|
|
147
|
-
|
|
138
|
+
const elementProps = ɵgetThreadsDrawerElementProps({
|
|
139
|
+
loading: threadsApi.isLoading.value,
|
|
140
|
+
licensed: licensed.value,
|
|
141
|
+
licensePending: licensePending.value,
|
|
142
|
+
});
|
|
143
|
+
el.loading = elementProps.loading;
|
|
148
144
|
el.error = threadsApi.listError.value?.message ?? null;
|
|
149
145
|
el.activeThreadId = activeThreadId.value;
|
|
150
|
-
el.licensed = licensed
|
|
146
|
+
el.licensed = elementProps.licensed;
|
|
151
147
|
el.hasMore = threadsApi.hasMoreThreads.value;
|
|
152
148
|
el.fetchingMore = threadsApi.isFetchingMoreThreads.value;
|
|
153
149
|
// Dedicated fetch-more error channel: drives the element's inline
|
|
@@ -177,101 +173,40 @@ const unregisterDrawer = config.value?.registerDrawer?.();
|
|
|
177
173
|
if (unregisterDrawer) onScopeDispose(unregisterDrawer);
|
|
178
174
|
|
|
179
175
|
// --- Outbound event handlers ------------------------------------------------
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* (`data-testid="copilot-chat-view"`), then returns the chat input within
|
|
191
|
-
* that subtree. This avoids focusing the wrong composer on a page hosting
|
|
192
|
-
* more than one chat (multi-chat dashboards), where a document-global lookup
|
|
193
|
-
* would grab whichever input appears first in DOM order rather than the one
|
|
194
|
-
* this drawer drives.
|
|
195
|
-
*
|
|
196
|
-
* Falls back to a document-global lookup when no scoping ancestor is found
|
|
197
|
-
* (e.g. the drawer and chat share no common container, or headless usage).
|
|
198
|
-
*/
|
|
199
|
-
function findChatInput(origin: Element | null): HTMLElement | null {
|
|
200
|
-
if (typeof document === "undefined") return null;
|
|
201
|
-
const container = origin?.closest?.(
|
|
202
|
-
`[data-testid="${CHAT_CONTAINER_TESTID}"]`,
|
|
203
|
-
);
|
|
204
|
-
if (container) {
|
|
205
|
-
const scoped = container.querySelector<HTMLElement>(
|
|
206
|
-
`[data-testid="${CHAT_INPUT_TESTID}"]`,
|
|
207
|
-
);
|
|
208
|
-
if (scoped) return scoped;
|
|
209
|
-
}
|
|
210
|
-
return document.querySelector<HTMLElement>(
|
|
211
|
-
`[data-testid="${CHAT_INPUT_TESTID}"]`,
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function focusChatInput() {
|
|
216
|
-
findChatInput(elRef.value)?.focus();
|
|
217
|
-
}
|
|
176
|
+
// Core owns the drawer actions; they read the latest values at call time.
|
|
177
|
+
const actions = ɵcreateThreadsDrawerActions({
|
|
178
|
+
getThreads: () => threadsApi,
|
|
179
|
+
getConfig: () => config.value,
|
|
180
|
+
getCallbacks: () => ({
|
|
181
|
+
onThreadSelect: props.onThreadSelect,
|
|
182
|
+
onNewThread: props.onNewThread,
|
|
183
|
+
}),
|
|
184
|
+
getElement: () => elRef.value,
|
|
185
|
+
});
|
|
218
186
|
|
|
219
187
|
function onThreadSelected(event: Event) {
|
|
220
188
|
const { threadId } = (event as CustomEvent<ThreadSelectedDetail>).detail;
|
|
221
|
-
|
|
222
|
-
else config.value?.setActiveThreadId?.(threadId, { explicit: true });
|
|
223
|
-
focusChatInput();
|
|
224
|
-
}
|
|
225
|
-
function handleNewThread() {
|
|
226
|
-
threadsApi.startNewThread();
|
|
227
|
-
if (props.onNewThread) props.onNewThread();
|
|
228
|
-
else config.value?.startNewThread?.();
|
|
189
|
+
actions.selectThread(threadId);
|
|
229
190
|
}
|
|
191
|
+
const handleNewThread = actions.newThread;
|
|
230
192
|
function onArchive(event: Event) {
|
|
231
|
-
|
|
232
|
-
void threadsApi
|
|
233
|
-
.archiveThread(threadId)
|
|
234
|
-
.catch((e) =>
|
|
235
|
-
console.error("CopilotThreadsDrawer: archiveThread failed", e),
|
|
236
|
-
);
|
|
193
|
+
actions.archive((event as CustomEvent<ArchiveDetail>).detail.threadId);
|
|
237
194
|
}
|
|
238
195
|
function onUnarchive(event: Event) {
|
|
239
|
-
|
|
240
|
-
void threadsApi
|
|
241
|
-
.unarchiveThread(threadId)
|
|
242
|
-
.catch((e) =>
|
|
243
|
-
console.error("CopilotThreadsDrawer: unarchiveThread failed", e),
|
|
244
|
-
);
|
|
196
|
+
actions.unarchive((event as CustomEvent<UnarchiveDetail>).detail.threadId);
|
|
245
197
|
}
|
|
246
198
|
function onDelete(event: Event) {
|
|
247
|
-
|
|
248
|
-
const wasActive = threadId === activeThreadId.value;
|
|
249
|
-
void threadsApi
|
|
250
|
-
.deleteThread(threadId)
|
|
251
|
-
.then(() => {
|
|
252
|
-
if (wasActive) {
|
|
253
|
-
handleNewThread();
|
|
254
|
-
}
|
|
255
|
-
})
|
|
256
|
-
.catch((e) =>
|
|
257
|
-
console.error("CopilotThreadsDrawer: deleteThread failed", e),
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
function onFilterChange() {
|
|
261
|
-
threadsApi.refetchThreads();
|
|
199
|
+
actions.remove((event as CustomEvent<DeleteDetail>).detail.threadId);
|
|
262
200
|
}
|
|
201
|
+
const onFilterChange = actions.refetch;
|
|
263
202
|
function onCollapseChange(event: Event) {
|
|
264
203
|
const { collapsed } = (event as CustomEvent<CollapseChangeDetail>).detail;
|
|
265
204
|
emit("collapse-change", collapsed);
|
|
266
205
|
}
|
|
267
206
|
function onRetry(event: Event) {
|
|
268
|
-
|
|
269
|
-
if (scope === "fetch-more") threadsApi.fetchMoreThreads();
|
|
270
|
-
else threadsApi.refetchThreads();
|
|
271
|
-
}
|
|
272
|
-
function onLoadMore() {
|
|
273
|
-
threadsApi.fetchMoreThreads();
|
|
207
|
+
actions.retry((event as CustomEvent<RetryDetail>).detail.scope);
|
|
274
208
|
}
|
|
209
|
+
const onLoadMore = actions.loadMore;
|
|
275
210
|
function onOpenChange(event: Event) {
|
|
276
211
|
const { open } = (event as CustomEvent<OpenChangeDetail>).detail;
|
|
277
212
|
setDrawerOpen(open);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, watchEffect } from "vue";
|
|
3
3
|
import { StreamingMarkdownRenderer } from "@copilotkit/markdown/vue";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
CHAT_MARKDOWN_NODE_CLASS_NAMES,
|
|
6
|
+
warnUnsupportedRichSyntaxOnce,
|
|
7
|
+
} from "@copilotkit/markdown";
|
|
6
8
|
import type { StreamingMarkdownParserOptions } from "@copilotkit/markdown";
|
|
7
9
|
import type { DefaultMarkdownRendererProps } from "../../providers/markdown-renderer";
|
|
8
10
|
|
|
@@ -10,34 +12,15 @@ const props = withDefaults(
|
|
|
10
12
|
defineProps<
|
|
11
13
|
{ content: string; isStreaming?: boolean } & DefaultMarkdownRendererProps
|
|
12
14
|
>(),
|
|
13
|
-
{ isStreaming: false },
|
|
15
|
+
{ isStreaming: false, caret: undefined },
|
|
14
16
|
);
|
|
15
17
|
|
|
18
|
+
// vue-tsc reads the renderer's `options` prop as the full options type; the
|
|
19
|
+
// renderer itself accepts a partial object.
|
|
16
20
|
const resolvedOptions = computed(
|
|
17
21
|
() => props.options as StreamingMarkdownParserOptions | undefined,
|
|
18
22
|
);
|
|
19
23
|
|
|
20
|
-
const defaultCodeBlockRenderer: VueStreamingMarkdownNodeRenderer = (
|
|
21
|
-
node,
|
|
22
|
-
defaultVNode,
|
|
23
|
-
) => {
|
|
24
|
-
if (node.type !== "code-block") return defaultVNode;
|
|
25
|
-
return h(
|
|
26
|
-
"pre",
|
|
27
|
-
{
|
|
28
|
-
"data-streaming-markdown-node": "code-block",
|
|
29
|
-
"data-node-open": String(!node.closed),
|
|
30
|
-
class: "cpk-markdown-code-block",
|
|
31
|
-
},
|
|
32
|
-
[h("code", { "data-code-info": node.info ?? undefined }, node.text)],
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const mergedNodeRenderers = computed(() => ({
|
|
37
|
-
codeBlock: defaultCodeBlockRenderer,
|
|
38
|
-
...props.nodeRenderers,
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
24
|
// Dev-only: nudge upgraders from the bundled Streamdown default when their
|
|
42
25
|
// content needs math/syntax highlighting the built-in renderer doesn't do.
|
|
43
26
|
watchEffect(() => warnUnsupportedRichSyntaxOnce(props.content));
|
|
@@ -45,11 +28,15 @@ watchEffect(() => warnUnsupportedRichSyntaxOnce(props.content));
|
|
|
45
28
|
|
|
46
29
|
<template>
|
|
47
30
|
<StreamingMarkdownRenderer
|
|
31
|
+
v-if="props.content"
|
|
48
32
|
:content="props.content"
|
|
49
33
|
:is-complete="!props.isStreaming"
|
|
50
|
-
:node-renderers="
|
|
34
|
+
:node-renderers="props.nodeRenderers"
|
|
35
|
+
:node-class-names="CHAT_MARKDOWN_NODE_CLASS_NAMES"
|
|
51
36
|
:caret="props.caret ?? props.isStreaming"
|
|
52
37
|
:options="resolvedOptions"
|
|
53
38
|
:class="props.class"
|
|
39
|
+
:on-link-click="props.onLinkClick"
|
|
40
|
+
:on-citation-click="props.onCitationClick"
|
|
54
41
|
/>
|
|
55
42
|
</template>
|
|
@@ -60,6 +60,11 @@ describe("CopilotChat", () => {
|
|
|
60
60
|
}) => void | Promise<void>,
|
|
61
61
|
errorMessage: string,
|
|
62
62
|
) => Promise<void>;
|
|
63
|
+
emitError: (params: {
|
|
64
|
+
error: Error;
|
|
65
|
+
code: CopilotKitCoreErrorCode;
|
|
66
|
+
context?: Record<string, unknown>;
|
|
67
|
+
}) => Promise<void>;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
beforeEach(() => {
|
|
@@ -405,16 +410,13 @@ describe("CopilotChat", () => {
|
|
|
405
410
|
},
|
|
406
411
|
});
|
|
407
412
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}),
|
|
416
|
-
"test chat onError",
|
|
417
|
-
);
|
|
413
|
+
// Through core's error path: the provider's onError is core's handler,
|
|
414
|
+
// the chat's is a subscriber.
|
|
415
|
+
await (core as unknown as CopilotKitCoreTestAccess).emitError({
|
|
416
|
+
error: new Error("matching agent"),
|
|
417
|
+
code: CopilotKitCoreErrorCode.AGENT_RUN_FAILED,
|
|
418
|
+
context: { agentId: "default" },
|
|
419
|
+
});
|
|
418
420
|
|
|
419
421
|
await nextTick();
|
|
420
422
|
|
|
@@ -186,6 +186,34 @@ describe("CopilotChatAttachmentQueue", () => {
|
|
|
186
186
|
).toContain("hello attachment preview");
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
it("decodes a text document as UTF-8", async () => {
|
|
190
|
+
const { getByTestId } = render(CopilotChatAttachmentQueue, {
|
|
191
|
+
props: {
|
|
192
|
+
attachments: [
|
|
193
|
+
createAttachment({
|
|
194
|
+
id: "doc-utf8",
|
|
195
|
+
type: "document",
|
|
196
|
+
status: "ready",
|
|
197
|
+
// "héllo ✓" in UTF-8, base64-encoded.
|
|
198
|
+
source: {
|
|
199
|
+
type: "data",
|
|
200
|
+
value: "aMOpbGxvIOKckw==",
|
|
201
|
+
mimeType: "text/plain",
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await fireEvent.click(
|
|
209
|
+
getByTestId("copilot-chat-attachment-document-button"),
|
|
210
|
+
);
|
|
211
|
+
expect(
|
|
212
|
+
screen.getByTestId("copilot-chat-attachment-lightbox-document-text")
|
|
213
|
+
.textContent,
|
|
214
|
+
).toContain("héllo ✓");
|
|
215
|
+
});
|
|
216
|
+
|
|
189
217
|
it("non-previewable document remains inert", async () => {
|
|
190
218
|
const { getByTestId, queryByTestId } = render(CopilotChatAttachmentQueue, {
|
|
191
219
|
props: {
|
|
@@ -128,9 +128,14 @@ beforeEach(() => {
|
|
|
128
128
|
mockOnSubmitMessage.mockClear();
|
|
129
129
|
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation(
|
|
130
130
|
() =>
|
|
131
|
+
// A partial 2D context: the input measures text, and the recorder's
|
|
132
|
+
// waveform scales, clears, and fills.
|
|
131
133
|
({
|
|
132
134
|
measureText: (text: string) => ({ width: text.length * 8 }),
|
|
133
135
|
font: "",
|
|
136
|
+
scale: () => {},
|
|
137
|
+
clearRect: () => {},
|
|
138
|
+
fillRect: () => {},
|
|
134
139
|
}) as unknown as CanvasRenderingContext2D,
|
|
135
140
|
);
|
|
136
141
|
});
|
|
@@ -397,6 +402,32 @@ describe("CopilotChatInput", () => {
|
|
|
397
402
|
});
|
|
398
403
|
});
|
|
399
404
|
|
|
405
|
+
it("does not empty the textarea to re-measure it on each keystroke on a phone", async () => {
|
|
406
|
+
vi.stubGlobal("matchMedia", (query: string) => ({
|
|
407
|
+
matches: query === "(max-width: 767px)",
|
|
408
|
+
}));
|
|
409
|
+
const valueSetter = vi.spyOn(HTMLTextAreaElement.prototype, "value", "set");
|
|
410
|
+
try {
|
|
411
|
+
renderWithProvider({
|
|
412
|
+
listeners: { onSubmitMessage: mockOnSubmitMessage },
|
|
413
|
+
});
|
|
414
|
+
const textarea = screen.getByRole("textbox");
|
|
415
|
+
await fireEvent.input(textarea, { target: { value: "ABCD" } });
|
|
416
|
+
const grid = textarea.closest("[data-layout]");
|
|
417
|
+
await waitFor(() => {
|
|
418
|
+
expect(grid?.getAttribute("data-layout")).toBe("expanded");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
valueSetter.mockClear();
|
|
422
|
+
await fireEvent.input(textarea, { target: { value: "AEBCD" } });
|
|
423
|
+
|
|
424
|
+
expect(valueSetter).not.toHaveBeenCalledWith("");
|
|
425
|
+
} finally {
|
|
426
|
+
valueSetter.mockRestore();
|
|
427
|
+
vi.unstubAllGlobals();
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
400
431
|
it("executes slash commands via keyboard selection", async () => {
|
|
401
432
|
const handleFirst = vi.fn();
|
|
402
433
|
const handleSecond = vi.fn();
|
|
@@ -477,6 +508,31 @@ describe("CopilotChatInput", () => {
|
|
|
477
508
|
});
|
|
478
509
|
});
|
|
479
510
|
|
|
511
|
+
it("opens the slash menu for typed text once commands arrive", async () => {
|
|
512
|
+
const toolsMenu = ref<{ label: string; action: () => void }[]>([]);
|
|
513
|
+
const Host = defineComponent({
|
|
514
|
+
setup: () => () =>
|
|
515
|
+
h(
|
|
516
|
+
CopilotChatConfigurationProvider,
|
|
517
|
+
{ threadId: TEST_THREAD_ID },
|
|
518
|
+
{
|
|
519
|
+
default: () => h(CopilotChatInput, { toolsMenu: toolsMenu.value }),
|
|
520
|
+
},
|
|
521
|
+
),
|
|
522
|
+
});
|
|
523
|
+
render(Host);
|
|
524
|
+
|
|
525
|
+
await fireEvent.input(screen.getByRole("textbox"), {
|
|
526
|
+
target: { value: "/sum" },
|
|
527
|
+
});
|
|
528
|
+
expect(screen.queryByTestId("copilot-slash-menu")).toBeNull();
|
|
529
|
+
|
|
530
|
+
toolsMenu.value = [{ label: "Summarize", action: vi.fn() }];
|
|
531
|
+
|
|
532
|
+
const menu = await screen.findByTestId("copilot-slash-menu");
|
|
533
|
+
expect(within(menu).getByRole("option").textContent).toContain("Summarize");
|
|
534
|
+
});
|
|
535
|
+
|
|
480
536
|
it("limits slash menu height when commands exceed five items", async () => {
|
|
481
537
|
const tools = Array.from({ length: 6 }, (_, index) => ({
|
|
482
538
|
label: `Command ${index + 1}`,
|