@copilotkit/vue 2.0.0-rc.1 → 2.0.0-rc.10
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 +13 -12
- package/README.md +1 -2
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts +3 -1
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts.map +1 -1
- package/dist/hooks/use-copilot-action.d.ts.map +1 -1
- package/dist/hooks/use-frontend-tool.d.ts +1 -1
- package/dist/hooks/use-frontend-tool.d.ts.map +1 -1
- package/dist/hooks/zod-parameters.d.ts +14 -0
- package/dist/hooks/zod-parameters.d.ts.map +1 -0
- package/dist/index.cjs +1 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +384 -14275
- package/dist/index.mjs.map +1 -1
- package/dist/use-render-activity-message-CTR_ziWV.js +18097 -0
- package/dist/use-render-activity-message-CTR_ziWV.js.map +1 -0
- package/dist/use-render-activity-message-Dky9_GRG.cjs +92 -0
- package/dist/use-render-activity-message-Dky9_GRG.cjs.map +1 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts +7 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +13 -45
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +11 -2
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +1 -1
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
- package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +144 -0
- package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -0
- package/dist/v2/components/a2ui.d.ts +3 -7
- package/dist/v2/components/a2ui.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChat.vue.d.ts +1 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +3 -1
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAssistantToolbarButton.vue.d.ts +16 -0
- package/dist/v2/components/chat/CopilotChatAssistantToolbarButton.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts +22 -0
- package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +21 -232
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts +12 -0
- package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts +22 -0
- package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +20 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts +32 -0
- package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +11 -1
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +1 -0
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts +5 -0
- package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts +1 -0
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +1 -0
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +1 -0
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +2 -0
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +2 -0
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +2 -0
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +0 -1
- package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
- package/dist/v2/components/chat/index.d.ts +17 -1
- package/dist/v2/components/chat/index.d.ts.map +1 -1
- package/dist/v2/components/chat/inspector-shortcuts.d.ts +12 -0
- package/dist/v2/components/chat/inspector-shortcuts.d.ts.map +1 -0
- package/dist/v2/components/chat/message-feedback.d.ts +89 -0
- package/dist/v2/components/chat/message-feedback.d.ts.map +1 -0
- package/dist/v2/components/chat/tool-call-capture.d.ts +39 -0
- package/dist/v2/components/chat/tool-call-capture.d.ts.map +1 -0
- package/dist/v2/components/chat/tool-call-display-context.d.ts +22 -0
- package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -0
- package/dist/v2/components/chat/tool-call-group-parts.d.ts +131 -0
- package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -0
- package/dist/v2/components/chat/turn-work.d.ts +33 -0
- package/dist/v2/components/chat/turn-work.d.ts.map +1 -0
- package/dist/v2/components/chat/types.d.ts +104 -5
- package/dist/v2/components/chat/types.d.ts.map +1 -1
- package/dist/v2/components/chat/use-chat-labels.d.ts +8 -0
- package/dist/v2/components/chat/use-chat-labels.d.ts.map +1 -0
- package/dist/v2/components/chat/use-lightbox.d.ts +18 -0
- package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -0
- package/dist/v2/components/icons/index.d.ts +968 -1
- package/dist/v2/components/icons/index.d.ts.map +1 -1
- package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +55 -0
- package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -0
- package/dist/v2/components/ui/CopilotPopover.vue.d.ts +40 -0
- package/dist/v2/components/ui/CopilotPopover.vue.d.ts.map +1 -0
- package/dist/v2/components/ui/CopilotTooltip.vue.d.ts +37 -0
- package/dist/v2/components/ui/CopilotTooltip.vue.d.ts.map +1 -0
- package/dist/v2/components/ui/dropdown-menu.d.ts +14 -0
- package/dist/v2/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/v2/components/ui/floating.d.ts +84 -0
- package/dist/v2/components/ui/floating.d.ts.map +1 -0
- package/dist/v2/components/ui/presence.d.ts +9 -0
- package/dist/v2/components/ui/presence.d.ts.map +1 -0
- package/dist/v2/components/ui/use-id.d.ts +9 -0
- package/dist/v2/components/ui/use-id.d.ts.map +1 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts +6 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
- package/dist/v2/hooks/use-frontend-tool.d.ts +5 -0
- package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-activity-message.d.ts +13 -9
- package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-custom-messages.d.ts +5 -7
- package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
- package/dist/v2/hooks/use-render-tool.d.ts +4 -0
- package/dist/v2/hooks/use-render-tool.d.ts.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.d.cts +3 -0
- package/dist/v2/index.d.mts +3 -0
- package/dist/v2/index.d.ts +3 -0
- package/dist/v2/index.d.ts.map +1 -1
- package/dist/v2/index.mjs +197 -197
- package/dist/v2/index.mjs.map +1 -1
- package/dist/v2/lib/message-feedback.d.ts +33 -0
- package/dist/v2/lib/message-feedback.d.ts.map +1 -0
- package/dist/v2/lib/processPartialHtml.d.ts +1 -2
- package/dist/v2/lib/processPartialHtml.d.ts.map +1 -1
- package/dist/v2/lib/vue-core.d.ts +0 -1
- 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 +56 -6
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts +6 -2
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
- package/dist/v2/providers/index.d.ts +1 -1
- package/dist/v2/providers/index.d.ts.map +1 -1
- package/dist/v2/providers/keys.d.ts +8 -0
- package/dist/v2/providers/keys.d.ts.map +1 -1
- package/dist/v2/providers/types.d.ts +3 -37
- package/dist/v2/providers/types.d.ts.map +1 -1
- package/dist/v2/styles/CopilotKitStyles.d.ts +18 -0
- package/dist/v2/styles/CopilotKitStyles.d.ts.map +1 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts +2 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts.map +1 -1
- package/dist/v2/types/human-in-the-loop.d.ts +8 -29
- package/dist/v2/types/human-in-the-loop.d.ts.map +1 -1
- package/dist/v2/types/interrupt.d.ts +1 -22
- package/dist/v2/types/interrupt.d.ts.map +1 -1
- package/dist/v2/types/vue-activity-message-renderer.d.ts +16 -15
- package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -1
- package/dist/v2/types/vue-custom-message-renderer.d.ts +7 -16
- package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -1
- package/dist/v2/types/vue-tool-call-renderer.d.ts +16 -28
- package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -1
- package/package.json +14 -27
- package/src/__tests__/build-entries.test.ts +27 -0
- package/src/components/copilot-provider/CopilotKit.vue +4 -1
- package/src/hooks/__tests__/zod-parameters.test.ts +520 -0
- package/src/hooks/use-copilot-action.ts +2 -1
- package/src/hooks/use-frontend-tool.ts +3 -6
- package/src/hooks/zod-parameters.ts +162 -0
- package/src/v2/__tests__/exports.test.ts +4 -2
- package/src/v2/components/A2UIMessageRenderer.ts +56 -68
- package/src/v2/components/A2UISurfaceActivityRenderer.vue +11 -23
- package/src/v2/components/LicenseWarningBanner.vue +31 -15
- package/src/v2/components/OpenGenerativeUIRenderer.ts +51 -373
- package/src/v2/components/__tests__/A2UIBasicCatalogColors.test.ts +8 -9
- package/src/v2/components/__tests__/A2UICatalogContext.test.ts +1 -1
- package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +63 -36
- package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +20 -0
- package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +55 -6
- package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +15 -258
- package/src/v2/components/a2ui/A2UICatalogContext.ts +10 -102
- package/src/v2/components/a2ui/A2UIRecoveryStates.ts +444 -0
- package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +175 -0
- package/src/v2/components/a2ui.ts +1 -1
- package/src/v2/components/chat/CopilotChat.vue +26 -40
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +267 -67
- package/src/v2/components/chat/CopilotChatAssistantToolbarButton.vue +46 -0
- package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +150 -173
- package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +77 -20
- package/src/v2/components/chat/CopilotChatAudioRecorder.vue +2 -2
- package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +240 -0
- package/src/v2/components/chat/CopilotChatInput.vue +250 -191
- package/src/v2/components/chat/CopilotChatInspectorButton.vue +331 -0
- package/src/v2/components/chat/CopilotChatLightbox.vue +105 -0
- package/src/v2/components/chat/CopilotChatMessageView.vue +262 -87
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +52 -35
- package/src/v2/components/chat/CopilotChatSuggestionPill.vue +5 -14
- package/src/v2/components/chat/CopilotChatSuggestionView.vue +1 -4
- package/src/v2/components/chat/CopilotChatToggleButton.vue +3 -9
- package/src/v2/components/chat/CopilotChatToggleButtonCloseIcon.ts +1 -1
- package/src/v2/components/chat/CopilotChatToggleButtonOpenIcon.ts +1 -1
- package/src/v2/components/chat/CopilotChatToolCallGroup.vue +353 -0
- package/src/v2/components/chat/CopilotChatToolCallItem.vue +24 -14
- package/src/v2/components/chat/CopilotChatToolCallsView.vue +194 -21
- package/src/v2/components/chat/CopilotChatUserMessage.vue +105 -42
- package/src/v2/components/chat/CopilotChatView.vue +37 -42
- package/src/v2/components/chat/CopilotChatWorkSummary.vue +111 -0
- package/src/v2/components/chat/CopilotModalHeader.vue +10 -8
- package/src/v2/components/chat/CopilotModalHeaderCloseButton.ts +7 -6
- package/src/v2/components/chat/CopilotModalHeaderTitle.ts +1 -4
- package/src/v2/components/chat/CopilotPopup.vue +3 -0
- package/src/v2/components/chat/CopilotPopupView.vue +1 -0
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +9 -19
- package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +5 -8
- package/src/v2/components/chat/CopilotSidebar.vue +5 -0
- package/src/v2/components/chat/CopilotSidebarView.vue +2 -0
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +24 -17
- package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +7 -10
- package/src/v2/components/chat/StreamingMarkdownDefault.vue +3 -5
- package/src/v2/components/chat/__tests__/CopilotChat.labels.test.ts +19 -4
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +26 -9
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.inspector.test.ts +269 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.learningFeedback.test.ts +760 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.ts +3 -5
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +2 -2
- package/src/v2/components/chat/__tests__/CopilotChatInput.addMenu.test.ts +328 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.ts +6 -3
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +55 -14
- package/src/v2/components/chat/__tests__/CopilotChatLightbox.test.ts +160 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +247 -403
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.toolCallDisplay.test.ts +637 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionPill.test.ts +1 -1
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.ts +3 -1
- package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.learning.test.ts +75 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.attachments.test.ts +143 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.ts +5 -4
- package/src/v2/components/chat/__tests__/CopilotChatView.rootWidth.test.ts +7 -33
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.ts +1 -1
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.ts +3 -3
- package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +126 -0
- package/src/v2/components/chat/__tests__/messageFeedbackDefaults.test.ts +33 -0
- package/src/v2/components/chat/__tests__/modalChatDisplayProps.test.ts +44 -0
- package/src/v2/components/chat/copilotChatToolCallItemRender.ts +3 -19
- package/src/v2/components/chat/index.ts +2 -1
- package/src/v2/components/chat/inspector-shortcuts.ts +34 -0
- package/src/v2/components/chat/message-feedback.ts +291 -0
- package/src/v2/components/chat/tool-call-capture.ts +55 -0
- package/src/v2/components/chat/tool-call-display-context.ts +28 -0
- package/src/v2/components/chat/tool-call-group-parts.ts +251 -0
- package/src/v2/components/chat/turn-work.ts +52 -0
- package/src/v2/components/chat/types.ts +114 -4
- package/src/v2/components/chat/use-chat-labels.ts +16 -0
- package/src/v2/components/chat/use-lightbox.ts +64 -0
- package/src/v2/components/icons/__tests__/icons.test.ts +72 -68
- package/src/v2/components/icons/index.ts +109 -23
- package/src/v2/components/ui/CopilotDropdownMenuContent.vue +465 -0
- package/src/v2/components/ui/CopilotPopover.vue +138 -0
- package/src/v2/components/ui/CopilotTooltip.vue +237 -0
- package/src/v2/components/ui/__tests__/CopilotTooltip.test.ts +168 -0
- package/src/v2/components/ui/dropdown-menu.ts +15 -0
- package/src/v2/components/ui/floating.ts +321 -0
- package/src/v2/components/ui/presence.ts +54 -0
- package/src/v2/components/ui/use-id.ts +16 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +62 -1
- package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +105 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.ts +3 -6
- package/src/v2/hooks/__tests__/zod-regression.test.ts +5 -4
- package/src/v2/hooks/use-default-render-tool.ts +123 -62
- package/src/v2/hooks/use-frontend-tool.ts +10 -10
- package/src/v2/hooks/use-render-activity-message.ts +27 -48
- package/src/v2/hooks/use-render-custom-messages.ts +14 -35
- package/src/v2/hooks/use-render-tool.ts +8 -0
- package/src/v2/index.ts +3 -0
- package/src/v2/lib/__tests__/message-feedback.test.ts +152 -0
- package/src/v2/lib/message-feedback.ts +120 -0
- package/src/v2/lib/processPartialHtml.ts +6 -21
- package/src/v2/lib/vue-core.ts +3 -5
- package/src/v2/providers/CopilotChatConfigurationProvider.vue +29 -2
- package/src/v2/providers/CopilotKitProvider.types.ts +57 -5
- package/src/v2/providers/CopilotKitProvider.vue +177 -114
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.openThread.test.ts +69 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +1 -1
- package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +3 -3
- package/src/v2/providers/__tests__/CopilotKitProvider.learning.test.ts +536 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +17 -33
- package/src/v2/providers/index.ts +4 -1
- package/src/v2/providers/keys.ts +12 -0
- package/src/v2/providers/types.ts +3 -38
- package/src/v2/styles/CopilotKitStyles.ts +95 -0
- package/src/v2/styles/__tests__/CopilotKitStyles.client.test.ts +149 -0
- package/src/v2/styles/__tests__/CopilotKitStyles.ssr.test.ts +71 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +28 -33
- package/src/v2/types/defineToolCallRenderer.ts +9 -4
- package/src/v2/types/human-in-the-loop.ts +8 -32
- package/src/v2/types/interrupt.ts +7 -31
- package/src/v2/types/vue-activity-message-renderer.ts +24 -15
- package/src/v2/types/vue-custom-message-renderer.ts +12 -16
- package/src/v2/types/vue-tool-call-renderer.ts +23 -31
- package/vite.config.ts +5 -5
- package/dist/styles.css +0 -2
- package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +0 -9
- package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +0 -1
- package/dist/v2/lib/transcription-client.d.ts +0 -19
- package/dist/v2/lib/transcription-client.d.ts.map +0 -1
- package/scripts/scope-preflight.mjs +0 -100
- package/src/styles/globals.css +0 -233
- package/src/v2/components/chat/BasicMarkdown.vue +0 -158
- package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +0 -69
- package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -84
- package/src/v2/lib/__tests__/transcription-client.test.ts +0 -67
- package/src/v2/lib/transcription-client.ts +0 -151
|
@@ -0,0 +1,760 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { defineComponent, h, nextTick, ref } from "vue";
|
|
3
|
+
import type { PropType } from "vue";
|
|
4
|
+
import { fireEvent, render, screen } from "@testing-library/vue";
|
|
5
|
+
import type { AssistantMessage, Message } from "@ag-ui/core";
|
|
6
|
+
import type { CopilotKitCore, LearningConfig } from "@copilotkit/core";
|
|
7
|
+
import CopilotKitProvider from "../../../providers/CopilotKitProvider.vue";
|
|
8
|
+
import CopilotChatConfigurationProvider from "../../../providers/CopilotChatConfigurationProvider.vue";
|
|
9
|
+
import { useCopilotKit } from "../../../providers/useCopilotKit";
|
|
10
|
+
import CopilotChatMessageView from "../CopilotChatMessageView.vue";
|
|
11
|
+
import CopilotChatAssistantMessage from "../CopilotChatAssistantMessage.vue";
|
|
12
|
+
import {
|
|
13
|
+
MESSAGE_FEEDBACK_EVENT_NAME,
|
|
14
|
+
getMessageFeedback,
|
|
15
|
+
setMessageFeedback,
|
|
16
|
+
ɵresetMessageFeedbackCache,
|
|
17
|
+
} from "../../../lib/message-feedback";
|
|
18
|
+
import { FEEDBACK_COMMENT_CLOSE_DELAY_MS } from "../message-feedback";
|
|
19
|
+
|
|
20
|
+
const THREAD_ID = "thread-1";
|
|
21
|
+
const AGENT_ID = "agent-1";
|
|
22
|
+
|
|
23
|
+
const reply: AssistantMessage = {
|
|
24
|
+
id: "reply-1",
|
|
25
|
+
role: "assistant",
|
|
26
|
+
content: "The deal is approved.",
|
|
27
|
+
};
|
|
28
|
+
const messages: Message[] = [
|
|
29
|
+
{ id: "user-1", role: "user", content: "Approve the deal" },
|
|
30
|
+
reply,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** The part of a learning sink batch these tests read. */
|
|
34
|
+
const batches: { events: { name: string; value: unknown }[] }[] = [];
|
|
35
|
+
const learning: LearningConfig = {
|
|
36
|
+
sink: (batch) => {
|
|
37
|
+
batches.push(batch);
|
|
38
|
+
},
|
|
39
|
+
capture: { clicks: false, navigation: false, network: false, inputs: false },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let core: CopilotKitCore | undefined;
|
|
43
|
+
|
|
44
|
+
const CaptureControls = defineComponent({
|
|
45
|
+
setup() {
|
|
46
|
+
core = useCopilotKit().copilotkit.value;
|
|
47
|
+
return () => null;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** A custom thumb slot named `label` that calls its handler without an event. */
|
|
52
|
+
const eventlessThumb =
|
|
53
|
+
(label: string, handler: "onThumbsUp" | "onThumbsDown") =>
|
|
54
|
+
(slotProps: Record<string, unknown>) =>
|
|
55
|
+
h(
|
|
56
|
+
"button",
|
|
57
|
+
{
|
|
58
|
+
type: "button",
|
|
59
|
+
"aria-pressed": slotProps.pressed ? "true" : "false",
|
|
60
|
+
onClick: () => (slotProps[handler] as () => void)(),
|
|
61
|
+
},
|
|
62
|
+
label,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const showChat = ref(true);
|
|
66
|
+
|
|
67
|
+
const Chat = defineComponent({
|
|
68
|
+
props: {
|
|
69
|
+
onThumbsDown: Function as PropType<(message: AssistantMessage) => void>,
|
|
70
|
+
eventlessThumbs: Boolean,
|
|
71
|
+
messageFeedback: { type: Boolean, default: undefined },
|
|
72
|
+
customComment: Boolean,
|
|
73
|
+
},
|
|
74
|
+
setup(props) {
|
|
75
|
+
return () =>
|
|
76
|
+
h(CopilotKitProvider, null, {
|
|
77
|
+
default: () => [
|
|
78
|
+
h(CaptureControls),
|
|
79
|
+
h(
|
|
80
|
+
CopilotChatConfigurationProvider,
|
|
81
|
+
{ threadId: THREAD_ID, agentId: AGENT_ID },
|
|
82
|
+
{
|
|
83
|
+
default: () =>
|
|
84
|
+
showChat.value
|
|
85
|
+
? h(
|
|
86
|
+
CopilotChatMessageView,
|
|
87
|
+
{
|
|
88
|
+
messages,
|
|
89
|
+
...(props.messageFeedback === undefined
|
|
90
|
+
? {}
|
|
91
|
+
: { messageFeedback: props.messageFeedback }),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"assistant-message": ({
|
|
95
|
+
message,
|
|
96
|
+
}: {
|
|
97
|
+
message: AssistantMessage;
|
|
98
|
+
}) =>
|
|
99
|
+
h(
|
|
100
|
+
CopilotChatAssistantMessage,
|
|
101
|
+
{
|
|
102
|
+
message,
|
|
103
|
+
messages,
|
|
104
|
+
...(props.onThumbsDown
|
|
105
|
+
? { onThumbsDown: props.onThumbsDown }
|
|
106
|
+
: {}),
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
...(props.eventlessThumbs
|
|
110
|
+
? {
|
|
111
|
+
"thumbs-up-button": eventlessThumb(
|
|
112
|
+
"Like",
|
|
113
|
+
"onThumbsUp",
|
|
114
|
+
),
|
|
115
|
+
"thumbs-down-button": eventlessThumb(
|
|
116
|
+
"Dislike",
|
|
117
|
+
"onThumbsDown",
|
|
118
|
+
),
|
|
119
|
+
}
|
|
120
|
+
: {}),
|
|
121
|
+
...(props.customComment
|
|
122
|
+
? {
|
|
123
|
+
"feedback-comment": (
|
|
124
|
+
slotProps: Record<string, unknown>,
|
|
125
|
+
) =>
|
|
126
|
+
h("form", {
|
|
127
|
+
"data-testid": "custom-comment",
|
|
128
|
+
"data-value": slotProps.value,
|
|
129
|
+
}),
|
|
130
|
+
}
|
|
131
|
+
: {}),
|
|
132
|
+
},
|
|
133
|
+
),
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
: null,
|
|
137
|
+
},
|
|
138
|
+
),
|
|
139
|
+
h("input", { "aria-label": "Elsewhere" }),
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const thumbsUp = () => screen.getByRole("button", { name: "Good response" });
|
|
146
|
+
const thumbsDown = () => screen.getByRole("button", { name: "Bad response" });
|
|
147
|
+
const commentBox = () =>
|
|
148
|
+
screen.queryByRole<HTMLInputElement>("textbox", {
|
|
149
|
+
name: "Add a comment (optional)",
|
|
150
|
+
});
|
|
151
|
+
const commentForm = () => screen.getByTestId("copilot-feedback-comment");
|
|
152
|
+
/** A pointer click; a keyboard click has `detail` 0. */
|
|
153
|
+
const click = (element: HTMLElement) => fireEvent.click(element, { detail: 1 });
|
|
154
|
+
const keyboardClick = (element: HTMLElement) =>
|
|
155
|
+
fireEvent.click(element, { detail: 0 });
|
|
156
|
+
/** Runs the hover timers of the comment popover. */
|
|
157
|
+
const wait = async (ms: number) => {
|
|
158
|
+
vi.advanceTimersByTime(ms);
|
|
159
|
+
await nextTick();
|
|
160
|
+
await nextTick();
|
|
161
|
+
};
|
|
162
|
+
/** Lets the popover's open and close (and its exit animation check) settle. */
|
|
163
|
+
const settle = async () => {
|
|
164
|
+
for (let i = 0; i < 4; i++) await nextTick();
|
|
165
|
+
};
|
|
166
|
+
const type = async (value: string) => {
|
|
167
|
+
await fireEvent.update(commentBox()!, value);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Renders the chat, turns capture on, and waits for the Trajectory to start. */
|
|
171
|
+
async function renderCapturingChat(
|
|
172
|
+
props: Record<string, unknown> = {},
|
|
173
|
+
config: LearningConfig = learning,
|
|
174
|
+
) {
|
|
175
|
+
const view = render(Chat, { props });
|
|
176
|
+
core!.setLearningConfig(config);
|
|
177
|
+
expect(await core!.startTrajectory()).toMatchObject({ status: "started" });
|
|
178
|
+
await settle();
|
|
179
|
+
const emit = vi.spyOn(core!, "emitTrajectoryEvent");
|
|
180
|
+
return { view, core: core!, emit };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Changes the reply's rating the way another chat of this Thread would. */
|
|
184
|
+
const rateElsewhere = async (
|
|
185
|
+
copilotkit: CopilotKitCore,
|
|
186
|
+
feedback: { rating: "up" | "down"; comment?: string } | null,
|
|
187
|
+
) => {
|
|
188
|
+
setMessageFeedback({
|
|
189
|
+
copilotkit,
|
|
190
|
+
agentId: AGENT_ID,
|
|
191
|
+
threadId: THREAD_ID,
|
|
192
|
+
message: reply,
|
|
193
|
+
feedback,
|
|
194
|
+
});
|
|
195
|
+
await settle();
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const feedbackEvents = (emit: { mock: { calls: unknown[][] } }) =>
|
|
199
|
+
emit.mock.calls
|
|
200
|
+
.filter(([name]) => name === MESSAGE_FEEDBACK_EVENT_NAME)
|
|
201
|
+
.map(([, value]) => value);
|
|
202
|
+
|
|
203
|
+
describe("learning feedback on assistant messages", () => {
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
ɵresetMessageFeedbackCache();
|
|
206
|
+
batches.length = 0;
|
|
207
|
+
showChat.value = true;
|
|
208
|
+
});
|
|
209
|
+
afterEach(() => {
|
|
210
|
+
core?.stopTrajectory();
|
|
211
|
+
core?.setLearningConfig(undefined);
|
|
212
|
+
vi.useRealTimers();
|
|
213
|
+
vi.restoreAllMocks();
|
|
214
|
+
document.body.innerHTML = "";
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("shows no thumbs without learning", () => {
|
|
218
|
+
render(Chat);
|
|
219
|
+
|
|
220
|
+
expect(screen.queryByRole("button", { name: "Good response" })).toBeNull();
|
|
221
|
+
expect(screen.queryByRole("button", { name: "Bad response" })).toBeNull();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("shows no thumbs while Learning is set up but capture has not started", async () => {
|
|
225
|
+
render(Chat);
|
|
226
|
+
core!.setLearningConfig(learning);
|
|
227
|
+
await settle();
|
|
228
|
+
|
|
229
|
+
expect(screen.queryByRole("button", { name: "Good response" })).toBeNull();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("shows thumbs once a Trajectory captures the chat", async () => {
|
|
233
|
+
await renderCapturingChat();
|
|
234
|
+
|
|
235
|
+
expect(thumbsUp().getAttribute("aria-pressed")).toBe("false");
|
|
236
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("false");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("sends no reply text when the sink omits agent text", async () => {
|
|
240
|
+
const { core } = await renderCapturingChat(
|
|
241
|
+
{},
|
|
242
|
+
{ ...learning, capture: { ...learning.capture, agentText: false } },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await click(thumbsUp());
|
|
246
|
+
core.stopTrajectory();
|
|
247
|
+
|
|
248
|
+
const sent = batches
|
|
249
|
+
.flatMap((batch) => batch.events)
|
|
250
|
+
.filter((event) => event.name === MESSAGE_FEEDBACK_EVENT_NAME);
|
|
251
|
+
expect(sent).toHaveLength(1);
|
|
252
|
+
expect(sent[0]!.value).not.toHaveProperty("response");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("records a rating at once and a comment when the user adds one", async () => {
|
|
256
|
+
const { emit } = await renderCapturingChat();
|
|
257
|
+
|
|
258
|
+
await click(thumbsDown());
|
|
259
|
+
await settle();
|
|
260
|
+
|
|
261
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("true");
|
|
262
|
+
expect(thumbsDown().getAttribute("aria-haspopup")).toBe("dialog");
|
|
263
|
+
expect(thumbsDown().getAttribute("aria-expanded")).toBe("true");
|
|
264
|
+
expect(feedbackEvents(emit)).toEqual([
|
|
265
|
+
{
|
|
266
|
+
feedbackId: `${THREAD_ID}:${reply.id}`,
|
|
267
|
+
threadId: THREAD_ID,
|
|
268
|
+
messageId: reply.id,
|
|
269
|
+
agentId: AGENT_ID,
|
|
270
|
+
rating: "down",
|
|
271
|
+
response: "The deal is approved.",
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
// A pointer rating opens the comment without taking focus.
|
|
276
|
+
const input = commentBox();
|
|
277
|
+
expect(input).not.toBeNull();
|
|
278
|
+
expect(document.activeElement).not.toBe(input);
|
|
279
|
+
const popover = input!.closest('[data-slot="popover-content"]')!;
|
|
280
|
+
expect(popover.getAttribute("role")).toBe("dialog");
|
|
281
|
+
expect(popover.getAttribute("aria-label")).toBe("Add a comment (optional)");
|
|
282
|
+
expect(popover.hasAttribute("data-copilotkit-ignore")).toBe(true);
|
|
283
|
+
await type(" Wrong deal ");
|
|
284
|
+
await fireEvent.submit(commentForm());
|
|
285
|
+
await settle();
|
|
286
|
+
|
|
287
|
+
expect(commentBox()).toBeNull();
|
|
288
|
+
expect(feedbackEvents(emit)[1]).toMatchObject({
|
|
289
|
+
feedbackId: `${THREAD_ID}:${reply.id}`,
|
|
290
|
+
rating: "down",
|
|
291
|
+
comment: "Wrong deal",
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("clicking the chosen thumb again removes the rating", async () => {
|
|
296
|
+
const { emit } = await renderCapturingChat();
|
|
297
|
+
await click(thumbsUp());
|
|
298
|
+
await click(thumbsDown());
|
|
299
|
+
|
|
300
|
+
await click(thumbsDown());
|
|
301
|
+
await settle();
|
|
302
|
+
|
|
303
|
+
expect(feedbackEvents(emit)).toEqual([
|
|
304
|
+
expect.objectContaining({ rating: "up" }),
|
|
305
|
+
expect.objectContaining({ rating: "down" }),
|
|
306
|
+
expect.objectContaining({ rating: null }),
|
|
307
|
+
]);
|
|
308
|
+
expect(thumbsUp().getAttribute("aria-pressed")).toBe("false");
|
|
309
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("false");
|
|
310
|
+
expect(commentBox()).toBeNull();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("hovering the chosen thumb opens its saved comment at once and leaving closes it", async () => {
|
|
314
|
+
const { emit } = await renderCapturingChat();
|
|
315
|
+
await click(thumbsUp());
|
|
316
|
+
await type("Fast");
|
|
317
|
+
await fireEvent.submit(commentForm());
|
|
318
|
+
await settle();
|
|
319
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
320
|
+
|
|
321
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
322
|
+
|
|
323
|
+
expect(commentBox()).toHaveProperty("value", "Fast");
|
|
324
|
+
expect(thumbsUp().getAttribute("aria-expanded")).toBe("true");
|
|
325
|
+
|
|
326
|
+
await fireEvent.pointerLeave(thumbsUp());
|
|
327
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
328
|
+
await settle();
|
|
329
|
+
|
|
330
|
+
expect(commentBox()).toBeNull();
|
|
331
|
+
expect(feedbackEvents(emit)).toHaveLength(2);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("the thumb's tooltip gives way to its comment", async () => {
|
|
335
|
+
await renderCapturingChat();
|
|
336
|
+
await fireEvent.pointerMove(thumbsDown());
|
|
337
|
+
expect(screen.getByRole("tooltip").textContent).toContain("Bad response");
|
|
338
|
+
await fireEvent.pointerLeave(thumbsDown());
|
|
339
|
+
|
|
340
|
+
await click(thumbsUp());
|
|
341
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
342
|
+
await fireEvent.pointerMove(thumbsUp());
|
|
343
|
+
await settle();
|
|
344
|
+
|
|
345
|
+
expect(commentBox()).not.toBeNull();
|
|
346
|
+
expect(
|
|
347
|
+
screen
|
|
348
|
+
.queryAllByRole("tooltip")
|
|
349
|
+
.some((tooltip) => tooltip.textContent?.includes("Good response")),
|
|
350
|
+
).toBe(false);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("moving from the thumb into the popover keeps it open, and an edit saves as it closes", async () => {
|
|
354
|
+
const { emit } = await renderCapturingChat();
|
|
355
|
+
await click(thumbsUp());
|
|
356
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
357
|
+
|
|
358
|
+
await fireEvent.pointerLeave(thumbsUp());
|
|
359
|
+
await fireEvent.pointerEnter(commentForm().parentElement!);
|
|
360
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
361
|
+
expect(commentBox()).not.toBeNull();
|
|
362
|
+
|
|
363
|
+
await type("Fast and right");
|
|
364
|
+
await fireEvent.pointerLeave(commentForm().parentElement!);
|
|
365
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
366
|
+
await settle();
|
|
367
|
+
|
|
368
|
+
expect(commentBox()).toBeNull();
|
|
369
|
+
expect(feedbackEvents(emit).at(-1)).toMatchObject({
|
|
370
|
+
rating: "up",
|
|
371
|
+
comment: "Fast and right",
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("stays open while the user edits, and saves when focus moves on", async () => {
|
|
376
|
+
const { emit } = await renderCapturingChat();
|
|
377
|
+
await click(thumbsDown());
|
|
378
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
379
|
+
vi.spyOn(document, "hasFocus").mockReturnValue(true);
|
|
380
|
+
commentBox()!.focus();
|
|
381
|
+
await type("Wrong deal");
|
|
382
|
+
|
|
383
|
+
await fireEvent.pointerLeave(commentForm().parentElement!);
|
|
384
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
385
|
+
expect(commentBox()).not.toBeNull();
|
|
386
|
+
|
|
387
|
+
screen.getByRole("textbox", { name: "Elsewhere" }).focus();
|
|
388
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
389
|
+
await settle();
|
|
390
|
+
|
|
391
|
+
expect(commentBox()).toBeNull();
|
|
392
|
+
expect(feedbackEvents(emit).at(-1)).toMatchObject({
|
|
393
|
+
rating: "down",
|
|
394
|
+
comment: "Wrong deal",
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("Escape closes the popover and discards an unsaved change", async () => {
|
|
399
|
+
const { emit } = await renderCapturingChat();
|
|
400
|
+
await click(thumbsUp());
|
|
401
|
+
await type("Never mind");
|
|
402
|
+
|
|
403
|
+
await fireEvent.keyDown(commentBox()!, { key: "Escape" });
|
|
404
|
+
await settle();
|
|
405
|
+
|
|
406
|
+
expect(commentBox()).toBeNull();
|
|
407
|
+
expect(feedbackEvents(emit)).toHaveLength(1);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it("a keyboard rating moves focus into the comment", async () => {
|
|
411
|
+
await renderCapturingChat();
|
|
412
|
+
|
|
413
|
+
await keyboardClick(thumbsUp());
|
|
414
|
+
await settle();
|
|
415
|
+
|
|
416
|
+
expect(document.activeElement).toBe(commentBox());
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("shows the rating and comment again when the chat mounts again", async () => {
|
|
420
|
+
await renderCapturingChat();
|
|
421
|
+
await click(thumbsUp());
|
|
422
|
+
await type("good");
|
|
423
|
+
await fireEvent.submit(commentForm());
|
|
424
|
+
showChat.value = false;
|
|
425
|
+
await settle();
|
|
426
|
+
|
|
427
|
+
showChat.value = true;
|
|
428
|
+
await settle();
|
|
429
|
+
|
|
430
|
+
expect(thumbsUp().getAttribute("aria-pressed")).toBe("true");
|
|
431
|
+
expect(commentBox()).toBeNull();
|
|
432
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
433
|
+
expect(commentBox()).toHaveProperty("value", "good");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("still calls the app's own handler", async () => {
|
|
437
|
+
const onThumbsDown = vi.fn();
|
|
438
|
+
const { emit } = await renderCapturingChat({ onThumbsDown });
|
|
439
|
+
|
|
440
|
+
await click(thumbsDown());
|
|
441
|
+
|
|
442
|
+
expect(onThumbsDown).toHaveBeenCalledWith(
|
|
443
|
+
expect.objectContaining({ id: reply.id }),
|
|
444
|
+
);
|
|
445
|
+
expect(feedbackEvents(emit)).toHaveLength(1);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("does not tell the app about a rating that is removed", async () => {
|
|
449
|
+
const onThumbsDown = vi.fn();
|
|
450
|
+
await renderCapturingChat({ onThumbsDown });
|
|
451
|
+
|
|
452
|
+
await click(thumbsDown());
|
|
453
|
+
await click(thumbsDown());
|
|
454
|
+
|
|
455
|
+
expect(onThumbsDown).toHaveBeenCalledTimes(1);
|
|
456
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("false");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("delivers the rating to the learning sink", async () => {
|
|
460
|
+
const { core } = await renderCapturingChat();
|
|
461
|
+
|
|
462
|
+
await click(thumbsDown());
|
|
463
|
+
core.stopTrajectory();
|
|
464
|
+
|
|
465
|
+
const events = batches.flatMap((batch) => batch.events);
|
|
466
|
+
expect(events).toContainEqual(
|
|
467
|
+
expect.objectContaining({
|
|
468
|
+
type: "CUSTOM",
|
|
469
|
+
name: MESSAGE_FEEDBACK_EVENT_NAME,
|
|
470
|
+
value: expect.objectContaining({
|
|
471
|
+
messageId: reply.id,
|
|
472
|
+
rating: "down",
|
|
473
|
+
}),
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it("keeps the thumbs after capture stops", async () => {
|
|
479
|
+
const { core } = await renderCapturingChat();
|
|
480
|
+
|
|
481
|
+
core.stopTrajectory();
|
|
482
|
+
await settle();
|
|
483
|
+
|
|
484
|
+
expect(thumbsUp()).toBeTruthy();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("leaves the thumbs to the app when the message view turns ratings off", async () => {
|
|
488
|
+
const onThumbsDown = vi.fn();
|
|
489
|
+
const { emit } = await renderCapturingChat({
|
|
490
|
+
messageFeedback: false,
|
|
491
|
+
onThumbsDown,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
expect(screen.queryByRole("button", { name: "Good response" })).toBeNull();
|
|
495
|
+
await click(thumbsDown());
|
|
496
|
+
|
|
497
|
+
expect(onThumbsDown).toHaveBeenCalledTimes(1);
|
|
498
|
+
expect(feedbackEvents(emit)).toEqual([]);
|
|
499
|
+
expect(commentBox()).toBeNull();
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("renders the comment field from the feedback-comment slot", async () => {
|
|
503
|
+
await renderCapturingChat({ customComment: true });
|
|
504
|
+
|
|
505
|
+
await click(thumbsUp());
|
|
506
|
+
await settle();
|
|
507
|
+
|
|
508
|
+
expect(screen.getByTestId("custom-comment")).toBeTruthy();
|
|
509
|
+
expect(commentBox()).toBeNull();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("works with a custom thumb that calls its handler without an event", async () => {
|
|
513
|
+
const { emit } = await renderCapturingChat({ eventlessThumbs: true });
|
|
514
|
+
|
|
515
|
+
await fireEvent.click(screen.getByRole("button", { name: "Like" }));
|
|
516
|
+
await settle();
|
|
517
|
+
|
|
518
|
+
expect(feedbackEvents(emit)).toEqual([
|
|
519
|
+
expect.objectContaining({ rating: "up" }),
|
|
520
|
+
]);
|
|
521
|
+
expect(commentBox()).not.toBeNull();
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("points the comment at the custom thumb that was chosen", async () => {
|
|
525
|
+
await renderCapturingChat({ eventlessThumbs: true });
|
|
526
|
+
const dislike = screen.getByRole("button", { name: "Dislike" });
|
|
527
|
+
|
|
528
|
+
await fireEvent.click(dislike);
|
|
529
|
+
await settle();
|
|
530
|
+
commentBox()!.focus();
|
|
531
|
+
await fireEvent.keyDown(commentBox()!, { key: "Escape" });
|
|
532
|
+
|
|
533
|
+
// Escape from the field returns focus to the thumb it points at.
|
|
534
|
+
expect(document.activeElement).toBe(dislike);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it("a click on a thumb while the comment is open does not send the draft", async () => {
|
|
538
|
+
const { emit } = await renderCapturingChat();
|
|
539
|
+
await click(thumbsUp());
|
|
540
|
+
await type("half a thought");
|
|
541
|
+
|
|
542
|
+
await fireEvent.pointerDown(thumbsUp());
|
|
543
|
+
await click(thumbsUp());
|
|
544
|
+
|
|
545
|
+
expect(feedbackEvents(emit)).toEqual([
|
|
546
|
+
expect.objectContaining({ rating: "up" }),
|
|
547
|
+
expect.objectContaining({ rating: null }),
|
|
548
|
+
]);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("Escape elsewhere closes the comment without taking focus", async () => {
|
|
552
|
+
await renderCapturingChat();
|
|
553
|
+
await click(thumbsUp());
|
|
554
|
+
await fireEvent.keyDown(commentBox()!, { key: "Escape" });
|
|
555
|
+
await settle();
|
|
556
|
+
const elsewhere = screen.getByRole("textbox", { name: "Elsewhere" });
|
|
557
|
+
elsewhere.focus();
|
|
558
|
+
// Hovering the thumb opens the comment while focus stays elsewhere.
|
|
559
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
560
|
+
expect(commentBox()).not.toBeNull();
|
|
561
|
+
|
|
562
|
+
await fireEvent.keyDown(elsewhere, { key: "Escape" });
|
|
563
|
+
await settle();
|
|
564
|
+
|
|
565
|
+
expect(commentBox()).toBeNull();
|
|
566
|
+
expect(document.activeElement).toBe(elsewhere);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it("Enter saves the comment and returns focus to the thumb", async () => {
|
|
570
|
+
await renderCapturingChat();
|
|
571
|
+
await keyboardClick(thumbsUp());
|
|
572
|
+
await settle();
|
|
573
|
+
await type("Clear");
|
|
574
|
+
|
|
575
|
+
await fireEvent.submit(commentForm());
|
|
576
|
+
await settle();
|
|
577
|
+
|
|
578
|
+
expect(commentBox()).toBeNull();
|
|
579
|
+
expect(document.activeElement).toBe(thumbsUp());
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("Shift+Tab from the comment returns to the thumb, Tab moves past it", async () => {
|
|
583
|
+
await renderCapturingChat();
|
|
584
|
+
await keyboardClick(thumbsUp());
|
|
585
|
+
await settle();
|
|
586
|
+
|
|
587
|
+
await fireEvent.keyDown(commentBox()!, { key: "Tab", shiftKey: true });
|
|
588
|
+
await settle();
|
|
589
|
+
expect(commentBox()).toBeNull();
|
|
590
|
+
expect(document.activeElement).toBe(thumbsUp());
|
|
591
|
+
|
|
592
|
+
await keyboardClick(thumbsDown());
|
|
593
|
+
await settle();
|
|
594
|
+
await fireEvent.keyDown(commentBox()!, { key: "Tab" });
|
|
595
|
+
await settle();
|
|
596
|
+
expect(commentBox()).toBeNull();
|
|
597
|
+
// The control after the thumbs-down button in page order.
|
|
598
|
+
expect(document.activeElement).toBe(
|
|
599
|
+
screen.getByRole("textbox", { name: "Elsewhere" }),
|
|
600
|
+
);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it("a keyboard switch to the other thumb moves focus into its comment", async () => {
|
|
604
|
+
await renderCapturingChat();
|
|
605
|
+
await keyboardClick(thumbsUp());
|
|
606
|
+
await settle();
|
|
607
|
+
|
|
608
|
+
await keyboardClick(thumbsDown());
|
|
609
|
+
await settle();
|
|
610
|
+
|
|
611
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("true");
|
|
612
|
+
expect(document.activeElement).toBe(commentBox());
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it("Escape in the comment does not reach the page around the chat", async () => {
|
|
616
|
+
await renderCapturingChat();
|
|
617
|
+
await click(thumbsUp());
|
|
618
|
+
const pageEscape = vi.fn();
|
|
619
|
+
window.addEventListener("keydown", pageEscape);
|
|
620
|
+
try {
|
|
621
|
+
commentBox()!.focus();
|
|
622
|
+
await fireEvent.keyDown(commentBox()!, { key: "Escape" });
|
|
623
|
+
} finally {
|
|
624
|
+
window.removeEventListener("keydown", pageEscape);
|
|
625
|
+
}
|
|
626
|
+
await settle();
|
|
627
|
+
|
|
628
|
+
expect(commentBox()).toBeNull();
|
|
629
|
+
expect(pageEscape).not.toHaveBeenCalled();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("sends an unsaved comment when its message unmounts", async () => {
|
|
633
|
+
const { emit } = await renderCapturingChat();
|
|
634
|
+
await click(thumbsUp());
|
|
635
|
+
await type("Keep me");
|
|
636
|
+
|
|
637
|
+
showChat.value = false;
|
|
638
|
+
await settle();
|
|
639
|
+
|
|
640
|
+
expect(feedbackEvents(emit).at(-1)).toMatchObject({
|
|
641
|
+
rating: "up",
|
|
642
|
+
comment: "Keep me",
|
|
643
|
+
});
|
|
644
|
+
expect(getMessageFeedback(AGENT_ID, THREAD_ID, reply.id)).toEqual({
|
|
645
|
+
rating: "up",
|
|
646
|
+
comment: "Keep me",
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it("switching to another window does not save a half-typed comment", async () => {
|
|
651
|
+
const { emit } = await renderCapturingChat();
|
|
652
|
+
await click(thumbsUp());
|
|
653
|
+
const input = commentBox()!;
|
|
654
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
655
|
+
await fireEvent.focusIn(input);
|
|
656
|
+
await type("half");
|
|
657
|
+
vi.spyOn(document, "hasFocus").mockReturnValue(false);
|
|
658
|
+
|
|
659
|
+
await fireEvent.focusOut(input, { relatedTarget: null });
|
|
660
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
661
|
+
// The pointer may leave while the user is in the other window.
|
|
662
|
+
await fireEvent.pointerLeave(commentForm().parentElement!);
|
|
663
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
664
|
+
|
|
665
|
+
expect(commentBox()).not.toBeNull();
|
|
666
|
+
expect(feedbackEvents(emit)).toHaveLength(1);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it("shows a rating changed elsewhere on the page", async () => {
|
|
670
|
+
const { core } = await renderCapturingChat();
|
|
671
|
+
|
|
672
|
+
await rateElsewhere(core, { rating: "down" });
|
|
673
|
+
|
|
674
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("true");
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
it("closes the comment when the rating is removed elsewhere on the page", async () => {
|
|
678
|
+
const { core, emit } = await renderCapturingChat();
|
|
679
|
+
await click(thumbsUp());
|
|
680
|
+
await type("Fast");
|
|
681
|
+
await fireEvent.submit(commentForm());
|
|
682
|
+
await settle();
|
|
683
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
684
|
+
expect(commentBox()).toHaveProperty("value", "Fast");
|
|
685
|
+
|
|
686
|
+
await rateElsewhere(core, null);
|
|
687
|
+
expect(thumbsUp().getAttribute("aria-pressed")).toBe("false");
|
|
688
|
+
expect(commentBox()).toBeNull();
|
|
689
|
+
|
|
690
|
+
// Rated again there, the comment that was open does not reopen by itself.
|
|
691
|
+
await rateElsewhere(core, { rating: "up", comment: "Fast" });
|
|
692
|
+
expect(thumbsUp().getAttribute("aria-pressed")).toBe("true");
|
|
693
|
+
expect(commentBox()).toBeNull();
|
|
694
|
+
// The two changes from this chat, then the two from elsewhere.
|
|
695
|
+
expect(feedbackEvents(emit)).toHaveLength(4);
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("does not save a draft for a rating changed elsewhere on the page", async () => {
|
|
699
|
+
const { core, emit } = await renderCapturingChat();
|
|
700
|
+
await click(thumbsUp());
|
|
701
|
+
await type("Fast");
|
|
702
|
+
|
|
703
|
+
await rateElsewhere(core, { rating: "down" });
|
|
704
|
+
|
|
705
|
+
expect(thumbsDown().getAttribute("aria-pressed")).toBe("true");
|
|
706
|
+
expect(commentBox()).toBeNull();
|
|
707
|
+
expect(feedbackEvents(emit)).toEqual([
|
|
708
|
+
expect.objectContaining({ rating: "up" }),
|
|
709
|
+
expect.objectContaining({ rating: "down" }),
|
|
710
|
+
]);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it("stays open while focus is on the send button", async () => {
|
|
714
|
+
await renderCapturingChat();
|
|
715
|
+
await click(thumbsUp());
|
|
716
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
717
|
+
vi.spyOn(document, "hasFocus").mockReturnValue(true);
|
|
718
|
+
commentBox()!.focus();
|
|
719
|
+
await type("Fast");
|
|
720
|
+
|
|
721
|
+
screen.getByRole("button", { name: "Send comment" }).focus();
|
|
722
|
+
await fireEvent.pointerLeave(commentForm().parentElement!);
|
|
723
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
724
|
+
|
|
725
|
+
expect(commentBox()).not.toBeNull();
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("stays open when focus leaves for nothing while the pointer is on the thumb", async () => {
|
|
729
|
+
const { emit } = await renderCapturingChat();
|
|
730
|
+
await click(thumbsUp());
|
|
731
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
732
|
+
vi.spyOn(document, "hasFocus").mockReturnValue(true);
|
|
733
|
+
commentBox()!.focus();
|
|
734
|
+
await type("half");
|
|
735
|
+
await fireEvent.pointerEnter(thumbsUp());
|
|
736
|
+
|
|
737
|
+
// Safari and Firefox on macOS do not focus a pressed button.
|
|
738
|
+
await fireEvent.focusOut(commentBox()!, { relatedTarget: null });
|
|
739
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
740
|
+
expect(commentBox()).not.toBeNull();
|
|
741
|
+
|
|
742
|
+
await fireEvent.pointerLeave(thumbsUp());
|
|
743
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
744
|
+
await settle();
|
|
745
|
+
expect(commentBox()).toBeNull();
|
|
746
|
+
expect(feedbackEvents(emit).at(-1)).toMatchObject({ comment: "half" });
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it("stays open when the focused thumb blurs while the pointer is on the popover", async () => {
|
|
750
|
+
await renderCapturingChat();
|
|
751
|
+
await click(thumbsUp());
|
|
752
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
|
|
753
|
+
await fireEvent.pointerEnter(commentForm().parentElement!);
|
|
754
|
+
|
|
755
|
+
await fireEvent.blur(thumbsUp());
|
|
756
|
+
await wait(FEEDBACK_COMMENT_CLOSE_DELAY_MS);
|
|
757
|
+
|
|
758
|
+
expect(commentBox()).not.toBeNull();
|
|
759
|
+
});
|
|
760
|
+
});
|