@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
@@ -0,0 +1,382 @@
1
+ import { ref, computed, shallowRef, watch, toValue, type MaybeRef } from 'vue';
2
+ import type {
3
+ HttpClient,
4
+ RequestConfig,
5
+ SseStream,
6
+ SseEvent,
7
+ FileEntry,
8
+ ScopedFileView,
9
+ DocumentUploadConfig,
10
+ DocumentUploadProvider,
11
+ DocumentUploadReturn,
12
+ UploadEvent,
13
+ } from './types';
14
+ import { useFileUpload } from './useFileUpload';
15
+
16
+ function buildHttpClient(
17
+ baseUrl: string,
18
+ defaultHeaders: Record<string, string>,
19
+ ): HttpClient {
20
+ return {
21
+ async get<T>(url: string, config?: RequestConfig): Promise<T> {
22
+ const fullUrl = `${baseUrl}/${url}`;
23
+ const res = await fetch(fullUrl, {
24
+ method: 'GET',
25
+ signal: config?.signal,
26
+ headers: defaultHeaders,
27
+ credentials: 'include',
28
+ });
29
+ if (!res.ok) throw new Error(`GET ${fullUrl} failed: ${res.status}`);
30
+ return res.json();
31
+ },
32
+
33
+ async post<T>(
34
+ url: string,
35
+ body: unknown,
36
+ config?: RequestConfig,
37
+ ): Promise<T> {
38
+ const fullUrl = `${baseUrl}/${url}`;
39
+ const headers: Record<string, string> = { ...defaultHeaders };
40
+ let resolvedBody: BodyInit | undefined;
41
+
42
+ if (body instanceof FormData) {
43
+ resolvedBody = body;
44
+ } else {
45
+ headers['Content-Type'] = 'application/json';
46
+ resolvedBody = JSON.stringify(body);
47
+ }
48
+
49
+ const res = await fetch(fullUrl, {
50
+ method: 'POST',
51
+ signal: config?.signal,
52
+ headers,
53
+ body: resolvedBody,
54
+ credentials: 'include',
55
+ });
56
+ if (!res.ok) throw new Error(`POST ${fullUrl} failed: ${res.status}`);
57
+ return res.json();
58
+ },
59
+
60
+ async delete<T>(url: string, config?: RequestConfig): Promise<T> {
61
+ const fullUrl = `${baseUrl}/${url}`;
62
+ const res = await fetch(fullUrl, {
63
+ method: 'DELETE',
64
+ signal: config?.signal,
65
+ headers: defaultHeaders,
66
+ credentials: 'include',
67
+ });
68
+ if (!res.ok) throw new Error(`DELETE ${fullUrl} failed: ${res.status}`);
69
+ return res.json();
70
+ },
71
+
72
+ stream(url: string, config?: RequestConfig): SseStream {
73
+ const controller = new AbortController();
74
+ const signal = config?.signal
75
+ ? AbortSignal.any([controller.signal, config.signal])
76
+ : controller.signal;
77
+
78
+ const listeners: Array<(event: SseEvent) => void> = [];
79
+ let closed = false;
80
+
81
+ const fullUrl = `${baseUrl}/${url}`;
82
+ const headers: Record<string, string> = {
83
+ Accept: 'text/event-stream',
84
+ ...defaultHeaders,
85
+ };
86
+
87
+ function parseSseLines(
88
+ lines: string[],
89
+ notify: (event: SseEvent) => void,
90
+ ): void {
91
+ let currentEvent = '';
92
+ let currentData = '';
93
+
94
+ for (const line of lines) {
95
+ if (line.startsWith('event:')) {
96
+ currentEvent = line.slice(6).trim();
97
+ } else if (line.startsWith('data:')) {
98
+ currentData = line.slice(5).trim();
99
+ } else if (line === '') {
100
+ if (currentData) {
101
+ notify({
102
+ event: currentEvent || 'message',
103
+ data: currentData,
104
+ });
105
+ }
106
+ currentEvent = '';
107
+ currentData = '';
108
+ }
109
+ }
110
+ }
111
+
112
+ (async () => {
113
+ try {
114
+ const response = await fetch(fullUrl, {
115
+ signal,
116
+ headers,
117
+ credentials: 'include',
118
+ });
119
+ if (!response.ok || !response.body) return;
120
+
121
+ const reader = response.body.getReader();
122
+ const decoder = new TextDecoder();
123
+ let buffer = '';
124
+
125
+ while (!closed) {
126
+ const { done, value } = await reader.read();
127
+ if (done) break;
128
+
129
+ buffer += decoder.decode(value, { stream: true });
130
+ const lines = buffer.split('\n');
131
+ buffer = lines.pop() ?? '';
132
+
133
+ parseSseLines(lines, (event) => {
134
+ for (const cb of listeners) cb(event);
135
+ });
136
+ }
137
+ } catch {
138
+ // stream ended or aborted
139
+ }
140
+ })();
141
+
142
+ return {
143
+ subscribe(callback: (event: SseEvent) => void) {
144
+ listeners.push(callback);
145
+ },
146
+ close() {
147
+ closed = true;
148
+ controller.abort();
149
+ },
150
+ };
151
+ },
152
+ };
153
+ }
154
+
155
+ export function createDocumentUploadProvider(
156
+ config: DocumentUploadConfig,
157
+ ): DocumentUploadProvider {
158
+ const {
159
+ baseUrl,
160
+ userId,
161
+ workflowId,
162
+ searchStoreConfig,
163
+ workflowConfig,
164
+ persist = true,
165
+ maxConcurrency = 3,
166
+ } = config;
167
+
168
+ const defaultHeaders: Record<string, string> = {
169
+ 'x-user-id': userId,
170
+ ...(config.headers ?? {}),
171
+ };
172
+
173
+ const httpClient = buildHttpClient(baseUrl, defaultHeaders);
174
+
175
+ // ---- Search store resolution (one per user) ----
176
+
177
+ let resolvedStoreId: string | null = null;
178
+ let resolvePromise: Promise<string> | null = null;
179
+
180
+ async function createSearchStoreWithWorkflow(): Promise<string> {
181
+ const name = `${userId}-store-${new Date().toISOString().replace(/[:.]/g, '-')}`;
182
+ const storeRes = await httpClient.post<{ id: string }>('search-stores', {
183
+ name,
184
+ ...searchStoreConfig,
185
+ metadata: { created_by: userId },
186
+ });
187
+ const newStoreId = storeRes.id;
188
+
189
+ await httpClient.post(
190
+ `search-stores/${newStoreId}/workflows`,
191
+ workflowConfig,
192
+ );
193
+
194
+ return newStoreId;
195
+ }
196
+
197
+ async function resolveSearchStore(): Promise<string> {
198
+ if (resolvedStoreId) return resolvedStoreId;
199
+ if (resolvePromise) return resolvePromise;
200
+
201
+ resolvePromise = (async () => {
202
+ try {
203
+ const stores = await httpClient.get<{ id: string }[]>('search-stores');
204
+
205
+ if (stores.length > 0 && stores[0]) {
206
+ resolvedStoreId = stores[0].id;
207
+ return resolvedStoreId;
208
+ }
209
+
210
+ resolvedStoreId = await createSearchStoreWithWorkflow();
211
+ return resolvedStoreId;
212
+ } catch (err) {
213
+ resolvePromise = null;
214
+ throw err;
215
+ }
216
+ })();
217
+
218
+ return resolvePromise;
219
+ }
220
+
221
+ // ---- Upload SDK singleton ----
222
+
223
+ let instance: ReturnType<typeof useFileUpload> | null = null;
224
+
225
+ function purgeInterruptedFiles() {
226
+ if (!instance) return;
227
+ const allFiles = [...instance.files.value.values()];
228
+ for (const entry of allFiles) {
229
+ if (entry.status === 'failed' && !entry.metadata.messageId) {
230
+ instance.removeFile(entry.id, { removeFromRemote: false });
231
+ }
232
+ }
233
+ }
234
+
235
+ function getUploadInstance(): ReturnType<typeof useFileUpload> {
236
+ if (!instance) {
237
+ const onEvent = (event: UploadEvent) => {
238
+ config.onEvent?.(event);
239
+ if (event.type === 'state:restored') {
240
+ purgeInterruptedFiles();
241
+ }
242
+ };
243
+
244
+ instance = useFileUpload({
245
+ client: httpClient,
246
+ workflowId,
247
+ persist,
248
+ maxConcurrency,
249
+ onEvent,
250
+ });
251
+ }
252
+ return instance;
253
+ }
254
+
255
+ // ---- Public composable ----
256
+
257
+ function useDocumentUpload(
258
+ conversationIdRef: MaybeRef<string>,
259
+ ): DocumentUploadReturn {
260
+ const fileInput = ref<HTMLInputElement | null>(null);
261
+ const upload = getUploadInstance();
262
+ const storeId = ref<string>(resolvedStoreId ?? '');
263
+
264
+ const currentScope = shallowRef<ScopedFileView | null>(
265
+ upload.scopeBy('conversationId', toValue(conversationIdRef)),
266
+ );
267
+
268
+ watch(
269
+ () => toValue(conversationIdRef),
270
+ (newId: string, oldId: string) => {
271
+ if (oldId && oldId !== newId) {
272
+ const oldScope = upload.scopeBy('conversationId', oldId);
273
+ for (const entry of oldScope.files.value) {
274
+ upload.updateFileMetadata(entry.id, { conversationId: newId });
275
+ }
276
+ }
277
+ currentScope.value = upload.scopeBy('conversationId', newId);
278
+ },
279
+ );
280
+
281
+ function scopedFiles(): FileEntry[] {
282
+ return currentScope.value?.files.value ?? [];
283
+ }
284
+
285
+ async function uploadFiles(files: File[]) {
286
+ if (!files.length) return;
287
+
288
+ const userStoreId = await resolveSearchStore();
289
+ upload.setStoreId(userStoreId);
290
+ storeId.value = userStoreId;
291
+
292
+ const conversationId = toValue(conversationIdRef);
293
+ const result = await upload.upload({
294
+ id: `${conversationId}::${Date.now()}`,
295
+ files,
296
+ forwardedProps: { conversationId },
297
+ });
298
+
299
+ for (const r of result.results) {
300
+ if (r.success && r.remoteFileId) {
301
+ upload.updateFileMetadata(r.fileId, {
302
+ data_platform_id: r.remoteFileId,
303
+ searchStoreId: storeId.value,
304
+ });
305
+ }
306
+ }
307
+
308
+ const successFileIds = result.results
309
+ .filter((r) => r.success && r.remoteFileId)
310
+ .map((r) => r.remoteFileId!);
311
+
312
+ if (successFileIds.length > 0) {
313
+ await upload.runWorkflow(successFileIds);
314
+ purgeCompletedFiles();
315
+ }
316
+
317
+ return result;
318
+ }
319
+
320
+ const unassignedFiles = computed(() =>
321
+ scopedFiles().filter((entry) => !entry.metadata.messageId),
322
+ );
323
+
324
+ function assignFilesToMessage(messageId: string) {
325
+ for (const entry of scopedFiles()) {
326
+ if (!entry.metadata.messageId && entry.status === 'success') {
327
+ upload.updateFileMetadata(entry.id, { messageId });
328
+ }
329
+ }
330
+ }
331
+
332
+ function getFilesForMessage(messageId: string): FileEntry[] {
333
+ return scopedFiles().filter(
334
+ (entry) =>
335
+ entry.metadata.messageId === messageId ||
336
+ (entry.metadata.responseId &&
337
+ messageId === `user-${entry.metadata.responseId}-0`),
338
+ );
339
+ }
340
+
341
+ function stampResponseId(responseId: string, localMessageId: string) {
342
+ for (const entry of scopedFiles()) {
343
+ if (entry.metadata.messageId === localMessageId) {
344
+ upload.updateFileMetadata(entry.id, { responseId });
345
+ }
346
+ }
347
+ }
348
+
349
+ function purgeCompletedFiles() {
350
+ const toPurge = scopedFiles().filter(
351
+ (entry) =>
352
+ entry.metadata.messageId &&
353
+ (entry.status === 'success' || entry.status === 'failed'),
354
+ );
355
+ for (const entry of toPurge) {
356
+ upload.removeFile(entry.id, { removeFromRemote: false });
357
+ }
358
+ }
359
+
360
+ function handleFilesSelected(event: Event) {
361
+ const input = event.target as HTMLInputElement;
362
+ const fileList = Array.from(input.files ?? []);
363
+ uploadFiles(fileList);
364
+ input.value = '';
365
+ }
366
+
367
+ return {
368
+ upload,
369
+ storeId,
370
+ fileInput,
371
+ uploadFiles,
372
+ unassignedFiles,
373
+ assignFilesToMessage,
374
+ getFilesForMessage,
375
+ stampResponseId,
376
+ purgeCompletedFiles,
377
+ handleFilesSelected,
378
+ };
379
+ }
380
+
381
+ return { useDocumentUpload };
382
+ }
@@ -12,4 +12,5 @@ export type {
12
12
  MessageHandler,
13
13
  UseConversationSessionsReturn,
14
14
  } from './useConversationSessions';
