@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.
Files changed (189) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-BAcBFs6I.js → CkHistory.vue_vue_type_script_setup_true_lang-Bp9hXdH6.js} +452 -348
  2. package/dist/adapters/responses-api.d.ts +11 -5
  3. package/dist/adapters/responses-api.d.ts.map +1 -1
  4. package/dist/adapters/responses-api.js +9 -3
  5. package/dist/chat-kit.css +36 -14
  6. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +14 -13
  7. package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
  8. package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts +8 -7
  9. package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
  10. package/dist/components/base/CkAgentMessage/types.d.ts +16 -5
  11. package/dist/components/base/CkAgentMessage/types.d.ts.map +1 -1
  12. package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts +16 -0
  13. package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts.map +1 -0
  14. package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts +21 -0
  15. package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts.map +1 -0
  16. package/dist/components/base/CkAgentMessageHeader/index.d.ts +3 -0
  17. package/dist/components/base/CkAgentMessageHeader/index.d.ts.map +1 -0
  18. package/dist/components/base/CkAgentMessageHeader/types.d.ts +22 -0
  19. package/dist/components/base/CkAgentMessageHeader/types.d.ts.map +1 -0
  20. package/dist/components/base/CkCitation/CkCitation.stories.d.ts +6 -0
  21. package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -1
  22. package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
  23. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +5 -4
  24. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -1
  25. package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -1
  26. package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts → CkCopyMessageButton/CkCopyMessageButton.stories.d.ts} +3 -3
  27. package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.stories.d.ts.map +1 -0
  28. package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts → CkCopyMessageButton/CkCopyMessageButton.vue.d.ts} +4 -4
  29. package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.vue.d.ts.map +1 -0
  30. package/dist/components/base/CkCopyMessageButton/index.d.ts +3 -0
  31. package/dist/components/base/CkCopyMessageButton/index.d.ts.map +1 -0
  32. package/dist/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.d.ts +3 -3
  33. package/dist/components/base/CkCopyMessageButton/types.d.ts.map +1 -0
  34. package/dist/components/base/CkFeedbackButton/types.d.ts +1 -1
  35. package/dist/components/base/CkFeedbackDialog/types.d.ts +1 -1
  36. package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts +15 -4
  37. package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts.map +1 -1
  38. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +2 -1
  39. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts.map +1 -1
  40. package/dist/components/base/CkFileItemsList/types.d.ts +17 -4
  41. package/dist/components/base/CkFileItemsList/types.d.ts.map +1 -1
  42. package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts +19 -0
  43. package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts.map +1 -0
  44. package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts +22 -0
  45. package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts.map +1 -0
  46. package/dist/components/base/CkHeaderActivityButton/index.d.ts +3 -0
  47. package/dist/components/base/CkHeaderActivityButton/index.d.ts.map +1 -0
  48. package/dist/components/base/CkHeaderActivityButton/types.d.ts +36 -0
  49. package/dist/components/base/CkHeaderActivityButton/types.d.ts.map +1 -0
  50. package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.stories.d.ts → CkMessageButton/CkMessageButton.stories.d.ts} +3 -3
  51. package/dist/components/base/CkMessageButton/CkMessageButton.stories.d.ts.map +1 -0
  52. package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.vue.d.ts → CkMessageButton/CkMessageButton.vue.d.ts} +3 -3
  53. package/dist/components/base/CkMessageButton/CkMessageButton.vue.d.ts.map +1 -0
  54. package/dist/components/base/CkMessageButton/index.d.ts +3 -0
  55. package/dist/components/base/CkMessageButton/index.d.ts.map +1 -0
  56. package/dist/components/base/{CkMessageActionButton → CkMessageButton}/types.d.ts +2 -2
  57. package/dist/components/base/CkMessageButton/types.d.ts.map +1 -0
  58. package/dist/components/base/CkPromptInput/CkPromptInput.stories.d.ts +6 -6
  59. package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
  60. package/dist/components/base/CkPromptInput/types.d.ts +4 -4
  61. package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts.map +1 -1
  62. package/dist/components/base/CkPromptInputDisclaimer/types.d.ts +1 -1
  63. package/dist/components/base/CkPromptSendButton/CkPromptSendButton.vue.d.ts +1 -1
  64. package/dist/components/base/CkPromptSendButton/types.d.ts +1 -1
  65. package/dist/components/base/CkPromptStopButton/types.d.ts +1 -1
  66. package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts +16 -0
  67. package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts.map +1 -0
  68. package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts +12 -0
  69. package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts.map +1 -0
  70. package/dist/components/base/CkRegenerateMessageButton/index.d.ts +3 -0
  71. package/dist/components/base/{CkCopyMessageActionButton → CkRegenerateMessageButton}/index.d.ts.map +1 -1
  72. package/dist/components/base/CkRegenerateMessageButton/types.d.ts +27 -0
  73. package/dist/components/base/CkRegenerateMessageButton/types.d.ts.map +1 -0
  74. package/dist/components/base/index.d.ts +7 -2
  75. package/dist/components/base/index.d.ts.map +1 -1
  76. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +1 -1
  77. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts +13 -1
  78. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
  79. package/dist/components/composed/CkConversation/types.d.ts +1 -1
  80. package/dist/components/composed/index.d.ts +0 -1
  81. package/dist/components/composed/index.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +3 -3
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/components/index.js +26 -24
  85. package/dist/composables/index.js +1 -1
  86. package/dist/handlers/index.js +2 -2
  87. package/dist/handlers/useChatKitLabels.d.ts +2 -0
  88. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  89. package/dist/handlers/useMessageHandler.d.ts +37 -0
  90. package/dist/handlers/useMessageHandler.d.ts.map +1 -1
  91. package/dist/helpers/conversationTree.d.ts +15 -0
  92. package/dist/helpers/conversationTree.d.ts.map +1 -1
  93. package/dist/{index-C6njJ8EB.js → index-CyrVnbS9.js} +1 -1
  94. package/dist/index.d.ts +2 -2
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +30 -28
  97. package/dist/{useChatKitLabels-BM2I7-9o.js → useChatKitLabels-zMFV7aEz.js} +1 -0
  98. package/dist/{useMessageHandler-CMdy-lm0.js → useMessageHandler-N50Yj-NU.js} +45 -7
  99. package/package.json +3 -3
  100. package/src/adapters/responses-api.spec.ts +52 -5
  101. package/src/adapters/responses-api.ts +26 -7
  102. package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +12 -17
  103. package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +124 -113
  104. package/src/components/base/CkAgentMessage/CkAgentMessage.vue +18 -16
  105. package/src/components/base/CkAgentMessage/types.ts +16 -5
  106. package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.spec.ts +64 -0
  107. package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.ts +90 -0
  108. package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue +47 -0
  109. package/src/components/base/CkAgentMessageHeader/index.ts +2 -0
  110. package/src/components/base/CkAgentMessageHeader/types.ts +21 -0
  111. package/src/components/base/CkCitation/CkCitation.stories.ts +12 -0
  112. package/src/components/base/CkCitation/CkCitation.vue +34 -9
  113. package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +5 -4
  114. package/src/components/base/CkCitationCard/CkCitationCard.vue +2 -2
  115. package/src/components/base/CkConversationLayout/CkConversationLayout.vue +1 -1
  116. package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts → CkCopyMessageButton/CkCopyMessageButton.spec.ts} +6 -6
  117. package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts → CkCopyMessageButton/CkCopyMessageButton.stories.ts} +8 -8
  118. package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue → CkCopyMessageButton/CkCopyMessageButton.vue} +6 -6
  119. package/src/components/base/CkCopyMessageButton/index.ts +2 -0
  120. package/src/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.ts +3 -3
  121. package/src/components/base/CkFeedbackButton/CkFeedbackButton.vue +5 -5
  122. package/src/components/base/CkFeedbackButton/types.ts +1 -1
  123. package/src/components/base/CkFeedbackDialog/types.ts +1 -1
  124. package/src/components/base/CkFileItemsList/CkFileItemsList.spec.ts +88 -13
  125. package/src/components/base/CkFileItemsList/CkFileItemsList.stories.ts +61 -13
  126. package/src/components/base/CkFileItemsList/CkFileItemsList.vue +153 -110
  127. package/src/components/base/CkFileItemsList/types.ts +17 -4
  128. package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.spec.ts +50 -0
  129. package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.ts +102 -0
  130. package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue +39 -0
  131. package/src/components/base/CkHeaderActivityButton/index.ts +2 -0
  132. package/src/components/base/CkHeaderActivityButton/types.ts +36 -0
  133. package/src/components/base/{CkMessageActionButton/CkMessageActionButton.spec.ts → CkMessageButton/CkMessageButton.spec.ts} +6 -6
  134. package/src/components/base/{CkMessageActionButton/CkMessageActionButton.stories.ts → CkMessageButton/CkMessageButton.stories.ts} +10 -10
  135. package/src/components/base/{CkMessageActionButton/CkMessageActionButton.vue → CkMessageButton/CkMessageButton.vue} +3 -3
  136. package/src/components/base/CkMessageButton/index.ts +2 -0
  137. package/src/components/base/{CkMessageActionButton → CkMessageButton}/types.ts +2 -2
  138. package/src/components/base/CkPromptInput/CkPromptInput.spec.ts +88 -0
  139. package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +6 -6
  140. package/src/components/base/CkPromptInput/CkPromptInput.vue +63 -12
  141. package/src/components/base/CkPromptInput/types.ts +4 -4
  142. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +1 -1
  143. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +1 -1
  144. package/src/components/base/CkPromptInputDisclaimer/types.ts +1 -1
  145. package/src/components/base/CkPromptSendButton/types.ts +1 -1
  146. package/src/components/base/CkPromptStopButton/types.ts +1 -1
  147. package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.spec.ts +49 -0
  148. package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.ts +63 -0
  149. package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue +43 -0
  150. package/src/components/base/CkRegenerateMessageButton/index.ts +2 -0
  151. package/src/components/base/CkRegenerateMessageButton/types.ts +27 -0
  152. package/src/components/base/index.ts +10 -2
  153. package/src/components/composed/CkConversation/CkConversation.spec.ts +81 -6
  154. package/src/components/composed/CkConversation/CkConversation.stories.ts +6 -6
  155. package/src/components/composed/CkConversation/CkConversation.vue +80 -37
  156. package/src/components/composed/CkConversation/types.ts +1 -1
  157. package/src/components/composed/index.ts +0 -1
  158. package/src/components/index.ts +9 -3
  159. package/src/handlers/useChatKitLabels.spec.ts +1 -0
  160. package/src/handlers/useChatKitLabels.ts +3 -0
  161. package/src/handlers/useMessageHandler.spec.ts +79 -0
  162. package/src/handlers/useMessageHandler.ts +50 -2
  163. package/src/helpers/conversationTree.spec.ts +120 -0
  164. package/src/helpers/conversationTree.ts +57 -8
  165. package/src/index.ts +8 -3
  166. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts.map +0 -1
  167. package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts.map +0 -1
  168. package/dist/components/base/CkCopyMessageActionButton/index.d.ts +0 -3
  169. package/dist/components/base/CkCopyMessageActionButton/types.d.ts.map +0 -1
  170. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts.map +0 -1
  171. package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts.map +0 -1
  172. package/dist/components/base/CkMessageActionButton/index.d.ts +0 -3
  173. package/dist/components/base/CkMessageActionButton/index.d.ts.map +0 -1
  174. package/dist/components/base/CkMessageActionButton/types.d.ts.map +0 -1
  175. package/dist/components/composed/CkInput/CkInput.stories.d.ts +0 -43
  176. package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +0 -1
  177. package/dist/components/composed/CkInput/CkInput.vue.d.ts +0 -50
  178. package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +0 -1
  179. package/dist/components/composed/CkInput/index.d.ts +0 -3
  180. package/dist/components/composed/CkInput/index.d.ts.map +0 -1
  181. package/dist/components/composed/CkInput/types.d.ts +0 -55
  182. package/dist/components/composed/CkInput/types.d.ts.map +0 -1
  183. package/src/components/base/CkCopyMessageActionButton/index.ts +0 -2
  184. package/src/components/base/CkMessageActionButton/index.ts +0 -2
  185. package/src/components/composed/CkInput/CkInput.spec.ts +0 -191
  186. package/src/components/composed/CkInput/CkInput.stories.ts +0 -434
  187. package/src/components/composed/CkInput/CkInput.vue +0 -106
  188. package/src/components/composed/CkInput/index.ts +0 -2
  189. 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
