@aleph-alpha/chat-kit 5.11.0 → 5.13.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 (69) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-SL1vOAd9.js → CkHistory.vue_vue_type_script_setup_true_lang-BNcJURkq.js} +216 -87
  2. package/dist/chat-kit.css +13 -13
  3. package/dist/components/base/CkCitation/CkCitation.stories.d.ts +1 -4
  4. package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -1
  5. package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
  6. package/dist/components/base/CkCitation/types.d.ts +6 -1
  7. package/dist/components/base/CkCitation/types.d.ts.map +1 -1
  8. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +13 -2
  9. package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -1
  10. package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts +1 -0
  11. package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -1
  12. package/dist/components/base/CkCitationCard/types.d.ts +6 -1
  13. package/dist/components/base/CkCitationCard/types.d.ts.map +1 -1
  14. package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts +39 -0
  15. package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts.map +1 -0
  16. package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts +9 -0
  17. package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts.map +1 -0
  18. package/dist/components/base/CkCitationLabel/index.d.ts +3 -0
  19. package/dist/components/base/CkCitationLabel/index.d.ts.map +1 -0
  20. package/dist/components/base/CkCitationLabel/types.d.ts +44 -0
  21. package/dist/components/base/CkCitationLabel/types.d.ts.map +1 -0
  22. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts +7 -0
  23. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts.map +1 -1
  24. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts +2 -2
  25. package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts.map +1 -1
  26. package/dist/components/base/CkFeedbackDialog/types.d.ts +19 -0
  27. package/dist/components/base/CkFeedbackDialog/types.d.ts.map +1 -1
  28. package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +1 -1
  29. package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
  30. package/dist/components/base/index.d.ts +2 -0
  31. package/dist/components/base/index.d.ts.map +1 -1
  32. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +12 -0
  33. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  34. package/dist/components/index.d.ts +2 -2
  35. package/dist/components/index.d.ts.map +1 -1
  36. package/dist/components/index.js +28 -27
  37. package/dist/composables/useIsTruncated.d.ts +22 -0
  38. package/dist/composables/useIsTruncated.d.ts.map +1 -0
  39. package/dist/index.d.ts +2 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +29 -28
  42. package/dist/types.d.ts +33 -4
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +5 -5
  45. package/src/components/base/CkCitation/CkCitation.spec.ts +14 -3
  46. package/src/components/base/CkCitation/CkCitation.stories.ts +85 -3
  47. package/src/components/base/CkCitation/CkCitation.vue +13 -36
  48. package/src/components/base/CkCitation/types.ts +6 -1
  49. package/src/components/base/CkCitationCard/CkCitationCard.spec.ts +37 -0
  50. package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +30 -3
  51. package/src/components/base/CkCitationCard/CkCitationCard.vue +53 -11
  52. package/src/components/base/CkCitationCard/types.ts +6 -1
  53. package/src/components/base/CkCitationLabel/CkCitationLabel.spec.ts +73 -0
  54. package/src/components/base/CkCitationLabel/CkCitationLabel.stories.ts +86 -0
  55. package/src/components/base/CkCitationLabel/CkCitationLabel.vue +70 -0
  56. package/src/components/base/CkCitationLabel/index.ts +2 -0
  57. package/src/components/base/CkCitationLabel/types.ts +43 -0
  58. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.spec.ts +36 -0
  59. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.ts +42 -0
  60. package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.vue +11 -1
  61. package/src/components/base/CkFeedbackDialog/types.ts +20 -0
  62. package/src/components/base/CkUserMessage/CkUserMessage.vue +1 -1
  63. package/src/components/base/index.ts +2 -0
  64. package/src/components/composed/CkConversation/CkConversation.spec.ts +65 -2
  65. package/src/components/composed/CkConversation/CkConversation.stories.ts +136 -0
  66. package/src/components/index.ts +2 -0
  67. package/src/composables/useIsTruncated.ts +82 -0
  68. package/src/index.ts +2 -0
  69. package/src/types.ts +34 -4
@@ -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,
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Local copy of ui-library's internal probe of the same name. chat-kit's
3
+ * supported ui-library range (^5.3.0) does not export it, and it is too small
4
+ * to justify widening that package's public API. Drop this file once the range
5
+ * moves up to a version that exports it.
6
+ */
7
+
8
+ import { onScopeDispose, ref, watch, type Ref, type ShallowRef } from 'vue';
9
+
10
+ /** An element ref as produced by `useTemplateRef`. */
11
+ type ElementRef = Readonly<ShallowRef<HTMLElement | null>>;
12
+
13
+ /**
14
+ * One observer for every caller. `ResizeObserver` is built to watch many
15
+ * targets and delivers them in a single batched callback, so a list of rows
16
+ * costs one callback and one layout pass rather than one per row. Created
17
+ * lazily so importing this module never touches the DOM.
18
+ */
19
+ const measurers = new WeakMap<Element, () => void>();
20
+ let sharedObserver: ResizeObserver | undefined;
21
+
22
+ function getSharedObserver(): ResizeObserver | undefined {
23
+ if (typeof ResizeObserver === 'undefined') return undefined;
24
+ sharedObserver ??= new ResizeObserver((entries) => {
25
+ for (const entry of entries) measurers.get(entry.target)?.();
26
+ });
27
+ return sharedObserver;
28
+ }
29
+
30
+ function watchElement(el: Element, measure: () => void): void {
31
+ measurers.set(el, measure);
32
+ getSharedObserver()?.observe(el);
33
+ }
34
+
35
+ function unwatchElement(el: Element): void {
36
+ measurers.delete(el);
37
+ getSharedObserver()?.unobserve(el);
38
+ }
39
+
40
+ /**
41
+ * Whether an element's text is currently clipped by `text-overflow: ellipsis`.
42
+ *
43
+ * Re-measures when the element resizes and whenever `watchSource` changes, so
44
+ * it stays correct as the container narrows or the text is swapped out. Use it
45
+ * to show a tooltip only for names that are actually cut off.
46
+ * @param elementRef - The element whose text may be clipped
47
+ * @param watchSource - Value to re-measure on, e.g. the rendered text
48
+ * @returns Reactive truncation state
49
+ */
50
+ export function useIsTruncated(
51
+ elementRef: ElementRef,
52
+ watchSource?: () => unknown,
53
+ ): Ref<boolean> {
54
+ const isTruncated = ref(false);
55
+
56
+ function measure(): void {
57
+ const el = elementRef.value;
58
+ // Sub-pixel widths round differently per browser, so require a whole
59
+ // pixel of overflow before calling it truncated.
60
+ isTruncated.value = el ? el.scrollWidth - el.clientWidth >= 1 : false;
61
+ }
62
+
63
+ // A resize alone doesn't cover a text swap that keeps the box the same size.
64
+ if (watchSource) watch(watchSource, measure, { flush: 'post' });
65
+
66
+ // Runs on mount too, when the template ref goes null -> element.
67
+ watch(
68
+ elementRef,
69
+ (el, previous) => {
70
+ if (previous) unwatchElement(previous);
71
+ if (el) watchElement(el, measure);
72
+ measure();
73
+ },
74
+ { immediate: true, flush: 'post' },
75
+ );
76
+
77
+ onScopeDispose(() => {
78
+ if (elementRef.value) unwatchElement(elementRef.value);
79
+ });
80
+
81
+ return isTruncated;
82
+ }
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;