@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,27 @@
1
+ /**
2
+ * Regenerate action button for chat messages. Lets the user request a new
3
+ * response for an assistant message. Renders an icon-only button with an
4
+ * accessible label and tooltip.
5
+ *
6
+ * @category Actions
7
+ * @useCases regenerate assistant reply, retry message, request new response, chat message action
8
+ * @keywords regenerate, retry, repeat, action, message, button, icon
9
+ * @related CkMessageButton, CkCopyMessageButton, CkAgentMessage, CkConversation
10
+ */
11
+ export interface CkRegenerateMessageButtonProps {
12
+ /**
13
+ * Whether the button is disabled and ignores clicks.
14
+ * @default false
15
+ */
16
+ disabled?: boolean;
17
+ /**
18
+ * Accessible label and tooltip content for the button.
19
+ * @default 'Regenerate response'
20
+ */
21
+ ariaLabel?: string;
22
+ }
23
+
24
+ export interface CkRegenerateMessageButtonEmits {
25
+ /** Emitted when the button is pressed and not disabled. */
26
+ (e: 'regenerate'): void;
27
+ }
@@ -1,5 +1,12 @@
1
1
  export { CkSendIcon, CkStopIcon } from './icons';
2
2
  export { CkAgentMessage } from './CkAgentMessage';
3
+ export { CkAgentMessageHeader } from './CkAgentMessageHeader';
4
+ export type { CkAgentMessageHeaderProps } from './CkAgentMessageHeader';
5
+ export { CkHeaderActivityButton } from './CkHeaderActivityButton';
6
+ export type {
7
+ CkHeaderActivityButtonProps,
8
+ CkHeaderActivityButtonEmits,
9
+ } from './CkHeaderActivityButton';
3
10
  export { CkCitation } from './CkCitation';
4
11
  export type {
5
12
  CkCitationProps,
@@ -13,7 +20,7 @@ export type {
13
20
  } from './CkCitationCard';
14
21
  export { CkConversationLayout } from './CkConversationLayout';
15
22
  export type { CkConversationLayoutProps } from './CkConversationLayout';
16
- export { CkCopyMessageActionButton } from './CkCopyMessageActionButton';
23
+ export { CkCopyMessageButton } from './CkCopyMessageButton';
17
24
  export { CkDropZoneOverlay } from './CkDropZoneOverlay';
18
25
  export type {
19
26
  CkDropZoneOverlayProps,
@@ -50,7 +57,7 @@ export type {
50
57
  CkMarkdownRendererEmits,
51
58
  CkMarkdownElementClasses,
52
59
  } from './CkMarkdownRenderer';
53
- export { CkMessageActionButton } from './CkMessageActionButton';
60
+ export { CkMessageButton } from './CkMessageButton';
54
61
  export { CkModelSelector } from './CkModelSelector';
55
62
  export type {
56
63
  CkModelOption,
@@ -63,5 +70,6 @@ export type { CkPromptMenuButtonProps } from './CkPromptMenuButton';
63
70
  export { CkPromptStopButton } from './CkPromptStopButton';
64
71
  export { CkPromptInput } from './CkPromptInput';
65
72
  export { CkPromptInputDisclaimer } from './CkPromptInputDisclaimer';
73
+ export { CkRegenerateMessageButton } from './CkRegenerateMessageButton';
66
74
  export { CkUserMessage } from './CkUserMessage';
67
75
  export type { CkUserMessageProps } from './CkUserMessage';
@@ -137,7 +137,7 @@ describe('CkConversation', () => {
137
137
  });
138
138
 
139
139
  expect(
140
- screen.queryByTestId('ck-agent-message-icon'),
140
+ screen.queryByTestId('ck-agent-message-header-icon'),
141
141
  ).not.toBeInTheDocument();
142
142
  });
143
143
 
@@ -146,7 +146,9 @@ describe('CkConversation', () => {
146
146
  props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
147
147
  });
148
148
 
149
- expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
149
+ expect(
150
+ screen.getByTestId('ck-agent-message-header-icon'),
151
+ ).toBeInTheDocument();
150
152
  });
151
153
 
152
154
  it('shows the icon area when agentAvatarImage URL is provided', () => {
@@ -157,7 +159,9 @@ describe('CkConversation', () => {
157
159
  },
158
160
  });
159
161
 
160
- expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
162
+ expect(
163
+ screen.getByTestId('ck-agent-message-header-icon'),
164
+ ).toBeInTheDocument();
161
165
  });