- // Tracks whether the current open was driven by the pointer (hover) rather than
47
- // the keyboard, so we can suppress the popover's focus management for hover only.
48
- const isPointerInteraction = ref(false);
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
- isPointerInteraction.value = true;
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
- // Suppress auto-focus for hover opens only (so hovering never steals focus or
78
- // scrolls). Keyboard opens keep it, moving focus into the card so the source
79
- // action is reachable.
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 (isPointerInteraction.value) event.preventDefault();
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 (the badge and external-link
96
- * action stay pinned, the card keeps its width); the full name stays available
97
- * on hover via the `title` attribute. The `truncate` behaviour holds for any
98
- * length, including a single unbroken token with no separators.
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-3">
45
+ <div class="flex items-center gap-2">
46
46
  <UiFileType :filename="fileName" class="shrink-0" />
47
47
  <p
48
- class="min-w-0 flex-1 truncate text-sm font-medium text-content-on-surface-primary"
48
+ class="min-w-0 truncate text-sm font-medium text-content-on-surface-primary"
49
49
  :title="fileName"
50
50
  >
51
51
  {{ fileName }}
@@ -181,7 +181,7 @@ watch(
181
181
  <template>
182
182
  <div
183
183
  ref="scrollContainerRef"
184
- class="relative flex flex-col gap-4 overflow-y-auto"
184
+ class="relative flex flex-col gap-8 overflow-y-auto"
185
185
  @scroll="handleScroll"
186
186
  >
187
187
  <div
@@ -1,10 +1,10 @@
1
- import CkCopyMessageActionButton from './CkCopyMessageActionButton.vue';
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('CkCopyMessageActionButton', () => {
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(CkCopyMessageActionButton, {
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(CkCopyMessageActionButton, {
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(CkCopyMessageActionButton, {
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(CkCopyMessageActionButton, {
70
+ render(CkCopyMessageButton, {
71
71
  props: { text: 'x', durationMs: 1500, writeToClipboard: writeText },
72
72
  });
73
73
 
@@ -1,9 +1,9 @@
1
- import CkCopyMessageActionButton from './CkCopyMessageActionButton.vue';
1
+ import CkCopyMessageButton from './CkCopyMessageButton.vue';
2
2
  import type { Meta, StoryObj } from '@storybook/vue3-vite';
3
3
 
4
- const meta: Meta<typeof CkCopyMessageActionButton> = {
5
- title: 'Base/CkCopyMessageActionButton',
6
- component: CkCopyMessageActionButton,
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 { CkCopyMessageActionButton } from '@aleph-alpha/chat-kit';
34
+ import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
35
35
  </script>
36
36
 
37
37
  <template>
38
- <CkCopyMessageActionButton text="Sample assistant reply text to copy." />
38
+ <CkCopyMessageButton text="Sample assistant reply text to copy." />
39
39
  </template>`;
40
40
 
41
41
  const disabledTemplateSource = `<script setup lang="ts">
42
- import { CkCopyMessageActionButton } from '@aleph-alpha/chat-kit';
42
+ import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
43
43
  </script>
44
44
 
45
45
  <template>
46
- <CkCopyMessageActionButton text="Cannot copy when disabled." disabled />
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 { CkMessageActionButton } from '../CkMessageActionButton';
3
- import type { CkCopyMessageActionButtonProps } from './types';
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: 'CkCopyMessageActionButton',
8
+ name: 'CkCopyMessageButton',
9
9
  });
10
10
 
11
- const props = withDefaults(defineProps<CkCopyMessageActionButtonProps>(), {
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
- <CkMessageActionButton
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
- </CkMessageActionButton>
86
+ </CkMessageButton>
87
87
  <span class="sr-only" aria-live="polite">
88
88
  {{ copied ? ariaLabelCopied : ariaLabelCopy }}
89
89
  </span>
@@ -0,0 +1,2 @@
1
+ export { default as CkCopyMessageButton } from './CkCopyMessageButton.vue';
2
+ export type * from './types';
@@ -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 CkMessageActionButton, CkAgentMessage, CkConversation
8
+ * @related CkMessageButton, CkAgentMessage, CkConversation
9
9
  */
10
- export interface CkCopyMessageActionButtonProps {
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 CkCopyMessageActionButtonEmits {
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 { CkMessageActionButton } from '../CkMessageActionButton';
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
- <CkMessageActionButton
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
- </CkMessageActionButton>
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
- <CkMessageActionButton
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
- </CkMessageActionButton>
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 CkMessageActionButton, CkCopyMessageActionButton, CkAgentMessage, CkConversation
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, CkMessageActionButton, CkAgentMessage,
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 count <= maxInline', () => {
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 count exceeds maxInline', () => {
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.getByRole('button', { name: /4 files/i });
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('uses an explicit triggerLabel when provided', () => {
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.getByRole('button')).toHaveTextContent('+1 more attachment');
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.getByRole('button');
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.getByRole('button', {
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.getByRole('button');
180
+ const trigger = await screen.findByRole('button');
106
181
  expect(trigger).toHaveTextContent('Uploaded documents');
107
182
  expect(trigger).toHaveTextContent('4 files');
108
183
  });