@aleph-alpha/chat-kit 1.3.1 → 1.5.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.
Files changed (73) hide show
  1. package/dist/{CkInput.vue_vue_type_script_setup_true_lang-CyGLItQZ.js → CkInput.vue_vue_type_script_setup_true_lang-C5C09u0z.js} +9 -8
  2. package/dist/adapters/index.d.ts +18 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +4 -0
  5. package/dist/adapters/responses-api.d.ts +180 -0
  6. package/dist/adapters/responses-api.d.ts.map +1 -0
  7. package/dist/adapters/responses-api.js +153 -0
  8. package/dist/components/base/CkPromptActionButton/CkPromptActionButton.vue.d.ts +1 -1
  9. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts +11 -3
  10. package/dist/components/composed/CkConversation/CkConversation.stories.d.ts.map +1 -1
  11. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts +5 -1
  12. package/dist/components/composed/CkConversation/CkConversation.vue.d.ts.map +1 -1
  13. package/dist/components/index.js +1 -1
  14. package/dist/composables/fileUpload/index.d.ts +7 -0
  15. package/dist/composables/fileUpload/index.d.ts.map +1 -0
  16. package/dist/composables/fileUpload/types.d.ts +295 -0
  17. package/dist/composables/fileUpload/types.d.ts.map +1 -0
  18. package/dist/composables/fileUpload/useFileUpload.d.ts +4 -0
  19. package/dist/composables/fileUpload/useFileUpload.d.ts.map +1 -0
  20. package/dist/composables/fileUpload/useFileUploadWrapper.d.ts +3 -0
  21. package/dist/composables/fileUpload/useFileUploadWrapper.d.ts.map +1 -0
  22. package/dist/composables/index.d.ts +2 -1
  23. package/dist/composables/index.d.ts.map +1 -1
  24. package/dist/composables/index.js +5 -2
  25. package/dist/composables/useConversations.d.ts +12 -0
  26. package/dist/composables/useConversations.d.ts.map +1 -1
  27. package/dist/generateId-BHf0W4Rq.js +6 -0
  28. package/dist/handlers/index.js +1 -1
  29. package/dist/handlers/useMessageHandler.d.ts +2 -2
  30. package/dist/handlers/useMessageHandler.d.ts.map +1 -1
  31. package/dist/index-BbTgIoYa.js +1557 -0
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +6 -8
  35. package/dist/services/index.d.ts +0 -2
  36. package/dist/services/index.d.ts.map +1 -1
  37. package/dist/services/index.js +1 -37
  38. package/dist/services/types.d.ts +2 -2
  39. package/dist/services/types.d.ts.map +1 -1
  40. package/dist/types.d.ts +47 -2
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/{useMessageHandler-BmBdu2EW.js → useMessageHandler-ZRC-gIOJ.js} +5 -5
  43. package/package.json +10 -2
  44. package/src/adapters/index.ts +17 -0
  45. package/src/adapters/responses-api.spec.ts +365 -0
  46. package/src/adapters/responses-api.ts +444 -0
  47. package/src/components/composed/CkConversation/CkConversation.spec.ts +171 -25
  48. package/src/components/composed/CkConversation/CkConversation.stories.ts +266 -46
  49. package/src/components/composed/CkConversation/CkConversation.vue +19 -7
  50. package/src/composables/fileUpload/index.spec.ts +213 -0
  51. package/src/composables/fileUpload/index.ts +83 -0
  52. package/src/composables/fileUpload/types.ts +409 -0
  53. package/src/composables/fileUpload/useFileUpload.ts +1242 -0
  54. package/src/composables/fileUpload/useFileUploadWrapper.ts +382 -0
  55. package/src/composables/index.ts +2 -1
  56. package/src/composables/useConversations.spec.ts +118 -0
  57. package/src/composables/useConversations.ts +32 -0
  58. package/src/handlers/useChatKitLabels.spec.ts +16 -2
  59. package/src/handlers/useMessageHandler.spec.ts +112 -44
  60. package/src/handlers/useMessageHandler.ts +16 -2
  61. package/src/helpers/conversationTree.spec.ts +46 -19
  62. package/src/index.ts +11 -2
  63. package/src/services/index.ts +0 -2
  64. package/src/services/types.ts +2 -2
  65. package/src/types.ts +55 -2
  66. package/dist/ChatService-LogwJNxh.js +0 -9
  67. package/dist/services/ChatService.d.ts +0 -42
  68. package/dist/services/ChatService.d.ts.map +0 -1
  69. package/dist/services/MockChatService.d.ts +0 -10
  70. package/dist/services/MockChatService.d.ts.map +0 -1
  71. package/dist/useConversationSessions-ulFhtpQr.js +0 -243
  72. package/src/services/ChatService.ts +0 -60
  73. package/src/services/MockChatService.ts +0 -47