15
- export type { ChatActions, ChatState, UseChatReturn } from './types';
15
+ export * from './types';
16
+ export * from './fileUpload';
@@ -1,3 +1,4 @@
1
+ import type { ResponseList, StoredResponse } from '../adapters/responses-api';
1
2
  import type {
2
3
  HttpClient,
3
4
  RawConversation,
@@ -554,4 +555,121 @@ describe('useConversations', () => {
554
555
  expect(conv.getById('missing')).toBeUndefined();
555
556
  });
556
557
  });
558
+
559
+ describe('fetchResponses', () => {
560
+ function makeStoredResponse(
561
+ overrides: Partial<StoredResponse> & { id: string },
562
+ ): StoredResponse {
563
+ return {
564
+ object: 'response',
565
+ created_at: 1_777_456_700,
566
+ completed_at: 1_777_456_701,
567
+ status: 'completed',
568
+ model: 'pharia-1',
569
+ output: [],
570
+ request_input: [],
571
+ ...overrides,
572
+ };
573
+ }
574
+
575
+ function makeResponseList(data: StoredResponse[]): ResponseList {
576
+ return { object: 'list', data };
577
+ }
578
+
579
+ it('GETs /:id/responses with order=asc by default and returns the unwrapped list', async () => {
580
+ const http = makeFakeHttp();
581
+ http.get.mockResolvedValue(
582
+ makeResponseList([
583
+ makeStoredResponse({ id: 'resp_1' }),
584
+ makeStoredResponse({ id: 'resp_2' }),
585
+ ]),
586
+ );
587
+
588
+ const conv = useConversations({
589
+ httpClient: asHttp(http),
590
+ path: '/conversations',
591
+ });
592
+
593
+ const result = await conv.fetchResponses('conv_22');
594
+
595
+ expect(http.get).toHaveBeenCalledWith(
596
+ '/conversations/conv_22/responses',
597
+ {
598
+ params: { order: 'asc' },
599
+ },
600
+ );
601
+ expect(result.map((r) => r.id)).toEqual(['resp_1', 'resp_2']);
602
+ });
603
+
604
+ it('passes through the caller-supplied order', async () => {
605
+ const http = makeFakeHttp();
606
+ http.get.mockResolvedValue(makeResponseList([]));
607
+
608
+ const conv = useConversations({
609
+ httpClient: asHttp(http),
610
+ path: '/conversations',
611
+ });
612
+
613
+ await conv.fetchResponses('conv_22', { order: 'desc' });
614
+
615
+ expect(http.get).toHaveBeenCalledWith(
616
+ '/conversations/conv_22/responses',
617
+ {
618
+ params: { order: 'desc' },
619
+ },
620
+ );
621
+ });
622
+
623
+ it('does not modify the conversations list', async () => {
624
+ const http = makeFakeHttp();
625
+ http.get
626
+ .mockResolvedValueOnce(makeRawList([makeRawConversation({ id: 'a' })]))
627
+ .mockResolvedValueOnce(
628
+ makeResponseList([makeStoredResponse({ id: 'resp_1' })]),
629
+ );
630
+
631
+ const conv = useConversations({
632
+ httpClient: asHttp(http),
633
+ path: '/conversations',
634
+ });
635
+ await conv.fetch();
636
+ await conv.fetchResponses('a');
637
+
638
+ expect(conv.conversations.value.map((c) => c.id)).toEqual(['a']);
639
+ });
640
+
641
+ it('toggles isLoading around the call', async () => {
642
+ const http = makeFakeHttp();
643
+ let resolve!: (value: ResponseList) => void;
644
+ http.get.mockReturnValue(new Promise((r) => (resolve = r)));
645
+
646
+ const conv = useConversations({
647
+ httpClient: asHttp(http),
648
+ path: '/conversations',
649
+ });
650
+ const promise = conv.fetchResponses('a');
651
+
652
+ await nextTick();
653
+ expect(conv.isLoading.value).toBe(true);
654
+
655
+ resolve(makeResponseList([]));
656
+ await promise;
657
+
658
+ expect(conv.isLoading.value).toBe(false);
659
+ });
660
+
661
+ it('records and re-throws errors', async () => {
662
+ const http = makeFakeHttp();
663
+ http.get.mockRejectedValue(new Error('boom'));
664
+
665
+ const conv = useConversations({
666
+ httpClient: asHttp(http),
667
+ path: '/conversations',
668
+ });
669
+
670
+ await expect(conv.fetchResponses('a')).rejects.toThrow('boom');
671
+ expect(conv.error.value?.message).toBe('boom');
672
+ expect(conv.isLoading.value).toBe(false);
673
+ });
674
+ });
557
675
  });
