@aleph-alpha/chat-kit 5.2.0 → 5.4.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-BxzHdcxY.js → CkHistory.vue_vue_type_script_setup_true_lang-D9oP6oEj.js} +217 -120
- package/dist/chat-kit.css +3 -3
- package/dist/components/base/CkConversationLayout/CkConversationLayout.vue.d.ts.map +1 -1
- package/dist/components/base/CkEditableUserMessage/CkEditableUserMessage.stories.d.ts +12 -0
- package/dist/components/base/CkEditableUserMessage/CkEditableUserMessage.stories.d.ts.map +1 -0
- package/dist/components/base/CkEditableUserMessage/CkEditableUserMessage.vue.d.ts +22 -0
- package/dist/components/base/CkEditableUserMessage/CkEditableUserMessage.vue.d.ts.map +1 -0
- package/dist/components/base/CkEditableUserMessage/index.d.ts +3 -0
- package/dist/components/base/CkEditableUserMessage/index.d.ts.map +1 -0
- package/dist/components/base/CkEditableUserMessage/types.d.ts +30 -0
- package/dist/components/base/CkEditableUserMessage/types.d.ts.map +1 -0
- package/dist/components/base/CkUserMessage/CkUserMessage.vue.d.ts.map +1 -1
- package/dist/components/base/CkUserMessage/types.d.ts +4 -1
- package/dist/components/base/CkUserMessage/types.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/CkHistory/CkHistory.vue.d.ts +1 -0
- package/dist/components/composed/CkHistory/CkHistory.vue.d.ts.map +1 -1
- package/dist/components/composed/CkHistory/types.d.ts +9 -1
- package/dist/components/composed/CkHistory/types.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 +21 -20
- package/dist/handlers/index.js +1 -1
- package/dist/handlers/useChatKitLabels.d.ts +4 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -22
- package/dist/{useChatKitLabels-zMFV7aEz.js → useChatKitLabels-DLy0BFx8.js} +2 -0
- package/package.json +2 -2
- package/src/components/base/CkConversationLayout/CkConversationLayout.spec.ts +84 -1
- package/src/components/base/CkConversationLayout/CkConversationLayout.vue +61 -18
- package/src/components/base/CkEditableUserMessage/CkEditableUserMessage.spec.ts +53 -0
- package/src/components/base/CkEditableUserMessage/CkEditableUserMessage.stories.ts +53 -0
- package/src/components/base/CkEditableUserMessage/CkEditableUserMessage.vue +58 -0
- package/src/components/base/CkEditableUserMessage/index.ts +5 -0
- package/src/components/base/CkEditableUserMessage/types.ts +30 -0
- package/src/components/base/CkHistoryWrapper/CkHistoryWrapper.vue +1 -1
- package/src/components/base/CkUserMessage/CkUserMessage.vue +19 -30
- package/src/components/base/CkUserMessage/types.ts +4 -1
- package/src/components/base/index.ts +5 -0
- package/src/components/composed/CkHistory/CkHistory.spec.ts +30 -0
- package/src/components/composed/CkHistory/CkHistory.vue +5 -1
- package/src/components/composed/CkHistory/types.ts +9 -1
- package/src/components/index.ts +3 -0
- package/src/handlers/useChatKitLabels.ts +6 -0
- package/src/index.ts +3 -0
|
@@ -18,6 +18,11 @@ const isAutoScrollEnabled = ref(true);
|
|
|
18
18
|
const hasBottomSpacer = ref(false);
|
|
19
19
|
const spacerHeight = ref(0);
|
|
20
20
|
|
|
21
|
+
// Whether there is scrollable content above / below the current viewport.
|
|
22
|
+
// Drives the top / bottom fade overlays that hint at more content to scroll to.
|
|
23
|
+
const showTopFade = ref(false);
|
|
24
|
+
const showBottomFade = ref(false);
|
|
25
|
+
|
|
21
26
|
let resizeObserver: ResizeObserver | null = null;
|
|
22
27
|
|
|
23
28
|
onMounted(() => {
|
|
@@ -28,12 +33,14 @@ onMounted(() => {
|
|
|
28
33
|
for (const entry of entries) {
|
|
29
34
|
containerHeight.value = entry.contentRect.height;
|
|
30
35
|
}
|
|
36
|
+
updateFades();
|
|
31
37
|
});
|
|
32
38
|
resizeObserver.observe(container);
|
|
33
39
|
|
|
34
40
|
// Initially scroll to the bottom of the conversation without any spacer
|
|
35
41
|
nextTick(() => {
|
|
36
42
|
container.scrollTop = container.scrollHeight;
|
|
43
|
+
updateFades();
|
|
37
44
|
});
|
|
38
45
|
}
|
|
39
46
|
});
|
|
@@ -60,6 +67,24 @@ function isFollowingContent(): boolean {
|
|
|
60
67
|
|
|
61
68
|
function handleScroll(): void {
|
|
62
69
|
isAutoScrollEnabled.value = isFollowingContent();
|
|
70
|
+
updateFades();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Update the visibility of the top / bottom fade overlays based on the current
|
|
75
|
+
* scroll position. The top fade appears once the user has scrolled away from
|
|
76
|
+
* the start; the bottom fade appears while there is still content below the
|
|
77
|
+
* viewport. When the content fits entirely, both fades stay hidden.
|
|
78
|
+
*/
|
|
79
|
+
function updateFades(): void {
|
|
80
|
+
const container = scrollContainerRef.value;
|
|
81
|
+
if (!container) return;
|
|
82
|
+
|
|
83
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
84
|
+
const maxScroll = scrollHeight - clientHeight;
|
|
85
|
+
|
|
86
|
+
showTopFade.value = scrollTop > 1;
|
|
87
|
+
showBottomFade.value = scrollTop < maxScroll - 1;
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
/**
|
|
@@ -153,7 +178,10 @@ watch(
|
|
|
153
178
|
if (newLen <= (oldLen ?? 0)) return;
|
|
154
179
|
const lastMessage = props.messages[newLen - 1];
|
|
155
180
|
if (lastMessage?.role === 'user') {
|
|
156
|
-
nextTick(() =>
|
|
181
|
+
nextTick(() => {
|
|
182
|
+
scrollUserMessageToTop();
|
|
183
|
+
updateFades();
|
|
184
|
+
});
|
|
157
185
|
}
|
|
158
186
|
},
|
|
159
187
|
);
|
|
@@ -171,6 +199,7 @@ watch(
|
|
|
171
199
|
if (isAutoScrollEnabled.value) {
|
|
172
200
|
scrollToEndOfContent();
|
|
173
201
|
}
|
|
202
|
+
updateFades();
|
|
174
203
|
});
|
|
175
204
|
}
|
|
176
205
|
},
|
|
@@ -179,29 +208,43 @@ watch(
|
|
|
179
208
|
</script>
|
|
180
209
|
|
|
181
210
|
<template>
|
|
182
|
-
<div
|
|
183
|
-
|
|
184
|
-
class="relative flex flex-col gap-8 overflow-y-auto"
|
|
185
|
-
@scroll="handleScroll"
|
|
186
|
-
>
|
|
211
|
+
<div class="relative flex min-h-0 flex-col overflow-hidden">
|
|
212
|
+
<!-- Top fade: hints that there is more content above the viewport -->
|
|
187
213
|
<div
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
:
|
|
191
|
-
|
|
214
|
+
aria-hidden="true"
|
|
215
|
+
class="pointer-events-none absolute inset-x-0 top-0 z-10 h-8 bg-gradient-to-b from-background-surface-primary to-transparent transition-opacity duration-200"
|
|
216
|
+
:class="showTopFade ? 'opacity-100' : 'opacity-0'"
|
|
217
|
+
/>
|
|
218
|
+
<div
|
|
219
|
+
ref="scrollContainerRef"
|
|
220
|
+
class="relative flex min-h-0 flex-1 flex-col gap-8 overflow-y-auto"
|
|
221
|
+
@scroll="handleScroll"
|
|
192
222
|
>
|
|
193
|
-
<div
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
<div
|
|
224
|
+
v-for="message in messages"
|
|
225
|
+
:key="message.id"
|
|
226
|
+
:data-message-row="message.role"
|
|
227
|
+
class="min-w-0"
|
|
228
|
+
>
|
|
229
|
+
<div v-if="message.role === 'user'" class="flex min-w-0 justify-end">
|
|
230
|
+
<slot name="user-message" :message="message" />
|
|
231
|
+
</div>
|
|
232
|
+
<div v-else class="flex min-w-0 justify-start">
|
|
233
|
+
<slot name="agent-message" :message="message" />
|
|
234
|
+
</div>
|
|
198
235
|
</div>
|
|
236
|
+
<!-- Bottom spacer: shrinks as agent content grows to keep viewport stable -->
|
|
237
|
+
<div
|
|
238
|
+
v-if="hasBottomSpacer"
|
|
239
|
+
aria-hidden="true"
|
|
240
|
+
:style="{ minHeight: `${spacerHeight}px` }"
|
|
241
|
+
/>
|
|
199
242
|
</div>
|
|
200
|
-
<!-- Bottom
|
|
243
|
+
<!-- Bottom fade: hints that there is more content below the viewport -->
|
|
201
244
|
<div
|
|
202
|
-
v-if="hasBottomSpacer"
|
|
203
245
|
aria-hidden="true"
|
|
204
|
-
|
|
246
|
+
class="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-8 bg-gradient-to-t from-background-surface-primary to-transparent transition-opacity duration-200"
|
|
247
|
+
:class="showBottomFade ? 'opacity-100' : 'opacity-0'"
|
|
205
248
|
/>
|
|
206
249
|
</div>
|
|
207
250
|
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import CkEditableUserMessage from './CkEditableUserMessage.vue';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/vue';
|
|
3
|
+
|
|
4
|
+
const FLOAT_ANCHOR_SELECTOR = '[data-slot="float-anchor"]';
|
|
5
|
+
const EDIT_ACTION_NAME = 'Edit message';
|
|
6
|
+
|
|
7
|
+
describe('CkEditableUserMessage', () => {
|
|
8
|
+
it('renders the message text inside a floating anchor', () => {
|
|
9
|
+
const { container } = render(CkEditableUserMessage, {
|
|
10
|
+
props: { message: 'Editable message' },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const anchor = container.querySelector(FLOAT_ANCHOR_SELECTOR);
|
|
14
|
+
expect(anchor).not.toBeNull();
|
|
15
|
+
expect(anchor).toHaveTextContent('Editable message');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('forwards default slot content to the message', () => {
|
|
19
|
+
render(CkEditableUserMessage, {
|
|
20
|
+
props: { message: 'Fallback text' },
|
|
21
|
+
slots: { default: 'Custom slot content' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(screen.getByText('Custom slot content')).toBeInTheDocument();
|
|
25
|
+
expect(screen.queryByText('Fallback text')).not.toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('does not render the edit action until opened', () => {
|
|
29
|
+
render(CkEditableUserMessage, { props: { message: 'Editable message' } });
|
|
30
|
+
|
|
31
|
+
expect(
|
|
32
|
+
screen.queryByRole('button', { name: EDIT_ACTION_NAME }),
|
|
33
|
+
).not.toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('emits `edit` when the edit action is activated', async () => {
|
|
37
|
+
const { container, emitted } = render(CkEditableUserMessage, {
|
|
38
|
+
props: { message: 'Editable message' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const anchor = container.querySelector<HTMLElement>(FLOAT_ANCHOR_SELECTOR);
|
|
42
|
+
expect(anchor).not.toBeNull();
|
|
43
|
+
await fireEvent.focus(anchor!);
|
|
44
|
+
|
|
45
|
+
const editButton = await screen.findByRole('button', {
|
|
46
|
+
name: EDIT_ACTION_NAME,
|
|
47
|
+
});
|
|
48
|
+
await fireEvent.click(editButton);
|
|
49
|
+
|
|
50
|
+
expect(emitted()).toHaveProperty('edit');
|
|
51
|
+
expect(emitted().edit).toHaveLength(1);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import CkEditableUserMessage from './CkEditableUserMessage.vue';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof CkEditableUserMessage> = {
|
|
5
|
+
title: 'Base/CkEditableUserMessage',
|
|
6
|
+
component: CkEditableUserMessage,
|
|
7
|
+
tags: ['autodocs'],
|
|
8
|
+
argTypes: {
|
|
9
|
+
message: {
|
|
10
|
+
control: 'text',
|
|
11
|
+
description:
|
|
12
|
+
'The message text to display. Can also be provided via the default slot.',
|
|
13
|
+
},
|
|
14
|
+
collapsedLines: {
|
|
15
|
+
control: { type: 'number', min: 0 },
|
|
16
|
+
description:
|
|
17
|
+
'When > 0, clamp the message to this many lines with a "Show more" / "Show less" toggle. Forwarded to the underlying CkUserMessage.',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
decorators: [
|
|
21
|
+
() => ({
|
|
22
|
+
template:
|
|
23
|
+
'<div style="display: flex; flex-direction: column; align-items: flex-end; width: 100%; padding-left: 64px;"><story /></div>',
|
|
24
|
+
}),
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof meta>;
|
|
30
|
+
|
|
31
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
32
|
+
import { CkEditableUserMessage } from '@aleph-alpha/chat-kit';
|
|
33
|
+
|
|
34
|
+
function onEdit() {
|
|
35
|
+
// open your inline editor / start the edit flow
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<CkEditableUserMessage message="..." @edit="onEdit" />
|
|
41
|
+
</template>`;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Editable message. Hover or keyboard-focus the message to reveal a floating
|
|
45
|
+
* toolbar with an edit action; activating it emits `edit`. The consumer owns
|
|
46
|
+
* the editing flow (e.g. swapping in an inline editor).
|
|
47
|
+
*/
|
|
48
|
+
export const Default: Story = {
|
|
49
|
+
args: {
|
|
50
|
+
message: 'Summarize the Q3 revenue report and highlight the top risks.',
|
|
51
|
+
},
|
|
52
|
+
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
53
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
|
|
3
|
+
import { CkUserMessage } from '../CkUserMessage';
|
|
4
|
+
import type {
|
|
5
|
+
CkEditableUserMessageEmits,
|
|
6
|
+
CkEditableUserMessageProps,
|
|
7
|
+
} from './types';
|
|
8
|
+
import {
|
|
9
|
+
UiFloatActionItems,
|
|
10
|
+
UiFloatActions,
|
|
11
|
+
UiIcon,
|
|
12
|
+
} from '@aleph-alpha/ui-library';
|
|
13
|
+
import { computed, useSlots } from 'vue';
|
|
14
|
+
|
|
15
|
+
defineOptions({ name: 'CkEditableUserMessage' });
|
|
16
|
+
|
|
17
|
+
withDefaults(defineProps<CkEditableUserMessageProps>(), {
|
|
18
|
+
message: undefined,
|
|
19
|
+
collapsedLines: 0,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const emit = defineEmits<CkEditableUserMessageEmits>();
|
|
23
|
+
|
|
24
|
+
const slots = useSlots();
|
|
25
|
+
|
|
26
|
+
const labels = useChatKitLabels();
|
|
27
|
+
|
|
28
|
+
const actionsLabel = computed(() => labels.value.userMessageActions);
|
|
29
|
+
const editActionLabel = computed(() => labels.value.userMessageEditAction);
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<template>
|
|
33
|
+
<UiFloatActions
|
|
34
|
+
trigger="hover"
|
|
35
|
+
placement="left"
|
|
36
|
+
:offset="4"
|
|
37
|
+
class="max-w-full"
|
|
38
|
+
>
|
|
39
|
+
<template #container>
|
|
40
|
+
<CkUserMessage :message="message" :collapsed-lines="collapsedLines">
|
|
41
|
+
<!-- Only forward the default slot when the consumer actually provides
|
|
42
|
+
one, so an empty wrapper still falls back to the `message` prop. -->
|
|
43
|
+
<template v-if="slots.default" #default><slot /></template>
|
|
44
|
+
</CkUserMessage>
|
|
45
|
+
</template>
|
|
46
|
+
<template #action>
|
|
47
|
+
<UiFloatActionItems :aria-label="actionsLabel">
|
|
48
|
+
<UiFloatActionItems
|
|
49
|
+
type="button"
|
|
50
|
+
:aria-label="editActionLabel"
|
|
51
|
+
@click="emit('edit')"
|
|
52
|
+
>
|
|
53
|
+
<UiIcon name="pencil" :size="16" aria-hidden="true" />
|
|
54
|
+
</UiFloatActionItems>
|
|
55
|
+
</UiFloatActionItems>
|
|
56
|
+
</template>
|
|
57
|
+
</UiFloatActions>
|
|
58
|
+
</template>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Editable variant of {@link CkUserMessage}. Wraps the plain user message in a
|
|
3
|
+
* floating actions toolbar (via `UiFloatActions`) that reveals an edit button on
|
|
4
|
+
* hover and keyboard focus. Activating it emits the `edit` event; the consumer
|
|
5
|
+
* owns the actual editing flow (e.g. swapping in an inline editor).
|
|
6
|
+
*
|
|
7
|
+
* Use this instead of `CkUserMessage` only when a turn needs the edit
|
|
8
|
+
* affordance — plain, non-interactive turns should keep using `CkUserMessage`.
|
|
9
|
+
*
|
|
10
|
+
* @category Messages
|
|
11
|
+
* @useCases edit user message, revise prompt, editable conversation turn, message hover actions
|
|
12
|
+
* @keywords user, message, edit, editable, chat, conversation, turn
|
|
13
|
+
* @related CkUserMessage, CkAgentMessage, CkConversation
|
|
14
|
+
*/
|
|
15
|
+
export interface CkEditableUserMessageProps {
|
|
16
|
+
/** Text content of the user message. Rendered inside the bubble when no default slot is provided. */
|
|
17
|
+
message?: string;
|
|
18
|
+
/**
|
|
19
|
+
* When greater than `0`, collapse the message to at most this many lines with
|
|
20
|
+
* a fade and a "Show more" / "Show less" toggle. Forwarded to the underlying
|
|
21
|
+
* {@link CkUserMessage}. Omit or pass `0` (the default) to disable clamping.
|
|
22
|
+
*/
|
|
23
|
+
collapsedLines?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Events emitted by {@link CkEditableUserMessage}. */
|
|
27
|
+
export interface CkEditableUserMessageEmits {
|
|
28
|
+
/** Emitted when the edit action is activated. */
|
|
29
|
+
edit: [];
|
|
30
|
+
}
|
|
@@ -13,7 +13,7 @@ const props = withDefaults(defineProps<CkHistoryWrapperProps>(), {
|
|
|
13
13
|
|
|
14
14
|
const attrs = useAttrs();
|
|
15
15
|
|
|
16
|
-
const baseClass = 'flex w-full min-w-0 flex-col
|
|
16
|
+
const baseClass = 'flex w-full min-w-0 flex-col py-2';
|
|
17
17
|
|
|
18
18
|
const rootClass = computed(() => {
|
|
19
19
|
const consumerClass =
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
defaultChatKitLabels,
|
|
4
|
-
useChatKitLabels,
|
|
5
|
-
} from '../../../handlers/useChatKitLabels';
|
|
2
|
+
import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
|
|
6
3
|
import type { CkUserMessageProps } from './types';
|
|
7
|
-
import {
|
|
8
|
-
computed,
|
|
9
|
-
nextTick,
|
|
10
|
-
onBeforeUnmount,
|
|
11
|
-
onMounted,
|
|
12
|
-
ref,
|
|
13
|
-
watch,
|
|
14
|
-
} from 'vue';
|
|
4
|
+
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue';
|
|
15
5
|
|
|
16
6
|
defineOptions({ name: 'CkUserMessage' });
|
|
17
7
|
|
|
@@ -22,17 +12,8 @@ const props = withDefaults(defineProps<CkUserMessageProps>(), {
|
|
|
22
12
|
|
|
23
13
|
const labels = useChatKitLabels();
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
() =>
|
|
28
|
-
labels.value.userMessageShowMore ??
|
|
29
|
-
defaultChatKitLabels.userMessageShowMore!,
|
|
30
|
-
);
|
|
31
|
-
const showLessLabel = computed(
|
|
32
|
-
() =>
|
|
33
|
-
labels.value.userMessageShowLess ??
|
|
34
|
-
defaultChatKitLabels.userMessageShowLess!,
|
|
35
|
-
);
|
|
15
|
+
const showMoreLabel = computed(() => labels.value.userMessageShowMore);
|
|
16
|
+
const showLessLabel = computed(() => labels.value.userMessageShowLess);
|
|
36
17
|
|
|
37
18
|
/** Clamping is opt-in: only active when a positive `collapsedLines` is given. */
|
|
38
19
|
const clampEnabled = computed(() => (props.collapsedLines ?? 0) > 0);
|
|
@@ -53,13 +34,21 @@ function toggle() {
|
|
|
53
34
|
|
|
54
35
|
let resizeObserver: ResizeObserver | undefined;
|
|
55
36
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
37
|
+
function observeContent(el: HTMLElement | null) {
|
|
38
|
+
if (typeof ResizeObserver === 'undefined') return;
|
|
39
|
+
if (!resizeObserver) resizeObserver = new ResizeObserver(() => measure());
|
|
40
|
+
resizeObserver.disconnect();
|
|
41
|
+
if (el) resizeObserver.observe(el);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
watch(
|
|
45
|
+
contentRef,
|
|
46
|
+
(el) => {
|
|
47
|
+
observeContent(el);
|
|
48
|
+
nextTick(measure);
|
|
49
|
+
},
|
|
50
|
+
{ flush: 'post' },
|
|
51
|
+
);
|
|
63
52
|
|
|
64
53
|
onBeforeUnmount(() => resizeObserver?.disconnect());
|
|
65
54
|
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* default slot for custom inline content. When `collapsedLines` is set, long
|
|
5
5
|
* messages are clamped with a fade and a "Show more" / "Show less" toggle.
|
|
6
6
|
*
|
|
7
|
+
* For an editable variant (hover/focus edit toolbar), see
|
|
8
|
+
* {@link CkEditableUserMessage}, which wraps this component.
|
|
9
|
+
*
|
|
7
10
|
* @category Messages
|
|
8
11
|
* @useCases render user message, conversation turn from user, chat input preview, right-aligned message, collapsible long message
|
|
9
12
|
* @keywords user, message, chat, conversation, turn, collapse, show more
|
|
10
|
-
* @related CkAgentMessage, CkConversation
|
|
13
|
+
* @related CkEditableUserMessage, CkAgentMessage, CkConversation
|
|
11
14
|
*/
|
|
12
15
|
export interface CkUserMessageProps {
|
|
13
16
|
/** Text content of the user message. Rendered inside the bubble when no default slot is provided. */
|
|
@@ -73,3 +73,8 @@ export { CkPromptInputDisclaimer } from './CkPromptInputDisclaimer';
|
|
|
73
73
|
export { CkRegenerateMessageButton } from './CkRegenerateMessageButton';
|
|
74
74
|
export { CkUserMessage } from './CkUserMessage';
|
|
75
75
|
export type { CkUserMessageProps } from './CkUserMessage';
|
|
76
|
+
export { CkEditableUserMessage } from './CkEditableUserMessage';
|
|
77
|
+
export type {
|
|
78
|
+
CkEditableUserMessageEmits,
|
|
79
|
+
CkEditableUserMessageProps,
|
|
80
|
+
} from './CkEditableUserMessage';
|
|
@@ -433,4 +433,34 @@ describe('CkHistory', () => {
|
|
|
433
433
|
expect(emitted().select).toBeUndefined();
|
|
434
434
|
});
|
|
435
435
|
});
|
|
436
|
+
|
|
437
|
+
describe('contentClass', () => {
|
|
438
|
+
it('applies contentClass to the inner history wrapper', () => {
|
|
439
|
+
const { container } = render(CkHistory, {
|
|
440
|
+
props: {
|
|
441
|
+
conversations: [makeConversation({ id: 'c-1' })],
|
|
442
|
+
hrefResolver,
|
|
443
|
+
contentClass: 'px-2 custom-content',
|
|
444
|
+
},
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
const wrapper = container.querySelector('[data-slot="chat-history"]');
|
|
448
|
+
expect(wrapper).toBeTruthy();
|
|
449
|
+
expect(wrapper?.className).toMatch(/px-2/);
|
|
450
|
+
expect(wrapper?.className).toMatch(/custom-content/);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
it('does not add any content class to the wrapper by default', () => {
|
|
454
|
+
const { container } = render(CkHistory, {
|
|
455
|
+
props: {
|
|
456
|
+
conversations: [makeConversation({ id: 'c-1' })],
|
|
457
|
+
hrefResolver,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const wrapper = container.querySelector('[data-slot="chat-history"]');
|
|
462
|
+
expect(wrapper).toBeTruthy();
|
|
463
|
+
expect(wrapper?.className).not.toMatch(/px-2/);
|
|
464
|
+
});
|
|
465
|
+
});
|
|
436
466
|
});
|
|
@@ -22,6 +22,7 @@ const props = withDefaults(defineProps<CkHistoryProps>(), {
|
|
|
22
22
|
timeFormatter: formatRelativeTime,
|
|
23
23
|
collapsedLimit: 5,
|
|
24
24
|
maxHeight: undefined,
|
|
25
|
+
contentClass: undefined,
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
const emit = defineEmits<CkHistoryEmits>();
|
|
@@ -106,7 +107,10 @@ function handleRowClick(c: Conversation, event: MouseEvent) {
|
|
|
106
107
|
<template>
|
|
107
108
|
<div>
|
|
108
109
|
<CkHistoryWrapper
|
|
109
|
-
:class="
|
|
110
|
+
:class="[
|
|
111
|
+
isExpanded && props.maxHeight ? 'overflow-y-auto' : undefined,
|
|
112
|
+
props.contentClass,
|
|
113
|
+
]"
|
|
110
114
|
:style="
|
|
111
115
|
isExpanded && props.maxHeight
|
|
112
116
|
? { maxHeight: props.maxHeight }
|
|
@@ -70,7 +70,7 @@ export interface CkHistoryProps {
|
|
|
70
70
|
* below the visible rows. Clicking it expands the list and makes it
|
|
71
71
|
* scrollable (capped at `maxHeight`).
|
|
72
72
|
*
|
|
73
|
-
* Set to `0`
|
|
73
|
+
* Set to `0` to disable collapsing — all conversations
|
|
74
74
|
* are shown and the list is always scrollable.
|
|
75
75
|
*
|
|
76
76
|
* @default 5
|
|
@@ -83,6 +83,14 @@ export interface CkHistoryProps {
|
|
|
83
83
|
* @default undefined — no max-height constraint
|
|
84
84
|
*/
|
|
85
85
|
maxHeight?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Additional CSS class(es) applied to the inner `CkHistoryWrapper` that
|
|
88
|
+
* wraps the conversation rows. Useful for adjusting padding, spacing, or
|
|
89
|
+
* other layout concerns of the list container.
|
|
90
|
+
*
|
|
91
|
+
* @default undefined
|
|
92
|
+
*/
|
|
93
|
+
contentClass?: string;
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
/**
|
package/src/components/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export {
|
|
|
25
25
|
CkSendIcon,
|
|
26
26
|
CkStopIcon,
|
|
27
27
|
CkUserMessage,
|
|
28
|
+
CkEditableUserMessage,
|
|
28
29
|
} from './base';
|
|
29
30
|
export type {
|
|
30
31
|
CkAgentMessageHeaderProps,
|
|
@@ -61,6 +62,8 @@ export type {
|
|
|
61
62
|
CkModelSelectorEmits,
|
|
62
63
|
CkPromptMenuButtonProps,
|
|
63
64
|
CkUserMessageProps,
|
|
65
|
+
CkEditableUserMessageProps,
|
|
66
|
+
CkEditableUserMessageEmits,
|
|
64
67
|
} from './base';
|
|
65
68
|
|
|
66
69
|
// Composed (pattern) components
|
|
@@ -71,6 +71,10 @@ export type ChatKitLabels = {
|
|
|
71
71
|
userMessageShowMore?: string;
|
|
72
72
|
/** Label for the `CkUserMessage` toggle when the message is expanded. */
|
|
73
73
|
userMessageShowLess?: string;
|
|
74
|
+
/** Accessible label for the `CkEditableUserMessage` floating actions toolbar. */
|
|
75
|
+
userMessageActions?: string;
|
|
76
|
+
/** Accessible label / tooltip for the edit action in the `CkEditableUserMessage` toolbar. */
|
|
77
|
+
userMessageEditAction?: string;
|
|
74
78
|
/** Accessible label for the `CkModelSelector` trigger button. */
|
|
75
79
|
modelSelectorAriaLabel?: string;
|
|
76
80
|
/** Accessible label for the `CkModelSelector` options list. */
|
|
@@ -133,6 +137,8 @@ export const defaultChatKitLabels: ChatKitLabels = {
|
|
|
133
137
|
feedbackDialogSubmitLabel: 'Submit',
|
|
134
138
|
userMessageShowMore: 'Show more',
|
|
135
139
|
userMessageShowLess: 'Show less',
|
|
140
|
+
userMessageActions: 'Message actions',
|
|
141
|
+
userMessageEditAction: 'Edit message',
|
|
136
142
|
modelSelectorAriaLabel: 'Select model',
|
|
137
143
|
modelSelectorListAriaLabel: 'Models',
|
|
138
144
|
modelSelectorPlaceholder: 'Select model',
|
package/src/index.ts
CHANGED
|
@@ -89,6 +89,7 @@ export {
|
|
|
89
89
|
CkSendIcon,
|
|
90
90
|
CkStopIcon,
|
|
91
91
|
CkUserMessage,
|
|
92
|
+
CkEditableUserMessage,
|
|
92
93
|
} from './components';
|
|
93
94
|
export type {
|
|
94
95
|
CkAgentMessageHeaderProps,
|
|
@@ -128,6 +129,8 @@ export type {
|
|
|
128
129
|
CkModelSelectorEmits,
|
|
129
130
|
CkPromptMenuButtonProps,
|
|
130
131
|
CkUserMessageProps,
|
|
132
|
+
CkEditableUserMessageProps,
|
|
133
|
+
CkEditableUserMessageEmits,
|
|
131
134
|
} from './components';
|
|
132
135
|
|
|
133
136
|
// Helpers
|