@aleph-alpha/chat-kit 3.3.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CkInput.vue_vue_type_script_setup_true_lang-BAcBFs6I.js → CkHistory.vue_vue_type_script_setup_true_lang-Bp9hXdH6.js} +452 -348
- package/dist/adapters/responses-api.d.ts +11 -5
- package/dist/adapters/responses-api.d.ts.map +1 -1
- package/dist/adapters/responses-api.js +9 -3
- package/dist/chat-kit.css +36 -14
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +14 -13
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts +8 -7
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessage/types.d.ts +16 -5
- package/dist/components/base/CkAgentMessage/types.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts +16 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts +21 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/index.d.ts +3 -0
- package/dist/components/base/CkAgentMessageHeader/index.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/types.d.ts +22 -0
- package/dist/components/base/CkAgentMessageHeader/types.d.ts.map +1 -0
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts +6 -0
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -1
- package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +5 -4
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -1
- package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -1
- package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts → CkCopyMessageButton/CkCopyMessageButton.stories.d.ts} +3 -3
- package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts → CkCopyMessageButton/CkCopyMessageButton.vue.d.ts} +4 -4
- package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkCopyMessageButton/index.d.ts +3 -0
- package/dist/components/base/CkCopyMessageButton/index.d.ts.map +1 -0
- package/dist/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.d.ts +3 -3
- package/dist/components/base/CkCopyMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/CkFeedbackButton/types.d.ts +1 -1
- package/dist/components/base/CkFeedbackDialog/types.d.ts +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts +15 -4
- package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts.map +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +2 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts.map +1 -1
- package/dist/components/base/CkFileItemsList/types.d.ts +17 -4
- package/dist/components/base/CkFileItemsList/types.d.ts.map +1 -1
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts +19 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts +22 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/index.d.ts +3 -0
- package/dist/components/base/CkHeaderActivityButton/index.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/types.d.ts +36 -0
- package/dist/components/base/CkHeaderActivityButton/types.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.stories.d.ts → CkMessageButton/CkMessageButton.stories.d.ts} +3 -3
- package/dist/components/base/CkMessageButton/CkMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.vue.d.ts → CkMessageButton/CkMessageButton.vue.d.ts} +3 -3
- package/dist/components/base/CkMessageButton/CkMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkMessageButton/index.d.ts +3 -0
- package/dist/components/base/CkMessageButton/index.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton → CkMessageButton}/types.d.ts +2 -2
- package/dist/components/base/CkMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts +6 -6
- package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
- package/dist/components/base/CkPromptInput/types.d.ts +4 -4
- package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts.map +1 -1
- package/dist/components/base/CkPromptInputDisclaimer/types.d.ts +1 -1
- package/dist/components/base/CkPromptSendButton/CkPromptSendButton.vue.d.ts +1 -1
- package/dist/components/base/CkPromptSendButton/types.d.ts +1 -1
- package/dist/components/base/CkPromptStopButton/types.d.ts +1 -1
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts +16 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts +12 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkRegenerateMessageButton/index.d.ts +3 -0
- package/dist/components/base/{CkCopyMessageActionButton → CkRegenerateMessageButton}/index.d.ts.map +1 -1
- package/dist/components/base/CkRegenerateMessageButton/types.d.ts +27 -0
- package/dist/components/base/CkRegenerateMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/index.d.ts +7 -2
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +1 -1
- package/dist/components/composed/CkConversation/CkConversation.vue.d.ts +13 -1
- package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
- package/dist/components/composed/CkConversation/types.d.ts +1 -1
- package/dist/components/composed/index.d.ts +0 -1
- package/dist/components/composed/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +26 -24
- package/dist/composables/index.js +1 -1
- package/dist/handlers/index.js +2 -2
- package/dist/handlers/useChatKitLabels.d.ts +2 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/handlers/useMessageHandler.d.ts +37 -0
- package/dist/handlers/useMessageHandler.d.ts.map +1 -1
- package/dist/helpers/conversationTree.d.ts +15 -0
- package/dist/helpers/conversationTree.d.ts.map +1 -1
- package/dist/{index-C6njJ8EB.js → index-CyrVnbS9.js} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -28
- package/dist/{useChatKitLabels-BM2I7-9o.js → useChatKitLabels-zMFV7aEz.js} +1 -0
- package/dist/{useMessageHandler-CMdy-lm0.js → useMessageHandler-N50Yj-NU.js} +45 -7
- package/package.json +3 -3
- package/src/adapters/responses-api.spec.ts +52 -5
- package/src/adapters/responses-api.ts +26 -7
- package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +12 -17
- package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +124 -113
- package/src/components/base/CkAgentMessage/CkAgentMessage.vue +18 -16
- package/src/components/base/CkAgentMessage/types.ts +16 -5
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.spec.ts +64 -0
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.ts +90 -0
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue +47 -0
- package/src/components/base/CkAgentMessageHeader/index.ts +2 -0
- package/src/components/base/CkAgentMessageHeader/types.ts +21 -0
- package/src/components/base/CkCitation/CkCitation.stories.ts +12 -0
- package/src/components/base/CkCitation/CkCitation.vue +34 -9
- package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +5 -4
- package/src/components/base/CkCitationCard/CkCitationCard.vue +2 -2
- package/src/components/base/CkConversationLayout/CkConversationLayout.vue +1 -1
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts → CkCopyMessageButton/CkCopyMessageButton.spec.ts} +6 -6
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts → CkCopyMessageButton/CkCopyMessageButton.stories.ts} +8 -8
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue → CkCopyMessageButton/CkCopyMessageButton.vue} +6 -6
- package/src/components/base/CkCopyMessageButton/index.ts +2 -0
- package/src/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.ts +3 -3
- package/src/components/base/CkFeedbackButton/CkFeedbackButton.vue +5 -5
- package/src/components/base/CkFeedbackButton/types.ts +1 -1
- package/src/components/base/CkFeedbackDialog/types.ts +1 -1
- package/src/components/base/CkFileItemsList/CkFileItemsList.spec.ts +88 -13
- package/src/components/base/CkFileItemsList/CkFileItemsList.stories.ts +61 -13
- package/src/components/base/CkFileItemsList/CkFileItemsList.vue +153 -110
- package/src/components/base/CkFileItemsList/types.ts +17 -4
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.spec.ts +50 -0
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.ts +102 -0
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue +39 -0
- package/src/components/base/CkHeaderActivityButton/index.ts +2 -0
- package/src/components/base/CkHeaderActivityButton/types.ts +36 -0
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.spec.ts → CkMessageButton/CkMessageButton.spec.ts} +6 -6
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.stories.ts → CkMessageButton/CkMessageButton.stories.ts} +10 -10
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.vue → CkMessageButton/CkMessageButton.vue} +3 -3
- package/src/components/base/CkMessageButton/index.ts +2 -0
- package/src/components/base/{CkMessageActionButton → CkMessageButton}/types.ts +2 -2
- package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +88 -0
- package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +6 -6
- package/src/components/base/CkPromptInput/CkPromptInput.vue +63 -12
- package/src/components/base/CkPromptInput/types.ts +4 -4
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +1 -1
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +1 -1
- package/src/components/base/CkPromptInputDisclaimer/types.ts +1 -1
- package/src/components/base/CkPromptSendButton/types.ts +1 -1
- package/src/components/base/CkPromptStopButton/types.ts +1 -1
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.spec.ts +49 -0
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.ts +63 -0
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue +43 -0
- package/src/components/base/CkRegenerateMessageButton/index.ts +2 -0
- package/src/components/base/CkRegenerateMessageButton/types.ts +27 -0
- package/src/components/base/index.ts +10 -2
- package/src/components/composed/CkConversation/CkConversation.spec.ts +81 -6
- package/src/components/composed/CkConversation/CkConversation.stories.ts +6 -6
- package/src/components/composed/CkConversation/CkConversation.vue +80 -37
- package/src/components/composed/CkConversation/types.ts +1 -1
- package/src/components/composed/index.ts +0 -1
- package/src/components/index.ts +9 -3
- package/src/handlers/useChatKitLabels.spec.ts +1 -0
- package/src/handlers/useChatKitLabels.ts +3 -0
- package/src/handlers/useMessageHandler.spec.ts +79 -0
- package/src/handlers/useMessageHandler.ts +50 -2
- package/src/helpers/conversationTree.spec.ts +120 -0
- package/src/helpers/conversationTree.ts +57 -8
- package/src/index.ts +8 -3
- package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts.map +0 -1
- package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts.map +0 -1
- package/dist/components/base/CkCopyMessageActionButton/index.d.ts +0 -3
- package/dist/components/base/CkCopyMessageActionButton/types.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/index.d.ts +0 -3
- package/dist/components/base/CkMessageActionButton/index.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/types.d.ts.map +0 -1
- package/dist/components/composed/CkInput/CkInput.stories.d.ts +0 -43
- package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +0 -1
- package/dist/components/composed/CkInput/CkInput.vue.d.ts +0 -50
- package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +0 -1
- package/dist/components/composed/CkInput/index.d.ts +0 -3
- package/dist/components/composed/CkInput/index.d.ts.map +0 -1
- package/dist/components/composed/CkInput/types.d.ts +0 -55
- package/dist/components/composed/CkInput/types.d.ts.map +0 -1
- package/src/components/base/CkCopyMessageActionButton/index.ts +0 -2
- package/src/components/base/CkMessageActionButton/index.ts +0 -2
- package/src/components/composed/CkInput/CkInput.spec.ts +0 -191
- package/src/components/composed/CkInput/CkInput.stories.ts +0 -434
- package/src/components/composed/CkInput/CkInput.vue +0 -106
- package/src/components/composed/CkInput/index.ts +0 -2
- package/src/components/composed/CkInput/types.ts +0 -55
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-width header row for an assistant (agent) message. Owns the agent icon,
|
|
3
|
+
* a header text and an optional activity button, and is intended to be passed
|
|
4
|
+
* into the `header` slot of `CkAgentMessage`.
|
|
5
|
+
*
|
|
6
|
+
* It has two visual states, driven purely by whether the `activity` slot is
|
|
7
|
+
* provided:
|
|
8
|
+
* - **with `activity`** — vertical padding and a bottom border separate the
|
|
9
|
+
* header from the message body;
|
|
10
|
+
* - **without `activity`** — a compact icon + text row with no padding or
|
|
11
|
+
* border.
|
|
12
|
+
*
|
|
13
|
+
* Slots: `icon` (leading agent avatar/icon), default (header text, ellipsised
|
|
14
|
+
* on overflow), `activity` (typically a `CkHeaderActivityButton`).
|
|
15
|
+
*
|
|
16
|
+
* @category Messages
|
|
17
|
+
* @useCases agent message header, assistant turn header, chain-of-thought summary row, activity indicator row
|
|
18
|
+
* @keywords agent, assistant, message, header, icon, activity, conversation
|
|
19
|
+
* @related CkAgentMessage, CkHeaderActivityButton, CkConversation
|
|
20
|
+
*/
|
|
21
|
+
export type CkAgentMessageHeaderProps = Record<string, never>;
|
|
@@ -100,6 +100,18 @@ export const WithoutLink: Story = {
|
|
|
100
100
|
args: { url: undefined },
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* A very long file name truncates with an ellipsis inside the hover card while
|
|
105
|
+
* the external-link action stays pinned to the right of the truncated name; the
|
|
106
|
+
* full name is exposed on hover via the `title` attribute.
|
|
107
|
+
*/
|
|
108
|
+
export const LongFileName: Story = {
|
|
109
|
+
args: {
|
|
110
|
+
fileName:
|
|
111
|
+
'Annual-Strategy-And-Operational-Review-And-Lessons-Learned-Digital-Transformation-Phase-1-FY2026-Final-APPROVED-v3.pdf',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
103
115
|
/**
|
|
104
116
|
* Non-paginated source — the card omits the page footer.
|
|
105
117
|
*/
|
|
@@ -43,9 +43,11 @@ const triggerLabel = computed(
|
|
|
43
43
|
const CLOSE_DELAY_MS = 80;
|
|
44
44
|
|
|
45
45
|
const open = ref(false);
|
|
46
|
-
//
|
|
47
|
-
// the keyboard
|
|
48
|
-
|
|
46
|
+
// Whether the current open session was initiated by the pointer (hover) rather
|
|
47
|
+
// than the keyboard. Persists for the whole session — including the close — so
|
|
48
|
+
// we can fully suppress the popover's focus management for hover while keeping
|
|
49
|
+
// it for keyboard users.
|
|
50
|
+
const openedByPointer = ref(false);
|
|
49
51
|
let closeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
50
52
|
|
|
51
53
|
function show(): void {
|
|
@@ -65,20 +67,41 @@ function scheduleHide(): void {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function onPointerEnter(): void {
|
|
68
|
-
|
|
70
|
+
// Only mark the session pointer-driven on a closed → open transition. A stray
|
|
71
|
+
// mouseenter while the card is already open (e.g. it was opened via keyboard,
|
|
72
|
+
// then the pointer drifts over it) must not flip the flag, or onCloseAutoFocus
|
|
73
|
+
// would wrongly skip restoring focus and leave focus lost on close.
|
|
74
|
+
if (!open.value) openedByPointer.value = true;
|
|
69
75
|
show();
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
// Don't reset `openedByPointer` here — the close fires one tick later
|
|
79
|
+
// (CLOSE_DELAY_MS), and `onCloseAutoFocus` still needs to know the session was
|
|
80
|
+
// hover-driven so it can skip restoring focus to the marker.
|
|
72
81
|
function onPointerLeave(): void {
|
|
73
|
-
isPointerInteraction.value = false;
|
|
74
82
|
scheduleHide();
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
85
|
+
// Enter/Space activate the marker (opening the card) via keyboard; mark those
|
|
86
|
+
// sessions keyboard-driven so focus management is restored (into the card on
|
|
87
|
+
// open, back to the marker on close/Escape). Other keys (Tab, arrows) must not
|
|
88
|
+
// flip the pointer/keyboard distinction.
|
|
89
|
+
function onTriggerKeydown(event: KeyboardEvent): void {
|
|
90
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
91
|
+
openedByPointer.value = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// For hover sessions, suppress the popover's focus management entirely: don't
|
|
96
|
+
// pull focus into the card on open (it would steal focus / scroll), and don't
|
|
97
|
+
// yank focus back to the marker on close. That close-time refocus is what
|
|
98
|
+
// painted a stray focus-visible ring on the marker you'd just hovered away from.
|
|
80
99
|
function onOpenAutoFocus(event: Event): void {
|
|
81
|
-
if (
|
|
100
|
+
if (openedByPointer.value) event.preventDefault();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function onCloseAutoFocus(event: Event): void {
|
|
104
|
+
if (openedByPointer.value) event.preventDefault();
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
// Close once focus leaves the card entirely (keyboard users tabbing out). Moving
|
|
@@ -108,6 +131,7 @@ onBeforeUnmount(() => {
|
|
|
108
131
|
? 'bg-background-accent-soft text-black'
|
|
109
132
|
: 'bg-background-accent-yellow text-background-status-warning',
|
|
110
133
|
]"
|
|
134
|
+
@keydown="onTriggerKeydown"
|
|
111
135
|
@mouseenter="onPointerEnter"
|
|
112
136
|
@mouseleave="onPointerLeave"
|
|
113
137
|
>
|
|
@@ -119,6 +143,7 @@ onBeforeUnmount(() => {
|
|
|
119
143
|
:side-offset="8"
|
|
120
144
|
class="w-auto border-0 bg-transparent p-0 shadow-none"
|
|
121
145
|
@open-auto-focus="onOpenAutoFocus"
|
|
146
|
+
@close-auto-focus="onCloseAutoFocus"
|
|
122
147
|
@mouseenter="onPointerEnter"
|
|
123
148
|
@mouseleave="onPointerLeave"
|
|
124
149
|
@focusin="show"
|
|
@@ -92,10 +92,11 @@ export const MinimalSnippetOnly: Story = {
|
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
|
-
* A long file name truncates with an ellipsis
|
|
96
|
-
*
|
|
97
|
-
* on hover via the `title` attribute. The
|
|
98
|
-
* length, including a single unbroken token
|
|
95
|
+
* A long file name truncates with an ellipsis while the external-link action
|
|
96
|
+
* stays pinned to the right of the (truncated) name and the card keeps its
|
|
97
|
+
* width; the full name stays available on hover via the `title` attribute. The
|
|
98
|
+
* `truncate` behaviour holds for any length, including a single unbroken token
|
|
99
|
+
* with no separators.
|
|
99
100
|
*/
|
|
100
101
|
export const LongFileName: Story = {
|
|
101
102
|
args: {
|
|
@@ -42,10 +42,10 @@ function handleOpen(): void {
|
|
|
42
42
|
<div
|
|
43
43
|
class="ck-citation-card flex w-[22.5rem] max-w-[90vw] flex-col gap-2 rounded-md bg-background-surface-floating px-6 pb-6 pt-4 shadow-md"
|
|
44
44
|
>
|
|
45
|
-
<div class="flex items-center gap-
|
|
45
|
+
<div class="flex items-center gap-2">
|
|
46
46
|
<UiFileType :filename="fileName" class="shrink-0" />
|
|
47
47
|
<p
|
|
48
|
-
class="min-w-0
|
|
48
|
+
class="min-w-0 truncate text-sm font-medium text-content-on-surface-primary"
|
|
49
49
|
:title="fileName"
|
|
50
50
|
>
|
|
51
51
|
{{ fileName }}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkCopyMessageButton from './CkCopyMessageButton.vue';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { render, screen, waitFor } from '@testing-library/vue';
|
|
4
4
|
import { flushPromises } from '@vue/test-utils';
|
|
5
5
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('CkCopyMessageButton', () => {
|
|
8
8
|
const writeText = vi.fn((_: string) => Promise.resolve());
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
@@ -14,7 +14,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
14
14
|
|
|
15
15
|
it('emits copy with success when clipboard succeeds', async () => {
|
|
16
16
|
const user = userEvent.setup();
|
|
17
|
-
const { emitted } = render(
|
|
17
|
+
const { emitted } = render(CkCopyMessageButton, {
|
|
18
18
|
props: { text: 'x', writeToClipboard: writeText },
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
27
27
|
|
|
28
28
|
it('copies text and updates the accessible name', async () => {
|
|
29
29
|
const user = userEvent.setup();
|
|
30
|
-
render(
|
|
30
|
+
render(CkCopyMessageButton, {
|
|
31
31
|
props: { text: 'Hello, world', writeToClipboard: writeText },
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -50,7 +50,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
50
50
|
|
|
51
51
|
it('emits copy with success false', async () => {
|
|
52
52
|
const user = userEvent.setup();
|
|
53
|
-
const { emitted } = render(
|
|
53
|
+
const { emitted } = render(CkCopyMessageButton, {
|
|
54
54
|
props: { text: 'x', writeToClipboard: writeText },
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -67,7 +67,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
67
67
|
it('reverts label after durationMs', async () => {
|
|
68
68
|
vi.useFakeTimers();
|
|
69
69
|
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
|
70
|
-
render(
|
|
70
|
+
render(CkCopyMessageButton, {
|
|
71
71
|
props: { text: 'x', durationMs: 1500, writeToClipboard: writeText },
|
|
72
72
|
});
|
|
73
73
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkCopyMessageButton from './CkCopyMessageButton.vue';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
3
|
|
|
4
|
-
const meta: Meta<typeof
|
|
5
|
-
title: 'Base/
|
|
6
|
-
component:
|
|
4
|
+
const meta: Meta<typeof CkCopyMessageButton> = {
|
|
5
|
+
title: 'Base/CkCopyMessageButton',
|
|
6
|
+
component: CkCopyMessageButton,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
8
|
argTypes: {
|
|
9
9
|
text: {
|
|
@@ -31,19 +31,19 @@ export default meta;
|
|
|
31
31
|
type Story = StoryObj<typeof meta>;
|
|
32
32
|
|
|
33
33
|
const defaultTemplateSource = `<script setup lang="ts">
|
|
34
|
-
import {
|
|
34
|
+
import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
37
|
<template>
|
|
38
|
-
<
|
|
38
|
+
<CkCopyMessageButton text="Sample assistant reply text to copy." />
|
|
39
39
|
</template>`;
|
|
40
40
|
|
|
41
41
|
const disabledTemplateSource = `<script setup lang="ts">
|
|
42
|
-
import {
|
|
42
|
+
import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
|
|
43
43
|
</script>
|
|
44
44
|
|
|
45
45
|
<template>
|
|
46
|
-
<
|
|
46
|
+
<CkCopyMessageButton text="Cannot copy when disabled." disabled />
|
|
47
47
|
</template>`;
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import { CkMessageButton } from '../CkMessageButton';
|
|
3
|
+
import type { CkCopyMessageButtonProps } from './types';
|
|
4
4
|
import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
|
|
5
5
|
import { onUnmounted, ref } from 'vue';
|
|
6
6
|
|
|
7
7
|
defineOptions({
|
|
8
|
-
name: '
|
|
8
|
+
name: 'CkCopyMessageButton',
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const props = withDefaults(defineProps<
|
|
11
|
+
const props = withDefaults(defineProps<CkCopyMessageButtonProps>(), {
|
|
12
12
|
disabled: false,
|
|
13
13
|
durationMs: 3000,
|
|
14
14
|
ariaLabelCopy: 'Copy message',
|
|
@@ -68,7 +68,7 @@ async function handleClick() {
|
|
|
68
68
|
align="center"
|
|
69
69
|
:delay-duration="0"
|
|
70
70
|
>
|
|
71
|
-
<
|
|
71
|
+
<CkMessageButton
|
|
72
72
|
v-bind="{ ariaLabel: copied ? ariaLabelCopied : ariaLabelCopy }"
|
|
73
73
|
:disabled="disabled"
|
|
74
74
|
@click="handleClick"
|
|
@@ -83,7 +83,7 @@ async function handleClick() {
|
|
|
83
83
|
/>
|
|
84
84
|
<UiIcon v-else key="check" name="check" :size="16" aria-hidden="true" />
|
|
85
85
|
</Transition>
|
|
86
|
-
</
|
|
86
|
+
</CkMessageButton>
|
|
87
87
|
<span class="sr-only" aria-live="polite">
|
|
88
88
|
{{ copied ? ariaLabelCopied : ariaLabelCopy }}
|
|
89
89
|
</span>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* @category Actions
|
|
6
6
|
* @useCases copy assistant reply, copy message to clipboard, chat message action, feedback action on message footer
|
|
7
7
|
* @keywords copy, clipboard, action, message, button, icon, feedback
|
|
8
|
-
* @related
|
|
8
|
+
* @related CkMessageButton, CkAgentMessage, CkConversation
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
10
|
+
export interface CkCopyMessageButtonProps {
|
|
11
11
|
/** Plain text to copy to the clipboard. */
|
|
12
12
|
text: string;
|
|
13
13
|
/**
|
|
@@ -37,7 +37,7 @@ export interface CkCopyMessageActionButtonProps {
|
|
|
37
37
|
writeToClipboard?: (text: string) => Promise<void>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface
|
|
40
|
+
export interface CkCopyMessageButtonEmits {
|
|
41
41
|
/** Emitted after the clipboard operation finishes (success or failure). */
|
|
42
42
|
(e: 'copy', payload: { success: boolean }): void;
|
|
43
43
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
defaultChatKitLabels,
|
|
4
4
|
useChatKitLabels,
|
|
5
5
|
} from '../../../handlers/useChatKitLabels';
|
|
6
|
-
import {
|
|
6
|
+
import { CkMessageButton } from '../CkMessageButton';
|
|
7
7
|
import type { CkFeedbackButtonProps, CkFeedbackValue } from './types';
|
|
8
8
|
import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
|
|
9
9
|
import { computed } from 'vue';
|
|
@@ -81,7 +81,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
81
81
|
align="center"
|
|
82
82
|
:delay-duration="0"
|
|
83
83
|
>
|
|
84
|
-
<
|
|
84
|
+
<CkMessageButton
|
|
85
85
|
v-bind="{ ariaLabel: thumbsUpLabel }"
|
|
86
86
|
:aria-pressed="isUpSelected"
|
|
87
87
|
:aria-disabled="locked || undefined"
|
|
@@ -98,7 +98,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
98
98
|
aria-hidden="true"
|
|
99
99
|
:class="isUpSelected ? 'fill-current' : undefined"
|
|
100
100
|
/>
|
|
101
|
-
</
|
|
101
|
+
</CkMessageButton>
|
|
102
102
|
</UiTooltip>
|
|
103
103
|
<UiTooltip
|
|
104
104
|
:content="thumbsDownLabel"
|
|
@@ -106,7 +106,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
106
106
|
align="center"
|
|
107
107
|
:delay-duration="0"
|
|
108
108
|
>
|
|
109
|
-
<
|
|
109
|
+
<CkMessageButton
|
|
110
110
|
v-bind="{ ariaLabel: thumbsDownLabel }"
|
|
111
111
|
:aria-pressed="isDownSelected"
|
|
112
112
|
:aria-disabled="locked || undefined"
|
|
@@ -123,7 +123,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
123
123
|
aria-hidden="true"
|
|
124
124
|
:class="isDownSelected ? 'fill-current' : undefined"
|
|
125
125
|
/>
|
|
126
|
-
</
|
|
126
|
+
</CkMessageButton>
|
|
127
127
|
</UiTooltip>
|
|
128
128
|
</div>
|
|
129
129
|
</template>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @category Feedback
|
|
11
11
|
* @useCases like or dislike an assistant reply, collect binary message rating, lock feedback after submission, rate AI response
|
|
12
12
|
* @keywords feedback, thumbs up, thumbs down, like, dislike, rating, vote, action, message, button
|
|
13
|
-
* @related
|
|
13
|
+
* @related CkMessageButton, CkCopyMessageButton, CkAgentMessage, CkConversation
|
|
14
14
|
*/
|
|
15
15
|
export type CkFeedbackValue = 'up' | 'down' | null;
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* capture a free-text comment, drive a two-step rating flow
|
|
19
19
|
* @keywords feedback, dialog, modal, reason codes, free text, qualitative,
|
|
20
20
|
* rating, comment
|
|
21
|
-
* @related CkFeedbackButton,
|
|
21
|
+
* @related CkFeedbackButton, CkMessageButton, CkAgentMessage,
|
|
22
22
|
* CkConversation, UiDialog, UiToggleGroup
|
|
23
23
|
*/
|
|
24
24
|
export type CkFeedbackSentiment = 'positive' | 'negative';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render, screen, within } from '@testing-library/vue';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import CkFileItemsList from './CkFileItemsList.vue';
|
|
5
5
|
import type { CkFileEntry } from './types';
|
|
6
6
|
|
|
@@ -11,8 +11,34 @@ const files: CkFileEntry[] = [
|
|
|
11
11
|
{ id: 'd', filename: 'archive.zip' },
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
+
// The inline capacity is derived purely from the measured container width: how
|
|
15
|
+
// many items fit at `minItemWidth` each. JSDOM has no layout, so we stub
|
|
16
|
+
// `clientWidth` (read in `onMounted`). The default 700px fits 3 inline items at
|
|
17
|
+
// the default 200px `minItemWidth`, reproducing the typical "3 inline, rest
|
|
18
|
+
// collapsed" behaviour for these specs.
|
|
19
|
+
//
|
|
20
|
+
// Note: width is measured in `onMounted`, which updates reactively *after* the
|
|
21
|
+
// initial render. Assertions that depend on the *narrowed* (width-collapsed)
|
|
22
|
+
// state must therefore await the re-render (e.g. via `findByRole`) rather than
|
|
23
|
+
// querying synchronously.
|
|
24
|
+
let clientWidthSpy: ReturnType<typeof vi.spyOn>;
|
|
25
|
+
|
|
26
|
+
function setContainerWidth(px: number) {
|
|
27
|
+
clientWidthSpy.mockReturnValue(px);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
clientWidthSpy = vi
|
|
32
|
+
.spyOn(Element.prototype, 'clientWidth', 'get')
|
|
33
|
+
.mockReturnValue(700);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
clientWidthSpy.mockRestore();
|
|
38
|
+
});
|
|
39
|
+
|
|
14
40
|
describe('CkFileItemsList', () => {
|
|
15
|
-
it('renders all files inline when
|
|
41
|
+
it('renders all files inline when they fit the available width', () => {
|
|
16
42
|
const { container } = render(CkFileItemsList, {
|
|
17
43
|
props: { files: files.slice(0, 3) },
|
|
18
44
|
});
|
|
@@ -25,34 +51,82 @@ describe('CkFileItemsList', () => {
|
|
|
25
51
|
).toBeNull();
|
|
26
52
|
});
|
|
27
53
|
|
|
28
|
-
it('collapses into a popover trigger when
|
|
54
|
+
it('collapses into a popover trigger when the files no longer fit', async () => {
|
|
29
55
|
render(CkFileItemsList, { props: { files } });
|
|
30
56
|
|
|
31
|
-
const trigger = screen.
|
|
57
|
+
const trigger = await screen.findByRole('button', { name: /4 files/i });
|
|
32
58
|
expect(trigger).toBeInTheDocument();
|
|
33
59
|
expect(trigger).toHaveTextContent('4 files');
|
|
34
60
|
});
|
|
35
61
|
|
|
36
|
-
it('
|
|
62
|
+
it('keeps every file inline on a wide wrapper (no upper cap)', () => {
|
|
63
|
+
setContainerWidth(2000);
|
|
64
|
+
const many: CkFileEntry[] = [
|
|
65
|
+
...files,
|
|
66
|
+
{ id: 'e', filename: 'slides.key' },
|
|
67
|
+
{ id: 'f', filename: 'budget.xlsx' },
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const { container } = render(CkFileItemsList, { props: { files: many } });
|
|
71
|
+
|
|
72
|
+
const list = container.querySelector('[data-slot="file-items-list"]');
|
|
73
|
+
expect(list?.querySelectorAll('li')).toHaveLength(6);
|
|
74
|
+
expect(
|
|
75
|
+
container.querySelector('[data-slot="file-items-list-trigger"]'),
|
|
76
|
+
).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reduces the inline count as the wrapper narrows', async () => {
|
|
80
|
+
// 360px fits only 1 item at the default 200px minItemWidth, so 3 files no
|
|
81
|
+
// longer fit and collapse. The narrowing is applied after the onMounted
|
|
82
|
+
// measurement, so await the collapsed trigger rather than querying
|
|
83
|
+
// synchronously.
|
|
84
|
+
setContainerWidth(360);
|
|
85
|
+
|
|
86
|
+
const { container } = render(CkFileItemsList, {
|
|
87
|
+
props: { files: files.slice(0, 3) },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(
|
|
91
|
+
await screen.findByRole('button', { name: /3 files/i }),
|
|
92
|
+
).toBeInTheDocument();
|
|
93
|
+
expect(container.querySelector('[data-slot="file-items-list"]')).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('constrains inline item width between minItemWidth and maxItemWidth', () => {
|
|
97
|
+
const { container } = render(CkFileItemsList, {
|
|
98
|
+
props: { files: files.slice(0, 2), minItemWidth: 140, maxItemWidth: 200 },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const item = container.querySelector<HTMLElement>(
|
|
102
|
+
'[data-slot="file-items-list"] li',
|
|
103
|
+
);
|
|
104
|
+
expect(item?.style.minWidth).toBe('140px');
|
|
105
|
+
expect(item?.style.maxWidth).toBe('200px');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('uses an explicit triggerLabel when provided', async () => {
|
|
37
109
|
render(CkFileItemsList, {
|
|
38
110
|
props: { files, triggerLabel: '+1 more attachment' },
|
|
39
111
|
});
|
|
40
112
|
|
|
41
|
-
expect(screen.
|
|
113
|
+
expect(await screen.findByRole('button')).toHaveTextContent(
|
|
114
|
+
'+1 more attachment',
|
|
115
|
+
);
|
|
42
116
|
});
|
|
43
117
|
|
|
44
|
-
it('keeps a single-line trigger (no count subtitle) when showStatus is off', () => {
|
|
118
|
+
it('keeps a single-line trigger (no count subtitle) when showStatus is off', async () => {
|
|
45
119
|
render(CkFileItemsList, {
|
|
46
120
|
props: { files, triggerLabel: 'Uploaded documents' },
|
|
47
121
|
});
|
|
48
122
|
|
|
49
|
-
const trigger = screen.
|
|
123
|
+
const trigger = await screen.findByRole('button');
|
|
50
124
|
expect(trigger).toHaveTextContent('Uploaded documents');
|
|
51
125
|
expect(trigger).not.toHaveTextContent('4 files');
|
|
52
126
|
});
|
|
53
127
|
|
|
54
128
|
describe('showStatus summary on the collapsed trigger', () => {
|
|
55
|
-
it('summarises the number of failed uploads in an error line', () => {
|
|
129
|
+
it('summarises the number of failed uploads in an error line', async () => {
|
|
56
130
|
const withFailures: CkFileEntry[] = [
|
|
57
131
|
{ id: 'a', filename: 'a.pdf', error: 'Upload failed' },
|
|
58
132
|
{ id: 'b', filename: 'b.md', error: 'Upload failed' },
|
|
@@ -68,13 +142,13 @@ describe('CkFileItemsList', () => {
|
|
|
68
142
|
},
|
|
69
143
|
});
|
|
70
144
|
|
|
71
|
-
const trigger = screen.
|
|
145
|
+
const trigger = await screen.findByRole('button', {
|
|
72
146
|
name: /Uploading documents, 2 uploads failed/i,
|
|
73
147
|
});
|
|
74
148
|
expect(trigger).toHaveTextContent('2 uploads failed');
|
|
75
149
|
});
|
|
76
150
|
|
|
77
|
-
it('renders an aggregate progress bar while files are uploading', () => {
|
|
151
|
+
it('renders an aggregate progress bar while files are uploading', async () => {
|
|
78
152
|
const uploading: CkFileEntry[] = [
|
|
79
153
|
{ id: 'a', filename: 'a.pdf', progress: 20 },
|
|
80
154
|
{ id: 'b', filename: 'b.md', progress: 60 },
|
|
@@ -90,10 +164,11 @@ describe('CkFileItemsList', () => {
|
|
|
90
164
|
},
|
|
91
165
|
});
|
|
92
166
|
|
|
167
|
+
await screen.findByRole('button');
|
|
93
168
|
expect(container.querySelector('[data-slot="progress"]')).toBeTruthy();
|
|
94
169
|
});
|
|
95
170
|
|
|
96
|
-
it('shows the file count once all uploads succeed', () => {
|
|
171
|
+
it('shows the file count once all uploads succeed', async () => {
|
|
97
172
|
render(CkFileItemsList, {
|
|
98
173
|
props: {
|
|
99
174
|
files,
|
|
@@ -102,7 +177,7 @@ describe('CkFileItemsList', () => {
|
|
|
102
177
|
},
|
|
103
178
|
});
|
|
104
179
|
|
|
105
|
-
const trigger = screen.
|
|
180
|
+
const trigger = await screen.findByRole('button');
|
|
106
181
|
expect(trigger).toHaveTextContent('Uploaded documents');
|
|
107
182
|
expect(trigger).toHaveTextContent('4 files');
|
|
108
183
|
});
|