@aleph-alpha/chat-kit 4.0.0 → 5.0.1
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-DJN-nZ2-.js → CkHistory.vue_vue_type_script_setup_true_lang-CtQUc3m3.js} +435 -378
- package/dist/chat-kit.css +28 -6
- 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 +10 -6
- 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/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/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/index.d.ts +4 -0
- package/dist/components/base/index.d.ts.map +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 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -26
- package/package.json +1 -1
- 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.spec.ts +9 -0
- package/src/components/base/CkCitationCard/CkCitationCard.stories.ts +11 -7
- package/src/components/base/CkCitationCard/CkCitationCard.vue +5 -3
- package/src/components/base/CkConversationLayout/CkConversationLayout.vue +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/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/index.ts +7 -0
- package/src/components/composed/CkConversation/CkConversation.spec.ts +81 -6
- package/src/components/composed/CkConversation/CkConversation.vue +78 -35
- package/src/components/composed/CkConversation/types.ts +1 -1
- package/src/components/composed/index.ts +0 -1
- package/src/components/index.ts +6 -1
- package/src/index.ts +5 -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/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
|
@@ -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
|
/**
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
export { CkSendIcon, CkStopIcon } from './icons';
|
|
2
2
|
export { CkAgentMessage } from './CkAgentMessage';
|
|
3
|
+
export { CkAgentMessageHeader } from './CkAgentMessageHeader';
|
|
4
|
+
export type { CkAgentMessageHeaderProps } from './CkAgentMessageHeader';
|
|
5
|
+
export { CkHeaderActivityButton } from './CkHeaderActivityButton';
|
|
6
|
+
export type {
|
|
7
|
+
CkHeaderActivityButtonProps,
|
|
8
|
+
CkHeaderActivityButtonEmits,
|
|
9
|
+
} from './CkHeaderActivityButton';
|
|
3
10
|
export { CkCitation } from './CkCitation';
|
|
4
11
|
export type {
|
|
5
12
|
CkCitationProps,
|
|
@@ -137,7 +137,7 @@ describe('CkConversation', () => {
|
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
expect(
|
|
140
|
-
screen.queryByTestId('ck-agent-message-icon'),
|
|
140
|
+
screen.queryByTestId('ck-agent-message-header-icon'),
|
|
141
141
|
).not.toBeInTheDocument();
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -146,7 +146,9 @@ describe('CkConversation', () => {
|
|
|
146
146
|
props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
expect(
|
|
149
|
+
expect(
|
|
150
|
+
screen.getByTestId('ck-agent-message-header-icon'),
|
|
151
|
+
).toBeInTheDocument();
|
|
150
152
|
});
|
|
151
153
|
|
|
152
154
|
it('shows the icon area when agentAvatarImage URL is provided', () => {
|
|
@@ -157,7 +159,9 @@ describe('CkConversation', () => {
|
|
|
157
159
|
},
|
|
158
160
|
});
|
|
159
161
|
|
|
160
|
-
expect(
|
|
162
|
+
expect(
|
|
163
|
+
screen.getByTestId('ck-agent-message-header-icon'),
|
|
164
|
+
).toBeInTheDocument();
|
|
161
165
|
});
|
|
162
166
|
|
|
163
167
|
it('shows the icon area when agent-message-icon slot is provided', () => {
|
|
@@ -169,7 +173,9 @@ describe('CkConversation', () => {
|
|
|
169
173
|
});
|
|
170
174
|
|
|
171
175
|
expect(screen.getByTestId('custom-icon')).toBeInTheDocument();
|
|
172
|
-
expect(
|
|
176
|
+
expect(
|
|
177
|
+
screen.getByTestId('ck-agent-message-header-icon'),
|
|
178
|
+
).toBeInTheDocument();
|
|
173
179
|
});
|
|
174
180
|
});
|
|
175
181
|
|
|
@@ -360,15 +366,44 @@ describe('CkConversation', () => {
|
|
|
360
366
|
expect(screen.getByAltText('custom-bot')).toBeInTheDocument();
|
|
361
367
|
});
|
|
362
368
|
|
|
363
|
-
it('allows
|
|
369
|
+
it('allows replacing the entire header via agent-message-header slot', () => {
|
|
370
|
+
render(CkConversation, {
|
|
371
|
+
// An avatar fallback would normally render the default header icon...
|
|
372
|
+
props: { messages: [agentMessage()], agentAvatarFallback: 'AI' },
|
|
373
|
+
slots: {
|
|
374
|
+
'agent-message-header':
|
|
375
|
+
'<div data-testid="custom-header">Custom Header</div>',
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
expect(screen.getByTestId('custom-header')).toBeInTheDocument();
|
|
380
|
+
// ...but the whole default header (and its icon) is replaced.
|
|
381
|
+
expect(
|
|
382
|
+
screen.queryByTestId('ck-agent-message-header-icon'),
|
|
383
|
+
).not.toBeInTheDocument();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('allows setting the header text via agent-message-header-content slot', () => {
|
|
364
387
|
render(CkConversation, {
|
|
365
388
|
props: { messages: [agentMessage()] },
|
|
366
|
-
slots: { 'agent-message-header': 'Custom Header' },
|
|
389
|
+
slots: { 'agent-message-header-content': 'Custom Header' },
|
|
367
390
|
});
|
|
368
391
|
|
|
369
392
|
expect(screen.getByText('Custom Header')).toBeInTheDocument();
|
|
370
393
|
});
|
|
371
394
|
|
|
395
|
+
it('allows adding a header activity control via agent-message-header-activity slot', () => {
|
|
396
|
+
render(CkConversation, {
|
|
397
|
+
props: { messages: [agentMessage()] },
|
|
398
|
+
slots: {
|
|
399
|
+
'agent-message-header-activity':
|
|
400
|
+
'<button data-testid="activity">1 tool</button>',
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
expect(screen.getByTestId('activity')).toBeInTheDocument();
|
|
405
|
+
});
|
|
406
|
+
|
|
372
407
|
it('allows adding footer actions via agent-message-footer slot', () => {
|
|
373
408
|
render(CkConversation, {
|
|
374
409
|
props: { messages: [agentMessage()] },
|
|
@@ -429,6 +464,46 @@ describe('CkConversation', () => {
|
|
|
429
464
|
});
|
|
430
465
|
});
|
|
431
466
|
|
|
467
|
+
describe('bottom separator', () => {
|
|
468
|
+
function agentRowBorders(container: Element): boolean[] {
|
|
469
|
+
const rows = container.querySelectorAll<HTMLElement>(
|
|
470
|
+
'[data-message-row="assistant"]',
|
|
471
|
+
);
|
|
472
|
+
return Array.from(rows).map((row) => {
|
|
473
|
+
const message = row.querySelector<HTMLElement>('[class*="pb-6"]');
|
|
474
|
+
return Boolean(message?.className.includes('border-b'));
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
it('keeps the separator on every assistant message except the last', () => {
|
|
479
|
+
const { container } = render(CkConversation, {
|
|
480
|
+
props: {
|
|
481
|
+
messages: [
|
|
482
|
+
{ ...agentMessage(), id: 'a1' },
|
|
483
|
+
{
|
|
484
|
+
id: 'u1',
|
|
485
|
+
role: 'user',
|
|
486
|
+
content: [text('Hi')],
|
|
487
|
+
createdAt: now,
|
|
488
|
+
status: 'completed',
|
|
489
|
+
},
|
|
490
|
+
{ ...agentMessage(), id: 'a2' },
|
|
491
|
+
],
|
|
492
|
+
},
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
expect(agentRowBorders(container)).toEqual([true, false]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('hides the separator on the only assistant message', () => {
|
|
499
|
+
const { container } = render(CkConversation, {
|
|
500
|
+
props: { messages: [{ ...agentMessage(), id: 'solo' }] },
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
expect(agentRowBorders(container)).toEqual([false]);
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
432
507
|
describe('empty conversation', () => {
|
|
433
508
|
it('renders nothing when messages array is empty', () => {
|
|
434
509
|
const { container } = render(CkConversation, { props: { messages: [] } });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
|
|
3
3
|
import type { BaseMessage, MessageTextContent } from '../../../types';
|
|
4
4
|
import CkAgentMessage from '../../base/CkAgentMessage/CkAgentMessage.vue';
|
|
5
|
+
import CkAgentMessageHeader from '../../base/CkAgentMessageHeader/CkAgentMessageHeader.vue';
|
|
5
6
|
import CkConversationLayout from '../../base/CkConversationLayout/CkConversationLayout.vue';
|
|
6
7
|
import CkCopyMessageButton from '../../base/CkCopyMessageButton/CkCopyMessageButton.vue';
|
|
7
8
|
import CkMarkdownRenderer from '../../base/CkMarkdownRenderer/CkMarkdownRenderer.vue';
|
|
@@ -34,8 +35,16 @@ const slots = defineSlots<{
|
|
|
34
35
|
'agent-message-icon': (props: { message: BaseMessage }) => void;
|
|
35
36
|
/** Replace the agent loading icon shown while a message is being generated */
|
|
36
37
|
'agent-message-loading-icon': (props: { message: BaseMessage }) => void;
|
|
37
|
-
/** Replace the agent message header */
|
|
38
|
+
/** Replace the entire agent message header (the whole `CkAgentMessageHeader`) */
|
|
38
39
|
'agent-message-header': (props: { message: BaseMessage }) => void;
|
|
40
|
+
/** Replace the agent message header text (rendered inside the header row) */
|
|
41
|
+
'agent-message-header-content': (props: { message: BaseMessage }) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Provide an activity control for the agent message header (rendered on the
|
|
44
|
+
* right of the header, typically a `CkHeaderActivityButton`). When provided,
|
|
45
|
+
* the header gains vertical padding and a bottom border.
|
|
46
|
+
*/
|
|
47
|
+
'agent-message-header-activity': (props: { message: BaseMessage }) => void;
|
|
39
48
|
/**
|
|
40
49
|
* Replace the agent message body. Receives the full `BaseMessage` so the
|
|
41
50
|
* consumer can inspect every content part (text, tool calls, reasoning)
|
|
@@ -64,6 +73,18 @@ const hasIcon = computed(
|
|
|
64
73
|
const isLoading = (message: BaseMessage) =>
|
|
65
74
|
message.status === 'started' || message.status === 'sending';
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Id of the last assistant message. Its bottom separator is hidden so the
|
|
78
|
+
* border divides user/assistant pairs without a trailing line after the final
|
|
79
|
+
* pair.
|
|
80
|
+
*/
|
|
81
|
+
const lastAgentMessageId = computed(() => {
|
|
82
|
+
for (let i = props.messages.length - 1; i >= 0; i--) {
|
|
83
|
+
if (props.messages[i].role !== 'user') return props.messages[i].id;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
});
|
|
87
|
+
|
|
67
88
|
/** Concatenated text from every text content part in a message. */
|
|
68
89
|
function getMessageText(message: BaseMessage): string {
|
|
69
90
|
return message.content
|
|
@@ -89,42 +110,64 @@ function getMessageText(message: BaseMessage): string {
|
|
|
89
110
|
|
|
90
111
|
<template #agent-message="{ message }">
|
|
91
112
|
<slot name="agent-message" :message="message">
|
|
92
|
-
<CkAgentMessage>
|
|
93
|
-
<template
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
<CkAgentMessage :bottom-separator="message.id !== lastAgentMessageId">
|
|
114
|
+
<template
|
|
115
|
+
v-if="
|
|
116
|
+
hasIcon ||
|
|
117
|
+
$slots['agent-message-header'] ||
|
|
118
|
+
$slots['agent-message-header-content'] ||
|
|
119
|
+
$slots['agent-message-header-activity']
|
|
120
|
+
"
|
|
121
|
+
#header
|
|
122
|
+
>
|
|
123
|
+
<slot name="agent-message-header" :message="message">
|
|
124
|
+
<CkAgentMessageHeader>
|
|
125
|
+
<template v-if="hasIcon" #icon>
|
|
126
|
+
<template v-if="isLoading(message)">
|
|
127
|
+
<slot name="agent-message-loading-icon" :message="message">
|
|
128
|
+
<UiAvatar v-if="agentLoadingImage" class="size-8">
|
|
129
|
+
<UiAvatarImage
|
|
130
|
+
:src="agentLoadingImage"
|
|
131
|
+
:alt="labels.agentLoadingAvatarAlt"
|
|
132
|
+
/>
|
|
133
|
+
</UiAvatar>
|
|
134
|
+
<UiSpinner
|
|
135
|
+
v-else
|
|
136
|
+
class="size-8"
|
|
137
|
+
:aria-label="labels.agentLoadingAvatarAlt"
|
|
138
|
+
/>
|
|
139
|
+
</slot>
|
|
140
|
+
</template>
|
|
109
141
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
<template v-else>
|
|
143
|
+
<slot name="agent-message-icon" :message="message">
|
|
144
|
+
<UiAvatar class="size-8">
|
|
145
|
+
<UiAvatarImage
|
|
146
|
+
v-if="agentAvatarImage"
|
|
147
|
+
:src="agentAvatarImage"
|
|
148
|
+
:alt="labels.agentAvatarAlt"
|
|
149
|
+
/>
|
|
150
|
+
<UiAvatarFallback v-if="agentAvatarFallback">{{
|
|
151
|
+
agentAvatarFallback
|
|
152
|
+
}}</UiAvatarFallback>
|
|
153
|
+
</UiAvatar>
|
|
154
|
+
</slot>
|
|
155
|
+
</template>
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<slot name="agent-message-header-content" :message="message" />
|
|
125
159
|
|
|
126
|
-
|
|
127
|
-
|
|
160
|
+
<template
|
|
161
|
+
v-if="$slots['agent-message-header-activity']"
|
|
162
|
+
#activity
|
|
163
|
+
>
|
|
164
|
+
<slot
|
|
165
|
+
name="agent-message-header-activity"
|
|
166
|
+
:message="message"
|
|
167
|
+
/>
|
|
168
|
+
</template>
|
|
169
|
+
</CkAgentMessageHeader>
|
|
170
|
+
</slot>
|
|
128
171
|
</template>
|
|
129
172
|
|
|
130
173
|
<slot name="agent-message-content" :message="message">
|
|
@@ -10,7 +10,7 @@ import type { CkMarkdownElementClasses } from '../../base/CkMarkdownRenderer';
|
|
|
10
10
|
* @category Messages
|
|
11
11
|
* @useCases complete chat conversation ui, streaming chat with markdown, assistant chat view, llm conversation component, drop-in chat surface
|
|
12
12
|
* @keywords conversation, chat, messages, assistant, agent, user, markdown, streaming, avatar
|
|
13
|
-
* @related CkConversationLayout, CkAgentMessage, CkUserMessage, CkMarkdownRenderer, CkCopyMessageButton,
|
|
13
|
+
* @related CkConversationLayout, CkAgentMessage, CkUserMessage, CkMarkdownRenderer, CkCopyMessageButton, CkPromptInput
|
|
14
14
|
*/
|
|
15
15
|
export interface CkConversationProps {
|
|
16
16
|
/** List of messages to display, in chronological order. */
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Base (presentational) components
|
|
2
2
|
export {
|
|
3
3
|
CkAgentMessage,
|
|
4
|
+
CkAgentMessageHeader,
|
|
5
|
+
CkHeaderActivityButton,
|
|
4
6
|
CkCitation,
|
|
5
7
|
CkCitationCard,
|
|
6
8
|
CkConversationLayout,
|
|
@@ -25,6 +27,9 @@ export {
|
|
|
25
27
|
CkUserMessage,
|
|
26
28
|
} from './base';
|
|
27
29
|
export type {
|
|
30
|
+
CkAgentMessageHeaderProps,
|
|
31
|
+
CkHeaderActivityButtonProps,
|
|
32
|
+
CkHeaderActivityButtonEmits,
|
|
28
33
|
CkCitationProps,
|
|
29
34
|
CkCitationEmits,
|
|
30
35
|
CkCitationSource,
|
|
@@ -59,7 +64,7 @@ export type {
|
|
|
59
64
|
} from './base';
|
|
60
65
|
|
|
61
66
|
// Composed (pattern) components
|
|
62
|
-
export { CkConversation, CkHistory
|
|
67
|
+
export { CkConversation, CkHistory } from './composed';
|
|
63
68
|
export type {
|
|
64
69
|
CkConversationProps,
|
|
65
70
|
CkHistoryEmits,
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,8 @@ export type {
|
|
|
63
63
|
// Components
|
|
64
64
|
export {
|
|
65
65
|
CkAgentMessage,
|
|
66
|
+
CkAgentMessageHeader,
|
|
67
|
+
CkHeaderActivityButton,
|
|
66
68
|
CkCitation,
|
|
67
69
|
CkCitationCard,
|
|
68
70
|
CkConversation,
|
|
@@ -75,7 +77,6 @@ export {
|
|
|
75
77
|
CkHistory,
|
|
76
78
|
CkHistoryItem,
|
|
77
79
|
CkHistoryWrapper,
|
|
78
|
-
CkInput,
|
|
79
80
|
CkMarkdownRenderer,
|
|
80
81
|
CkMessageButton,
|
|
81
82
|
CkModelSelector,
|
|
@@ -90,6 +91,9 @@ export {
|
|
|
90
91
|
CkUserMessage,
|
|
91
92
|
} from './components';
|
|
92
93
|
export type {
|
|
94
|
+
CkAgentMessageHeaderProps,
|
|
95
|
+
CkHeaderActivityButtonProps,
|
|
96
|
+
CkHeaderActivityButtonEmits,
|
|
93
97
|
CkCitationProps,
|
|
94
98
|
CkCitationEmits,
|
|
95
99
|
CkCitationSource,
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import CkInput from './CkInput.vue';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
3
|
-
declare const meta: Meta<typeof CkInput>;
|
|
4
|
-
export default meta;
|
|
5
|
-
type Story = StoryObj<typeof meta>;
|
|
6
|
-
/**
|
|
7
|
-
* Default chat input with disclaimer and the built-in send button.
|
|
8
|
-
* Use as the primary way to let users submit a prompt to the assistant.
|
|
9
|
-
*/
|
|
10
|
-
export declare const Default: Story;
|
|
11
|
-
/**
|
|
12
|
-
* Adds a mode toggle (Express/Smart) to the left side of the toolbar via
|
|
13
|
-
* `#toolbar-left`. Use when the user needs to switch between assistant
|
|
14
|
-
* presets or capabilities before submitting.
|
|
15
|
-
*/
|
|
16
|
-
export declare const WithModeToggle: Story;
|
|
17
|
-
/**
|
|
18
|
-
* End-to-end example: mode toggle on the left, attachment button + custom
|
|
19
|
-
* send button on the right, and a disclaimer underneath. Use as a reference
|
|
20
|
-
* for a production-ready chat compose UI.
|
|
21
|
-
*/
|
|
22
|
-
export declare const FullExample: Story;
|
|
23
|
-
/**
|
|
24
|
-
* Disabled state — the textarea is read-only and the send button is inactive.
|
|
25
|
-
* Use while waiting for an upstream action to resolve.
|
|
26
|
-
*/
|
|
27
|
-
export declare const Disabled: Story;
|
|
28
|
-
/**
|
|
29
|
-
* Loading state — shown while the assistant is generating a reply. The
|
|
30
|
-
* send button displays a spinner and the input is blocked.
|
|
31
|
-
*/
|
|
32
|
-
export declare const Loading: Story;
|
|
33
|
-
/**
|
|
34
|
-
* Demonstrates the textarea's auto-grow behavior with multi-paragraph content.
|
|
35
|
-
* Use when validating how the input handles long prompts.
|
|
36
|
-
*/
|
|
37
|
-
export declare const WithLongText: Story;
|
|
38
|
-
/**
|
|
39
|
-
* Narrow viewport preview (≈320px). Use to verify the input stays usable on
|
|
40
|
-
* small screens.
|
|
41
|
-
*/
|
|
42
|
-
export declare const MobileWidth: Story;
|
|
43
|
-
//# sourceMappingURL=CkInput.stories.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CkInput.stories.d.ts","sourceRoot":"","sources":["../../../../src/components/composed/CkInput/CkInput.stories.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,eAAe,CAAC;AACpC,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAM3D,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,OAAO,OAAO,CAqC9B,CAAC;AAEF,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAqGnC;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KAerB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,KAgD5B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,KA2FzB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,KAiBtB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,OAAO,EAAE,KA2BrB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAuB1B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,KAsBzB,CAAC"}
|