162
166
 
163
167
  it('shows the icon area when agent-message-icon slot is provided', () => {
@@ -169,7 +173,9 @@ describe('CkConversation', () => {
169
173
  });
170
174
 
171
175
  expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
172
- expect(screen.getByTestId('ck-agent-message-icon')).toBeInTheDocument();
176
+ expect(
177
+ screen.getByTestId('ck-agent-message-header-icon'),
178
+ ).toBeInTheDocument();
173
179
  });
174
180
  });
175
181
 
@@ -360,15 +366,44 @@ describe('CkConversation', () => {
360
366
  expect(screen.getByAltText('custom-bot')).toBeInTheDocument();
361
367
  });
362
368
 
363
- it('allows adding a header via agent-message-header slot', () => {
369
+ it('allows replacing the entire header via agent-message-header slot', () => {
370
+ render(CkConversation, {
371
+ // An avatar fallback would normally render the default header icon...
372
+ props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
373
+ slots: {
374
+ 'agent-message-header':
375
+ '<div data-testid="custom-header">Custom Header</div>',
376
+ },
377
+ });
378
+
379
+ expect(screen.getByTestId('custom-header')).toBeInTheDocument();
380
+ // ...but the whole default header (and its icon) is replaced.
381
+ expect(
382
+ screen.queryByTestId('ck-agent-message-header-icon'),
383
+ ).not.toBeInTheDocument();
384
+ });
385
+
386
+ it('allows setting the header text via agent-message-header-content slot', () => {
364
387
  render(CkConversation, {
365
388
  props: { messages: [agentMessage()] },
366
- slots: { 'agent-message-header': 'Custom Header' },
389
+ slots: { 'agent-message-header-content': 'Custom Header' },
367
390
  });
368
391
 
369
392
  expect(screen.getByText('Custom Header')).toBeInTheDocument();
370
393
  });
371
394
 
395
+ it('allows adding a header activity control via agent-message-header-activity slot', () => {
396
+ render(CkConversation, {
397
+ props: { messages: [agentMessage()] },
398
+ slots: {
399
+ 'agent-message-header-activity':
400
+ '<button data-testid="activity">1 tool</button>',
401
+ },
402
+ });
403
+
404
+ expect(screen.getByTestId('activity')).toBeInTheDocument();
405
+ });
406
+
372
407
  it('allows adding footer actions via agent-message-footer slot', () => {
373
408
  render(CkConversation, {
374
409
  props: { messages: [agentMessage()] },
@@ -429,6 +464,46 @@ describe('CkConversation', () => {
429
464
  });
430
465
  });
431
466
 
467
+ describe('bottom separator', () => {
468
+ function agentRowBorders(container: Element): boolean[] {
469
+ const rows = container.querySelectorAll<HTMLElement>(
470
+ '[data-message-row="assistant"]',
471
+ );
472
+ return Array.from(rows).map((row) => {
473
+ const message = row.querySelector<HTMLElement>('[class*="pb-6"]');
474
+ return Boolean(message?.className.includes('border-b'));
475
+ });
476
+ }
477
+
478
+ it('keeps the separator on every assistant message except the last', () => {
479
+ const { container } = render(CkConversation, {
480
+ props: {
481
+ messages: [
482
+ { ...agentMessage(), id: 'a1' },
483
+ {
484
+ id: 'u1',
485
+ role: 'user',
486
+ content: [text('Hi')],
487
+ createdAt: now,
488
+ status: 'completed',
489
+ },
490
+ { ...agentMessage(), id: 'a2' },
491
+ ],
492
+ },
493
+ });
494
+
495
+ expect(agentRowBorders(container)).toEqual([true, false]);
496
+ });
497
+
498
+ it('hides the separator on the only assistant message', () => {
499
+ const { container } = render(CkConversation, {
500
+ props: { messages: [{ ...agentMessage(), id: 'solo' }] },
501
+ });
502
+
503
+ expect(agentRowBorders(container)).toEqual([false]);
504
+ });
505
+ });
506
+
432
507
  describe('empty conversation', () => {
433
508
  it('renders nothing when messages array is empty', () => {
434
509
  const { container } = render(CkConversation, { props: { messages: [] } });
@@ -1,4 +1,4 @@
1
- import { CkCopyMessageActionButton } from '../../base/CkCopyMessageActionButton';
1
+ import { CkCopyMessageButton } from '../../base/CkCopyMessageButton';
2
2
  import { CkFeedbackButton } from '../../base/CkFeedbackButton';
3
3
  import type { CkFeedbackValue } from '../../base/CkFeedbackButton';
4
4
  import type {
@@ -292,7 +292,7 @@ const withFooterActionsTemplateSource = `<script setup lang="ts">
292
292
  import { ref } from 'vue';
293
293
  import {
294
294
  CkConversation,
295
- CkCopyMessageActionButton,
295
+ CkCopyMessageButton,
296
296
  CkFeedbackButton,
297
297
  } from '@aleph-alpha/chat-kit';
298
298
  import type { BaseMessage, CkFeedbackValue } from '@aleph-alpha/chat-kit';
@@ -315,7 +315,7 @@ function onFeedback(messageId: string, value: CkFeedbackValue) {
315
315
  <template>
316
316
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
317
317
  <template #agent-message-footer="{ message }">
318
- <CkCopyMessageActionButton :text="getMessageText(message)" />
318
+ <CkCopyMessageButton :text="getMessageText(message)" />
319
319
  <CkFeedbackButton
320
320
  :value="feedback[message.id]?.value ?? null"
321
321
  :locked="feedback[message.id]?.locked ?? false"
@@ -590,7 +590,7 @@ export const WithCustomIcon: Story = {
590
590
 
591
591
  /**
592
592
  * Overrides `#agent-message-footer` to add real action buttons next to each
593
- * assistant reply: `CkCopyMessageActionButton` for clipboard copy and
593
+ * assistant reply: `CkCopyMessageButton` for clipboard copy and
594
594
  * `CkFeedbackButton` for binary thumbs up / thumbs down feedback. The
595
595
  * feedback state is owned by the host and locked once submitted.
596
596
  */
@@ -598,7 +598,7 @@ export const WithFooterActions: Story = {
598
598
  render: () => ({
599
599
  components: {
600
600
  CkConversation,
601
- CkCopyMessageActionButton,
601
+ CkCopyMessageButton,
602
602
  CkFeedbackButton,
603
603
  },
604
604
  setup() {
@@ -618,7 +618,7 @@ export const WithFooterActions: Story = {
618
618
  template: `
619
619
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
620
620
  <template #agent-message-footer="{ message }">
621
- <CkCopyMessageActionButton :text="getMessageText(message)" />
621
+ <CkCopyMessageButton :text="getMessageText(message)" />
622
622
  <CkFeedbackButton
623
623
  :value="feedback[message.id]?.value ?? null"
624
624
  :locked="feedback[message.id]?.locked ?? false"
@@ -2,8 +2,9 @@
2
2
  import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
3
3
  import type { BaseMessage, MessageTextContent } from '../../../types';
4
4
  import CkAgentMessage from '../../base/CkAgentMessage/CkAgentMessage.vue';
5
+ import CkAgentMessageHeader from '../../base/CkAgentMessageHeader/CkAgentMessageHeader.vue';
5
6
  import CkConversationLayout from '../../base/CkConversationLayout/CkConversationLayout.vue';
6
- import CkCopyMessageActionButton from '../../base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue';
7
+ import CkCopyMessageButton from '../../base/CkCopyMessageButton/CkCopyMessageButton.vue';
7
8
  import CkMarkdownRenderer from '../../base/CkMarkdownRenderer/CkMarkdownRenderer.vue';
8
9
  import CkUserMessage from '../../base/CkUserMessage/CkUserMessage.vue';
9
10
  import type { CkConversationProps } from './types';
@@ -34,8 +35,16 @@ const slots = defineSlots<{
34
35
  'agent-message-icon': (props: { message: BaseMessage }) => void;
35
36
  /** Replace the agent loading icon shown while a message is being generated */
36
37
  'agent-message-loading-icon': (props: { message: BaseMessage }) => void;
37
- /** Replace the agent message header */
38
+ /** Replace the entire agent message header (the whole `CkAgentMessageHeader`) */
38
39
  'agent-message-header': (props: { message: BaseMessage }) => void;
40
+ /** Replace the agent message header text (rendered inside the header row) */
41
+ 'agent-message-header-content': (props: { message: BaseMessage }) => void;
42
+ /**
43
+ * Provide an activity control for the agent message header (rendered on the
44
+ * right of the header, typically a `CkHeaderActivityButton`). When provided,
45
+ * the header gains vertical padding and a bottom border.
46
+ */
47
+ 'agent-message-header-activity': (props: { message: BaseMessage }) => void;
39
48
  /**
40
49
  * Replace the agent message body. Receives the full `BaseMessage` so the
41
50
  * consumer can inspect every content part (text, tool calls, reasoning)
@@ -64,6 +73,18 @@ const hasIcon = computed(
64
73
  const isLoading = (message: BaseMessage) =>
65
74
  message.status === 'started' || message.status === 'sending';
66
75
 
76
+ /**
77
+ * Id of the last assistant message. Its bottom separator is hidden so the
78
+ * border divides user/assistant pairs without a trailing line after the final
79
+ * pair.
80
+ */
81
+ const lastAgentMessageId = computed(() => {
82
+ for (let i = props.messages.length - 1; i >= 0; i--) {
83
+ if (props.messages[i].role !== 'user') return props.messages[i].id;
84
+ }
85
+ return undefined;
86
+ });
87
+
67
88
  /** Concatenated text from every text content part in a message. */
68
89
  function getMessageText(message: BaseMessage): string {
69
90
  return message.content
@@ -89,42 +110,64 @@ function getMessageText(message: BaseMessage): string {
89
110
 
90
111
  <template #agent-message="{ message }">
91
112
  <slot name="agent-message" :message="message">
92
- <CkAgentMessage>
93
- <template v-if="hasIcon" #icon>
94
- <template v-if="isLoading(message)">
95
- <slot name="agent-message-loading-icon" :message="message">
96
- <UiAvatar v-if="agentLoadingImage" class="size-6">
97
- <UiAvatarImage
98
- :src="agentLoadingImage"
99
- :alt="labels.agentLoadingAvatarAlt"
100
- />
101
- </UiAvatar>
102
- <UiSpinner
103
- v-else
104
- class="size-6"
105
- :aria-label="labels.agentLoadingAvatarAlt"
106
- />
107
- </slot>
108
- </template>
113
+ <CkAgentMessage :bottom-separator="message.id !== lastAgentMessageId">
114
+ <template
115
+ v-if="
116
+ hasIcon ||
117
+ $slots['agent-message-header'] ||
118
+ $slots['agent-message-header-content'] ||
119
+ $slots['agent-message-header-activity']
120
+ "
121
+ #header
122
+ >
123
+ <slot name="agent-message-header" :message="message">
124
+ <CkAgentMessageHeader>
125
+ <template v-if="hasIcon" #icon>
126
+ <template v-if="isLoading(message)">
127
+ <slot name="agent-message-loading-icon" :message="message">
128
+ <UiAvatar v-if="agentLoadingImage" class="size-8">
129
+ <UiAvatarImage
130
+ :src="agentLoadingImage"
131
+ :alt="labels.agentLoadingAvatarAlt"
132
+ />
133
+ </UiAvatar>
134
+ <UiSpinner
135
+ v-else
136
+ class="size-8"
137
+ :aria-label="labels.agentLoadingAvatarAlt"
138
+ />
139
+ </slot>
140
+ </template>
109
141
 
110
- <template v-else>
111
- <slot name="agent-message-icon" :message="message">
112
- <UiAvatar class="size-6">
113
- <UiAvatarImage
114
- v-if="agentAvatarImage"
115
- :src="agentAvatarImage"
116
- :alt="labels.agentAvatarAlt"
117
- />
118
- <UiAvatarFallback v-if="agentAvatarFallback">{{
119
- agentAvatarFallback
120
- }}</UiAvatarFallback>
121
- </UiAvatar>
122
- </slot>
123
- </template>
124
- </template>
142
+ <template v-else>
143
+ <slot name="agent-message-icon" :message="message">
144
+ <UiAvatar class="size-8">
145
+ <UiAvatarImage
146
+ v-if="agentAvatarImage"
147
+ :src="agentAvatarImage"
148
+ :alt="labels.agentAvatarAlt"
149
+ />
150
+ <UiAvatarFallback v-if="agentAvatarFallback">{{
151
+ agentAvatarFallback
152
+ }}</UiAvatarFallback>
153
+ </UiAvatar>
154
+ </slot>
155
+ </template>
156
+ </template>
157
+
158
+ <slot name="agent-message-header-content" :message="message" />
125
159
 
126
- <template v-if="$slots['agent-message-header']" #header>
127
- <slot name="agent-message-header" :message="message" />
160
+ <template
161
+ v-if="$slots['agent-message-header-activity']"
162
+ #activity
163
+ >
164
+ <slot
165
+ name="agent-message-header-activity"
166
+ :message="message"
167
+ />
168
+ </template>
169
+ </CkAgentMessageHeader>
170
+ </slot>
128
171
  </template>
129
172
 
130
173
  <slot name="agent-message-content" :message="message">
@@ -143,7 +186,7 @@ function getMessageText(message: BaseMessage): string {
143
186
 
144
187
  <template #footer>
145
188
  <slot name="agent-message-footer" :message="message">
146
- <CkCopyMessageActionButton
189
+ <CkCopyMessageButton
147
190
  v-if="getMessageText(message) && !isLoading(message)"
148
191
  :text="getMessageText(message)"
149
192
  :aria-label-copy="labels.copyMessageAction"
@@ -10,7 +10,7 @@ import type { CkMarkdownElementClasses } from '../../base/CkMarkdownRenderer';
10
10
  * @category Messages
11
11
  * @useCases complete chat conversation ui, streaming chat with markdown, assistant chat view, llm conversation component, drop-in chat surface
12
12
  * @keywords conversation, chat, messages, assistant, agent, user, markdown, streaming, avatar
13
- * @related CkConversationLayout, CkAgentMessage, CkUserMessage, CkMarkdownRenderer, CkCopyMessageActionButton, CkInput
13
+ * @related CkConversationLayout, CkAgentMessage, CkUserMessage, CkMarkdownRenderer, CkCopyMessageButton, CkPromptInput
14
14
  */
15
15
  export interface CkConversationProps {
16
16
  /** List of messages to display, in chronological order. */
@@ -2,4 +2,3 @@ export { CkConversation } from './CkConversation';
2
2
  export type { CkConversationProps } from './CkConversation';
3
3
  export { CkHistory } from './CkHistory';
4
4
  export type { CkHistoryEmits, CkHistoryProps } from './CkHistory';
5
- export { CkInput } from './CkInput';
@@ -1,10 +1,12 @@
1
1
  // Base (presentational) components
2
2
  export {
3
3
  CkAgentMessage,
4
+ CkAgentMessageHeader,
5
+ CkHeaderActivityButton,
4
6
  CkCitation,
5
7
  CkCitationCard,
6
8
  CkConversationLayout,
7
- CkCopyMessageActionButton,
9
+ CkCopyMessageButton,
8
10
  CkDropZoneOverlay,
9
11
  CkFeedbackButton,
10
12
  CkFeedbackDialog,
@@ -12,18 +14,22 @@ export {
12
14
  CkHistoryItem,
13
15
  CkHistoryWrapper,
14
16
  CkMarkdownRenderer,
15
- CkMessageActionButton,
17
+ CkMessageButton,
16
18
  CkModelSelector,
17
19
  CkPromptSendButton,
18
20
  CkPromptMenuButton,
19
21
  CkPromptStopButton,
20
22
  CkPromptInput,
21
23
  CkPromptInputDisclaimer,
24
+ CkRegenerateMessageButton,
22
25
  CkSendIcon,
23
26
  CkStopIcon,
24
27
  CkUserMessage,
25
28
  } from './base';
26
29
  export type {
30
+ CkAgentMessageHeaderProps,
31
+ CkHeaderActivityButtonProps,
32
+ CkHeaderActivityButtonEmits,
27
33
  CkCitationProps,
28
34
  CkCitationEmits,
29
35
  CkCitationSource,
@@ -58,7 +64,7 @@ export type {
58
64
  } from './base';
59
65
 
60
66
  // Composed (pattern) components
61
- export { CkConversation, CkHistory, CkInput } from './composed';
67
+ export { CkConversation, CkHistory } from './composed';
62
68
  export type {
63
69
  CkConversationProps,
64
70
  CkHistoryEmits,
@@ -102,6 +102,7 @@ describe('useChatKitLabels', () => {
102
102
  inputPlaceholder: 'Custom placeholder',
103
103
  copyMessageAction: 'Custom copy',
104
104
  copyMessageActionCopied: 'Custom copied',
105
+ regenerateMessageAction: 'Custom regenerate',
105
106
  feedbackThumbsUp: 'Custom up',
106
107
  feedbackThumbsDown: 'Custom down',
107
108
  feedbackGroup: 'Custom group',
@@ -29,6 +29,8 @@ export type ChatKitLabels = {
29
29
  copyMessageAction: string;
30
30
  /** Accessible label / tooltip for the copy-message action button after a successful copy */
31
31
  copyMessageActionCopied: string;
32
+ /** Accessible label / tooltip for the regenerate-message action button */
33
+ regenerateMessageAction: string;
32
34
  /**
33
35
  * Accessible label / tooltip for the thumbs-up button in `CkFeedbackButton`.
34
36
  * Optional for backward compatibility; falls back to
@@ -114,6 +116,7 @@ export const defaultChatKitLabels: ChatKitLabels = {
114
116
  inputPlaceholder: 'Type a message...',
115
117
  copyMessageAction: 'Copy response',
116
118
  copyMessageActionCopied: 'Copied!',
119
+ regenerateMessageAction: 'Regenerate response',
117
120
  feedbackThumbsUp: 'Good response',
118
121
  feedbackThumbsDown: 'Bad response',
119
122
  feedbackGroup: 'Rate this response',
@@ -146,6 +146,36 @@ describe('useMessageHandler', () => {
146
146
  expect('metadata' in msg).toBe(false);
147
147
  expect('metadata' in handler.messages.value[0]).toBe(false);
148
148
  });
149
+
150
+ it('branches off an explicit parentId, updating the parent and activating the new branch', () => {
151
+ const handler = useMessageHandler();
152
+
153
+ handler.setCurrentUserMessage('First');
154
+ const first = handler.commitUserMessage();
155
+ handler.setCurrentUserMessage('Second');
156
+ const second = handler.commitUserMessage();
157
+
158
+ // Branch a sibling of `second` off their shared parent `first`.
159
+ handler.setCurrentUserMessage('Sibling');
160
+ const sibling = handler.commitUserMessage({ parentId: first.id });
161
+
162
+ const parent = handler.getTree().nodes[first.id];
163
+ expect(parent.childrenIds).toEqual([second.id, sibling.id]);
164
+ // The freshly created branch becomes the active path.
165
+ expect(parent.activeChildIndex).toBe(1);
166
+ expect(handler.messages.value.map((m) => m.id)).toEqual([
167
+ first.id,
168
+ sibling.id,
169
+ ]);
170
+ });
171
+
172
+ it('throws when branching off an unknown parentId', () => {
173
+ const handler = useMessageHandler();
174
+ handler.setCurrentUserMessage('Hello');
175
+ expect(() =>
176
+ handler.commitUserMessage({ parentId: 'does-not-exist' }),
177
+ ).toThrow('Unknown parent message id: does-not-exist');
178
+ });
149
179
  });
150
180
 
151
181
  describe('updateMessage', () => {
@@ -342,6 +372,55 @@ describe('useMessageHandler', () => {
342
372
  expect(handler.messages.value).toHaveLength(2);
343
373
  expect(getText(handler.messages.value[1].content)).toBe('Streaming...');
344
374
  });
375
+
376
+ it('branches a new node off an explicit parentId on create', () => {
377
+ const handler = useMessageHandler();
378
+ handler.setTree(
379
+ makeTree(
380
+ {
381
+ u1: makeTreeMessage({ id: 'u1', content: [text('Hello')] }),
382
+ a1: makeTreeMessage({
383
+ id: 'a1',
384
+ parentId: 'u1',
385
+ role: 'assistant',
386
+ content: [text('Hi')],
387
+ }),
388
+ },
389
+ 'u1',
390
+ ),
391
+ );
392
+
393
+ // Regenerate: add a sibling assistant answer under the same user turn.
394
+ handler.updateMessage({
395
+ id: 'a2',
396
+ content: [text('Hi again')],
397
+ role: 'assistant',
398
+ status: 'completed',
399
+ parentId: 'u1',
400
+ });
401
+
402
+ const parent = handler.getTree().nodes.u1;
403
+ expect(parent.childrenIds).toEqual(['a1', 'a2']);
404
+ expect(parent.activeChildIndex).toBe(1);
405
+ expect(handler.messages.value.map((m) => m.id)).toEqual(['u1', 'a2']);
406
+ });
407
+
408
+ it('ignores parentId when the node already exists', () => {
409
+ const handler = useMessageHandler();
410
+ handler.setCurrentUserMessage('Hello');
411
+ const msg = handler.commitUserMessage();
412
+
413
+ handler.updateMessage({
414
+ id: msg.id,
415
+ content: [text('Updated')],
416
+ role: 'user',
417
+ status: 'completed',
418
+ parentId: 'does-not-exist',
419
+ });
420
+
421
+ expect(getText(handler.getMessageById(msg.id)!.content)).toBe('Updated');
422
+ expect(handler.getTree().nodes[msg.id].parentId).toBeNull();
423
+ });
345
424
  });
346
425
 
347
426
  describe('getMessageById', () => {