@aleph-alpha/chat-kit 1.2.0 → 1.3.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 (36) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-CRymGkQs.js → CkInput.vue_vue_type_script_setup_true_lang-BoUxOQbH.js} +154 -39
  2. package/dist/chat-kit.css +18 -0
  3. package/dist/components/base/CkFeedbackButton/CkFeedbackButton.stories.d.ts +38 -0
  4. package/dist/components/base/CkFeedbackButton/CkFeedbackButton.stories.d.ts.map +1 -0
  5. package/dist/components/base/CkFeedbackButton/CkFeedbackButton.vue.d.ts +16 -0
  6. package/dist/components/base/CkFeedbackButton/CkFeedbackButton.vue.d.ts.map +1 -0
  7. package/dist/components/base/CkFeedbackButton/index.d.ts +3 -0
  8. package/dist/components/base/CkFeedbackButton/index.d.ts.map +1 -0
  9. package/dist/components/base/CkFeedbackButton/types.d.ts +60 -0
  10. package/dist/components/base/CkFeedbackButton/types.d.ts.map +1 -0
  11. package/dist/components/base/index.d.ts +2 -0
  12. package/dist/components/base/index.d.ts.map +1 -1
  13. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +4 -3
  14. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  15. package/dist/components/index.d.ts +2 -2
  16. package/dist/components/index.d.ts.map +1 -1
  17. package/dist/components/index.js +15 -14
  18. package/dist/handlers/index.js +1 -1
  19. package/dist/handlers/useChatKitLabels.d.ts +18 -0
  20. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +16 -15
  24. package/dist/{useChatKitLabels-BmWmMyqX.js → useChatKitLabels-ftC52m7f.js} +3 -0
  25. package/package.json +2 -2
  26. package/src/components/base/CkFeedbackButton/CkFeedbackButton.spec.ts +174 -0
  27. package/src/components/base/CkFeedbackButton/CkFeedbackButton.stories.ts +179 -0
  28. package/src/components/base/CkFeedbackButton/CkFeedbackButton.vue +157 -0
  29. package/src/components/base/CkFeedbackButton/index.ts +2 -0
  30. package/src/components/base/CkFeedbackButton/types.ts +61 -0
  31. package/src/components/base/index.ts +5 -0
  32. package/src/components/composed/CkConversation/CkConversation.stories.ts +46 -17
  33. package/src/components/index.ts +3 -0
  34. package/src/handlers/useChatKitLabels.spec.ts +70 -50
  35. package/src/handlers/useChatKitLabels.ts +21 -0
  36. package/src/index.ts +3 -0
@@ -1,3 +1,6 @@
1
+ import { CkCopyMessageActionButton } from '../../base/CkCopyMessageActionButton';
2
+ import { CkFeedbackButton } from '../../base/CkFeedbackButton';
3
+ import type { CkFeedbackValue } from '../../base/CkFeedbackButton';
1
4
  import type { BaseMessage } from '../../../types';
2
5
  import CkConversation from './CkConversation.vue';
3
6
  import type { Meta, StoryObj } from '@storybook/vue3-vite';
@@ -169,19 +172,32 @@ const messages: BaseMessage[] = [/* ... */];
169
172
  </template>`;
170
173
 
171
174
  const withFooterActionsTemplateSource = `<script setup lang="ts">
172
- import { CkConversation } from '@aleph-alpha/chat-kit';
173
- import type { BaseMessage } from '@aleph-alpha/chat-kit';
175
+ import { ref } from 'vue';
176
+ import {
177
+ CkConversation,
178
+ CkCopyMessageActionButton,
179
+ CkFeedbackButton,
180
+ } from '@aleph-alpha/chat-kit';
181
+ import type { BaseMessage, CkFeedbackValue } from '@aleph-alpha/chat-kit';
174
182
 
175
183
  const messages: BaseMessage[] = [/* ... */];