@@ -1,13 +1,35 @@
1
1
  import { CkCopyMessageActionButton } from '../../base/CkCopyMessageActionButton';
2
2
  import { CkFeedbackButton } from '../../base/CkFeedbackButton';
3
3
  import type { CkFeedbackValue } from '../../base/CkFeedbackButton';
4
- import type { BaseMessage } from '../../../types';
4
+ import type {
5
+ BaseMessage,
6
+ MessageContent,
7
+ MessageTextContent,
8
+ } from '../../../types';
5
9
  import CkConversation from './CkConversation.vue';
6
10
  import type { Meta, StoryObj } from '@storybook/vue3-vite';
7
11
  import { ref } from 'vue';
8
12
 
9
13
  const now = new Date();
10
14
 
15
+ let textIdCounter = 0;
16
+ function text(value: string): MessageTextContent {
17
+ textIdCounter += 1;
18
+ return {
19
+ type: 'text',
20
+ id: `txt-${textIdCounter}`,
21
+ text: value,
22
+ status: 'completed',
23
+ };
24
+ }
25
+
26
+ function getMessageText(message: BaseMessage): string {
27
+ return message.content
28
+ .filter((c): c is MessageTextContent => c.type === 'text')
29
+ .map((c) => c.text)
30
+ .join('');
31
+ }
32
+
11
33
  const markdownContent = [
12
34
  'Based on a review of internal documents, here are the **key lessons learned** and best practices from the *Digital Transformation Phase 1* project, broken down by category.',
13
35
  '',
@@ -37,31 +59,64 @@ const sampleMessages: BaseMessage[] = [
37
59
  {
38
60
  id: '1',
39
61
  role: 'user',
40
- content: 'Hello! Can you help me with something?',
62
+ content: [text('Hello! Can you help me with something?')],
41
63
  createdAt: now,
42
64
  status: 'completed',
43
65
  },
44
66
  {
45
67
  id: '2',
46
68
  role: 'assistant',
47
- content: 'Of course! I am here to help. What would you like to know?',
69
+ content: [
70
+ text('Of course! I am here to help. What would you like to know?'),
71
+ ],
48
72
  createdAt: now,
49
73
  status: 'completed',
50
74
  },
51
75
  {
52
76
  id: '3',
53
77
  role: 'user',
54
- content:
55
- "Find the lessons learned and best practices from the 'Digital Transformation Phase 1' project, specifically focusing on process optimization and digital tool integration.",
78
+ content: [
79
+ text(
80
+ "Find the lessons learned and best practices from the 'Digital Transformation Phase 1' project, specifically focusing on process optimization and digital tool integration.",
81
+ ),
82
+ ],
56
83
  createdAt: now,
57
84
  status: 'completed',
58
85
  },
59
86
  {
60
87
  id: '4',
61
88
  role: 'assistant',
62
- content: markdownContent,
89
+ content: [text(markdownContent)],
90
+ createdAt: now,
91
+ status: 'completed',
92
+ },
93
+ ];
94
+
95
+ const chainOfThoughtMessages: BaseMessage[] = [
96
+ {
97
+ id: '1',
98
+ role: 'user',
99
+ content: [
100
+ text(
101
+ 'Look up the recent revenue numbers for Q3 and tell me how they compare to last year.',
102
+ ),
103
+ ],
104
+ createdAt: now,
105
+ status: 'completed',
106
+ },
107
+ {
108
+ id: '2',
109
+ role: 'assistant',
63
110
  createdAt: now,
64
111
  status: 'completed',
112
+ content: [
113
+ { type: 'reasoning', id: 'r-1', status: 'completed' },
114
+ { type: 'tool', id: 't-1', name: 'finance_lookup' },
115
+ { type: 'tool', id: 't-2', name: 'compare_periods' },
116
+ text(
117
+ '## Revenue summary\n\n- **Q3 2025:** **$48.2M** (+12% YoY)\n- **Q3 2024:** $43.0M\n\nThe biggest contributor was the EMEA segment, which grew **18%** year over year. North America was roughly flat.',
118
+ ),
119
+ ],
65
120
  },
66
121
  ];
67
122
 
@@ -109,8 +164,20 @@ import { CkConversation } from '@aleph-alpha/chat-kit';
109
164
  import type { BaseMessage } from '@aleph-alpha/chat-kit';
110
165
 
111
166
  const messages: BaseMessage[] = [
112
- { id: '1', role: 'user', content: 'Hello!', createdAt: new Date(), status: 'completed' },
113
- { id: '2', role: 'assistant', content: 'Hi there!', createdAt: new Date(), status: 'completed' },
167
+ {
168
+ id: '1',
169
+ role: 'user',
170
+ content: [{ type: 'text', id: 't1', text: 'Hello!', status: 'completed' }],
171
+ createdAt: new Date(),
172
+ status: 'completed',
173
+ },
174
+ {
175
+ id: '2',
176
+ role: 'assistant',
177
+ content: [{ type: 'text', id: 't2', text: 'Hi there!', status: 'completed' }],
178
+ createdAt: new Date(),
179
+ status: 'completed',
180
+ },
114
181
  ];
115
182
  </script>
116
183
 
@@ -118,6 +185,56 @@ const messages: BaseMessage[] = [
118
185
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;" />
119
186
  </template>`;
120
187
 
188
+ const chainOfThoughtTemplateSource = `<script setup lang="ts">
189
+ import { CkConversation } from '@aleph-alpha/chat-kit';
190
+ import type { BaseMessage } from '@aleph-alpha/chat-kit';
191
+
192
+ const messages: BaseMessage[] = [
193
+ {
194
+ id: '1',
195
+ role: 'user',
196
+ content: [{ type: 'text', id: 't1', text: 'Compare Q3 revenue.', status: 'completed' }],
197
+ createdAt: new Date(),
198
+ status: 'completed',
199
+ },
200
+ {
201
+ id: '2',
202
+ role: 'assistant',
203
+ createdAt: new Date(),
204
+ status: 'completed',
205
+ content: [
206
+ { type: 'reasoning', id: 'r1', status: 'completed' },
207
+ { type: 'tool', id: 'tl1', name: 'finance_lookup' },
208
+ { type: 'tool', id: 'tl2', name: 'compare_periods' },
209
+ { type: 'text', id: 't2', text: 'Q3 grew **12% YoY**.', status: 'completed' },
210
+ ],
211
+ },
212
+ ];
213
+ </script>
214
+
215
+ <template>
216
+ <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
217
+ <!--
218
+ Use #agent-message-content to take over rendering of the entire body.
219
+ The slot exposes the full message so you can iterate every content
220
+ part (text, tool, reasoning) and render them however you like.
221
+ -->
222
+ <template #agent-message-content="{ message }">
223
+ <div class="flex flex-col gap-2">
224
+ <template v-for="part in message.content" :key="part.id">
225
+ <span v-if="part.type === 'reasoning'" class="text-xs italic opacity-60">
226
+ Reasoning: {{ part.status }}
227
+ </span>
228
+ <span v-else-if="part.type === 'tool'" class="text-xs font-mono opacity-70">
229
+ ⚙️ {{ part.name }}
230
+ </span>
231
+ <p v-else-if="part.type === 'text'">{{ part.text }}</p>
232
+ </template>
233
+ </div>
234
+ </template>
235
+ </CkConversation>
236
+ </template>`;
237
+
121
238
  const withIconTemplateSource = `<script setup lang="ts">
122
239
  import { CkConversation } from '@aleph-alpha/chat-kit';
123
240
  import type { BaseMessage } from '@aleph-alpha/chat-kit';
@@ -182,7 +299,13 @@ import type { BaseMessage, CkFeedbackValue } from '@aleph-alpha/chat-kit';
182
299
 
183
300
  const messages: BaseMessage[] = [/* ... */];
184
301
 
185
- // In a real app, key feedback by message id and persist to your backend.
302
+ function getMessageText(message: BaseMessage): string {
303
+ return message.content
304
+ .filter((c) => c.type === 'text')
305
+ .map((c) => c.text)
306
+ .join('');
307
+ }
308
+
186
309
  const feedback = ref<Record<string, { value: CkFeedbackValue; locked: boolean }>>({});
187
310
  function onFeedback(messageId: string, value: CkFeedbackValue) {
188
311
  feedback.value[messageId] = { value, locked: value !== null };
@@ -192,7 +315,7 @@ function onFeedback(messageId: string, value: CkFeedbackValue) {
192
315
  <template>
193
316
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
194
317
  <template #agent-message-footer="{ message }">
195
- <CkCopyMessageActionButton :text="message.content" />
318
+ <CkCopyMessageActionButton :text="getMessageText(message)" />
196
319
  <CkFeedbackButton
197
320
  :value="feedback[message.id]?.value ?? null"
198
321
  :locked="feedback[message.id]?.locked ?? false"
@@ -207,12 +330,19 @@ import { CkConversation } from '@aleph-alpha/chat-kit';
207
330
  import type { BaseMessage } from '@aleph-alpha/chat-kit';
208
331
 
209
332
  const messages: BaseMessage[] = [/* ... */];
333
+
334
+ function getMessageText(message: BaseMessage): string {
335
+ return message.content
336
+ .filter((c) => c.type === 'text')
337
+ .map((c) => c.text)
338
+ .join('');
339
+ }
210
340
  </script>
211
341
 
212
342
  <template>
213
343
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
214
344
  <template #user-message-content="{ message }">
215
- <strong>{{ message.content }}</strong>
345
+ <strong>{{ getMessageText(message) }}</strong>
216
346
  </template>
217
347
  </CkConversation>
218
348
  </template>`;
@@ -222,12 +352,19 @@ import { CkConversation } from '@aleph-alpha/chat-kit';
222
352
  import type { BaseMessage } from '@aleph-alpha/chat-kit';
223
353
 
224
354
  const messages: BaseMessage[] = [/* ... */];
355
+
356
+ function getMessageText(message: BaseMessage): string {
357
+ return message.content
358
+ .filter((c) => c.type === 'text')
359
+ .map((c) => c.text)
360
+ .join('');
361
+ }
225
362
  </script>
226
363
 
227
364
  <template>
228
365
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
229
366
  <template #agent-message-content="{ message }">
230
- <div class="whitespace-pre-wrap font-mono text-sm">{{ message.content }}</div>
367
+ <div class="whitespace-pre-wrap font-mono text-sm">{{ getMessageText(message) }}</div>
231
368
  </template>
232
369
  </CkConversation>
233
370
  </template>`;
@@ -242,19 +379,37 @@ const messages = ref<BaseMessage[]>([]);
242
379
  function sendAndStream(): void {
243
380
  messages.value = [
244
381
  ...messages.value,
245
- { id: '1', role: 'user', content: 'Tell me something.', createdAt: new Date(), status: 'completed' },
382
+ {
383
+ id: '1',
384
+ role: 'user',
385
+ content: [{ type: 'text', id: 'u-t', text: 'Tell me something.', status: 'completed' }],
386
+ createdAt: new Date(),
387
+ status: 'completed',
388
+ },
246
389
  ];
390
+ const replyId = 'a-t';
247
391
  messages.value = [
248
392
  ...messages.value,
249
- { id: '2', role: 'assistant', content: '', createdAt: new Date(), status: 'sending' },
393
+ {
394
+ id: '2',
395
+ role: 'assistant',
396
+ content: [{ type: 'text', id: replyId, text: '', status: 'in_progress' }],
397
+ createdAt: new Date(),
398
+ status: 'sending',
399
+ },
250
400
  ];
251
401
  const reply = 'Key lessons learned from the project...';
252
402
  let i = 0;
253
403
  const interval = setInterval(() => {
254
404
  i = Math.min(i + 3, reply.length);
255
405
  const last = messages.value.length - 1;
256
- messages.value[last] = { ...messages.value[last], content: reply.slice(0, i) };
257
- if (i >= reply.length) clearInterval(interval);
406
+ const completed = i >= reply.length;
407
+ messages.value[last] = {
408
+ ...messages.value[last],
409
+ content: [{ type: 'text', id: replyId, text: reply.slice(0, i), status: completed ? 'completed' : 'in_progress' }],
410
+ status: completed ? 'completed' : 'sending',
411
+ };
412
+ if (completed) clearInterval(interval);
258
413
  }, 30);
259
414
  }
260
415
  </script>
@@ -312,9 +467,10 @@ import { CkConversation } from '@aleph-alpha/chat-kit';
312
467
  </template>`;
313
468
 
314
469
  /**
315
- * Drop-in conversation UI. Pass `messages` with `role`, `content`, and `status`
316
- * fields and CkConversation renders user bubbles, assistant markdown, avatars,
317
- * and a copy button on each reply.
470
+ * Drop-in conversation UI. Pass `messages` with `role`, `content` (a list of
471
+ * text / tool / reasoning parts), and `status` fields and CkConversation
472
+ * renders user bubbles, assistant markdown, avatars, and a copy button on
473
+ * each reply.
318
474
  */
319
475
  export const Default: Story = {
320
476
  render: () => ({
@@ -328,6 +484,39 @@ export const Default: Story = {
328
484
  parameters: { docs: { source: { code: defaultTemplateSource } } },
329
485
  };
330
486
 
487
+ /**
488
+ * Demonstrates a chain-of-thought assistant turn with reasoning and tool
489
+ * calls in `message.content`. By default only text parts are rendered, so
490
+ * use `#agent-message-content` to opt-in to a custom rendering of the
491
+ * non-text parts. The host owns the visual treatment for those segments.
492
+ */
493
+ export const ChainOfThought: Story = {
494
+ render: () => ({
495
+ components: { CkConversation },
496
+ setup() {
497
+ return { messages: chainOfThoughtMessages };
498
+ },
499
+ template: `
500
+ <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
501
+ <template #agent-message-content="{ message }">
502
+ <div style="display: flex; flex-direction: column; gap: 8px;">
503
+ <template v-for="part in message.content" :key="part.id">
504
+ <span v-if="part.type === 'reasoning'" style="font-size: 12px; font-style: italic; opacity: 0.6;">
505
+ Reasoning: {{ part.status }}
506
+ </span>
507
+ <span v-else-if="part.type === 'tool'" style="font-size: 12px; font-family: monospace; opacity: 0.7;">
508
+ ⚙️ {{ part.name }}
509
+ </span>
510
+ <p v-else-if="part.type === 'text'" style="margin: 0;">{{ part.text }}</p>
511
+ </template>
512
+ </div>
513
+ </template>
514
+ </CkConversation>
515
+ `,
516
+ }),
517
+ parameters: { docs: { source: { code: chainOfThoughtTemplateSource } } },
518
+ };
519
+
331
520
  /**
332
521
  * Uses a remote avatar image (with fallback text). Use when you want a
333
522
  * branded agent identity in each reply.
@@ -419,12 +608,17 @@ export const WithFooterActions: Story = {
419
608
  function onFeedback(messageId: string, value: CkFeedbackValue) {
420
609
  feedback.value[messageId] = { value, locked: value !== null };
421
610
  }
422
- return { messages: sampleMessages, feedback, onFeedback };
611
+ return {
612
+ messages: sampleMessages,
613
+ feedback,
614
+ onFeedback,
615
+ getMessageText,
616
+ };
423
617
  },
424
618
  template: `
425
619
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
426
620
  <template #agent-message-footer="{ message }">
427
- <CkCopyMessageActionButton :text="message.content" />
621
+ <CkCopyMessageActionButton :text="getMessageText(message)" />
428
622
  <CkFeedbackButton
429
623
  :value="feedback[message.id]?.value ?? null"
430
624
  :locked="feedback[message.id]?.locked ?? false"
@@ -445,12 +639,12 @@ export const WithCustomUserContent: Story = {
445
639
  render: () => ({
446
640
  components: { CkConversation },
447
641
  setup() {
448
- return { messages: sampleMessages };
642
+ return { messages: sampleMessages, getMessageText };
449
643
  },
450
644
  template: `
451
645
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
452
646
  <template #user-message-content="{ message }">
453
- <strong>{{ message.content }}</strong>
647
+ <strong>{{ getMessageText(message) }}</strong>
454
648
  </template>
455
649
  </CkConversation>
456
650
  `,
@@ -469,12 +663,12 @@ export const WithCustomAgentContent: Story = {
469
663
  render: () => ({
470
664
  components: { CkConversation },
471
665
  setup() {
472
- return { messages: sampleMessages };
666
+ return { messages: sampleMessages, getMessageText };
473
667
  },
474
668
  template: `
475
669
  <CkConversation :messages="messages" agent-avatar-fallback="AI" style="height: 100%;">
476
670
  <template #agent-message-content="{ message }">
477
- <div style="white-space: pre-wrap; font-family: monospace; font-size: 13px;">{{ message.content }}</div>
671
+ <div style="white-space: pre-wrap; font-family: monospace; font-size: 13px;">{{ getMessageText(message) }}</div>
478
672
  </template>
479
673
  </CkConversation>
480
674
  `,
@@ -497,14 +691,14 @@ export const StreamingSimulation: Story = {
497
691
  {
498
692
  id: '1',
499
693
  role: 'user',
500
- content: 'Hello!',
694
+ content: [text('Hello!')],
501
695
  createdAt: now,
502
696
  status: 'completed',
503
697
  },
504
698
  {
505
699
  id: '2',
506
700
  role: 'assistant',
507
- content: 'Hi there!',
701
+ content: [text('Hi there!')],
508
702
  createdAt: now,
509
703
  status: 'completed',
510
704
  },
@@ -513,6 +707,7 @@ export const StreamingSimulation: Story = {
513
707
  let streamInterval: ReturnType<typeof setInterval> | null = null;
514
708
  let charIndex = 0;
515
709
  let nextId = 3;
710
+ let streamingTextId = '';
516
711
 
517
712
  function streamNextChunk(): void {
518
713
  if (charIndex >= markdownContent.length) {
@@ -523,20 +718,38 @@ export const StreamingSimulation: Story = {
523
718
  const chunkSize = Math.floor(Math.random() * 5) + 3;
524
719
  charIndex = Math.min(charIndex + chunkSize, markdownContent.length);
525
720
  const lastIdx = messages.value.length - 1;
721
+ const completed = charIndex >= markdownContent.length;
722
+ const content: MessageContent[] = [
723
+ {
724
+ type: 'text',
725
+ id: streamingTextId,
726
+ text: markdownContent.slice(0, charIndex),
727
+ status: completed ? 'completed' : 'in_progress',
728
+ },
729
+ ];
526
730
  messages.value[lastIdx] = {
527
731
  ...messages.value[lastIdx],
528
- content: markdownContent.slice(0, charIndex),
732
+ content,
733
+ status: completed ? 'completed' : 'sending',
529
734
  };
530
735
  }
531
736
 
532
737
  function startStreaming(): void {
533
738
  const id = String(nextId++);
739
+ streamingTextId = `txt-stream-${id}`;
534
740
  messages.value = [
535
741
  ...messages.value,
536
742
  {
537
743
  id,
538
744
  role: 'assistant',
539
- content: '',
745
+ content: [
746
+ {
747
+ type: 'text',
748
+ id: streamingTextId,
749
+ text: '',
750
+ status: 'in_progress',
751
+ },
752
+ ],
540
753
  createdAt: new Date(),
541
754
  status: 'sending',
542
755
  },
@@ -552,8 +765,11 @@ export const StreamingSimulation: Story = {
552
765
  {
553
766
  id,
554
767
  role: 'user',
555
- content:
556
- 'Tell me about Digital Transformation Phase 1 lessons learned.',
768
+ content: [
769
+ text(
770
+ 'Tell me about Digital Transformation Phase 1 lessons learned.',
771
+ ),
772
+ ],
557
773
  createdAt: new Date(),
558
774
  status: 'completed',
559
775
  },
@@ -616,7 +832,7 @@ export const WithCustomCodeBlock: Story = {
616
832
  {
617
833
  id: '1',
618
834
  role: 'user',
619
- content: 'Show me a code example',
835
+ content: [text('Show me a code example')],
620
836
  createdAt: now,
621
837
  status: 'completed',
622
838
  },
@@ -624,20 +840,24 @@ export const WithCustomCodeBlock: Story = {
624
840
  id: '2',
625
841
  role: 'assistant',
626
842
  content: [
627
- 'Here is an example:',
628
- '',
629
- '```typescript',
630
- 'const greeting = "Hello, world!";',
631
- 'console.log(greeting);',
632
- '```',
633
- '',
634
- 'And another one:',
635
- '',
636
- '```python',
637
- 'def greet(name: str) -> str:',
638
- ' return f"Hello, {name}!"',
639
- '```',
640
- ].join('\n'),
843
+ text(
844
+ [
845
+ 'Here is an example:',
846
+ '',
847
+ '```typescript',
848
+ 'const greeting = "Hello, world!";',
849
+ 'console.log(greeting);',
850
+ '```',
851
+ '',
852
+ 'And another one:',
853
+ '',
854
+ '```python',
855
+ 'def greet(name: str) -> str:',
856
+ ' return f"Hello, {name}!"',
857
+ '```',
858
+ ].join('\n'),
859
+ ),
860
+ ],
641
861
  createdAt: now,
642
862
  status: 'completed',
643
863
  },
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
3
- import type { BaseMessage } from '../../../types';
3
+ import type { BaseMessage, MessageTextContent } from '../../../types';
4
4
  import CkAgentMessage from '../../base/CkAgentMessage/CkAgentMessage.vue';
5
5
  import CkConversationLayout from '../../base/CkConversationLayout/CkConversationLayout.vue';
6
6
  import CkCopyMessageActionButton from '../../base/CkCopyMessageActionButton/CkCopyMessageActionButton.vue';
@@ -36,7 +36,11 @@ const slots = defineSlots<{
36
36
  'agent-message-loading-icon': (props: { message: BaseMessage }) => void;
37
37
  /** Replace the agent message header */
38
38
  'agent-message-header': (props: { message: BaseMessage }) => void;
39
- /** Replace the agent message body */
39
+ /**
40
+ * Replace the agent message body. Receives the full `BaseMessage` so the
41
+ * consumer can inspect every content part (text, tool calls, reasoning)
42
+ * and render them however they want.
43
+ */
40
44
  'agent-message-content': (props: { message: BaseMessage }) => void;
41
45
  /** Provide footer actions for the agent message */
42
46
  'agent-message-footer': (props: { message: BaseMessage }) => void;
@@ -59,6 +63,14 @@ const hasIcon = computed(
59
63
 
60
64
  const isLoading = (message: BaseMessage) =>
61
65
  message.status === 'started' || message.status === 'sending';
66
+
67
+ /** Concatenated text from every text content part in a message. */
68
+ function getMessageText(message: BaseMessage): string {
69
+ return message.content
70
+ .filter((c): c is MessageTextContent => c.type === 'text')
71
+ .map((c) => c.text)
72
+ .join('');
73
+ }
62
74
  </script>
63
75
 
64
76
  <template>
@@ -66,9 +78,9 @@ const isLoading = (message: BaseMessage) =>
66
78
  <template #user-message="{ message }">
67
79
  <div :class="{ 'ml-8': hasIcon }">
68
80
  <slot name="user-message" :message="message">
69
- <CkUserMessage :message="message.content">
81
+ <CkUserMessage>
70
82
  <slot name="user-message-content" :message="message">
71
- {{ message.content }}
83
+ {{ getMessageText(message) }}
72
84
  </slot>
73
85
  </CkUserMessage>
74
86
  </slot>
@@ -117,7 +129,7 @@ const isLoading = (message: BaseMessage) =>
117
129
 
118
130
  <slot name="agent-message-content" :message="message">
119
131
  <CkMarkdownRenderer
120
- :content="message.content"
132
+ :content="getMessageText(message)"
121
133
  :classes="agentMarkdownClasses"
122
134
  >
123
135
  <template
@@ -132,8 +144,8 @@ const isLoading = (message: BaseMessage) =>
132
144
  <template #footer>
133
145
  <slot name="agent-message-footer" :message="message">
134
146
  <CkCopyMessageActionButton
135
- v-if="message.content && !isLoading(message)"
136
- :text="message.content"
147
+ v-if="getMessageText(message) && !isLoading(message)"
148
+ :text="getMessageText(message)"
137
149
  :aria-label-copy="labels.copyMessageAction"
138
150
  :aria-label-copied="labels.copyMessageActionCopied"
139
151
  />