@aleph-alpha/chat-kit 5.10.1 → 5.12.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 (51) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-CKM3MwF_.js → CkHistory.vue_vue_type_script_setup_true_lang-CImSzLYs.js} +123 -75
  2. package/dist/chat-kit.css +13 -13
  3. package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
  4. package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts +39 -0
  5. package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts.map +1 -0
  6. package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts +9 -0
  7. package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts.map +1 -0
  8. package/dist/components/base/CkCitationLabel/index.d.ts +3 -0
  9. package/dist/components/base/CkCitationLabel/index.d.ts.map +1 -0
  10. package/dist/components/base/CkCitationLabel/types.d.ts +44 -0
  11. package/dist/components/base/CkCitationLabel/types.d.ts.map +1 -0
  12. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts +12 -0
  13. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts.map +1 -1
  14. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts +2 -2
  15. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts.map +1 -1
  16. package/dist/components/base/CkFeedbackDialog/types.d.ts +28 -0
  17. package/dist/components/base/CkFeedbackDialog/types.d.ts.map +1 -1
  18. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +1 -1
  19. package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
  20. package/dist/components/base/index.d.ts +2 -0
  21. package/dist/components/base/index.d.ts.map +1 -1
  22. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +12 -0
  23. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  24. package/dist/components/index.d.ts +2 -2
  25. package/dist/components/index.d.ts.map +1 -1
  26. package/dist/components/index.js +28 -27
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +29 -28
  30. package/dist/types.d.ts +33 -4
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +5 -5
  33. package/src/components/base/CkCitation/CkCitation.spec.ts +11 -0
  34. package/src/components/base/CkCitation/CkCitation.vue +9 -36
  35. package/src/components/base/CkCitationLabel/CkCitationLabel.spec.ts +73 -0
  36. package/src/components/base/CkCitationLabel/CkCitationLabel.stories.ts +86 -0
  37. package/src/components/base/CkCitationLabel/CkCitationLabel.vue +69 -0
  38. package/src/components/base/CkCitationLabel/index.ts +2 -0
  39. package/src/components/base/CkCitationLabel/types.ts +43 -0
  40. package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +1 -1
  41. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.spec.ts +67 -0
  42. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.ts +57 -0
  43. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.vue +19 -5
  44. package/src/components/base/CkFeedbackDialog/types.ts +29 -0
  45. package/src/components/base/CkUserMessage/CkUserMessage.vue +1 -1
  46. package/src/components/base/index.ts +2 -0
  47. package/src/components/composed/CkConversation/CkConversation.spec.ts +65 -2
  48. package/src/components/composed/CkConversation/CkConversation.stories.ts +136 -0
  49. package/src/components/index.ts +2 -0
  50. package/src/index.ts +2 -0
  51. package/src/types.ts +34 -4
@@ -1,7 +1,13 @@
1
- import type { BaseMessage, MessageContent } from '../../../types';
1
+ import type {
2
+ BaseMessage,
3
+ MessageContent,
4
+ MessageTextContent,
5
+ } from '../../../types';
2
6
  import CkConversation from './CkConversation.vue';
7
+ import { CkMarkdownRenderer } from '../../base/CkMarkdownRenderer';
3
8
  import { defaultMarkdownClasses } from '../../base/CkMarkdownRenderer/defaultMarkdownClasses';
4
- import { render, screen } from '@testing-library/vue';
9
+ import type { CkCitationSource } from '../../base/CkCitation';
10
+ import { fireEvent, render, screen } from '@testing-library/vue';
5
11
  import {
6
12
  createCommentVNode,
7
13
  defineComponent,
@@ -26,6 +32,11 @@ function text(value: string): MessageContent {
26
32
  };
27
33
  }
28
34
 
