@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,36 @@
1
+ /**
2
+ * Compact, ghost-styled button shown on the right of an agent message header
3
+ * (see `CkAgentMessageHeader`). Renders a short label followed by a trailing
4
+ * icon (an arrow by default) and emits a generic `click` event — it performs
5
+ * no action of its own, leaving the consumer to decide what happens (e.g. open
6
+ * an activity / chain-of-thought panel).
7
+ *
8
+ * @category Actions
9
+ * @useCases agent message header action, open activity panel, chain-of-thought summary toggle, tool/source indicator
10
+ * @keywords header, activity, action, button, ghost, arrow, agent, message
11
+ * @related CkAgentMessageHeader, CkAgentMessage, CkConversation, CkMessageButton
12
+ */
13
+ export interface CkHeaderActivityButtonProps {
14
+ /**
15
+ * Whether the button is disabled and ignores clicks.
16
+ * @default false
17
+ */
18
+ disabled?: boolean;
19
+ /**
20
+ * Accessible label for the button. Defaults to the rendered label text when
21
+ * omitted, but should be set when the slot content is not descriptive on its
22
+ * own.
23
+ */
24
+ ariaLabel?: string;
25
+ /**
26
+ * Trailing [Lucide](https://lucide.dev/icons) icon name shown after the
27
+ * label. Set to an empty string to hide the icon.
28
+ * @default 'arrow-right'
29
+ */
30
+ icon?: string;
31
+ }
32
+
33
+ export interface CkHeaderActivityButtonEmits {
34
+ /** Emitted when the button is pressed and not disabled. */
35
+ (e: 'click'): void;
36
+ }
@@ -1,10 +1,10 @@
1
- import CkMessageActionButton from './CkMessageActionButton.vue';
1
+ import CkMessageButton from './CkMessageButton.vue';
2
2
  import { render, screen } from '@testing-library/vue';
3
3
  import { describe, expect, it } from 'vitest';
4
4
 
