@aleph-alpha/chat-kit 3.3.0 → 5.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-BAcBFs6I.js → CkHistory.vue_vue_type_script_setup_true_lang-Bp9hXdH6.js} +452 -348
- 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 +36 -14
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts +14 -13
- package/dist/components/base/CkAgentMessage/CkAgentMessage.stories.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts +8 -7
- package/dist/components/base/CkAgentMessage/CkAgentMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessage/types.d.ts +16 -5
- package/dist/components/base/CkAgentMessage/types.d.ts.map +1 -1
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts +16 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts +21 -0
- package/dist/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/index.d.ts +3 -0
- package/dist/components/base/CkAgentMessageHeader/index.d.ts.map +1 -0
- package/dist/components/base/CkAgentMessageHeader/types.d.ts +22 -0
- package/dist/components/base/CkAgentMessageHeader/types.d.ts.map +1 -0
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts +6 -0
- package/dist/components/base/CkCitation/CkCitation.stories.d.ts.map +1 -1
- package/dist/components/base/CkCitation/CkCitation.vue.d.ts.map +1 -1
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts +5 -4
- package/dist/components/base/CkCitationCard/CkCitationCard.stories.d.ts.map +1 -1
- package/dist/components/base/CkCitationCard/CkCitationCard.vue.d.ts.map +1 -1
- 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/CkFeedbackButton/types.d.ts +1 -1
- package/dist/components/base/CkFeedbackDialog/types.d.ts +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts +15 -4
- package/dist/components/base/CkFileItemsList/CkFileItemsList.stories.d.ts.map +1 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts +2 -1
- package/dist/components/base/CkFileItemsList/CkFileItemsList.vue.d.ts.map +1 -1
- package/dist/components/base/CkFileItemsList/types.d.ts +17 -4
- package/dist/components/base/CkFileItemsList/types.d.ts.map +1 -1
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts +19 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts +22 -0
- package/dist/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/index.d.ts +3 -0
- package/dist/components/base/CkHeaderActivityButton/index.d.ts.map +1 -0
- package/dist/components/base/CkHeaderActivityButton/types.d.ts +36 -0
- package/dist/components/base/CkHeaderActivityButton/types.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/CkPromptInput/CkPromptInput.stories.d.ts +6 -6
- package/dist/components/base/CkPromptInput/CkPromptInput.vue.d.ts.map +1 -1
- package/dist/components/base/CkPromptInput/types.d.ts +4 -4
- package/dist/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue.d.ts.map +1 -1
- package/dist/components/base/CkPromptInputDisclaimer/types.d.ts +1 -1
- package/dist/components/base/CkPromptSendButton/CkPromptSendButton.vue.d.ts +1 -1
- package/dist/components/base/CkPromptSendButton/types.d.ts +1 -1
- package/dist/components/base/CkPromptStopButton/types.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 +7 -2
- 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/CkConversation.vue.d.ts +13 -1
- package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
- package/dist/components/composed/CkConversation/types.d.ts +1 -1
- package/dist/components/composed/index.d.ts +0 -1
- package/dist/components/composed/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +26 -24
- package/dist/composables/index.js +1 -1
- package/dist/handlers/index.js +2 -2
- package/dist/handlers/useChatKitLabels.d.ts +2 -0
- package/dist/handlers/useChatKitLabels.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/{index-C6njJ8EB.js → index-CyrVnbS9.js} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -28
- package/dist/{useChatKitLabels-BM2I7-9o.js → useChatKitLabels-zMFV7aEz.js} +1 -0
- package/dist/{useMessageHandler-CMdy-lm0.js → useMessageHandler-N50Yj-NU.js} +45 -7
- package/package.json +3 -3
- package/src/adapters/responses-api.spec.ts +52 -5
- package/src/adapters/responses-api.ts +26 -7
- package/src/components/base/CkAgentMessage/CkAgentMessage.spec.ts +12 -17
- package/src/components/base/CkAgentMessage/CkAgentMessage.stories.ts +124 -113
- package/src/components/base/CkAgentMessage/CkAgentMessage.vue +18 -16
- package/src/components/base/CkAgentMessage/types.ts +16 -5
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.spec.ts +64 -0
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.stories.ts +90 -0
- package/src/components/base/CkAgentMessageHeader/CkAgentMessageHeader.vue +47 -0
- package/src/components/base/CkAgentMessageHeader/index.ts +2 -0
- package/src/components/base/CkAgentMessageHeader/types.ts +21 -0
- package/src/components/base/CkCitation/CkCitation.stories.ts +12 -0
- package/src/components/base/CkCitation/CkCitation.vue +34 -9
- package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +5 -4
- package/src/components/base/CkCitationCard/CkCitationCard.vue +2 -2
- package/src/components/base/CkConversationLayout/CkConversationLayout.vue +1 -1
- 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/CkFileItemsList/CkFileItemsList.spec.ts +88 -13
- package/src/components/base/CkFileItemsList/CkFileItemsList.stories.ts +61 -13
- package/src/components/base/CkFileItemsList/CkFileItemsList.vue +153 -110
- package/src/components/base/CkFileItemsList/types.ts +17 -4
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.spec.ts +50 -0
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.stories.ts +102 -0
- package/src/components/base/CkHeaderActivityButton/CkHeaderActivityButton.vue +39 -0
- package/src/components/base/CkHeaderActivityButton/index.ts +2 -0
- package/src/components/base/CkHeaderActivityButton/types.ts +36 -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/CkPromptInput/CkPromptInput.spec.ts +88 -0
- package/src/components/base/CkPromptInput/CkPromptInput.stories.ts +6 -6
- package/src/components/base/CkPromptInput/CkPromptInput.vue +63 -12
- package/src/components/base/CkPromptInput/types.ts +4 -4
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.spec.ts +1 -1
- package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.vue +1 -1
- package/src/components/base/CkPromptInputDisclaimer/types.ts +1 -1
- package/src/components/base/CkPromptSendButton/types.ts +1 -1
- package/src/components/base/CkPromptStopButton/types.ts +1 -1
- 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 +10 -2
- package/src/components/composed/CkConversation/CkConversation.spec.ts +81 -6
- package/src/components/composed/CkConversation/CkConversation.stories.ts +6 -6
- package/src/components/composed/CkConversation/CkConversation.vue +80 -37
- package/src/components/composed/CkConversation/types.ts +1 -1
- package/src/components/composed/index.ts +0 -1
- package/src/components/index.ts +9 -3
- package/src/handlers/useChatKitLabels.spec.ts +1 -0
- package/src/handlers/useChatKitLabels.ts +3 -0
- 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/index.ts +8 -3
- 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/dist/components/composed/CkInput/CkInput.stories.d.ts +0 -43
- package/dist/components/composed/CkInput/CkInput.stories.d.ts.map +0 -1
- package/dist/components/composed/CkInput/CkInput.vue.d.ts +0 -50
- package/dist/components/composed/CkInput/CkInput.vue.d.ts.map +0 -1
- package/dist/components/composed/CkInput/index.d.ts +0 -3
- package/dist/components/composed/CkInput/index.d.ts.map +0 -1
- package/dist/components/composed/CkInput/types.d.ts +0 -55
- package/dist/components/composed/CkInput/types.d.ts.map +0 -1
- package/src/components/base/CkCopyMessageActionButton/index.ts +0 -2
- package/src/components/base/CkMessageActionButton/index.ts +0 -2
- package/src/components/composed/CkInput/CkInput.spec.ts +0 -191
- package/src/components/composed/CkInput/CkInput.stories.ts +0 -434
- package/src/components/composed/CkInput/CkInput.vue +0 -106
- package/src/components/composed/CkInput/index.ts +0 -2
- package/src/components/composed/CkInput/types.ts +0 -55
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact, ghost-styled button shown on the right of an agent message header
|
|
3
|
+
* (see `CkAgentMessageHeader`). Renders a short label followed by a trailing
|
|
4
|
+
* icon (an arrow by default) and emits a generic `click` event — it performs
|
|
5
|
+
* no action of its own, leaving the consumer to decide what happens (e.g. open
|
|
6
|
+
* an activity / chain-of-thought panel).
|
|
7
|
+
*
|
|
8
|
+
* @category Actions
|
|
9
|
+
* @useCases agent message header action, open activity panel, chain-of-thought summary toggle, tool/source indicator
|
|
10
|
+
* @keywords header, activity, action, button, ghost, arrow, agent, message
|
|
11
|
+
* @related CkAgentMessageHeader, CkAgentMessage, CkConversation, CkMessageButton
|
|
12
|
+
*/
|
|
13
|
+
export interface CkHeaderActivityButtonProps {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the button is disabled and ignores clicks.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Accessible label for the button. Defaults to the rendered label text when
|
|
21
|
+
* omitted, but should be set when the slot content is not descriptive on its
|
|
22
|
+
* own.
|
|
23
|
+
*/
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Trailing [Lucide](https://lucide.dev/icons) icon name shown after the
|
|
27
|
+
* label. Set to an empty string to hide the icon.
|
|
28
|
+
* @default 'arrow-right'
|
|
29
|
+
*/
|
|
30
|
+
icon?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CkHeaderActivityButtonEmits {
|
|
34
|
+
/** Emitted when the button is pressed and not disabled. */
|
|
35
|
+
(e: 'click'): void;
|
|
36
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkMessageButton from './CkMessageButton.vue';
|
|
2
2
|
import { render, screen } from '@testing-library/vue';
|
|
3
3
|
import { describe, expect, it } from 'vitest';
|
|
4
4
|
|
|
5
|
-
describe('
|
|
5
|
+
describe('CkMessageButton', () => {
|
|
6
6
|
it('renders slot content inside the button', () => {
|
|
7
|
-
render(
|
|
7
|
+
render(CkMessageButton, {
|
|
8
8
|
props: { ariaLabel: 'Action' },
|
|
9
9
|
slots: { default: '<svg data-testid="icon"></svg>' },
|
|
10
10
|
});
|
|
@@ -13,7 +13,7 @@ describe('CkMessageActionButton', () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
it('sets the accessible label on the button', () => {
|
|
16
|
-
render(
|
|
16
|
+
render(CkMessageButton, {
|
|
17
17
|
props: { ariaLabel: 'Copy message' },
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -23,7 +23,7 @@ describe('CkMessageActionButton', () => {
|
|
|
23
23
|
});
|
|
24
24
|
|
|
25
25
|
it('passes disabled state to the button', () => {
|
|
26
|
-
render(
|
|
26
|
+
render(CkMessageButton, {
|
|
27
27
|
props: { ariaLabel: 'Action', disabled: true },
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -31,7 +31,7 @@ describe('CkMessageActionButton', () => {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
it('defaults to enabled', () => {
|
|
34
|
-
render(
|
|
34
|
+
render(CkMessageButton, {
|
|
35
35
|
props: { ariaLabel: 'Action' },
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import CkMessageButton from './CkMessageButton.vue';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
3
|
import { UiIcon } from '@aleph-alpha/ui-library';
|
|
4
4
|
|
|
5
|
-
const meta: Meta<typeof
|
|
6
|
-
title: 'Base/
|
|
7
|
-
component:
|
|
5
|
+
const meta: Meta<typeof CkMessageButton> = {
|
|
6
|
+
title: 'Base/CkMessageButton',
|
|
7
|
+
component: CkMessageButton,
|
|
8
8
|
tags: ['autodocs'],
|
|
9
9
|
argTypes: {
|
|
10
10
|
ariaLabel: {
|
|
@@ -29,14 +29,14 @@ export default meta;
|
|
|
29
29
|
type Story = StoryObj<typeof meta>;
|
|
30
30
|
|
|
31
31
|
const defaultTemplateSource = `<script setup lang="ts">
|
|
32
|
-
import {
|
|
32
|
+
import { CkMessageButton } from '@aleph-alpha/chat-kit';
|
|
33
33
|
import { UiIcon } from '@aleph-alpha/ui-library';
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
<template>
|
|
37
|
-
<
|
|
37
|
+
<CkMessageButton ariaLabel="Copy message">
|
|
38
38
|
<UiIcon name="copy" :size="16" aria-hidden="true" />
|
|
39
|
-
</
|
|
39
|
+
</CkMessageButton>
|
|
40
40
|
</template>`;
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -45,11 +45,11 @@ import { UiIcon } from '@aleph-alpha/ui-library';
|
|
|
45
45
|
*/
|
|
46
46
|
export const Default: Story = {
|
|
47
47
|
render: () => ({
|
|
48
|
-
components: {
|
|
48
|
+
components: { CkMessageButton, UiIcon },
|
|
49
49
|
template: `
|
|
50
|
-
<
|
|
50
|
+
<CkMessageButton ariaLabel="Copy message">
|
|
51
51
|
<UiIcon name="copy" :size="16" aria-hidden="true" />
|
|
52
|
-
</
|
|
52
|
+
</CkMessageButton>
|
|
53
53
|
`,
|
|
54
54
|
}),
|
|
55
55
|
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type { CkMessageButtonProps } from './types';
|
|
3
3
|
import { UiButton } from '@aleph-alpha/ui-library';
|
|
4
4
|
|
|
5
5
|
defineOptions({
|
|
6
|
-
name: '
|
|
6
|
+
name: 'CkMessageButton',
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
withDefaults(defineProps<
|
|
9
|
+
withDefaults(defineProps<CkMessageButtonProps>(), {
|
|
10
10
|
disabled: false,
|
|
11
11
|
});
|
|
12
12
|
</script>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* @category Actions
|
|
6
6
|
* @useCases message footer action, like/dislike reaction, share button on reply, secondary message action
|
|
7
7
|
* @keywords icon button, action, message, ghost, accessible, aria-label
|
|
8
|
-
* @related
|
|
8
|
+
* @related CkCopyMessageButton, CkAgentMessage, CkConversation
|
|
9
9
|
*/
|
|
10
|
-
export interface
|
|
10
|
+
export interface CkMessageButtonProps {
|
|
11
11
|
/**
|
|
12
12
|
* Accessible label for the button. **Required** because the button only
|
|
13
13
|
* renders an icon.
|
|
@@ -199,6 +199,94 @@ describe('CkPromptInput', () => {
|
|
|
199
199
|
});
|
|
200
200
|
});
|
|
201
201
|
|
|
202
|
+
// The focus highlight (border + ring) must appear for keyboard navigation
|
|
203
|
+
// only — not when the textarea is focused by a mouse click. The component
|
|
204
|
+
// distinguishes the two by remembering whether a pointerdown immediately
|
|
205
|
+
// preceded the focus event.
|
|
206
|
+
describe('keyboard vs mouse focus highlight', () => {
|
|
207
|
+
const HIGHLIGHT_CLASS = 'ring-2';
|
|
208
|
+
|
|
209
|
+
it('shows the highlight when focused via keyboard (no preceding pointer)', async () => {
|
|
210
|
+
const { container } = render(CkPromptInput);
|
|
211
|
+
|
|
212
|
+
await fireEvent.focus(screen.getByRole('textbox'));
|
|
213
|
+
|
|
214
|
+
const form = container.querySelector('form');
|
|
215
|
+
expect(form?.className).toContain(HIGHLIGHT_CLASS);
|
|
216
|
+
expect(form?.className).toContain('border-border-surface-focus-ring');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('does NOT show the highlight when focused via mouse click', async () => {
|
|
220
|
+
const { container } = render(CkPromptInput);
|
|
221
|
+
const textarea = screen.getByRole('textbox');
|
|
222
|
+
|
|
223
|
+
// A real mouse click fires pointerdown before focus.
|
|
224
|
+
await fireEvent.pointerDown(textarea);
|
|
225
|
+
await fireEvent.focus(textarea);
|
|
226
|
+
|
|
227
|
+
expect(container.querySelector('form')?.className).not.toContain(
|
|
228
|
+
HIGHLIGHT_CLASS,
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('removes the highlight on blur', async () => {
|
|
233
|
+
const { container } = render(CkPromptInput);
|
|
234
|
+
const textarea = screen.getByRole('textbox');
|
|
235
|
+
|
|
236
|
+
await fireEvent.focus(textarea);
|
|
237
|
+
expect(container.querySelector('form')?.className).toContain(
|
|
238
|
+
HIGHLIGHT_CLASS,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await fireEvent.blur(textarea);
|
|
242
|
+
expect(container.querySelector('form')?.className).not.toContain(
|
|
243
|
+
HIGHLIGHT_CLASS,
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('shows the highlight on a later keyboard focus after a prior mouse focus', async () => {
|
|
248
|
+
const { container } = render(CkPromptInput);
|
|
249
|
+
const textarea = screen.getByRole('textbox');
|
|
250
|
+
|
|
251
|
+
// First interaction: mouse click → no highlight.
|
|
252
|
+
await fireEvent.pointerDown(textarea);
|
|
253
|
+
await fireEvent.focus(textarea);
|
|
254
|
+
expect(container.querySelector('form')?.className).not.toContain(
|
|
255
|
+
HIGHLIGHT_CLASS,
|
|
256
|
+
);
|
|
257
|
+
await fireEvent.blur(textarea);
|
|
258
|
+
|
|
259
|
+
// Second interaction: tab in (focus with no preceding pointerdown). The
|
|
260
|
+
// pointer flag must have reset, so the highlight returns.
|
|
261
|
+
await fireEvent.focus(textarea);
|
|
262
|
+
expect(container.querySelector('form')?.className).toContain(
|
|
263
|
+
HIGHLIGHT_CLASS,
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('is not misclassified by a pointerdown inside an already-focused field', async () => {
|
|
268
|
+
const { container } = render(CkPromptInput);
|
|
269
|
+
const textarea = screen.getByRole('textbox');
|
|
270
|
+
|
|
271
|
+
// Keyboard focus → highlight on.
|
|
272
|
+
await fireEvent.focus(textarea);
|
|
273
|
+
expect(container.querySelector('form')?.className).toContain(
|
|
274
|
+
HIGHLIGHT_CLASS,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
// Click inside the already-focused field: pointerdown fires with no
|
|
278
|
+
// following focus, which would otherwise leave the pointer flag stale.
|
|
279
|
+
await fireEvent.pointerDown(textarea);
|
|
280
|
+
|
|
281
|
+
// Blur then keyboard-focus again — the stale flag must not suppress it.
|
|
282
|
+
await fireEvent.blur(textarea);
|
|
283
|
+
await fireEvent.focus(textarea);
|
|
284
|
+
expect(container.querySelector('form')?.className).toContain(
|
|
285
|
+
HIGHLIGHT_CLASS,
|
|
286
|
+
);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
202
290
|
// The form switches between compact (single-row, rounded-3xl) and stacked
|
|
203
291
|
// (multi-row, rounded-2xl) layouts based on (a) the presence of the
|
|
204
292
|
// `toolbar-top` slot and (b) whether the textarea content exceeds one line.
|
|
@@ -657,8 +657,8 @@ function onSubmit(value: string): void {
|
|
|
657
657
|
* End-to-end composition matching the Figma "Prompt field" spec: file
|
|
658
658
|
* attachments via the `+` button and drag-and-drop, model selection next to
|
|
659
659
|
* the send button, and a hallucination disclaimer in the footer. File chips
|
|
660
|
-
* collapse to a popover once
|
|
661
|
-
*
|
|
660
|
+
* collapse to a popover once they no longer fit the prompt field's width
|
|
661
|
+
* (`CkFileItemsList` keeps each chip at least `minItemWidth` wide).
|
|
662
662
|
*/
|
|
663
663
|
export const WithUploadAndModelSelection: Story = {
|
|
664
664
|
render: () => ({
|
|
@@ -955,10 +955,10 @@ function removeFile(id: CkFileEntry['id']) {
|
|
|
955
955
|
</template>`;
|
|
956
956
|
|
|
957
957
|
/**
|
|
958
|
-
* Five attached files —
|
|
959
|
-
* `CkFileItemsList
|
|
960
|
-
*
|
|
961
|
-
*
|
|
958
|
+
* Five attached files — more than fit inline at the prompt field's width, so
|
|
959
|
+
* `CkFileItemsList` collapses to a single popover trigger labeled "5 files".
|
|
960
|
+
* Click it to inspect / remove the individual items. The toolbar-left `+` menu
|
|
961
|
+
* stays available so users can keep adding more.
|
|
962
962
|
*/
|
|
963
963
|
export const WithCollapsedFileChips: Story = {
|
|
964
964
|
render: () => ({
|
|
@@ -14,13 +14,8 @@ import {
|
|
|
14
14
|
|
|
15
15
|
defineOptions({ name: 'CkPromptInput' });
|
|
16
16
|
|
|
17
|
-
// Pixel height of one rendered line — must match the textarea's `leading-6`
|
|
18
|
-
// (24 px). Used to derive how many lines the current content occupies.
|
|
19
|
-
const LINE_HEIGHT = 24;
|
|
20
|
-
|
|
21
17
|
// Cap the textarea at 6 visible lines; content beyond that scrolls inside.
|
|
22
18
|
const MAX_LINES = 6;
|
|
23
|
-
const MAX_HEIGHT = LINE_HEIGHT * MAX_LINES;
|
|
24
19
|
|
|
25
20
|
const props = withDefaults(defineProps<CkPromptInputProps>(), {
|
|
26
21
|
modelValue: '',
|
|
@@ -84,19 +79,25 @@ function adjustHeight(): void {
|
|
|
84
79
|
return;
|
|
85
80
|
}
|
|
86
81
|
|
|
82
|
+
// Line height is fluid (it scales with the container via `clamp()`), so read
|
|
83
|
+
// the resolved px value from the DOM instead of a hard-coded constant — the
|
|
84
|
+
// 6-line cap and the 2-line latch threshold both depend on it.
|
|
85
|
+
const lineHeight = parseFloat(getComputedStyle(el).lineHeight) || 24;
|
|
86
|
+
const maxHeight = lineHeight * MAX_LINES;
|
|
87
|
+
|
|
87
88
|
// Collapse to natural content height first so the next scrollHeight read
|
|
88
89
|
// reflects what the content needs, not what the inline height is forcing.
|
|
89
90
|
el.style.height = 'auto';
|
|
90
91
|
const measured = el.scrollHeight;
|
|
91
|
-
el.style.height = `${Math.min(measured,
|
|
92
|
+
el.style.height = `${Math.min(measured, maxHeight)}px`;
|
|
92
93
|
|
|
93
94
|
// Latch only on a *real* second line: either explicit newline OR scrollHeight
|
|
94
|
-
// ≥ 2 × line-height. Using `Math.round` on a borderline
|
|
95
|
-
// emoji/CJK rendering on Chromium/Linux) would falsely latch
|
|
95
|
+
// ≥ 2 × line-height. Using `Math.round` on a borderline single line (sub-pixel
|
|
96
|
+
// emoji/CJK rendering on Chromium/Linux) would falsely latch it.
|
|
96
97
|
const hasExplicitNewline = props.modelValue.includes('\n');
|
|
97
98
|
if (
|
|
98
99
|
!isMultiLine.value &&
|
|
99
|
-
(hasExplicitNewline || measured >=
|
|
100
|
+
(hasExplicitNewline || measured >= lineHeight * 2)
|
|
100
101
|
) {
|
|
101
102
|
isMultiLine.value = true;
|
|
102
103
|
}
|
|
@@ -124,6 +125,28 @@ function focus(): void {
|
|
|
124
125
|
textareaRef.value?.focus();
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// Highlight (border + ring) should appear for keyboard navigation only, not
|
|
129
|
+
// mouse clicks. `:focus-visible` can't express this on a textarea — browsers
|
|
130
|
+
// always treat text fields as focus-visible since they take keyboard input —
|
|
131
|
+
// so we track the focus origin ourselves: a pointerdown immediately preceding
|
|
132
|
+
// a focus means the mouse caused it, so suppress the highlight.
|
|
133
|
+
const isKeyboardFocused = ref(false);
|
|
134
|
+
let focusedByPointer = false;
|
|
135
|
+
|
|
136
|
+
function onPointerDown(): void {
|
|
137
|
+
focusedByPointer = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function onFocus(): void {
|
|
141
|
+
isKeyboardFocused.value = !focusedByPointer;
|
|
142
|
+
focusedByPointer = false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function onBlur(): void {
|
|
146
|
+
isKeyboardFocused.value = false;
|
|
147
|
+
focusedByPointer = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
watch(
|
|
128
151
|
() => props.modelValue,
|
|
129
152
|
() => nextTick(adjustHeight),
|
|
@@ -134,11 +157,14 @@ defineExpose({ focus, submit });
|
|
|
134
157
|
</script>
|
|
135
158
|
|
|
136
159
|
<template>
|
|
137
|
-
<div class="flex flex-col gap-2">
|
|
160
|
+
<div class="ck-prompt-root flex flex-col gap-2">
|
|
138
161
|
<form
|
|
139
162
|
:class="[
|
|
140
|
-
'
|
|
163
|
+
'border-border-surface-default bg-background-input-strong min-h-17 grid items-center gap-x-0 gap-y-5 border p-4 transition-[border-radius] duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] ',
|
|
141
164
|
hasMultipleRows ? 'rounded-2xl' : 'rounded-3xl',
|
|
165
|
+
isKeyboardFocused
|
|
166
|
+
? 'border-border-surface-focus-ring ring-border-surface-focus-ring/40 ring-2'
|
|
167
|
+
: 'ck-prompt-form',
|
|
142
168
|
]"
|
|
143
169
|
:style="{
|
|
144
170
|
gridTemplateColumns: 'auto 1fr auto',
|
|
@@ -164,11 +190,14 @@ defineExpose({ focus, submit });
|
|
|
164
190
|
:disabled="disabled"
|
|
165
191
|
style="grid-area: input"
|
|
166
192
|
:class="[
|
|
167
|
-
'ck-prompt-textarea text-content-on-surface-primary placeholder-content-on-surface-muted w-full min-w-0 resize-none overflow-y-auto bg-transparent py-0 font-sans
|
|
193
|
+
'ck-prompt-textarea text-content-on-surface-primary placeholder-content-on-surface-muted w-full min-w-0 resize-none overflow-y-auto bg-transparent py-0 font-sans font-normal focus:outline-none disabled:cursor-not-allowed disabled:opacity-60',
|
|
168
194
|
isMultiLine ? 'pl-3 pr-3' : 'px-0',
|
|
169
195
|
]"
|
|
170
196
|
@input="onInput"
|
|
171
197
|
@keydown="onKeydown"
|
|
198
|
+
@pointerdown="onPointerDown"
|
|
199
|
+
@focus="onFocus"
|
|
200
|
+
@blur="onBlur"
|
|
172
201
|
/>
|
|
173
202
|
<div :class="[!isMultiLine && 'pl-4']" style="grid-area: right">
|
|
174
203
|
<slot
|
|
@@ -184,15 +213,37 @@ defineExpose({ focus, submit });
|
|
|
184
213
|
</template>
|
|
185
214
|
|
|
186
215
|
<style scoped>
|
|
216
|
+
/* Fluid mobile↔desktop sizing. Sizes interpolate against the component's own
|
|
217
|
+
container width (`cqw`), gradually transforming between the Figma Mobile
|
|
218
|
+
variant (≤320px container) and the Desktop variant (≥768px container) rather
|
|
219
|
+
than snapping at a breakpoint. Range = 768 − 320 = 448; each term is
|
|
220
|
+
`clamp(mobile, calc(mobile + (100cqw − 320px) × span / 448), desktop)`. */
|
|
221
|
+
.ck-prompt-root {
|
|
222
|
+
container-type: inline-size;
|
|
223
|
+
}
|
|
224
|
+
|
|
187
225
|
/* Solea two-layer drop shadow — warm grey + subtle orange tint. Matches the
|
|
188
226
|
`shadow/md/{1,2}` tokens from the design system; the generic `shadow-md`
|
|
189
227
|
utility from preset-wind4 uses a cold black-based shadow instead. */
|
|
190
228
|
.ck-prompt-form {
|
|
229
|
+
/* 8px (mobile) → 16px (desktop) */
|
|
230
|
+
padding: clamp(8px, calc(8px + (100cqw - 320px) * 8 / 448), 16px);
|
|
231
|
+
/* 52px → 68px (= 36px button + 2× padding) */
|
|
232
|
+
min-height: clamp(52px, calc(52px + (100cqw - 320px) * 16 / 448), 68px);
|
|
191
233
|
box-shadow:
|
|
192
234
|
0 4px 6px -2px rgba(112, 95, 90, 0.1),
|
|
193
235
|
0 6px 14px -2px rgba(248, 94, 43, 0.05);
|
|
194
236
|
}
|
|
195
237
|
|
|
238
|
+
/* 14px → 16px font, 20px → 24px line-height, 120px → 144px (6 lines) cap.
|
|
239
|
+
`adjustHeight` reads the resolved line-height from the DOM so the auto-grow
|
|
240
|
+
math tracks these fluid values. */
|
|
241
|
+
.ck-prompt-textarea {
|
|
242
|
+
font-size: clamp(14px, calc(14px + (100cqw - 320px) * 2 / 448), 16px);
|
|
243
|
+
line-height: clamp(20px, calc(20px + (100cqw - 320px) * 4 / 448), 24px);
|
|
244
|
+
max-height: clamp(120px, calc(120px + (100cqw - 320px) * 24 / 448), 144px);
|
|
245
|
+
}
|
|
246
|
+
|
|
196
247
|
/* Slim styled scrollbar for the multi-line textarea — matches Figma's 4 px
|
|
197
248
|
muted-default track. Firefox uses the native `scrollbar-*` properties;
|
|
198
249
|
Chromium/WebKit needs the `::-webkit-scrollbar` pseudo-elements. */
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* slots and keyboard submission (Enter to submit, Shift+Enter for newline).
|
|
5
5
|
* The form layout adapts between a compact single-row variant (large 3xl
|
|
6
6
|
* radius) and a stacked multi-row variant (2xl radius) automatically — driven
|
|
7
|
-
* by content height and whether `toolbar-top` is filled.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* by content height and whether `toolbar-top` is filled. Compose it with
|
|
8
|
+
* `CkPromptSendButton`, `CkPromptStopButton`, and `CkPromptInputDisclaimer`
|
|
9
|
+
* to build a complete chat input.
|
|
10
10
|
*
|
|
11
11
|
* @category Form Inputs
|
|
12
12
|
* @useCases raw chat prompt textarea, custom prompt toolbar, auto-growing compose box, file-attachment chip row, multi-line chat input, keyboard submit
|
|
13
13
|
* @keywords prompt, input, textarea, chat, compose, submit, v-model, auto-grow, multi-line, toolbar
|
|
14
|
-
* @related
|
|
14
|
+
* @related CkPromptSendButton, CkPromptStopButton, CkPromptMenuButton, CkPromptInputDisclaimer, CkFileItemsList
|
|
15
15
|
*/
|
|
16
16
|
export interface CkPromptInputProps {
|
|
17
17
|
/**
|
|
@@ -33,7 +33,7 @@ describe('CkPromptInputDisclaimer', () => {
|
|
|
33
33
|
|
|
34
34
|
const el = screen.getByText('Disclaimer');
|
|
35
35
|
expect(el.tagName).toBe('P');
|
|
36
|
-
expect(el.className).toContain('text-
|
|
36
|
+
expect(el.className).toContain('text-content-on-surface-muted');
|
|
37
37
|
expect(el.className).toContain('text-center');
|
|
38
38
|
expect(el.className).toContain('text-xs');
|
|
39
39
|
});
|
|
@@ -9,7 +9,7 @@ defineProps<CkPromptInputDisclaimerProps>();
|
|
|
9
9
|
<template>
|
|
10
10
|
<p
|
|
11
11
|
v-if="$slots.default || text"
|
|
12
|
-
class="text-
|
|
12
|
+
class="text-center text-content-on-surface-muted text-xs font-normal leading-4"
|
|
13
13
|
>
|
|
14
14
|
<slot>{{ text }}</slot>
|
|
15
15
|
</p>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @category Feedback
|
|
7
7
|
* @useCases chat disclaimer, ai may make mistakes, prompt footnote, small help text under input
|
|
8
8
|
* @keywords disclaimer, notice, footer, help text, ai, prompt
|
|
9
|
-
* @related
|
|
9
|
+
* @related CkPromptInput, CkPromptSendButton
|
|
10
10
|
*/
|
|
11
11
|
export interface CkPromptInputDisclaimerProps {
|
|
12
12
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @category Actions
|
|
8
8
|
* @useCases send message button, submit prompt, primary chat send action, loading send button, disabled send button
|
|
9
9
|
* @keywords prompt, send, submit, button, icon, loading, brand
|
|
10
|
-
* @related CkPromptInput, CkPromptStopButton,
|
|
10
|
+
* @related CkPromptInput, CkPromptStopButton, CkSendIcon
|
|
11
11
|
*/
|
|
12
12
|
export interface CkPromptSendButtonProps {
|
|
13
13
|
/**
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @category Actions
|
|
9
9
|
* @useCases stop assistant generation, cancel streaming reply, abort in-flight request, halt llm response
|
|
10
10
|
* @keywords stop, cancel, abort, prompt, button, icon, streaming, secondary
|
|
11
|
-
* @related CkPromptSendButton, CkPromptMenuButton, CkPromptInput,
|
|
11
|
+
* @related CkPromptSendButton, CkPromptMenuButton, CkPromptInput, CkStopIcon
|
|
12
12
|
*/
|
|
13
13
|
export interface CkPromptStopButtonProps {
|
|
14
14
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import CkRegenerateMessageButton from './CkRegenerateMessageButton.vue';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { render, screen } from '@testing-library/vue';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('CkRegenerateMessageButton', () => {
|
|
7
|
+
it('renders an accessible button with the default label', () => {
|
|
8
|
+
render(CkRegenerateMessageButton);
|
|
9
|
+
|
|
10
|
+
expect(
|
|
11
|
+
screen.getByRole('button', { name: 'Regenerate response' }),
|
|
12
|
+
).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('uses a custom aria-label when provided', () => {
|
|
16
|
+
render(CkRegenerateMessageButton, {
|
|
17
|
+
props: { ariaLabel: 'Try again' },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(
|
|
21
|
+
screen.getByRole('button', { name: 'Try again' }),
|
|
22
|
+
).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('emits regenerate when clicked', async () => {
|
|
26
|
+
const user = userEvent.setup();
|
|
27
|
+
const { emitted } = render(CkRegenerateMessageButton);
|
|
28
|
+
|
|
29
|
+
await user.click(
|
|
30
|
+
screen.getByRole('button', { name: 'Regenerate response' }),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(emitted('regenerate')).toEqual([[]]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('does not emit regenerate when disabled', async () => {
|
|
37
|
+
const user = userEvent.setup();
|
|
38
|
+
const { emitted } = render(CkRegenerateMessageButton, {
|
|
39
|
+
props: { disabled: true },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const button = screen.getByRole('button', { name: 'Regenerate response' });
|
|
43
|
+
expect(button).toBeDisabled();
|
|
44
|
+
|
|
45
|
+
await user.click(button);
|
|
46
|
+
|
|
47
|
+
expect(emitted('regenerate')).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import CkRegenerateMessageButton from './CkRegenerateMessageButton.vue';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CkRegenerateMessageButton> = {
|
|
5
|
+
title: 'Base/CkRegenerateMessageButton',
|
|
6
|
+
component: CkRegenerateMessageButton,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
disabled: {
|
|
10
|
+
control: 'boolean',
|
|
11
|
+
description: 'Whether the button is disabled and ignores clicks.',
|
|
12
|
+
},
|
|
13
|
+
ariaLabel: {
|
|
14
|
+
control: 'text',
|
|
15
|
+
description: 'Accessible label and tooltip content for the button.',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
decorators: [
|
|
19
|
+
() => ({
|
|
20
|
+
template:
|
|
21
|
+
'<div style="max-width: 640px; margin: 0 auto; padding: 24px;"><story /></div>',
|
|
22
|
+
}),
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
type Story = StoryObj<typeof meta>;
|
|
28
|
+
|
|
29
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
30
|
+
import { CkRegenerateMessageButton } from '@aleph-alpha/chat-kit';
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<CkRegenerateMessageButton @regenerate="() => {}" />
|
|
35
|
+
</template>`;
|
|
36
|
+
|
|
37
|
+
const disabledTemplateSource = `<script setup lang="ts">
|
|
38
|
+
import { CkRegenerateMessageButton } from '@aleph-alpha/chat-kit';
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<CkRegenerateMessageButton disabled />
|
|
43
|
+
</template>`;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default regenerate action. Clicking emits a `regenerate` event so the host
|
|
47
|
+
* can request a new response for the message.
|
|
48
|
+
*/
|
|
49
|
+
export const Default: Story = {
|
|
50
|
+
args: {},
|
|
51
|
+
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Disabled state — clicks are ignored and the button appears inactive.
|
|
56
|
+
* Use while a message is still streaming or in an error state.
|
|
57
|
+
*/
|
|
58
|
+
export const Disabled: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
disabled: true,
|
|
61
|
+
},
|
|
62
|
+
parameters: { docs: { source: { code: disabledTemplateSource } } },
|
|
63
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { CkMessageButton } from '../CkMessageButton';
|
|
3
|
+
import type { CkRegenerateMessageButtonProps } from './types';
|
|
4
|
+
import { UiIcon, UiTooltip } from '@aleph-alpha/ui-library';
|
|
5
|
+
|
|
6
|
+
defineOptions({
|
|
7
|
+
name: 'CkRegenerateMessageButton',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<CkRegenerateMessageButtonProps>(), {
|
|
11
|
+
disabled: false,
|
|
12
|
+
ariaLabel: 'Regenerate response',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits<{
|
|
16
|
+
regenerate: [];
|
|
17
|
+
}>();
|
|
18
|
+
|
|
19
|
+
function handleClick() {
|
|
20
|
+
if (props.disabled) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
emit('regenerate');
|
|
25
|
+
}
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<template>
|
|
29
|
+
<UiTooltip
|
|
30
|
+
:content="ariaLabel"
|
|
31
|
+
side="bottom"
|
|
32
|
+
align="center"
|
|
33
|
+
:delay-duration="0"
|
|
34
|
+
>
|
|
35
|
+
<CkMessageButton
|
|
36
|
+
v-bind="{ ariaLabel }"
|
|
37
|
+
:disabled="disabled"
|
|
38
|
+
@click="handleClick"
|
|
39
|
+
>
|
|
40
|
+
<UiIcon name="repeat" :size="16" aria-hidden="true" />
|
|
41
|
+
</CkMessageButton>
|
|
42
|
+
</UiTooltip>
|
|
43
|
+
</template>
|