184
+
185
+ // In a real app, key feedback by message id and persist to your backend.
186
+ const feedback = ref<Record<string, { value: CkFeedbackValue; locked: boolean }>>({});
187
+ function onFeedback(messageId: string, value: CkFeedbackValue) {
188
+ feedback.value[messageId] = { value, locked: value !== null };
189
+ }
176
190
  </script>
177
191
 
178
192
  <template>
179
193
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
180
- <template #agent-message-footer>
181
- <button>Copy</button>
182
- <button>Regenerate</button>
183
- <button>Like</button>
184
- <button>Dislike</button>
194
+ <template #agent-message-footer="{ message }">
195
+ <CkCopyMessageActionButton :text="message.content" />
196
+ <CkFeedbackButton
197
+ :value="feedback[message.id]?.value ?? null"
198
+ :locked="feedback[message.id]?.locked ?? false"
199
+ @update:value="(v) => onFeedback(message.id, v)"
200
+ />
185
201
  </template>
186
202
  </CkConversation>
187
203
  </template>`;
@@ -384,23 +400,36 @@ export const WithCustomIcon: Story = {
384
400
  };
385
401
 
386
402
  /**
387
- * Overrides `#agent-message-footer` to add custom action buttons (copy,
388
- * regenerate, thumbs up/down) next to each assistant reply. Use to add
389
- * feedback or follow-up actions.
403
+ * Overrides `#agent-message-footer` to add real action buttons next to each
404
+ * assistant reply: `CkCopyMessageActionButton` for clipboard copy and
405
+ * `CkFeedbackButton` for binary thumbs up / thumbs down feedback. The
406
+ * feedback state is owned by the host and locked once submitted.
390
407
  */