@@ -1,3 +1,4 @@
1
+ import type { ResponseList, StoredResponse } from '../adapters/responses-api';
1
2
  import { generateId, secondsToDate, toError } from '../helpers';
2
3
  import type {
3
4
  ConversationSummary,
@@ -85,6 +86,18 @@ export interface UseConversationsReturn {
85
86
  remove(id: string, options?: MutationOptions): Promise<void>;
86
87
  /** Local lookup. Does not fetch — only checks the in-memory list. */
87
88
  getById(id: string): ConversationSummary | undefined;
89
+ /**
90
+ * List the OpenAI Responses-API records attached to a conversation. Pure
91
+ * fetch: it does not touch the conversations list. The endpoint is not
92
+ * paginated, so the returned array is the full timeline ordered by
93
+ * `created_at` (ascending by default). Pair with `responsesToTree` from
94
+ * `@aleph-alpha/chat-kit/adapters/responses-api` to hydrate a chat
95
+ * handler: `setTree(responsesToTree(await fetchResponses(id)))`.
96
+ */
97
+ fetchResponses(
98
+ id: string,
99
+ options?: { order?: 'asc' | 'desc' },
100
+ ): Promise<StoredResponse[]>;
88
101
  }
89
102
 
90
103
  const DEFAULT_PAGE_SIZE = 20;
@@ -277,6 +290,24 @@ export function useConversations(
277
290
  return conversations.value.find((c) => c.id === id);
278
291
  }
279
292
 
293
+ async function fetchResponses(
294
+ id: string,
295
+ opts?: { order?: 'asc' | 'desc' },
296
+ ): Promise<StoredResponse[]> {
297
+ beginRequest();
298
+ try {
299
+ const raw = await http.get<ResponseList>(`${path}/${id}/responses`, {
300
+ params: { order: opts?.order ?? 'asc' },
301
+ });
302
+ return raw.data;
303
+ } catch (e) {
304
+ error.value = toError(e);
305
+ throw error.value;
306
+ } finally {
307
+ endRequest();
308
+ }
309
+ }
310
+
280
311
  return {
281
312
  conversations: readonly(conversations) as Readonly<
282
313
  Ref<readonly ConversationSummary[]>
@@ -291,6 +322,7 @@ export function useConversations(
291
322
  rename,
292
323
  remove,
293
324
  getById,
325
+ fetchResponses,
294
326
  };
295
327
  }
296
328
 
@@ -261,7 +261,14 @@ describe('CkConversation uses injected labels', () => {
261
261
  {
262
262
  id: '1',
263
263
  role: 'assistant',
264
- content: 'Hi',
264
+ content: [
265
+ {
266
+ type: 'text',
267
+ id: 'txt-1',
268
+ text: 'Hi',
269
+ status: 'completed',
270
+ },
271
+ ],
265
272
  createdAt: new Date(),
266
273
  status: 'completed',
267
274
  },
@@ -290,7 +297,14 @@ describe('CkConversation uses injected labels', () => {
290
297
  {
291
298
  id: '1',
292
299
  role: 'assistant',
293
- content: 'Hi',
300
+ content: [
301
+ {
302
+ type: 'text',
303
+ id: 'txt-1',
304
+ text: 'Hi',
305
+ status: 'completed',
306
+ },
307
+ ],
294
308
  createdAt: new Date(),
295
309
  status: 'completed',
296
310
  },