35
+ const CITATIONS: CkCitationSource[] = [
36
+ { index: 1, fileName: 'q3-report.pdf', snippet: 'The cited chunk.' },
37
+ { index: 2, fileName: 'goals-26.pdf', snippet: 'Another cited chunk.' },
38
+ ];
39
+
29
40
  const messages: BaseMessage[] = [
30
41
  {
31
42
  id: '1',
@@ -258,6 +269,12 @@ describe('CkConversation', () => {
258
269
  });
259
270
 
260
271
  describe('copy action', () => {
272
+ // jsdom ships no Clipboard API, so the copy button's default write target
273
+ // has to be stubbed in. Removed again so the stub can't leak.
274
+ afterEach(() => {
275
+ Reflect.deleteProperty(globalThis.navigator, 'clipboard');
276
+ });
277
+
261
278
  it('exposes the joined text via the copy action button', () => {
262
279
  const message: BaseMessage = {
263
280
  id: 'mix2',
@@ -300,6 +317,52 @@ describe('CkConversation', () => {
300
317
  container.querySelector('button[aria-label="Copy response"]'),
301
318
  ).toBeNull();
302
319
  });
320
+
321
+ // A host that wires citations renders the `[n]` references as markers, so
322
+ // they are no longer part of the message text in the DOM. The copy action
323
+ // copies the message's own text, which must still carry every reference —
324
+ // pasting a cited answer keeps its citations.
325
+ it('copies the citation references of a cited answer', async () => {
326
+ const writeText = vi.fn(() => Promise.resolve());
327
+ Object.defineProperty(globalThis.navigator, 'clipboard', {
328
+ value: { writeText },
329
+ configurable: true,
330
+ });
331
+
332
+ const message: BaseMessage = {
333
+ id: 'cited',
334
+ role: 'assistant',
335
+ content: [text('Adoption rose by 25%.[1] Rework fell.[2]')],
336
+ createdAt: now,
337
+ status: 'completed',
338
+ };
339
+ const { container } = render(CkConversation, {
340
+ props: { messages: [message] },
341
+ slots: {
342
+ // How a host opts into citations: override the content slot and pass
343
+ // its sources to the renderer.
344
+ 'agent-message-content': (slotProps: { message: BaseMessage }) =>
345
+ h(CkMarkdownRenderer, {
346
+ content: (slotProps.message.content[0] as MessageTextContent)
347
+ .text,
348
+ citations: CITATIONS,
349
+ }),
350
+ },
351
+ });
352
+
353
+ // Both references really did become markers rather than staying text.
354
+ expect(container.querySelectorAll('.ck-citation')).toHaveLength(2);
355
+
356
+ // `fireEvent`, not `userEvent`: the latter's `setup()` installs its own
357
+ // `navigator.clipboard` stub over the one under test here.
358
+ await fireEvent.click(
359
+ container.querySelector('button[aria-label="Copy response"]')!,
360
+ );
361
+
362
+ expect(writeText).toHaveBeenCalledWith(
363
+ 'Adoption rose by 25%.[1] Rework fell.[2]',
364
+ );
365
+ });
303
366
  });
304
367
 
305
368
  describe('slot overrides', () => {
@@ -1,3 +1,5 @@
1
+ import type { CkCitationSource } from '../../base/CkCitation';
2
+ import { CkMarkdownRenderer } from '../../base/CkMarkdownRenderer';
1
3
  import { CkCopyMessageButton } from '../../base/CkCopyMessageButton';
2
4
  import { CkFeedbackButton } from '../../base/CkFeedbackButton';
3
5
  import type { CkFeedbackValue } from '../../base/CkFeedbackButton';
@@ -65,6 +67,53 @@ const markdownContent = [
65
67
  '| Feedback Integration | Planned | High |',
66
68
  ].join('\n');
67
69
 
70
+ const citedAnswer = [
71
+ 'Based on a review of internal documents, the rollout produced two measurable effects:',
72
+ '',
73
+ '- **Adoption:** Departments that received customized training saw a **25% higher adoption rate** of the new tools than those with generic training.[1]',
74
+ '- **Rework:** A lack of initial stakeholder engagement led to significant process rework during the first wave.[2]',
75
+ '',
76
+ 'Both findings are consistent across the quarterly reviews.[1][2]',
77
+ ].join('\n');
78
+
79
+ /** One entry per `[n]` reference in `citedAnswer`, keyed by `index`. */
80
+ const citations: CkCitationSource[] = [
81
+ {
82
+ index: 1,
83
+ fileName: 'Q2_Business_Plan.pdf',
84
+ snippet:
85
+ 'Departments that received customized training saw a 25% higher adoption rate of the new tools compared to those with generic training.',
86
+ locationLabel: 'Page 3',
87
+ url: '/api/v1/search-stores/ss_abc/files/file_123/public-urls',
88
+ },
89
+ {
90
+ index: 2,
91
+ fileName: 'Goal-planning-26.pdf',
92
+ snippet:
93
+ 'Early stakeholder involvement remains the single strongest predictor of a rollout that does not require process rework.',
94
+ locationLabel: 'Page 12',
95
+ },
96
+ ];
97
+
98
+ const citedMessages: BaseMessage[] = [
99
+ {
100
+ id: 'c1',
101
+ role: 'user',
102
+ content: [
103
+ text('What did we learn from the Digital Transformation rollout?'),
104
+ ],
105
+ createdAt: now,
106
+ status: 'completed',
107
+ },
108
+ {
109
+ id: 'c2',
110
+ role: 'assistant',
111
+ content: [text(citedAnswer)],
112
+ createdAt: now,
113
+ status: 'completed',
114
+ },
115
+ ];
116
+
68
117
  const sampleMessages: BaseMessage[] = [
69
118
  {
70
119
  id: '1',
@@ -384,6 +433,51 @@ function getMessageText(message: BaseMessage): string {
384
433
  </CkConversation>
385
434
  </template>`;
386
435
 
436
+ const withCitationsTemplateSource = `<script setup lang="ts">
437
+ import { CkConversation, CkMarkdownRenderer } from '@aleph-alpha/chat-kit';
438
+ import type { BaseMessage, CkCitationSource } from '@aleph-alpha/chat-kit';
439
+
440
+ const messages: BaseMessage[] = [/* ... */];
441
+
442
+ // One entry per \`[n]\` reference in the answer, keyed by \`index\`. Keep the
443
+ // array reference stable so the markdown processor isn't rebuilt on every
444
+ // render.
445
+ const citations: CkCitationSource[] = [
446
+ {
447
+ index: 1,
448
+ fileName: 'Q2_Business_Plan.pdf',
449
+ snippet: 'Departments that received customized training saw a 25% higher adoption rate.',
450
+ locationLabel: 'Page 3',
451
+ url: '/api/v1/search-stores/ss_abc/files/file_123/public-urls',
452
+ },
453
+ ];
454
+
455
+ function getMessageText(message: BaseMessage): string {
456
+ return message.content
457
+ .filter((c) => c.type === 'text')
458
+ .map((c) => c.text)
459
+ .join('');
460
+ }
461
+
462
+ // The card's external-link action is only a request — the host performs the
463
+ // (often auth-proxied) navigation.
464
+ function openSource(url: string): void {
465
+ window.open(url, '_blank', 'noopener');
466
+ }
467
+ </script>
468
+
469
+ <template>
470
+ <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
471
+ <template #agent-message-content="{ message }">
472
+ <CkMarkdownRenderer
473
+ :content="getMessageText(message)"
474
+ :citations="citations"
475
+ @open-source="openSource"
476
+ />
477
+ </template>
478
+ </CkConversation>
479
+ </template>`;
480
+
387
481
  const streamingSimulationTemplateSource = `<script setup lang="ts">
388
482
  import { CkConversation } from '@aleph-alpha/chat-kit';
389
483
  import type { BaseMessage } from '@aleph-alpha/chat-kit';
@@ -910,6 +1004,48 @@ export const WithCustomAgentContent: Story = {
910
1004
  },
911
1005
  };
912
1006
 
1007
+ /**
1008
+ * Citations are opt-in: override `#agent-message-content` and hand the message's
1009
+ * sources to `CkMarkdownRenderer`. Every `[n]` in the answer whose number
1010
+ * matches a source's `index` becomes a marker that reveals the source on hover
1011
+ * or keyboard activation; unmatched markers stay plain text. `open-source` is
1012
+ * re-emitted for the host to perform the (often auth-proxied) navigation — the
1013
+ * second source here has no `url`, so its card omits the action.
1014
+ *
1015
+ * The footer copy button is unaffected: it copies the message text, so the
1016
+ * `[n]` references travel with a pasted answer.
1017
+ */
1018
+ export const WithCitations: Story = {
1019
+ render: () => ({
1020
+ components: { CkConversation, CkMarkdownRenderer },
1021
+ setup() {
1022
+ function openSource(url: string) {
1023
+ window.open(url, '_blank', 'noopener');
1024
+ }
1025
+ return {
1026
+ messages: citedMessages,
1027
+ citations,
1028
+ getMessageText,
1029
+ openSource,
1030
+ };
1031
+ },
1032
+ template: `
1033
+ <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
1034
+ <template #agent-message-content="{ message }">
1035
+ <CkMarkdownRenderer
1036
+ :content="getMessageText(message)"
1037
+ :citations="citations"
1038
+ @open-source="openSource"
1039
+ />
1040
+ </template>
1041
+ </CkConversation>
1042
+ `,
1043
+ }),
1044
+ parameters: {
1045
+ docs: { source: { code: withCitationsTemplateSource } },
1046
+ },
1047
+ };
1048
+
913
1049
  /**
914
1050
  * End-to-end streaming scenario: appending a new user message and then
915
1051
  * streaming an assistant reply chunk-by-chunk. Use as a reference for wiring
@@ -4,6 +4,7 @@ export {
4
4
  CkAgentMessageHeader,
5
5
  CkHeaderActivityButton,
6
6
  CkCitation,
7
+ CkCitationLabel,
7
8
  CkCitationCard,
8
9
  CkConversationLayout,
9
10
  CkCopyMessageButton,
@@ -34,6 +35,7 @@ export type {
34
35
  CkCitationProps,
35
36
  CkCitationEmits,
36
37
  CkCitationSource,
38
+ CkCitationLabelProps,
37
39
  CkCitationCardProps,
38
40
  CkCitationCardEmits,
39
41
  CkConversationLayoutProps,
package/src/index.ts CHANGED
@@ -74,6 +74,7 @@ export {
74
74
  CkAgentMessageHeader,
75
75
  CkHeaderActivityButton,
76
76
  CkCitation,
77
+ CkCitationLabel,
77
78
  CkCitationCard,
78
79
  CkConversation,
79
80
  CkConversationLayout,
@@ -106,6 +107,7 @@ export type {
106
107
  CkCitationProps,
107
108
  CkCitationEmits,
108
109
  CkCitationSource,
110
+ CkCitationLabelProps,
109
111
  CkCitationCardProps,
110
112
  CkCitationCardEmits,
111
113
  CkConversationLayoutProps,
package/src/types.ts CHANGED
@@ -68,6 +68,36 @@ export type MessageContent =
68
68
  | MessageToolContent
69
69
  | MessageReasoningContent;
70
70
 
71
+ declare global {
72
+ /**
73
+ * Extension point for {@link MessageMetadata}. Consumers declare their
74
+ * well-known metadata keys here and get them typed on every message,
75
+ * without chat-kit knowing what any of them mean:
76
+ *
77
+ * ```ts
78
+ * // app/src/types/chat-kit.d.ts
79
+ * declare global {
80
+ * interface ChatKitMessageMetadata {
81
+ * responseId?: string;
82
+ * citations?: CitationManifest;
83
+ * }
84
+ * }
85
+ * export {};
86
+ * ```
87
+ *
88
+ * Declare every key **optional**: chat-kit itself never populates them, and
89
+ * a message can reach a consumer before its host has stamped anything (mid
90
+ * stream, or on an optimistic node).
91
+ *
92
+ * This is a `declare global` interface rather than a module augmentation
93
+ * because `MessageMetadata` is re-exported through the package barrel —
94
+ * `declare module '@aleph-alpha/chat-kit'` does not merge across a
95
+ * re-export, and would silently type nothing.
96
+ */
97
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
98
+ interface ChatKitMessageMetadata {}
99
+ }
100
+
71
101
  /**
72
102
  * Open-ended metadata bag carried alongside a message. chat-kit is
73
103
  * intentionally agnostic about its contents — adapter authors
@@ -75,11 +105,11 @@ export type MessageContent =
75
105
  * through the tree → linear path projection, but never inspect it.
76
106
  *
77
107
  * Consumers attach their own well-known keys (attachments, citations,
78
- * ratings, …) and define their own typed projections at the read site
79
- * rather than extending this base type. That keeps the handler API stable
80
- * even as the shape of what gets stored on a message evolves.
108
+ * ratings, …). Declaring them on {@link ChatKitMessageMetadata} types those
109
+ * reads everywhere; the retained index signature keeps any other key legal,
110
+ * so the handler API stays stable as the stored shape evolves.
81
111
  */
82
- export type MessageMetadata = Record<string, unknown>;
112
+ export type MessageMetadata = ChatKitMessageMetadata & Record<string, unknown>;
83
113
 
84
114
  export interface BaseMessage {
85
115
  id: string;