391
408
  export const WithFooterActions: Story = {
392
409
  render: () => ({
393
- components: { CkConversation },
410
+ components: {
411
+ CkConversation,
412
+ CkCopyMessageActionButton,
413
+ CkFeedbackButton,
414
+ },
394
415
  setup() {
395
- return { messages: sampleMessages };
416
+ const feedback = ref<
417
+ Record<string, { value: CkFeedbackValue; locked: boolean }>
418
+ >({});
419
+ function onFeedback(messageId: string, value: CkFeedbackValue) {
420
+ feedback.value[messageId] = { value, locked: value !== null };
421
+ }
422
+ return { messages: sampleMessages, feedback, onFeedback };
396
423
  },
397
424
  template: `
398
425
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
399
- <template #agent-message-footer>
400
- <button class="p-1 opacity-60 hover:opacity-100">📋</button>
401
- <button class="p-1 opacity-60 hover:opacity-100">🔄</button>
402
- <button class="p-1 opacity-60 hover:opacity-100">👍</button>
403
- <button class="p-1 opacity-60 hover:opacity-100">👎</button>
426
+ <template #agent-message-footer="{ message }">
427
+ <CkCopyMessageActionButton :text="message.content" />
428
+ <CkFeedbackButton
429
+ :value="feedback[message.id]?.value ?? null"
430
+ :locked="feedback[message.id]?.locked ?? false"
431
+ @update:value="(v) => onFeedback(message.id, v)"
432
+ />
404
433
  </template>
405
434
  </CkConversation>
406
435
  `,
@@ -3,6 +3,7 @@ export {
3
3
  CkAgentMessage,
4
4
  CkConversationLayout,
5
5
  CkCopyMessageActionButton,
6
+ CkFeedbackButton,
6
7
  CkHistoryItem,
7
8
  CkHistoryWrapper,
8
9
  CkMarkdownRenderer,
@@ -17,6 +18,8 @@ export {
17
18
  } from './base';
18
19
  export type {
19
20
  CkConversationLayoutProps,
21
+ CkFeedbackButtonProps,
22
+ CkFeedbackValue,
20
23
  CkHistoryItemEmits,
21
24
  CkHistoryItemProps,
22
25
  CkHistoryWrapperProps,
@@ -40,6 +40,9 @@ describe('useChatKitLabels', () => {
40
40
  inputPlaceholder: 'Nachricht eingeben...',
41
41
  copyMessageAction: 'Nachricht kopieren',
42
42
  copyMessageActionCopied: 'Kopiert',
43
+ feedbackThumbsUp: 'Gute Antwort',
44
+ feedbackThumbsDown: 'Schlechte Antwort',
45
+ feedbackGroup: 'Antwort bewerten',
43
46
  historyItemUntitled: 'Unbenannte Konversation',
44
47
  historyItemActionAriaLabel: 'Konversationsoptionen',
45
48
  historyItemEditAction: 'Bearbeiten',
@@ -108,6 +111,9 @@ describe('useChatKitLabels', () => {
108
111
  inputPlaceholder: 'Custom placeholder',
109
112
  copyMessageAction: 'Custom copy',
110
113
  copyMessageActionCopied: 'Custom copied',
114
+ feedbackThumbsUp: 'Custom up',
115
+ feedbackThumbsDown: 'Custom down',
116
+ feedbackGroup: 'Custom group',
111
117
  historyItemUntitled: 'Custom untitled',
112
118
  historyItemActionAriaLabel: 'Custom options',
113
119
  historyItemEditAction: 'Custom edit',
@@ -236,55 +242,69 @@ describe('useChatKitLabels', () => {
236
242
  });
237
243
  });
238
244
 
239
- describe('CkConversation uses injected labels', () => {
240
- it('uses default agentAvatarAlt when no labels are provided', async () => {
241
- const { default: CkConversation } =
242
- await import('../components/composed/CkConversation/CkConversation.vue');
243
-
244
- render(CkConversation, {
245
- props: {
246
- messages: [
247
- {
248
- id: '1',
249
- role: 'assistant',
250
- content: 'Hi',
251
- createdAt: new Date(),
252
- status: 'completed',
253
- },
254
- ],
255
- agentAvatarImage: 'bot.png',
256
- },
257
- });
245
+ // These two tests dynamically import CkConversation, which transitively pulls
246
+ // in the markdown renderer + ui-library tree. First-time module load + jsdom
247
+ // render can take ~5s under suite-wide load (vitest's default `testTimeout`),
248
+ // so we bump the per-test timeout to keep them stable on slower CI runners.
249
+ const CK_CONVERSATION_RENDER_TIMEOUT_MS = 15_000;
258
250
 
259
- expect(screen.getByAltText('Agent avatar')).toBeInTheDocument();
260
- });
261
-
262
- it('uses injected agentAvatarAlt from provideChatKitLabels', async () => {
263
- const { default: CkConversation } =
264
- await import('../components/composed/CkConversation/CkConversation.vue');
265
-
266
- const Wrapper = defineComponent({
267
- setup() {
268
- provideChatKitLabels(() => ({ agentAvatarAlt: 'Bot-Bild' }));
269
- return () =>
270
- h(CkConversation, {
271
- messages: [
272
- {
273
- id: '1',
274
- role: 'assistant',
275
- content: 'Hi',
276
- createdAt: new Date(),
277
- status: 'completed',
278
- },
279
- ],
280
- agentAvatarImage: 'bot.png',
281
- });
282
- },
283
- });
284
-
285
- render(Wrapper);
286
-
287
- expect(screen.getByAltText('Bot-Bild')).toBeInTheDocument();
288
- expect(screen.queryByAltText('Agent avatar')).not.toBeInTheDocument();
289
- });
251
+ describe('CkConversation uses injected labels', () => {
252
+ it(
253
+ 'uses default agentAvatarAlt when no labels are provided',
254
+ async () => {
255
+ const { default: CkConversation } =
256
+ await import('../components/composed/CkConversation/CkConversation.vue');
257
+
258
+ render(CkConversation, {
259
+ props: {
260
+ messages: [
261
+ {
262
+ id: '1',
263
+ role: 'assistant',
264
+ content: 'Hi',
265
+ createdAt: new Date(),
266
+ status: 'completed',
267
+ },
268
+ ],
269
+ agentAvatarImage: 'bot.png',
270
+ },
271
+ });
272
+
273
+ expect(screen.getByAltText('Agent avatar')).toBeInTheDocument();
274
+ },
275
+ CK_CONVERSATION_RENDER_TIMEOUT_MS,
276
+ );
277
+
278
+ it(
279
+ 'uses injected agentAvatarAlt from provideChatKitLabels',
280
+ async () => {
281
+ const { default: CkConversation } =
282
+ await import('../components/composed/CkConversation/CkConversation.vue');
283
+
284
+ const Wrapper = defineComponent({
285
+ setup() {
286
+ provideChatKitLabels(() => ({ agentAvatarAlt: 'Bot-Bild' }));
287
+ return () =>
288
+ h(CkConversation, {
289
+ messages: [
290
+ {
291
+ id: '1',
292
+ role: 'assistant',
293
+ content: 'Hi',
294
+ createdAt: new Date(),
295
+ status: 'completed',
296
+ },
297
+ ],
298
+ agentAvatarImage: 'bot.png',
299
+ });
300
+ },
301
+ });
302
+
303
+ render(Wrapper);
304
+
305
+ expect(screen.getByAltText('Bot-Bild')).toBeInTheDocument();
306
+ expect(screen.queryByAltText('Agent avatar')).not.toBeInTheDocument();
307
+ },
308
+ CK_CONVERSATION_RENDER_TIMEOUT_MS,
309
+ );
290
310
  });
@@ -29,6 +29,24 @@ 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
+ /**
33
+ * Accessible label / tooltip for the thumbs-up button in `CkFeedbackButton`.
34
+ * Optional for backward compatibility; falls back to
35
+ * `defaultChatKitLabels.feedbackThumbsUp` when omitted.
36
+ */
37
+ feedbackThumbsUp?: string;
38
+ /**
39
+ * Accessible label / tooltip for the thumbs-down button in `CkFeedbackButton`.
40
+ * Optional for backward compatibility; falls back to
41
+ * `defaultChatKitLabels.feedbackThumbsDown` when omitted.
42
+ */
43
+ feedbackThumbsDown?: string;
44
+ /**
45
+ * Accessible group label announced when focus enters `CkFeedbackButton`.
46
+ * Optional for backward compatibility; falls back to
47
+ * `defaultChatKitLabels.feedbackGroup` when omitted.
48
+ */
49
+ feedbackGroup?: string;
32
50
  /** Fallback title used by `CkHistory` when a conversation has no `title`. */
33
51
  historyItemUntitled: string;
34
52
  /** Accessible name for the trailing kebab trigger on each `CkHistory` row. */
@@ -46,6 +64,9 @@ export const defaultChatKitLabels: ChatKitLabels = {
46
64
  inputPlaceholder: 'Type a message...',
47
65
  copyMessageAction: 'Copy response',
48
66
  copyMessageActionCopied: 'Copied!',
67
+ feedbackThumbsUp: 'Good response',
68
+ feedbackThumbsDown: 'Bad response',
69
+ feedbackGroup: 'Rate this response',
49
70
  historyItemUntitled: 'Untitled conversation',
50
71
  historyItemActionAriaLabel: 'Conversation options',
51
72
  historyItemEditAction: 'Edit',
package/src/index.ts CHANGED
@@ -48,6 +48,7 @@ export {
48
48
  CkConversation,
49
49
  CkConversationLayout,
50
50
  CkCopyMessageActionButton,
51
+ CkFeedbackButton,
51
52
  CkHistory,
52
53
  CkHistoryItem,
53
54
  CkHistoryWrapper,
@@ -65,6 +66,8 @@ export {
65
66
  export type {
66
67
  CkConversationLayoutProps,
67
68
  CkConversationProps,
69
+ CkFeedbackButtonProps,
70
+ CkFeedbackValue,
68
71
  CkHistoryEmits,
69
72
  CkHistoryItemEmits,
70
73
  CkHistoryItemProps,