@aleph-alpha/chat-kit 3.2.0 → 4.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.
- package/dist/{CkInput.vue_vue_type_script_setup_true_lang-B-4xnH2c.js → CkInput.vue_vue_type_script_setup_true_lang-DJN-nZ2-.js} +391 -113
- package/dist/adapters/responses-api.d.ts +11 -5
- package/dist/adapters/responses-api.d.ts.map +1 -1
- package/dist/adapters/responses-api.js +9 -3
- package/dist/chat-kit.css +14 -8
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +7 -0
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessage/types.d.ts +1 -1
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts +47 -0
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -0
- package/dist/components/base/CkCitation/CkCitation.vue.d.ts +15 -0
- package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -0
- package/dist/components/base/CkCitation/index.d.ts +3 -0
- package/dist/components/base/CkCitation/index.d.ts.map +1 -0
- package/dist/components/base/CkCitation/types.d.ts +79 -0
- package/dist/components/base/CkCitation/types.d.ts.map +1 -0
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +51 -0
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -0
- package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts +13 -0
- package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -0
- package/dist/components/base/CkCitationCard/index.d.ts +3 -0
- package/dist/components/base/CkCitationCard/index.d.ts.map +1 -0
- package/dist/components/base/CkCitationCard/types.d.ts +51 -0
- package/dist/components/base/CkCitationCard/types.d.ts.map +1 -0
- package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts → CkCopyMessageButton/CkCopyMessageButton.stories.d.ts} +3 -3
- package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts → CkCopyMessageButton/CkCopyMessageButton.vue.d.ts} +4 -4
- package/dist/components/base/CkCopyMessageButton/CkCopyMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkCopyMessageButton/index.d.ts +3 -0
- package/dist/components/base/CkCopyMessageButton/index.d.ts.map +1 -0
- package/dist/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.d.ts +3 -3
- package/dist/components/base/CkCopyMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/CkDropZoneOverlay/CkDropZoneOverlay.vue.d.ts +1 -1
- package/dist/components/base/CkFeedbackButton/types.d.ts +1 -1
- package/dist/components/base/CkFeedbackDialog/CkFeedbackDialog.vue.d.ts +2 -2
- package/dist/components/base/CkFeedbackDialog/types.d.ts +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +1 -1
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts +15 -0
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts +16 -2
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/index.d.ts +1 -1
- package/dist/components/base/CkMarkdownRenderer/index.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/types.d.ts +25 -0
- package/dist/components/base/CkMarkdownRenderer/types.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/useCitationRendering.d.ts +17 -0
- package/dist/components/base/CkMarkdownRenderer/useCitationRendering.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.stories.d.ts → CkMessageButton/CkMessageButton.stories.d.ts} +3 -3
- package/dist/components/base/CkMessageButton/CkMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton/CkMessageActionButton.vue.d.ts → CkMessageButton/CkMessageButton.vue.d.ts} +3 -3
- package/dist/components/base/CkMessageButton/CkMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkMessageButton/index.d.ts +3 -0
- package/dist/components/base/CkMessageButton/index.d.ts.map +1 -0
- package/dist/components/base/{CkMessageActionButton → CkMessageButton}/types.d.ts +2 -2
- package/dist/components/base/CkMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/CkPromptSendButton/CkPromptSendButton.vue.d.ts +1 -1
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts +16 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.d.ts.map +1 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts +12 -0
- package/dist/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkRegenerateMessageButton/index.d.ts +3 -0
- package/dist/components/base/{CkCopyMessageActionButton → CkRegenerateMessageButton}/index.d.ts.map +1 -1
- package/dist/components/base/CkRegenerateMessageButton/types.d.ts +27 -0
- package/dist/components/base/CkRegenerateMessageButton/types.d.ts.map +1 -0
- package/dist/components/base/index.d.ts +8 -3
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +1 -1
- package/dist/components/composed/CkConversation/types.d.ts +1 -1
- package/dist/components/composed/CkInput/CkInput.vue.d.ts +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +26 -23
- package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -1
- package/dist/composables/fileUpload/useFileUploadWrapper.d.ts.map +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/handlers/index.d.ts +2 -0
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/index.js +4 -3
- package/dist/handlers/useChatKitLabels.d.ts +13 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/handlers/useMarkdownParser.d.ts.map +1 -1
- package/dist/handlers/useMessageHandler.d.ts +37 -0
- package/dist/handlers/useMessageHandler.d.ts.map +1 -1
- package/dist/helpers/conversationTree.d.ts +15 -0
- package/dist/helpers/conversationTree.d.ts.map +1 -1
- package/dist/helpers/unified/remarkCitations.d.ts +17 -0
- package/dist/helpers/unified/remarkCitations.d.ts.map +1 -0
- package/dist/{index-DAmWGYNH.js → index-CyrVnbS9.js} +86 -71
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -27
- package/dist/{useChatKitLabels-D5gNv8se.js → useChatKitLabels-zMFV7aEz.js} +31 -3
- package/dist/{useMessageHandler-CMdy-lm0.js → useMessageHandler-N50Yj-NU.js} +45 -7
- package/package.json +5 -3
- package/src/adapters/responses-api.spec.ts +52 -5
- package/src/adapters/responses-api.ts +26 -7
- package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +112 -0
- package/src/components/base/CkAgentMessage/types.ts +1 -1
- package/src/components/base/CkCitation/CkCitation.spec.ts +79 -0
- package/src/components/base/CkCitation/CkCitation.stories.ts +136 -0
- package/src/components/base/CkCitation/CkCitation.vue +145 -0
- package/src/components/base/CkCitation/index.ts +2 -0
- package/src/components/base/CkCitation/types.ts +80 -0
- package/src/components/base/CkCitationCard/CkCitationCard.spec.ts +77 -0
- package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +144 -0
- package/src/components/base/CkCitationCard/CkCitationCard.vue +76 -0
- package/src/components/base/CkCitationCard/index.ts +2 -0
- package/src/components/base/CkCitationCard/types.ts +51 -0
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.spec.ts → CkCopyMessageButton/CkCopyMessageButton.spec.ts} +6 -6
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.stories.ts → CkCopyMessageButton/CkCopyMessageButton.stories.ts} +8 -8
- package/src/components/base/{CkCopyMessageActionButton/CkCopyMessageActionButton.vue → CkCopyMessageButton/CkCopyMessageButton.vue} +6 -6
- package/src/components/base/CkCopyMessageButton/index.ts +2 -0
- package/src/components/base/{CkCopyMessageActionButton → CkCopyMessageButton}/types.ts +3 -3
- package/src/components/base/CkFeedbackButton/CkFeedbackButton.vue +5 -5
- package/src/components/base/CkFeedbackButton/types.ts +1 -1
- package/src/components/base/CkFeedbackDialog/types.ts +1 -1
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +124 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +118 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +70 -28
- package/src/components/base/CkMarkdownRenderer/index.ts +1 -0
- package/src/components/base/CkMarkdownRenderer/types.ts +26 -0
- package/src/components/base/CkMarkdownRenderer/useCitationRendering.spec.ts +94 -0
- package/src/components/base/CkMarkdownRenderer/useCitationRendering.ts +52 -0
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.spec.ts → CkMessageButton/CkMessageButton.spec.ts} +6 -6
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.stories.ts → CkMessageButton/CkMessageButton.stories.ts} +10 -10
- package/src/components/base/{CkMessageActionButton/CkMessageActionButton.vue → CkMessageButton/CkMessageButton.vue} +3 -3
- package/src/components/base/CkMessageButton/index.ts +2 -0
- package/src/components/base/{CkMessageActionButton → CkMessageButton}/types.ts +2 -2
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.spec.ts +49 -0
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.stories.ts +63 -0
- package/src/components/base/CkRegenerateMessageButton/CkRegenerateMessageButton.vue +43 -0
- package/src/components/base/CkRegenerateMessageButton/index.ts +2 -0
- package/src/components/base/CkRegenerateMessageButton/types.ts +27 -0
- package/src/components/base/index.ts +15 -2
- package/src/components/composed/CkConversation/CkConversation.stories.ts +6 -6
- package/src/components/composed/CkConversation/CkConversation.vue +2 -2
- package/src/components/composed/CkConversation/types.ts +1 -1
- package/src/components/index.ts +11 -2
- package/src/composables/fileUpload/useFileUpload.spec.ts +2 -61
- package/src/composables/fileUpload/useFileUpload.ts +2 -9
- package/src/composables/fileUpload/useFileUploadWrapper.spec.ts +195 -14
- package/src/composables/fileUpload/useFileUploadWrapper.ts +114 -81
- package/src/handlers/index.ts +2 -0
- package/src/handlers/useChatKitLabels.spec.ts +1 -0
- package/src/handlers/useChatKitLabels.ts +16 -0
- package/src/handlers/useMarkdownParser.ts +5 -2
- package/src/handlers/useMessageHandler.spec.ts +79 -0
- package/src/handlers/useMessageHandler.ts +50 -2
- package/src/helpers/conversationTree.spec.ts +120 -0
- package/src/helpers/conversationTree.ts +57 -8
- package/src/helpers/unified/remarkCitations.spec.ts +112 -0
- package/src/helpers/unified/remarkCitations.ts +55 -0
- package/src/index.ts +11 -2
- package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.stories.d.ts.map +0 -1
- package/dist/components/base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue.d.ts.map +0 -1
- package/dist/components/base/CkCopyMessageActionButton/index.d.ts +0 -3
- package/dist/components/base/CkCopyMessageActionButton/types.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/CkMessageActionButton.stories.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/CkMessageActionButton.vue.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/index.d.ts +0 -3
- package/dist/components/base/CkMessageActionButton/index.d.ts.map +0 -1
- package/dist/components/base/CkMessageActionButton/types.d.ts.map +0 -1
- package/src/components/base/CkCopyMessageActionButton/index.ts +0 -2
- package/src/components/base/CkMessageActionButton/index.ts +0 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import CkCitationCard from './CkCitationCard.vue';
|
|
2
|
+
import { provideChatKitLabels } from '../../../handlers/useChatKitLabels';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { render, screen } from '@testing-library/vue';
|
|
5
|
+
import { defineComponent, h } from 'vue';
|
|
6
|
+
|
|
7
|
+
const base = {
|
|
8
|
+
fileName: 'q3-report.pdf',
|
|
9
|
+
snippet: 'The cited chunk text.',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
describe('CkCitationCard', () => {
|
|
13
|
+
it('renders the file name and snippet', () => {
|
|
14
|
+
render(CkCitationCard, { props: { ...base, locationLabel: 'Page 3' } });
|
|
15
|
+
|
|
16
|
+
expect(screen.getByText('q3-report.pdf')).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByText('The cited chunk text.')).toBeInTheDocument();
|
|
18
|
+
expect(screen.getByText('Page 3')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('hides the location footer when no locationLabel is given', () => {
|
|
22
|
+
render(CkCitationCard, { props: base });
|
|
23
|
+
|
|
24
|
+
expect(screen.queryByText('Page 3')).not.toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('hides the external-link action when no url is provided', () => {
|
|
28
|
+
render(CkCitationCard, { props: base });
|
|
29
|
+
|
|
30
|
+
expect(
|
|
31
|
+
screen.queryByRole('button', { name: 'Open source' }),
|
|
32
|
+
).not.toBeInTheDocument();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('shows the external-link action and emits open-source with the url', async () => {
|
|
36
|
+
const user = userEvent.setup();
|
|
37
|
+
const { emitted } = render(CkCitationCard, {
|
|
38
|
+
props: { ...base, url: '/api/v1/.../public-urls' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await user.click(screen.getByRole('button', { name: 'Open source' }));
|
|
42
|
+
|
|
43
|
+
expect(emitted('open-source')).toEqual([['/api/v1/.../public-urls']]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('uses a custom openSourceLabel for the action', () => {
|
|
47
|
+
render(CkCitationCard, {
|
|
48
|
+
props: { ...base, url: '/u', openSourceLabel: 'Quelle öffnen' },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(
|
|
52
|
+
screen.getByRole('button', { name: 'Quelle öffnen' }),
|
|
53
|
+
).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('wraps a long unbroken snippet so it cannot overflow the fixed-width card', () => {
|
|
57
|
+
const snippet = 'x'.repeat(200);
|
|
58
|
+
render(CkCitationCard, { props: { ...base, snippet } });
|
|
59
|
+
|
|
60
|
+
expect(screen.getByText(snippet).className).toContain('break-words');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('uses the injected citationOpenSource label via provideChatKitLabels', () => {
|
|
64
|
+
const Wrapper = defineComponent({
|
|
65
|
+
setup() {
|
|
66
|
+
provideChatKitLabels(() => ({ citationOpenSource: 'Quelle öffnen' }));
|
|
67
|
+
return () => h(CkCitationCard, { ...base, url: '/u' });
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
render(Wrapper);
|
|
72
|
+
|
|
73
|
+
expect(
|
|
74
|
+
screen.getByRole('button', { name: 'Quelle öffnen' }),
|
|
75
|
+
).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import CkCitationCard from './CkCitationCard.vue';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
|
+
|
|
4
|
+
const LOREM =
|
|
5
|
+
'Lorem ipsum dolor sit amet consectetur. Phasellus ipsum nunc pellentesque diam donec id fermentum. Morbi amet lacus cras sagittis sagittis libero. Elit netus sodales in risus diam ut.';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CkCitationCard is the floating card that describes a single citation source:
|
|
9
|
+
* the file, the cited excerpt, an optional location (e.g. page), and an
|
|
10
|
+
* optional external-link action. It is the hover/focus content of `CkCitation`,
|
|
11
|
+
* but can be used on its own wherever a source preview is needed. The
|
|
12
|
+
* external-link action is exposed as an `open-source` event rather than a plain
|
|
13
|
+
* link, because the source URL is often auth-proxied.
|
|
14
|
+
*/
|
|
15
|
+
const meta: Meta<typeof CkCitationCard> = {
|
|
16
|
+
title: 'Base/CkCitationCard',
|
|
17
|
+
component: CkCitationCard,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
argTypes: {
|
|
20
|
+
fileName: { control: 'text' },
|
|
21
|
+
snippet: { control: 'text' },
|
|
22
|
+
locationLabel: { control: 'text' },
|
|
23
|
+
url: { control: 'text' },
|
|
24
|
+
openSourceLabel: { control: 'text' },
|
|
25
|
+
},
|
|
26
|
+
args: {
|
|
27
|
+
fileName: 'Q2_Business_Plan.pdf',
|
|
28
|
+
snippet: LOREM,
|
|
29
|
+
locationLabel: 'Page 3',
|
|
30
|
+
url: '/api/v1/search-stores/ss_abc/files/file_123/public-urls',
|
|
31
|
+
},
|
|
32
|
+
decorators: [
|
|
33
|
+
() => ({
|
|
34
|
+
template:
|
|
35
|
+
'<div style="max-width: 640px; margin: 0 auto; padding: 24px;"><story /></div>',
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof meta>;
|
|
42
|
+
|
|
43
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
44
|
+
import { CkCitationCard } from '@aleph-alpha/chat-kit';
|
|
45
|
+
|
|
46
|
+
function openSource(url: string) {
|
|
47
|
+
// The source URL is often auth-proxied — the host opens it.
|
|
48
|
+
window.open(url, '_blank', 'noopener');
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<CkCitationCard
|
|
54
|
+
file-name="Q2_Business_Plan.pdf"
|
|
55
|
+
snippet="Lorem ipsum dolor sit amet consectetur…"
|
|
56
|
+
location-label="Page 3"
|
|
57
|
+
url="/api/v1/search-stores/ss_abc/files/file_123/public-urls"
|
|
58
|
+
@open-source="openSource"
|
|
59
|
+
/>
|
|
60
|
+
</template>`;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Full card: file-type badge, title, external-link action, excerpt, and a
|
|
64
|
+
* page footer. Matches the Figma `citation card` frame.
|
|
65
|
+
*/
|
|
66
|
+
export const Default: Story = {
|
|
67
|
+
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* No `url` — the external-link action is hidden. Use for sources that can't
|
|
72
|
+
* be opened (e.g. an internal chunk with no servable file).
|
|
73
|
+
*/
|
|
74
|
+
export const WithoutLink: Story = {
|
|
75
|
+
args: { url: undefined },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* No `locationLabel` — the footer is hidden. Use for non-paginated sources
|
|
80
|
+
* such as a plain web page or a whole-document citation.
|
|
81
|
+
*/
|
|
82
|
+
export const WithoutLocation: Story = {
|
|
83
|
+
args: { locationLabel: undefined },
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Minimal: title + excerpt only (no link, no location). The leanest source a
|
|
88
|
+
* card can describe.
|
|
89
|
+
*/
|
|
90
|
+
export const MinimalSnippetOnly: Story = {
|
|
91
|
+
args: { url: undefined, locationLabel: undefined },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A long file name truncates with an ellipsis (the badge and external-link
|
|
96
|
+
* action stay pinned, the card keeps its width); the full name stays available
|
|
97
|
+
* on hover via the `title` attribute. The `truncate` behaviour holds for any
|
|
98
|
+
* length, including a single unbroken token with no separators.
|
|
99
|
+
*/
|
|
100
|
+
export const LongFileName: Story = {
|
|
101
|
+
args: {
|
|
102
|
+
fileName:
|
|
103
|
+
'Annual-Strategy-And-Operational-Review-And-Lessons-Learned-Digital-Transformation-Phase-1-FY2026-Final-APPROVED-v3.pdf',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* A long excerpt wraps freely — the card grows vertically while keeping its
|
|
109
|
+
* fixed width.
|
|
110
|
+
*/
|
|
111
|
+
export const LongSnippet: Story = {
|
|
112
|
+
args: {
|
|
113
|
+
snippet: `${LOREM} ${LOREM}`,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The file-type badge derives its icon and tone from the file extension:
|
|
119
|
+
* `.pdf`/`.docx` → pink, `.md`/`.html` → soft, `.csv`/`.xlsx` → yellow.
|
|
120
|
+
*/
|
|
121
|
+
export const FileTypeVariants: Story = {
|
|
122
|
+
render: (args) => ({
|
|
123
|
+
components: { CkCitationCard },
|
|
124
|
+
setup() {
|
|
125
|
+
const files = [
|
|
126
|
+
'q3-report.pdf',
|
|
127
|
+
'README.md',
|
|
128
|
+
'sales-2026.csv',
|
|
129
|
+
'notes.txt',
|
|
130
|
+
];
|
|
131
|
+
return { args, files };
|
|
132
|
+
},
|
|
133
|
+
template: `
|
|
134
|
+
<div style="display: flex; flex-direction: column; gap: 16px;">
|
|
135
|
+
<CkCitationCard
|
|
136
|
+
v-for="f in files"
|
|
137
|
+
:key="f"
|
|
138
|
+
v-bind="args"
|
|
139
|
+
:file-name="f"
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
`,
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { UiFileType, UiButton, UiIcon } from '@aleph-alpha/ui-library';
|
|
4
|
+
import {
|
|
5
|
+
defaultChatKitLabels,
|
|
6
|
+
useChatKitLabels,
|
|
7
|
+
} from '../../../handlers/useChatKitLabels';
|
|
8
|
+
import type { CkCitationCardProps } from './types';
|
|
9
|
+
|
|
10
|
+
defineOptions({
|
|
11
|
+
name: 'CkCitationCard',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(defineProps<CkCitationCardProps>(), {
|
|
15
|
+
locationLabel: undefined,
|
|
16
|
+
url: undefined,
|
|
17
|
+
openSourceLabel: undefined,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'open-source': [url: string];
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
const labels = useChatKitLabels();
|
|
25
|
+
|
|
26
|
+
// Resolution order: per-instance prop, injected label, English default.
|
|
27
|
+
const openSourceAriaLabel = computed(
|
|
28
|
+
() =>
|
|
29
|
+
props.openSourceLabel ??
|
|
30
|
+
labels.value.citationOpenSource ??
|
|
31
|
+
defaultChatKitLabels.citationOpenSource!,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
function handleOpen(): void {
|
|
35
|
+
if (props.url) {
|
|
36
|
+
emit('open-source', props.url);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div
|
|
43
|
+
class="ck-citation-card flex w-[22.5rem] max-w-[90vw] flex-col gap-2 rounded-md bg-background-surface-floating px-6 pb-6 pt-4 shadow-md"
|
|
44
|
+
>
|
|
45
|
+
<div class="flex items-center gap-3">
|
|
46
|
+
<UiFileType :filename="fileName" class="shrink-0" />
|
|
47
|
+
<p
|
|
48
|
+
class="min-w-0 flex-1 truncate text-sm font-medium text-content-on-surface-primary"
|
|
49
|
+
:title="fileName"
|
|
50
|
+
>
|
|
51
|
+
{{ fileName }}
|
|
52
|
+
</p>
|
|
53
|
+
<UiButton
|
|
54
|
+
v-if="url"
|
|
55
|
+
variant="ghost"
|
|
56
|
+
size="icon-sm"
|
|
57
|
+
:aria-label="openSourceAriaLabel"
|
|
58
|
+
class="shrink-0"
|
|
59
|
+
@click="handleOpen"
|
|
60
|
+
>
|
|
61
|
+
<UiIcon name="external-link" :size="16" aria-hidden="true" />
|
|
62
|
+
</UiButton>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<p class="break-words text-sm leading-5 text-content-on-surface-secondary">
|
|
66
|
+
{{ snippet }}
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<p
|
|
70
|
+
v-if="locationLabel"
|
|
71
|
+
class="text-sm leading-5 text-content-on-surface-muted"
|
|
72
|
+
>
|
|
73
|
+
{{ locationLabel }}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The floating card that describes a single citation source: the source file,
|
|
3
|
+
* the cited excerpt, an optional location (e.g. page), and an optional
|
|
4
|
+
* external-link action. Rendered as the hover/focus content of `CkCitation`,
|
|
5
|
+
* but usable on its own wherever a source preview is needed.
|
|
6
|
+
*
|
|
7
|
+
* Purely presentational and prop-driven. The external-link action is exposed
|
|
8
|
+
* as an `open-source` event rather than a plain link, because the source URL
|
|
9
|
+
* is often auth-proxied and cannot be navigated to directly.
|
|
10
|
+
*
|
|
11
|
+
* @category Data Display
|
|
12
|
+
* @useCases citation source preview, hover card for an inline reference, show the cited file/page/excerpt
|
|
13
|
+
* @keywords citation, source, card, popover, reference, file, page, excerpt, preview
|
|
14
|
+
* @related CkCitation, CkMarkdownRenderer, CkAgentMessage
|
|
15
|
+
*/
|
|
16
|
+
export interface CkCitationCardProps {
|
|
17
|
+
/**
|
|
18
|
+
* Source file name. Rendered as the card title and used to derive the
|
|
19
|
+
* file-type badge icon/tone (pass the original name with its extension,
|
|
20
|
+
* e.g. `q3-report.pdf`).
|
|
21
|
+
*/
|
|
22
|
+
fileName: string;
|
|
23
|
+
/** The cited chunk text, shown in the card body. */
|
|
24
|
+
snippet: string;
|
|
25
|
+
/**
|
|
26
|
+
* Location label shown in the footer, e.g. `"Page 3"`. Pass a fully
|
|
27
|
+
* formatted, localized string — the component does no number formatting.
|
|
28
|
+
* Omit for non-paginated sources to hide the footer.
|
|
29
|
+
*/
|
|
30
|
+
locationLabel?: string;
|
|
31
|
+
/**
|
|
32
|
+
* When set, the external-link action is shown in the header. Activating it
|
|
33
|
+
* emits `open-source` with this value; the host opens it (it may be an
|
|
34
|
+
* auth-proxied URL that can't be a plain link). Omit to hide the action.
|
|
35
|
+
*/
|
|
36
|
+
url?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Accessible label for the external-link action. When omitted, resolves from
|
|
39
|
+
* `useChatKitLabels().citationOpenSource` (English default `"Open source"`).
|
|
40
|
+
* Set this to override per instance.
|
|
41
|
+
*/
|
|
42
|
+
openSourceLabel?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CkCitationCardEmits {
|
|
46
|
+
/**
|
|
47
|
+
* Emitted when the user activates the header external-link action. The
|
|
48
|
+
* payload is `url`. Only emitted when `url` is set.
|
|
49
|
+
*/
|
|
50
|
+
(e: 'open-source', url: string): void;
|
|
51
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkCopyMessageButton from './CkCopyMessageButton.vue';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { render, screen, waitFor } from '@testing-library/vue';
|
|
4
4
|
import { flushPromises } from '@vue/test-utils';
|
|
5
5
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
6
|
|
|
7
|
-
describe('
|
|
7
|
+
describe('CkCopyMessageButton', () => {
|
|
8
8
|
const writeText = vi.fn((_: string) => Promise.resolve());
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
@@ -14,7 +14,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
14
14
|
|
|
15
15
|
it('emits copy with success when clipboard succeeds', async () => {
|
|
16
16
|
const user = userEvent.setup();
|
|
17
|
-
const { emitted } = render(
|
|
17
|
+
const { emitted } = render(CkCopyMessageButton, {
|
|
18
18
|
props: { text: 'x', writeToClipboard: writeText },
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -27,7 +27,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
27
27
|
|
|
28
28
|
it('copies text and updates the accessible name', async () => {
|
|
29
29
|
const user = userEvent.setup();
|
|
30
|
-
render(
|
|
30
|
+
render(CkCopyMessageButton, {
|
|
31
31
|
props: { text: 'Hello, world', writeToClipboard: writeText },
|
|
32
32
|
});
|
|
33
33
|
|
|
@@ -50,7 +50,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
50
50
|
|
|
51
51
|
it('emits copy with success false', async () => {
|
|
52
52
|
const user = userEvent.setup();
|
|
53
|
-
const { emitted } = render(
|
|
53
|
+
const { emitted } = render(CkCopyMessageButton, {
|
|
54
54
|
props: { text: 'x', writeToClipboard: writeText },
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -67,7 +67,7 @@ describe('CkCopyMessageActionButton', () => {
|
|
|
67
67
|
it('reverts label after durationMs', async () => {
|
|
68
68
|
vi.useFakeTimers();
|
|
69
69
|
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
|
|
70
|
-
render(
|
|
70
|
+
render(CkCopyMessageButton, {
|
|
71
71
|
props: { text: 'x', durationMs: 1500, writeToClipboard: writeText },
|
|
72
72
|
});
|
|
73
73
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkCopyMessageButton from './CkCopyMessageButton.vue';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
3
|
|
|
4
|
-
const meta: Meta<typeof
|
|
5
|
-
title: 'Base/
|
|
6
|
-
component:
|
|
4
|
+
const meta: Meta<typeof CkCopyMessageButton> = {
|
|
5
|
+
title: 'Base/CkCopyMessageButton',
|
|
6
|
+
component: CkCopyMessageButton,
|
|
7
7
|
tags: ['autodocs'],
|
|
8
8
|
argTypes: {
|
|
9
9
|
text: {
|
|
@@ -31,19 +31,19 @@ export default meta;
|
|
|
31
31
|
type Story = StoryObj<typeof meta>;
|
|
32
32
|
|
|
33
33
|
const defaultTemplateSource = `<script setup lang="ts">
|
|
34
|
-
import {
|
|
34
|
+
import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
37
|
<template>
|
|
38
|
-
<
|
|
38
|
+
<CkCopyMessageButton text="Sample assistant reply text to copy." />
|
|
39
39
|
</template>`;
|
|
40
40
|
|
|
41
41
|
const disabledTemplateSource = `<script setup lang="ts">
|
|
42
|
-
import {
|
|
42
|
+
import { CkCopyMessageButton } from '@aleph-alpha/chat-kit';
|
|
43
43
|
</script>
|
|
44
44
|
|
|
45
45
|
<template>
|
|
46
|
-
<
|
|
46
|
+
<CkCopyMessageButton text="Cannot copy when disabled." disabled />
|
|
47
47
|
</template>`;
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
2
|
+
import { CkMessageButton } from '../CkMessageButton';
|
|
3
|
+
import type { CkCopyMessageButtonProps } from './types';
|
|
4
4
|
import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
|
|
5
5
|
import { onUnmounted, ref } from 'vue';
|
|
6
6
|
|
|
7
7
|
defineOptions({
|
|
8
|
-
name: '
|
|
8
|
+
name: 'CkCopyMessageButton',
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const props = withDefaults(defineProps<
|
|
11
|
+
const props = withDefaults(defineProps<CkCopyMessageButtonProps>(), {
|
|
12
12
|
disabled: false,
|
|
13
13
|
durationMs: 3000,
|
|
14
14
|
ariaLabelCopy: 'Copy message',
|
|
@@ -68,7 +68,7 @@ async function handleClick() {
|
|
|
68
68
|
align="center"
|
|
69
69
|
:delay-duration="0"
|
|
70
70
|
>
|
|
71
|
-
<
|
|
71
|
+
<CkMessageButton
|
|
72
72
|
v-bind="{ ariaLabel: copied ? ariaLabelCopied : ariaLabelCopy }"
|
|
73
73
|
:disabled="disabled"
|
|
74
74
|
@click="handleClick"
|
|
@@ -83,7 +83,7 @@ async function handleClick() {
|
|
|
83
83
|
/>
|
|
84
84
|
<UiIcon v-else key="check" name="check" :size="16" aria-hidden="true" />
|
|
85
85
|
</Transition>
|
|
86
|
-
</
|
|
86
|
+
</CkMessageButton>
|
|
87
87
|
<span class="sr-only" aria-live="polite">
|
|
88
88
|
{{ copied ? ariaLabelCopied : ariaLabelCopy }}
|
|
89
89
|
</span>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* @category Actions
|
|
6
6
|
* @useCases copy assistant reply, copy message to clipboard, chat message action, feedback action on message footer
|
|
7
7
|
* @keywords copy, clipboard, action, message, button, icon, feedback
|
|
8
|
-
* @related
|
|
8
|
+
* @related CkMessageButton, CkAgentMessage, CkConversation
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
10
|
+
export interface CkCopyMessageButtonProps {
|
|
11
11
|
/** Plain text to copy to the clipboard. */
|
|
12
12
|
text: string;
|
|
13
13
|
/**
|
|
@@ -37,7 +37,7 @@ export interface CkCopyMessageActionButtonProps {
|
|
|
37
37
|
writeToClipboard?: (text: string) => Promise<void>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export interface
|
|
40
|
+
export interface CkCopyMessageButtonEmits {
|
|
41
41
|
/** Emitted after the clipboard operation finishes (success or failure). */
|
|
42
42
|
(e: 'copy', payload: { success: boolean }): void;
|
|
43
43
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
defaultChatKitLabels,
|
|
4
4
|
useChatKitLabels,
|
|
5
5
|
} from '../../../handlers/useChatKitLabels';
|
|
6
|
-
import {
|
|
6
|
+
import { CkMessageButton } from '../CkMessageButton';
|
|
7
7
|
import type { CkFeedbackButtonProps, CkFeedbackValue } from './types';
|
|
8
8
|
import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
|
|
9
9
|
import { computed } from 'vue';
|
|
@@ -81,7 +81,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
81
81
|
align="center"
|
|
82
82
|
:delay-duration="0"
|
|
83
83
|
>
|
|
84
|
-
<
|
|
84
|
+
<CkMessageButton
|
|
85
85
|
v-bind="{ ariaLabel: thumbsUpLabel }"
|
|
86
86
|
:aria-pressed="isUpSelected"
|
|
87
87
|
:aria-disabled="locked || undefined"
|
|
@@ -98,7 +98,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
98
98
|
aria-hidden="true"
|
|
99
99
|
:class="isUpSelected ? 'fill-current' : undefined"
|
|
100
100
|
/>
|
|
101
|
-
</
|
|
101
|
+
</CkMessageButton>
|
|
102
102
|
</UiTooltip>
|
|
103
103
|
<UiTooltip
|
|
104
104
|
:content="thumbsDownLabel"
|
|
@@ -106,7 +106,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
106
106
|
align="center"
|
|
107
107
|
:delay-duration="0"
|
|
108
108
|
>
|
|
109
|
-
<
|
|
109
|
+
<CkMessageButton
|
|
110
110
|
v-bind="{ ariaLabel: thumbsDownLabel }"
|
|
111
111
|
:aria-pressed="isDownSelected"
|
|
112
112
|
:aria-disabled="locked || undefined"
|
|
@@ -123,7 +123,7 @@ function handleClick(target: 'up' | 'down'): void {
|
|
|
123
123
|
aria-hidden="true"
|
|
124
124
|
:class="isDownSelected ? 'fill-current' : undefined"
|
|
125
125
|
/>
|
|
126
|
-
</
|
|
126
|
+
</CkMessageButton>
|
|
127
127
|
</UiTooltip>
|
|
128
128
|
</div>
|
|
129
129
|
</template>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @category Feedback
|
|
11
11
|
* @useCases like or dislike an assistant reply, collect binary message rating, lock feedback after submission, rate AI response
|
|
12
12
|
* @keywords feedback, thumbs up, thumbs down, like, dislike, rating, vote, action, message, button
|
|
13
|
-
* @related
|
|
13
|
+
* @related CkMessageButton, CkCopyMessageButton, CkAgentMessage, CkConversation
|
|
14
14
|
*/
|
|
15
15
|
export type CkFeedbackValue = 'up' | 'down' | null;
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* capture a free-text comment, drive a two-step rating flow
|
|
19
19
|
* @keywords feedback, dialog, modal, reason codes, free text, qualitative,
|
|
20
20
|
* rating, comment
|
|
21
|
-
* @related CkFeedbackButton,
|
|
21
|
+
* @related CkFeedbackButton, CkMessageButton, CkAgentMessage,
|
|
22
22
|
* CkConversation, UiDialog, UiToggleGroup
|
|
23
23
|
*/
|
|
24
24
|
export type CkFeedbackSentiment = 'positive' | 'negative';
|