@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.
- package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-CKM3MwF_.js → CkHistory.vue_vue_type_script_setup_true_lang-CImSzLYs.js} +123 -75
- package/dist/chat-kit.css +13 -13
- package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
- package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts +39 -0
- package/dist/components/base/CkCitationLabel/CkCitationLabel.stories.d.ts.map +1 -0
- package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts +9 -0
- package/dist/components/base/CkCitationLabel/CkCitationLabel.vue.d.ts.map +1 -0
- package/dist/components/base/CkCitationLabel/index.d.ts +3 -0
- package/dist/components/base/CkCitationLabel/index.d.ts.map +1 -0
- package/dist/components/base/CkCitationLabel/types.d.ts +44 -0
- package/dist/components/base/CkCitationLabel/types.d.ts.map +1 -0
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts +12 -0
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.d.ts.map +1 -1
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts +2 -2
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts.map +1 -1
- package/dist/components/base/CkFeedbackDialog/types.d.ts +28 -0
- package/dist/components/base/CkFeedbackDialog/types.d.ts.map +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +1 -1
- package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
- package/dist/components/base/index.d.ts +2 -0
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +12 -0
- package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +28 -27
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -28
- package/dist/types.d.ts +33 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/components/base/CkCitation/CkCitation.spec.ts +11 -0
- package/src/components/base/CkCitation/CkCitation.vue +9 -36
- package/src/components/base/CkCitationLabel/CkCitationLabel.spec.ts +73 -0
- package/src/components/base/CkCitationLabel/CkCitationLabel.stories.ts +86 -0
- package/src/components/base/CkCitationLabel/CkCitationLabel.vue +69 -0
- package/src/components/base/CkCitationLabel/index.ts +2 -0
- package/src/components/base/CkCitationLabel/types.ts +43 -0
- package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +1 -1
- package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.spec.ts +67 -0
- package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.stories.ts +57 -0
- package/src/components/base/CkFeedbackDialog/CkFeedbackDialog.vue +19 -5
- package/src/components/base/CkFeedbackDialog/types.ts +29 -0
- package/src/components/base/CkUserMessage/CkUserMessage.vue +1 -1
- package/src/components/base/index.ts +2 -0
- package/src/components/composed/CkConversation/CkConversation.spec.ts +65 -2
- package/src/components/composed/CkConversation/CkConversation.stories.ts +136 -0
- package/src/components/index.ts +2 -0
- package/src/index.ts +2 -0
- package/src/types.ts +34 -4
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type {
|
|
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 {
|
|
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
|
package/src/components/index.ts
CHANGED
|
@@ -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, …)
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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;
|