5
- describe('CkMessageActionButton', () => {
5
+ describe('CkMessageButton', () => {
6
6
  it('renders slot content inside the button', () => {
7
- render(CkMessageActionButton, {
7
+ render(CkMessageButton, {
8
8
  props: { ariaLabel: 'Action' },
9
9
  slots: { default: '<svg data-testid="icon"></svg>' },
10
10
  });
@@ -13,7 +13,7 @@ describe('CkMessageActionButton', () => {
13
13
  });
14
14
 
15
15
  it('sets the accessible label on the button', () => {
16
- render(CkMessageActionButton, {
16
+ render(CkMessageButton, {
17
17
  props: { ariaLabel: 'Copy message' },
18
18
  });
19
19
 
@@ -23,7 +23,7 @@ describe('CkMessageActionButton', () => {
23
23
  });
24
24
 
25
25
  it('passes disabled state to the button', () => {
26
- render(CkMessageActionButton, {
26
+ render(CkMessageButton, {
27
27
  props: { ariaLabel: 'Action', disabled: true },
28
28
  });
29
29
 
@@ -31,7 +31,7 @@ describe('CkMessageActionButton', () => {
31
31
  });
32
32
 
33
33
  it('defaults to enabled', () => {
34
- render(CkMessageActionButton, {
34
+ render(CkMessageButton, {
35
35
  props: { ariaLabel: 'Action' },
36
36
  });
37
37
 
@@ -1,10 +1,10 @@
1
- import CkMessageActionButton from './CkMessageActionButton.vue';
1
+ import CkMessageButton from './CkMessageButton.vue';
2
2
  import type { Meta, StoryObj } from '@storybook/vue3-vite';
3
3
  import { UiIcon } from '@aleph-alpha/ui-library';
4
4
 
5
- const meta: Meta<typeof CkMessageActionButton> = {
6
- title: 'Base/CkMessageActionButton',
7
- component: CkMessageActionButton,
5
+ const meta: Meta<typeof CkMessageButton> = {
6
+ title: 'Base/CkMessageButton',
7
+ component: CkMessageButton,
8
8
  tags: ['autodocs'],
9
9
  argTypes: {
10
10
  ariaLabel: {
@@ -29,14 +29,14 @@ export default meta;
29
29
  type Story = StoryObj<typeof meta>;
30
30
 
31
31
  const defaultTemplateSource = `<script setup lang="ts">
32
- import { CkMessageActionButton } from '@aleph-alpha/chat-kit';
32
+ import { CkMessageButton } from '@aleph-alpha/chat-kit';
33
33
  import { UiIcon } from '@aleph-alpha/ui-library';
34
34
  </script>
35
35
 
36
36
  <template>
37
- <CkMessageActionButton ariaLabel="Copy message">
37
+ <CkMessageButton ariaLabel="Copy message">
38
38
  <UiIcon name="copy" :size="16" aria-hidden="true" />
39
- </CkMessageActionButton>
39
+ </CkMessageButton>
40
40
  </template>`;
41
41
 
42
42
  /**
@@ -45,11 +45,11 @@ import { UiIcon } from '@aleph-alpha/ui-library';
45
45
  */
46
46
  export const Default: Story = {
47
47
  render: () => ({
48
- components: { CkMessageActionButton, UiIcon },
48
+ components: { CkMessageButton, UiIcon },
49
49
  template: `
50
- <CkMessageActionButton ariaLabel="Copy message">
50
+ <CkMessageButton ariaLabel="Copy message">
51
51
  <UiIcon name="copy" :size="16" aria-hidden="true" />
52
- </CkMessageActionButton>
52
+ </CkMessageButton>
53
53
  `,
54
54
  }),
55
55
  parameters: { docs: { source: { code: defaultTemplateSource } } },
@@ -1,12 +1,12 @@
1
1
  <script setup lang="ts">
2
- import type { CkMessageActionButtonProps } from './types';
2
+ import type { CkMessageButtonProps } from './types';
3
3
  import { UiButton } from '@aleph-alpha/ui-library';
4
4
 
5
5
  defineOptions({
6
- name: 'CkMessageActionButton',
6
+ name: 'CkMessageButton',
7
7
  });
8
8
 
9
- withDefaults(defineProps<CkMessageActionButtonProps>(), {
9
+ withDefaults(defineProps<CkMessageButtonProps>(), {
10
10
  disabled: false,
11
11
  });
12
12
  </script>
@@ -0,0 +1,2 @@
1
+ export { default as CkMessageButton } from './CkMessageButton.vue';
2
+ export type * from './types';
@@ -5,9 +5,9 @@
5
5
  * @category Actions
6
6
  * @useCases message footer action, like/dislike reaction, share button on reply, secondary message action
7
7
  * @keywords icon button, action, message, ghost, accessible, aria-label
8
- * @related CkCopyMessageActionButton, CkAgentMessage, CkConversation
8
+ * @related CkCopyMessageButton, CkAgentMessage, CkConversation
9
9
  */
10
- export interface CkMessageActionButtonProps {
10
+ export interface CkMessageButtonProps {
11
11
  /**
12
12
  * Accessible label for the button. **Required** because the button only
13
13
  * renders an icon.
@@ -199,6 +199,94 @@ describe('CkPromptInput', () => {
199
199
  });
200
200
  });
201
201
 
202
+ // The focus highlight (border + ring) must appear for keyboard navigation
203
+ // only — not when the textarea is focused by a mouse click. The component
204
+ // distinguishes the two by remembering whether a pointerdown immediately
205
+ // preceded the focus event.
206
+ describe('keyboard vs mouse focus highlight', () => {
207
+ const HIGHLIGHT_CLASS = 'ring-2';
208
+
209
+ it('shows the highlight when focused via keyboard (no preceding pointer)', async () => {
210
+ const { container } = render(CkPromptInput);
211
+
212
+ await fireEvent.focus(screen.getByRole('textbox'));
213
+
214
+ const form = container.querySelector('form');
215
+ expect(form?.className).toContain(HIGHLIGHT_CLASS);
216
+ expect(form?.className).toContain('border-border-surface-focus-ring');
217
+ });
218
+
219
+ it('does NOT show the highlight when focused via mouse click', async () => {
220
+ const { container } = render(CkPromptInput);
221
+ const textarea = screen.getByRole('textbox');
222
+
223
+ // A real mouse click fires pointerdown before focus.
224
+ await fireEvent.pointerDown(textarea);
225
+ await fireEvent.focus(textarea);
226
+
227
+ expect(container.querySelector('form')?.className).not.toContain(
228
+ HIGHLIGHT_CLASS,
229
+ );
230
+ });
231
+
232
+ it('removes the highlight on blur', async () => {
233
+ const { container } = render(CkPromptInput);
234
+ const textarea = screen.getByRole('textbox');
235
+
236
+ await fireEvent.focus(textarea);
237
+ expect(container.querySelector('form')?.className).toContain(
238
+ HIGHLIGHT_CLASS,
239
+ );
240
+
241
+ await fireEvent.blur(textarea);
242
+ expect(container.querySelector('form')?.className).not.toContain(
243
+ HIGHLIGHT_CLASS,
244
+ );
245
+ });
246
+
247
+ it('shows the highlight on a later keyboard focus after a prior mouse focus', async () => {
248
+ const { container } = render(CkPromptInput);
249
+ const textarea = screen.getByRole('textbox');
250
+
251
+ // First interaction: mouse click → no highlight.
252
+ await fireEvent.pointerDown(textarea);
253
+ await fireEvent.focus(textarea);
254
+ expect(container.querySelector('form')?.className).not.toContain(
255
+ HIGHLIGHT_CLASS,
256
+ );
257
+ await fireEvent.blur(textarea);
258
+
259
+ // Second interaction: tab in (focus with no preceding pointerdown). The
260
+ // pointer flag must have reset, so the highlight returns.
261
+ await fireEvent.focus(textarea);
262
+ expect(container.querySelector('form')?.className).toContain(
263
+ HIGHLIGHT_CLASS,
264
+ );
265
+ });
266
+
267
+ it('is not misclassified by a pointerdown inside an already-focused field', async () => {
268
+ const { container } = render(CkPromptInput);
269
+ const textarea = screen.getByRole('textbox');
270
+
271
+ // Keyboard focus → highlight on.
272
+ await fireEvent.focus(textarea);
273
+ expect(container.querySelector('form')?.className).toContain(
274
+ HIGHLIGHT_CLASS,
275
+ );
276
+
277
+ // Click inside the already-focused field: pointerdown fires with no
278
+ // following focus, which would otherwise leave the pointer flag stale.
279
+ await fireEvent.pointerDown(textarea);
280
+
281
+ // Blur then keyboard-focus again — the stale flag must not suppress it.
282
+ await fireEvent.blur(textarea);
283
+ await fireEvent.focus(textarea);
284
+ expect(container.querySelector('form')?.className).toContain(
285
+ HIGHLIGHT_CLASS,
286
+ );
287
+ });
288
+ });
289
+
202
290
  // The form switches between compact (single-row, rounded-3xl) and stacked
203
291
  // (multi-row, rounded-2xl) layouts based on (a) the presence of the
204
292
  // `toolbar-top` slot and (b) whether the textarea content exceeds one line.
@@ -657,8 +657,8 @@ function onSubmit(value: string): void {
657
657
  * End-to-end composition matching the Figma "Prompt field" spec: file
658
658
  * attachments via the `+` button and drag-and-drop, model selection next to
659
659
  * the send button, and a hallucination disclaimer in the footer. File chips
660
- * collapse to a popover once more than three are attached (`CkFileItemsList`
661
- * default).
660
+ * collapse to a popover once they no longer fit the prompt field's width
661
+ * (`CkFileItemsList` keeps each chip at least `minItemWidth` wide).
662
662
  */
663
663
  export const WithUploadAndModelSelection: Story = {
664
664
  render: () => ({
@@ -955,10 +955,10 @@ function removeFile(id: CkFileEntry['id']) {
955
955
  </template>`;
956
956
 
957
957
  /**
958
- * Five attached files — past the default `maxInline=3` threshold of
959
- * `CkFileItemsList`, so the list collapses to a single popover trigger
960
- * labeled "5 files". Click it to inspect / remove the individual items.
961
- * The toolbar-left `+` menu stays available so users can keep adding more.
958
+ * Five attached files — more than fit inline at the prompt field's width, so
959
+ * `CkFileItemsList` collapses to a single popover trigger labeled "5 files".
960
+ * Click it to inspect / remove the individual items. The toolbar-left `+` menu
961
+ * stays available so users can keep adding more.
962
962
  */
963
963
  export const WithCollapsedFileChips: Story = {
964
964
  render: () => ({
@@ -14,13 +14,8 @@ import {
14
14
 
15
15
  defineOptions({ name: 'CkPromptInput' });
16
16
 
17
- // Pixel height of one rendered line — must match the textarea's `leading-6`
18
- // (24 px). Used to derive how many lines the current content occupies.
19
- const LINE_HEIGHT = 24;
20
-
21
17
  // Cap the textarea at 6 visible lines; content beyond that scrolls inside.
22
18
  const MAX_LINES = 6;
23
- const MAX_HEIGHT = LINE_HEIGHT * MAX_LINES;
24
19
 
25
20
  const props = withDefaults(defineProps<CkPromptInputProps>(), {
26
21
  modelValue: '',
@@ -84,19 +79,25 @@ function adjustHeight(): void {
84
79
  return;
85
80
  }
86
81
 
82
+ // Line height is fluid (it scales with the container via `clamp()`), so read
83
+ // the resolved px value from the DOM instead of a hard-coded constant — the
84
+ // 6-line cap and the 2-line latch threshold both depend on it.
85
+ const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 24;
86
+ const maxHeight = lineHeight * MAX_LINES;
87
+
87
88
  // Collapse to natural content height first so the next scrollHeight read
88
89
  // reflects what the content needs, not what the inline height is forcing.
89
90
  el.style.height = 'auto';
90
91
  const measured = el.scrollHeight;
91
- el.style.height = `${Math.min(measured, MAX_HEIGHT)}px`;
92
+ el.style.height = `${Math.min(measured, maxHeight)}px`;
92
93
 
93
94
  // Latch only on a *real* second line: either explicit newline OR scrollHeight
94
- // ≥ 2 × line-height. Using `Math.round` on a borderline 36 px (sub-pixel
95
- // emoji/CJK rendering on Chromium/Linux) would falsely latch a single line.
95
+ // ≥ 2 × line-height. Using `Math.round` on a borderline single line (sub-pixel
96
+ // emoji/CJK rendering on Chromium/Linux) would falsely latch it.
96
97
  const hasExplicitNewline = props.modelValue.includes('\n');
97
98
  if (
98
99
  !isMultiLine.value &&
99
- (hasExplicitNewline || measured >= LINE_HEIGHT * 2)
100
+ (hasExplicitNewline || measured >= lineHeight * 2)
100
101
  ) {
101
102
  isMultiLine.value = true;
102
103
  }
@@ -124,6 +125,28 @@ function focus(): void {
124
125
  textareaRef.value?.focus();
125
126
  }
126
127
 
128
+ // Highlight (border + ring) should appear for keyboard navigation only, not
129
+ // mouse clicks. `:focus-visible` can't express this on a textarea — browsers
130
+ // always treat text fields as focus-visible since they take keyboard input —
131
+ // so we track the focus origin ourselves: a pointerdown immediately preceding
132
+ // a focus means the mouse caused it, so suppress the highlight.
133
+ const isKeyboardFocused = ref(false);
134
+ let focusedByPointer = false;
135
+
136
+ function onPointerDown(): void {
137
+ focusedByPointer = true;
138
+ }
139
+
140
+ function onFocus(): void {
141
+ isKeyboardFocused.value = !focusedByPointer;
142
+ focusedByPointer = false;
143
+ }
144
+
145
+ function onBlur(): void {
146
+ isKeyboardFocused.value = false;
147
+ focusedByPointer = false;
148
+ }
149
+
127
150
  watch(
128
151
  () => props.modelValue,
129
152
  () => nextTick(adjustHeight),
@@ -134,11 +157,14 @@ defineExpose({ focus, submit });
134
157
  </script>
135
158
 
136
159
  <template>
137
- <div class="flex flex-col gap-2">
160
+ <div class="ck-prompt-root flex flex-col gap-2">
138
161
  <form
139
162
  :class="[
140
- 'ck-prompt-form border-border-surface-default bg-background-input-strong [&:has(textarea:focus)]:border-border-surface-focus-ring [&:has(textarea:focus)]:ring-border-surface-focus-ring/40 min-h-17 grid items-center gap-x-0 gap-y-5 border p-4 transition-[border-radius] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] [&:has(textarea:focus)]:ring-2',
163
+ 'border-border-surface-default bg-background-input-strong min-h-17 grid items-center gap-x-0 gap-y-5 border p-4 transition-[border-radius] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] ',
141
164
  hasMultipleRows ? 'rounded-2xl' : 'rounded-3xl',
165
+ isKeyboardFocused
166
+ ? 'border-border-surface-focus-ring ring-border-surface-focus-ring/40 ring-2'
167
+ : 'ck-prompt-form',
142
168
  ]"
143
169
  :style="{
144
170
  gridTemplateColumns: 'auto 1fr auto',
@@ -164,11 +190,14 @@ defineExpose({ focus, submit });
164
190
  :disabled="disabled"
165
191
  style="grid-area: input"
166
192
  :class="[
167
- 'ck-prompt-textarea text-content-on-surface-primary placeholder-content-on-surface-muted w-full min-w-0 resize-none overflow-y-auto bg-transparent py-0 font-sans text-base font-normal leading-6 max-h-36 focus:outline-none disabled:cursor-not-allowed disabled:opacity-60',
193
+ 'ck-prompt-textarea text-content-on-surface-primary placeholder-content-on-surface-muted w-full min-w-0 resize-none overflow-y-auto bg-transparent py-0 font-sans font-normal focus:outline-none disabled:cursor-not-allowed disabled:opacity-60',
168
194
  isMultiLine ? 'pl-3 pr-3' : 'px-0',
169
195
  ]"
170
196
  @input="onInput"
171
197
  @keydown="onKeydown"
198
+ @pointerdown="onPointerDown"
199
+ @focus="onFocus"
200
+ @blur="onBlur"
172
201
  />
173
202
  <div :class="[!isMultiLine && 'pl-4']" style="grid-area: right">
174
203
  <slot
@@ -184,15 +213,37 @@ defineExpose({ focus, submit });
184
213
  </template>
185
214
 
186
215
  <style scoped>
216
+ /* Fluid mobile↔desktop sizing. Sizes interpolate against the component's own
217
+ container width (`cqw`), gradually transforming between the Figma Mobile
218
+ variant (≤320px container) and the Desktop variant (≥768px container) rather
219
+ than snapping at a breakpoint. Range = 768 − 320 = 448; each term is
220
+ `clamp(mobile, calc(mobile + (100cqw − 320px) × span / 448), desktop)`. */
221
+ .ck-prompt-root {
222
+ container-type: inline-size;
223
+ }
224
+
187
225
  /* Solea two-layer drop shadow — warm grey + subtle orange tint. Matches the
188
226
  `shadow/md/{1,2}` tokens from the design system; the generic `shadow-md`
189
227
  utility from preset-wind4 uses a cold black-based shadow instead. */
190
228
  .ck-prompt-form {
229
+ /* 8px (mobile) → 16px (desktop) */
230
+ padding: clamp(8px, calc(8px + (100cqw - 320px) * 8 / 448), 16px);
231
+ /* 52px → 68px (= 36px button + 2× padding) */
232
+ min-height: clamp(52px, calc(52px + (100cqw - 320px) * 16 / 448), 68px);
191
233
  box-shadow:
192
234
  0 4px 6px -2px rgba(112, 95, 90, 0.1),
193
235
  0 6px 14px -2px rgba(248, 94, 43, 0.05);
194
236
  }
195
237
 
238
+ /* 14px → 16px font, 20px → 24px line-height, 120px → 144px (6 lines) cap.
239
+ `adjustHeight` reads the resolved line-height from the DOM so the auto-grow
240
+ math tracks these fluid values. */
241
+ .ck-prompt-textarea {
242
+ font-size: clamp(14px, calc(14px + (100cqw - 320px) * 2 / 448), 16px);
243
+ line-height: clamp(20px, calc(20px + (100cqw - 320px) * 4 / 448), 24px);
244
+ max-height: clamp(120px, calc(120px + (100cqw - 320px) * 24 / 448), 144px);
245
+ }
246
+
196
247
  /* Slim styled scrollbar for the multi-line textarea — matches Figma's 4 px
197
248
  muted-default track. Firefox uses the native `scrollbar-*` properties;
198
249
  Chromium/WebKit needs the `::-webkit-scrollbar` pseudo-elements. */
@@ -4,14 +4,14 @@
4
4
  * slots and keyboard submission (Enter to submit, Shift+Enter for newline).
5
5
  * The form layout adapts between a compact single-row variant (large 3xl
6
6
  * radius) and a stacked multi-row variant (2xl radius) automatically — driven
7
- * by content height and whether `toolbar-top` is filled. Use when you need
8
- * full control over the prompt layout; for a turnkey chat input use `CkInput`
9
- * instead.
7
+ * by content height and whether `toolbar-top` is filled. Compose it with
8
+ * `CkPromptSendButton`, `CkPromptStopButton`, and `CkPromptInputDisclaimer`
9
+ * to build a complete chat input.
10
10
  *
11
11
  * @category Form Inputs
12
12
  * @useCases raw chat prompt textarea, custom prompt toolbar, auto-growing compose box, file-attachment chip row, multi-line chat input, keyboard submit
13
13
  * @keywords prompt, input, textarea, chat, compose, submit, v-model, auto-grow, multi-line, toolbar
14
- * @related CkInput, CkPromptSendButton, CkPromptStopButton, CkPromptMenuButton, CkPromptInputDisclaimer, CkFileItemsList
14
+ * @related CkPromptSendButton, CkPromptStopButton, CkPromptMenuButton, CkPromptInputDisclaimer, CkFileItemsList
15
15
  */
16
16
  export interface CkPromptInputProps {
17
17
  /**
@@ -33,7 +33,7 @@ describe('CkPromptInputDisclaimer', () => {
33
33
 
34
34
  const el = screen.getByText('Disclaimer');
35
35
  expect(el.tagName).toBe('P');
36
- expect(el.className).toContain('text-core-content-tertiary');
36
+ expect(el.className).toContain('text-content-on-surface-muted');
37
37
  expect(el.className).toContain('text-center');
38
38
  expect(el.className).toContain('text-xs');
39
39
  });
@@ -9,7 +9,7 @@ defineProps<CkPromptInputDisclaimerProps>();
9
9
  <template>
10
10
  <p
11
11
  v-if="$slots.default || text"
12
- class="text-core-content-tertiary text-center text-xs leading-3.5"
12
+ class="text-center text-content-on-surface-muted text-xs font-normal leading-4"
13
13
  >
14
14
  <slot>{{ text }}</slot>
15
15
  </p>
@@ -6,7 +6,7 @@
6
6
  * @category Feedback
7
7
  * @useCases chat disclaimer, ai may make mistakes, prompt footnote, small help text under input
8
8
  * @keywords disclaimer, notice, footer, help text, ai, prompt
9
- * @related CkInput, CkPromptInput
9
+ * @related CkPromptInput, CkPromptSendButton
10
10
  */
11
11
  export interface CkPromptInputDisclaimerProps {
12
12
  /**
@@ -7,7 +7,7 @@
7
7
  * @category Actions
8
8
  * @useCases send message button, submit prompt, primary chat send action, loading send button, disabled send button
9
9
  * @keywords prompt, send, submit, button, icon, loading, brand
10
- * @related CkPromptInput, CkPromptStopButton, CkInput, CkSendIcon
10
+ * @related CkPromptInput, CkPromptStopButton, CkSendIcon
11
11
  */
12
12
  export interface CkPromptSendButtonProps {
13
13
  /**
@@ -8,7 +8,7 @@
8
8
  * @category Actions
9
9
  * @useCases stop assistant generation, cancel streaming reply, abort in-flight request, halt llm response
10
10
  * @keywords stop, cancel, abort, prompt, button, icon, streaming, secondary
11
- * @related CkPromptSendButton, CkPromptMenuButton, CkPromptInput, CkInput, CkStopIcon
11
+ * @related CkPromptSendButton, CkPromptMenuButton, CkPromptInput, CkStopIcon
12
12
  */
13
13
  export interface CkPromptStopButtonProps {
14
14
  /**
@@ -0,0 +1,49 @@
1
+ import CkRegenerateMessageButton from './CkRegenerateMessageButton.vue';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/vue';
4
+ import { describe, expect, it } from 'vitest';
5
+
6
+ describe('CkRegenerateMessageButton', () => {
7
+ it('renders an accessible button with the default label', () => {
8
+ render(CkRegenerateMessageButton);
9
+
10
+ expect(
11
+ screen.getByRole('button', { name: 'Regenerate response' }),
12
+ ).toBeInTheDocument();
13
+ });
14
+
15
+ it('uses a custom aria-label when provided', () => {
16
+ render(CkRegenerateMessageButton, {
17
+ props: { ariaLabel: 'Try again' },
18
+ });
19
+
20
+ expect(
21
+ screen.getByRole('button', { name: 'Try again' }),
22
+ ).toBeInTheDocument();
23
+ });
24
+
25
+ it('emits regenerate when clicked', async () => {
26
+ const user = userEvent.setup();
27
+ const { emitted } = render(CkRegenerateMessageButton);
28
+
29
+ await user.click(
30
+ screen.getByRole('button', { name: 'Regenerate response' }),
31
+ );
32
+
33
+ expect(emitted('regenerate')).toEqual([[]]);
34
+ });
35
+
36
+ it('does not emit regenerate when disabled', async () => {
37
+ const user = userEvent.setup();
38
+ const { emitted } = render(CkRegenerateMessageButton, {
39
+ props: { disabled: true },
40
+ });
41
+
42
+ const button = screen.getByRole('button', { name: 'Regenerate response' });
43
+ expect(button).toBeDisabled();
44
+
45
+ await user.click(button);
46
+
47
+ expect(emitted('regenerate')).toBeUndefined();
48
+ });
49
+ });
@@ -0,0 +1,63 @@
1
+ import CkRegenerateMessageButton from './CkRegenerateMessageButton.vue';
2
+ import type { Meta, StoryObj } from '@storybook/vue3-vite';
3
+
4
+ const meta: Meta<typeof CkRegenerateMessageButton> = {
5
+ title: 'Base/CkRegenerateMessageButton',
6
+ component: CkRegenerateMessageButton,
7
+ tags: ['autodocs'],
8
+ argTypes: {
9
+ disabled: {
10
+ control: 'boolean',
11
+ description: 'Whether the button is disabled and ignores clicks.',
12
+ },
13
+ ariaLabel: {
14
+ control: 'text',
15
+ description: 'Accessible label and tooltip content for the button.',
16
+ },
17
+ },
18
+ decorators: [
19
+ () => ({
20
+ template:
21
+ '<div style="max-width: 640px; margin: 0 auto; padding: 24px;"><story /></div>',
22
+ }),
23
+ ],
24
+ };
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ const defaultTemplateSource = `<script setup lang="ts">
30
+ import { CkRegenerateMessageButton } from '@aleph-alpha/chat-kit';
31
+ </script>
32
+
33
+ <template>
34
+ <CkRegenerateMessageButton @regenerate="() => {}" />
35
+ </template>`;
36
+
37
+ const disabledTemplateSource = `<script setup lang="ts">
38
+ import { CkRegenerateMessageButton } from '@aleph-alpha/chat-kit';
39
+ </script>
40
+
41
+ <template>
42
+ <CkRegenerateMessageButton disabled />
43
+ </template>`;
44
+
45
+ /**
46
+ * Default regenerate action. Clicking emits a `regenerate` event so the host
47
+ * can request a new response for the message.
48
+ */
49
+ export const Default: Story = {
50
+ args: {},
51
+ parameters: { docs: { source: { code: defaultTemplateSource } } },
52
+ };
53
+
54
+ /**
55
+ * Disabled state — clicks are ignored and the button appears inactive.
56
+ * Use while a message is still streaming or in an error state.
57
+ */
58
+ export const Disabled: Story = {
59
+ args: {
60
+ disabled: true,
61
+ },
62
+ parameters: { docs: { source: { code: disabledTemplateSource } } },
63
+ };
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+ import { CkMessageButton } from '../CkMessageButton';
3
+ import type { CkRegenerateMessageButtonProps } from './types';
4
+ import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
5
+
6
+ defineOptions({
7
+ name: 'CkRegenerateMessageButton',
8
+ });
9
+
10
+ const props = withDefaults(defineProps<CkRegenerateMessageButtonProps>(), {
11
+ disabled: false,
12
+ ariaLabel: 'Regenerate response',
13
+ });
14
+
15
+ const emit = defineEmits<{
16
+ regenerate: [];
17
+ }>();
18
+
19
+ function handleClick() {
20
+ if (props.disabled) {
21
+ return;
22
+ }
23
+
24
+ emit('regenerate');
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <UiTooltip
30
+ :content="ariaLabel"
31
+ side="bottom"
32
+ align="center"
33
+ :delay-duration="0"
34
+ >
35
+ <CkMessageButton
36
+ v-bind="{ ariaLabel }"
37
+ :disabled="disabled"
38
+ @click="handleClick"
39
+ >
40
+ <UiIcon name="repeat" :size="16" aria-hidden="true" />
41
+ </CkMessageButton>
42
+ </UiTooltip>
43
+ </template>
@@ -0,0 +1,2 @@
1
+ export { default as CkRegenerateMessageButton } from './CkRegenerateMessageButton.vue';
2
+ export type * from './types';