@ai-sdk/react 0.0.22 → 0.0.23
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/.turbo/turbo-build.log +4 -4
- package/.turbo/turbo-clean.log +1 -1
- package/CHANGELOG.md +7 -0
- package/package.json +2 -2
- package/src/use-chat.ui.test.tsx +597 -344
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @ai-sdk/react@0.0.
|
|
2
|
+
> @ai-sdk/react@0.0.23 build /home/runner/work/ai/ai/packages/react
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[32mCJS[39m [1mdist/index.js [22m[32m26.77 KB[39m
|
|
13
13
|
[32mCJS[39m [1mdist/index.js.map [22m[32m53.33 KB[39m
|
|
14
|
-
[32mCJS[39m ⚡️ Build success in
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 61ms
|
|
15
15
|
[32mESM[39m [1mdist/index.mjs [22m[32m24.29 KB[39m
|
|
16
16
|
[32mESM[39m [1mdist/index.mjs.map [22m[32m53.26 KB[39m
|
|
17
|
-
[32mESM[39m ⚡️ Build success in
|
|
17
|
+
[32mESM[39m ⚡️ Build success in 69ms
|
|
18
18
|
[34mDTS[39m Build start
|
|
19
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
+
[32mDTS[39m ⚡️ Build success in 5498ms
|
|
20
20
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m10.42 KB[39m
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m10.42 KB[39m
|
package/.turbo/turbo-clean.log
CHANGED
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ai-sdk/provider-utils": "1.0.2",
|
|
19
|
-
"@ai-sdk/ui-utils": "0.0.
|
|
19
|
+
"@ai-sdk/ui-utils": "0.0.15",
|
|
20
20
|
"swr": "2.2.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
package/src/use-chat.ui.test.tsx
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/* eslint-disable @next/next/no-img-element */
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
mockFetchDataStreamWithGenerator,
|
|
5
|
-
mockFetchError,
|
|
6
|
-
} from '@ai-sdk/ui-utils/test';
|
|
2
|
+
import { withTestServer } from '@ai-sdk/provider-utils/test';
|
|
3
|
+
import { formatStreamPart, getTextFromDataUrl } from '@ai-sdk/ui-utils';
|
|
7
4
|
import '@testing-library/jest-dom/vitest';
|
|
8
5
|
import {
|
|
9
|
-
|
|
6
|
+
RenderResult,
|
|
10
7
|
cleanup,
|
|
11
8
|
findByText,
|
|
12
9
|
render,
|
|
13
10
|
screen,
|
|
11
|
+
waitFor,
|
|
14
12
|
} from '@testing-library/react';
|
|
15
13
|
import userEvent from '@testing-library/user-event';
|
|
16
14
|
import React, { useRef, useState } from 'react';
|
|
17
15
|
import { useChat } from './use-chat';
|
|
18
|
-
import { formatStreamPart, getTextFromDataUrl } from '@ai-sdk/ui-utils';
|
|
19
16
|
|
|
20
17
|
describe('stream data stream', () => {
|
|
21
18
|
const TestComponent = () => {
|
|
@@ -59,110 +56,127 @@ describe('stream data stream', () => {
|
|
|
59
56
|
cleanup();
|
|
60
57
|
});
|
|
61
58
|
|
|
62
|
-
it(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
await screen.findByTestId('message-1');
|
|
74
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
75
|
-
'AI: Hello, world.',
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should show streamed response with data', async () => {
|
|
80
|
-
mockFetchDataStream({
|
|
81
|
-
url: 'https://example.com/api/chat',
|
|
82
|
-
chunks: ['2:[{"t1":"v1"}]\n', '0:"Hello"\n'],
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
await userEvent.click(screen.getByTestId('do-append'));
|
|
86
|
-
|
|
87
|
-
await screen.findByTestId('data');
|
|
88
|
-
expect(screen.getByTestId('data')).toHaveTextContent('[{"t1":"v1"}]');
|
|
59
|
+
it(
|
|
60
|
+
'should show streamed response',
|
|
61
|
+
withTestServer(
|
|
62
|
+
{
|
|
63
|
+
type: 'stream-values',
|
|
64
|
+
url: '/api/chat',
|
|
65
|
+
content: ['0:"Hello"\n', '0:","\n', '0:" world"\n', '0:"."\n'],
|
|
66
|
+
},
|
|
67
|
+
async () => {
|
|
68
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
89
69
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
70
|
+
await screen.findByTestId('message-0');
|
|
71
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
93
72
|
|
|
94
|
-
|
|
95
|
-
|
|
73
|
+
await screen.findByTestId('message-1');
|
|
74
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
75
|
+
'AI: Hello, world.',
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
it(
|
|
82
|
+
'should show streamed response with data',
|
|
83
|
+
withTestServer(
|
|
84
|
+
{
|
|
85
|
+
type: 'stream-values',
|
|
86
|
+
url: '/api/chat',
|
|
87
|
+
content: ['2:[{"t1":"v1"}]\n', '0:"Hello"\n'],
|
|
88
|
+
},
|
|
89
|
+
async () => {
|
|
90
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
await screen.findByTestId('data');
|
|
93
|
+
expect(screen.getByTestId('data')).toHaveTextContent('[{"t1":"v1"}]');
|
|
98
94
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
await screen.findByTestId('message-1');
|
|
96
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent('AI: Hello');
|
|
97
|
+
},
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
it(
|
|
102
|
+
'should show error response',
|
|
103
|
+
withTestServer(
|
|
104
|
+
{ type: 'error', url: '/api/chat', status: 404, content: 'Not found' },
|
|
105
|
+
async () => {
|
|
106
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
107
|
+
|
|
108
|
+
await screen.findByTestId('error');
|
|
109
|
+
expect(screen.getByTestId('error')).toHaveTextContent(
|
|
110
|
+
'Error: Not found',
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
),
|
|
114
|
+
);
|
|
102
115
|
|
|
103
116
|
describe('loading state', () => {
|
|
104
|
-
it(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
mockFetchDataStreamWithGenerator({
|
|
111
|
-
url: 'https://example.com/api/chat',
|
|
112
|
-
chunkGenerator: (async function* generate() {
|
|
113
|
-
const encoder = new TextEncoder();
|
|
114
|
-
yield encoder.encode('0:"Hello"\n');
|
|
115
|
-
await finishGenerationPromise;
|
|
116
|
-
})(),
|
|
117
|
-
});
|
|
117
|
+
it(
|
|
118
|
+
'should show loading state',
|
|
119
|
+
withTestServer(
|
|
120
|
+
{ url: '/api/chat', type: 'controlled-stream' },
|
|
121
|
+
async ({ streamController }) => {
|
|
122
|
+
streamController.enqueue('0:"Hello"\n');
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
120
125
|
|
|
121
|
-
|
|
122
|
-
|
|
126
|
+
await screen.findByTestId('loading');
|
|
127
|
+
expect(screen.getByTestId('loading')).toHaveTextContent('true');
|
|
123
128
|
|
|
124
|
-
|
|
129
|
+
streamController.close();
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
mockFetchError({ statusCode: 404, errorMessage: 'Not found' });
|
|
131
|
+
await findByText(await screen.findByTestId('loading'), 'false');
|
|
132
|
+
expect(screen.getByTestId('loading')).toHaveTextContent('false');
|
|
133
|
+
},
|
|
134
|
+
),
|
|
135
|
+
);
|
|
132
136
|
|
|
133
|
-
|
|
137
|
+
it(
|
|
138
|
+
'should reset loading state on error',
|
|
139
|
+
withTestServer(
|
|
140
|
+
{ type: 'error', url: '/api/chat', status: 404, content: 'Not found' },
|
|
141
|
+
async () => {
|
|
142
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
134
143
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
await screen.findByTestId('loading');
|
|
145
|
+
expect(screen.getByTestId('loading')).toHaveTextContent('false');
|
|
146
|
+
},
|
|
147
|
+
),
|
|
148
|
+
);
|
|
138
149
|
});
|
|
139
150
|
|
|
140
151
|
describe('id', () => {
|
|
141
|
-
it(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
152
|
+
it(
|
|
153
|
+
'should clear out messages when the id changes',
|
|
154
|
+
withTestServer(
|
|
155
|
+
{
|
|
156
|
+
url: '/api/chat',
|
|
157
|
+
type: 'stream-values',
|
|
158
|
+
content: ['0:"Hello"\n', '0:","\n', '0:" world"\n', '0:"."\n'],
|
|
159
|
+
},
|
|
160
|
+
async () => {
|
|
161
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
148
162
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
163
|
+
await screen.findByTestId('message-1');
|
|
164
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
165
|
+
'AI: Hello, world.',
|
|
166
|
+
);
|
|
153
167
|
|
|
154
|
-
|
|
168
|
+
await userEvent.click(screen.getByTestId('do-change-id'));
|
|
155
169
|
|
|
156
|
-
|
|
157
|
-
|
|
170
|
+
expect(screen.queryByTestId('message-0')).not.toBeInTheDocument();
|
|
171
|
+
},
|
|
172
|
+
),
|
|
173
|
+
);
|
|
158
174
|
});
|
|
159
175
|
});
|
|
160
176
|
|
|
161
177
|
describe('text stream', () => {
|
|
162
178
|
const TestComponent = () => {
|
|
163
|
-
const { messages, append } = useChat({
|
|
164
|
-
streamMode: 'text',
|
|
165
|
-
});
|
|
179
|
+
const { messages, append } = useChat({ streamMode: 'text' });
|
|
166
180
|
|
|
167
181
|
return (
|
|
168
182
|
<div>
|
|
@@ -192,38 +206,35 @@ describe('text stream', () => {
|
|
|
192
206
|
cleanup();
|
|
193
207
|
});
|
|
194
208
|
|
|
195
|
-
it(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
it(
|
|
210
|
+
'should show streamed response',
|
|
211
|
+
withTestServer(
|
|
212
|
+
{
|
|
213
|
+
url: '/api/chat',
|
|
214
|
+
type: 'stream-values',
|
|
215
|
+
content: ['Hello', ',', ' world', '.'],
|
|
216
|
+
},
|
|
217
|
+
async () => {
|
|
218
|
+
await userEvent.click(screen.getByTestId('do-append-text-stream'));
|
|
219
|
+
|
|
220
|
+
await screen.findByTestId('message-0-text-stream');
|
|
221
|
+
expect(screen.getByTestId('message-0-text-stream')).toHaveTextContent(
|
|
222
|
+
'User: hi',
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
await screen.findByTestId('message-1-text-stream');
|
|
226
|
+
expect(screen.getByTestId('message-1-text-stream')).toHaveTextContent(
|
|
227
|
+
'AI: Hello, world.',
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
),
|
|
231
|
+
);
|
|
213
232
|
});
|
|
214
233
|
|
|
215
234
|
describe('form actions', () => {
|
|
216
235
|
const TestComponent = () => {
|
|
217
|
-
const {
|
|
218
|
-
|
|
219
|
-
append,
|
|
220
|
-
handleSubmit,
|
|
221
|
-
handleInputChange,
|
|
222
|
-
isLoading,
|
|
223
|
-
input,
|
|
224
|
-
} = useChat({
|
|
225
|
-
streamMode: 'text',
|
|
226
|
-
});
|
|
236
|
+
const { messages, handleSubmit, handleInputChange, isLoading, input } =
|
|
237
|
+
useChat({ streamMode: 'text' });
|
|
227
238
|
|
|
228
239
|
return (
|
|
229
240
|
<div>
|
|
@@ -256,37 +267,44 @@ describe('form actions', () => {
|
|
|
256
267
|
cleanup();
|
|
257
268
|
});
|
|
258
269
|
|
|
259
|
-
it(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
270
|
+
it(
|
|
271
|
+
'should show streamed response using handleSubmit',
|
|
272
|
+
withTestServer(
|
|
273
|
+
[
|
|
274
|
+
{
|
|
275
|
+
url: '/api/chat',
|
|
276
|
+
type: 'stream-values',
|
|
277
|
+
content: ['Hello', ',', ' world', '.'],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
url: '/api/chat',
|
|
281
|
+
type: 'stream-values',
|
|
282
|
+
content: ['How', ' can', ' I', ' help', ' you', '?'],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
async () => {
|
|
286
|
+
const firstInput = screen.getByTestId('do-input');
|
|
287
|
+
await userEvent.type(firstInput, 'hi');
|
|
288
|
+
await userEvent.keyboard('{Enter}');
|
|
289
|
+
|
|
290
|
+
await screen.findByTestId('message-0');
|
|
291
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
292
|
+
|
|
293
|
+
await screen.findByTestId('message-1');
|
|
294
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
295
|
+
'AI: Hello, world.',
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const secondInput = screen.getByTestId('do-input');
|
|
299
|
+
await userEvent.type(secondInput, '{Enter}');
|
|
300
|
+
|
|
301
|
+
await screen.findByTestId('message-2');
|
|
302
|
+
expect(screen.getByTestId('message-2')).toHaveTextContent(
|
|
303
|
+
'AI: How can I help you?',
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
),
|
|
307
|
+
);
|
|
290
308
|
});
|
|
291
309
|
|
|
292
310
|
describe('prepareRequestBody', () => {
|
|
@@ -336,30 +354,35 @@ describe('prepareRequestBody', () => {
|
|
|
336
354
|
cleanup();
|
|
337
355
|
});
|
|
338
356
|
|
|
339
|
-
it(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
357
|
+
it(
|
|
358
|
+
'should show streamed response',
|
|
359
|
+
withTestServer(
|
|
360
|
+
{
|
|
361
|
+
url: '/api/chat',
|
|
362
|
+
type: 'stream-values',
|
|
363
|
+
content: ['0:"Hello"\n', '0:","\n', '0:" world"\n', '0:"."\n'],
|
|
364
|
+
},
|
|
365
|
+
async ({ call }) => {
|
|
366
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
346
367
|
|
|
347
|
-
|
|
348
|
-
|
|
368
|
+
await screen.findByTestId('message-0');
|
|
369
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent('User: hi');
|
|
349
370
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
371
|
+
expect(bodyOptions).toStrictEqual({
|
|
372
|
+
messages: [{ role: 'user', content: 'hi', id: expect.any(String) }],
|
|
373
|
+
requestData: { 'test-data-key': 'test-data-value' },
|
|
374
|
+
requestBody: { 'request-body-key': 'request-body-value' },
|
|
375
|
+
});
|
|
355
376
|
|
|
356
|
-
|
|
377
|
+
expect(await call(0).getRequestBodyJson()).toBe('test-request-body');
|
|
357
378
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
379
|
+
await screen.findByTestId('message-1');
|
|
380
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
381
|
+
'AI: Hello, world.',
|
|
382
|
+
);
|
|
383
|
+
},
|
|
384
|
+
),
|
|
385
|
+
);
|
|
363
386
|
});
|
|
364
387
|
|
|
365
388
|
describe('onToolCall', () => {
|
|
@@ -405,36 +428,203 @@ describe('onToolCall', () => {
|
|
|
405
428
|
cleanup();
|
|
406
429
|
});
|
|
407
430
|
|
|
408
|
-
it(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
431
|
+
it(
|
|
432
|
+
"should invoke onToolCall when a tool call is received from the server's response",
|
|
433
|
+
withTestServer(
|
|
434
|
+
{
|
|
435
|
+
url: '/api/chat',
|
|
436
|
+
type: 'stream-values',
|
|
437
|
+
content: [
|
|
438
|
+
formatStreamPart('tool_call', {
|
|
439
|
+
toolCallId: 'tool-call-0',
|
|
440
|
+
toolName: 'test-tool',
|
|
441
|
+
args: { testArg: 'test-value' },
|
|
442
|
+
}),
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
async () => {
|
|
446
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
419
447
|
|
|
420
|
-
|
|
448
|
+
await screen.findByTestId('message-1');
|
|
449
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
450
|
+
'test-tool-response: test-tool tool-call-0 {"testArg":"test-value"}',
|
|
451
|
+
);
|
|
452
|
+
},
|
|
453
|
+
),
|
|
454
|
+
);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
describe('tool invocations', () => {
|
|
458
|
+
let rerender: RenderResult['rerender'];
|
|
459
|
+
|
|
460
|
+
const TestComponent = () => {
|
|
461
|
+
const { messages, append } = useChat();
|
|
421
462
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
463
|
+
return (
|
|
464
|
+
<div>
|
|
465
|
+
{messages.map((m, idx) => (
|
|
466
|
+
<div data-testid={`message-${idx}`} key={m.id}>
|
|
467
|
+
{m.toolInvocations?.map((toolInvocation, toolIdx) => {
|
|
468
|
+
return (
|
|
469
|
+
<div key={toolIdx} data-testid={`tool-invocation-${toolIdx}`}>
|
|
470
|
+
{JSON.stringify(toolInvocation)}
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
})}
|
|
474
|
+
</div>
|
|
475
|
+
))}
|
|
476
|
+
|
|
477
|
+
<button
|
|
478
|
+
data-testid="do-append"
|
|
479
|
+
onClick={() => {
|
|
480
|
+
append({ role: 'user', content: 'hi' });
|
|
481
|
+
}}
|
|
482
|
+
/>
|
|
483
|
+
</div>
|
|
425
484
|
);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
beforeEach(() => {
|
|
488
|
+
const result = render(<TestComponent />);
|
|
489
|
+
rerender = result.rerender;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
afterEach(() => {
|
|
493
|
+
vi.restoreAllMocks();
|
|
494
|
+
cleanup();
|
|
426
495
|
});
|
|
496
|
+
|
|
497
|
+
it(
|
|
498
|
+
'should display partial tool call, tool call, and tool result',
|
|
499
|
+
withTestServer(
|
|
500
|
+
{ url: '/api/chat', type: 'controlled-stream' },
|
|
501
|
+
async ({ streamController }) => {
|
|
502
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
503
|
+
|
|
504
|
+
streamController.enqueue(
|
|
505
|
+
formatStreamPart('tool_call_streaming_start', {
|
|
506
|
+
toolCallId: 'tool-call-0',
|
|
507
|
+
toolName: 'test-tool',
|
|
508
|
+
}),
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
await waitFor(() => {
|
|
512
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
513
|
+
'{"state":"partial-call","toolCallId":"tool-call-0","toolName":"test-tool"}',
|
|
514
|
+
);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
streamController.enqueue(
|
|
518
|
+
formatStreamPart('tool_call_delta', {
|
|
519
|
+
toolCallId: 'tool-call-0',
|
|
520
|
+
argsTextDelta: '{"testArg":"t',
|
|
521
|
+
}),
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
await waitFor(() => {
|
|
525
|
+
rerender(<TestComponent />);
|
|
526
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
527
|
+
'{"state":"partial-call","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"t"}}',
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
streamController.enqueue(
|
|
532
|
+
formatStreamPart('tool_call_delta', {
|
|
533
|
+
toolCallId: 'tool-call-0',
|
|
534
|
+
argsTextDelta: 'est-value"}}',
|
|
535
|
+
}),
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
await waitFor(() => {
|
|
539
|
+
rerender(<TestComponent />);
|
|
540
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
541
|
+
'{"state":"partial-call","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"test-value"}}',
|
|
542
|
+
);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
streamController.enqueue(
|
|
546
|
+
formatStreamPart('tool_call', {
|
|
547
|
+
toolCallId: 'tool-call-0',
|
|
548
|
+
toolName: 'test-tool',
|
|
549
|
+
args: { testArg: 'test-value' },
|
|
550
|
+
}),
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
await waitFor(() => {
|
|
554
|
+
rerender(<TestComponent />);
|
|
555
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
556
|
+
'{"state":"call","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"test-value"}}',
|
|
557
|
+
);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
streamController.enqueue(
|
|
561
|
+
formatStreamPart('tool_result', {
|
|
562
|
+
toolCallId: 'tool-call-0',
|
|
563
|
+
toolName: 'test-tool',
|
|
564
|
+
args: { testArg: 'test-value' },
|
|
565
|
+
result: 'test-result',
|
|
566
|
+
}),
|
|
567
|
+
);
|
|
568
|
+
streamController.close();
|
|
569
|
+
|
|
570
|
+
await waitFor(() => {
|
|
571
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
572
|
+
'{"state":"result","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"test-value"},"result":"test-result"}',
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
),
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
it(
|
|
580
|
+
'should display partial tool call and tool result (when there is no tool call streaming)',
|
|
581
|
+
withTestServer(
|
|
582
|
+
{ url: '/api/chat', type: 'controlled-stream' },
|
|
583
|
+
async ({ streamController }) => {
|
|
584
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
585
|
+
|
|
586
|
+
streamController.enqueue(
|
|
587
|
+
formatStreamPart('tool_call', {
|
|
588
|
+
toolCallId: 'tool-call-0',
|
|
589
|
+
toolName: 'test-tool',
|
|
590
|
+
args: { testArg: 'test-value' },
|
|
591
|
+
}),
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
await waitFor(() => {
|
|
595
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
596
|
+
'{"state":"call","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"test-value"}}',
|
|
597
|
+
);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
streamController.enqueue(
|
|
601
|
+
formatStreamPart('tool_result', {
|
|
602
|
+
toolCallId: 'tool-call-0',
|
|
603
|
+
toolName: 'test-tool',
|
|
604
|
+
args: { testArg: 'test-value' },
|
|
605
|
+
result: 'test-result',
|
|
606
|
+
}),
|
|
607
|
+
);
|
|
608
|
+
streamController.close();
|
|
609
|
+
|
|
610
|
+
await waitFor(() => {
|
|
611
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
612
|
+
'{"state":"result","toolCallId":"tool-call-0","toolName":"test-tool","args":{"testArg":"test-value"},"result":"test-result"}',
|
|
613
|
+
);
|
|
614
|
+
});
|
|
615
|
+
},
|
|
616
|
+
),
|
|
617
|
+
);
|
|
427
618
|
});
|
|
428
619
|
|
|
429
620
|
describe('maxToolRoundtrips', () => {
|
|
430
621
|
describe('single automatic tool roundtrip', () => {
|
|
622
|
+
let onToolCallInvoked = false;
|
|
623
|
+
|
|
431
624
|
const TestComponent = () => {
|
|
432
625
|
const { messages, append } = useChat({
|
|
433
626
|
async onToolCall({ toolCall }) {
|
|
434
|
-
|
|
435
|
-
url: 'https://example.com/api/chat',
|
|
436
|
-
chunks: [formatStreamPart('text', 'final result')],
|
|
437
|
-
});
|
|
627
|
+
onToolCallInvoked = true;
|
|
438
628
|
|
|
439
629
|
return `test-tool-response: ${toolCall.toolName} ${
|
|
440
630
|
toolCall.toolCallId
|
|
@@ -463,6 +653,7 @@ describe('maxToolRoundtrips', () => {
|
|
|
463
653
|
|
|
464
654
|
beforeEach(() => {
|
|
465
655
|
render(<TestComponent />);
|
|
656
|
+
onToolCallInvoked = false;
|
|
466
657
|
});
|
|
467
658
|
|
|
468
659
|
afterEach(() => {
|
|
@@ -470,35 +661,48 @@ describe('maxToolRoundtrips', () => {
|
|
|
470
661
|
cleanup();
|
|
471
662
|
});
|
|
472
663
|
|
|
473
|
-
it(
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
664
|
+
it(
|
|
665
|
+
'should automatically call api when tool call gets executed via onToolCall',
|
|
666
|
+
withTestServer(
|
|
667
|
+
[
|
|
668
|
+
{
|
|
669
|
+
url: '/api/chat',
|
|
670
|
+
type: 'stream-values',
|
|
671
|
+
content: [
|
|
672
|
+
formatStreamPart('tool_call', {
|
|
673
|
+
toolCallId: 'tool-call-0',
|
|
674
|
+
toolName: 'test-tool',
|
|
675
|
+
args: { testArg: 'test-value' },
|
|
676
|
+
}),
|
|
677
|
+
],
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
url: '/api/chat',
|
|
681
|
+
type: 'stream-values',
|
|
682
|
+
content: [formatStreamPart('text', 'final result')],
|
|
683
|
+
},
|
|
482
684
|
],
|
|
483
|
-
|
|
685
|
+
async () => {
|
|
686
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
484
687
|
|
|
485
|
-
|
|
688
|
+
expect(onToolCallInvoked).toBe(true);
|
|
486
689
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
690
|
+
await screen.findByTestId('message-2');
|
|
691
|
+
expect(screen.getByTestId('message-2')).toHaveTextContent(
|
|
692
|
+
'final result',
|
|
693
|
+
);
|
|
694
|
+
},
|
|
695
|
+
),
|
|
696
|
+
);
|
|
490
697
|
});
|
|
491
698
|
|
|
492
699
|
describe('single roundtrip with error response', () => {
|
|
700
|
+
let onToolCallCounter = 0;
|
|
701
|
+
|
|
493
702
|
const TestComponent = () => {
|
|
494
703
|
const { messages, append, error } = useChat({
|
|
495
704
|
async onToolCall({ toolCall }) {
|
|
496
|
-
|
|
497
|
-
url: 'https://example.com/api/chat',
|
|
498
|
-
chunks: [formatStreamPart('error', 'some failure')],
|
|
499
|
-
maxCalls: 1,
|
|
500
|
-
});
|
|
501
|
-
|
|
705
|
+
onToolCallCounter++;
|
|
502
706
|
return `test-tool-response: ${toolCall.toolName} ${
|
|
503
707
|
toolCall.toolCallId
|
|
504
708
|
} ${JSON.stringify(toolCall.args)}`;
|
|
@@ -534,6 +738,7 @@ describe('maxToolRoundtrips', () => {
|
|
|
534
738
|
|
|
535
739
|
beforeEach(() => {
|
|
536
740
|
render(<TestComponent />);
|
|
741
|
+
onToolCallCounter = 0;
|
|
537
742
|
});
|
|
538
743
|
|
|
539
744
|
afterEach(() => {
|
|
@@ -541,34 +746,47 @@ describe('maxToolRoundtrips', () => {
|
|
|
541
746
|
cleanup();
|
|
542
747
|
});
|
|
543
748
|
|
|
544
|
-
it(
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
749
|
+
it(
|
|
750
|
+
'should automatically call api when tool call gets executed via onToolCall',
|
|
751
|
+
withTestServer(
|
|
752
|
+
[
|
|
753
|
+
{
|
|
754
|
+
url: '/api/chat',
|
|
755
|
+
type: 'stream-values',
|
|
756
|
+
content: [
|
|
757
|
+
formatStreamPart('tool_call', {
|
|
758
|
+
toolCallId: 'tool-call-0',
|
|
759
|
+
toolName: 'test-tool',
|
|
760
|
+
args: { testArg: 'test-value' },
|
|
761
|
+
}),
|
|
762
|
+
],
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
url: '/api/chat',
|
|
766
|
+
type: 'error',
|
|
767
|
+
status: 400,
|
|
768
|
+
content: 'call failure',
|
|
769
|
+
},
|
|
553
770
|
],
|
|
554
|
-
|
|
771
|
+
async () => {
|
|
772
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
555
773
|
|
|
556
|
-
|
|
774
|
+
await screen.findByTestId('error');
|
|
775
|
+
expect(screen.getByTestId('error')).toHaveTextContent(
|
|
776
|
+
'Error: call failure',
|
|
777
|
+
);
|
|
557
778
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
});
|
|
779
|
+
expect(onToolCallCounter).toBe(1);
|
|
780
|
+
},
|
|
781
|
+
),
|
|
782
|
+
);
|
|
563
783
|
});
|
|
564
784
|
});
|
|
565
785
|
|
|
566
786
|
describe('file attachments with data url', () => {
|
|
567
787
|
const TestComponent = () => {
|
|
568
788
|
const { messages, handleSubmit, handleInputChange, isLoading, input } =
|
|
569
|
-
useChat(
|
|
570
|
-
api: '/api/stream-chat',
|
|
571
|
-
});
|
|
789
|
+
useChat();
|
|
572
790
|
|
|
573
791
|
const [attachments, setAttachments] = useState<FileList | undefined>(
|
|
574
792
|
undefined,
|
|
@@ -648,102 +866,124 @@ describe('file attachments with data url', () => {
|
|
|
648
866
|
cleanup();
|
|
649
867
|
});
|
|
650
868
|
|
|
651
|
-
it(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
const messageInput = screen.getByTestId('message-input');
|
|
665
|
-
await userEvent.type(messageInput, 'Message with text attachment');
|
|
666
|
-
|
|
667
|
-
const submitButton = screen.getByTestId('submit-button');
|
|
668
|
-
await userEvent.click(submitButton);
|
|
669
|
-
|
|
670
|
-
const sentBody = JSON.parse((await requestBody) as string);
|
|
671
|
-
expect(sentBody.messages[0].content).toBe('Message with text attachment');
|
|
672
|
-
expect(sentBody.messages[0].experimental_attachments).toBeDefined();
|
|
673
|
-
expect(sentBody.messages[0].experimental_attachments.length).toBe(1);
|
|
674
|
-
expect(sentBody.messages[0].experimental_attachments[0].name).toBe(
|
|
675
|
-
'test.txt',
|
|
676
|
-
);
|
|
677
|
-
|
|
678
|
-
await screen.findByTestId('message-0');
|
|
679
|
-
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
680
|
-
'User: Message with text attachment',
|
|
681
|
-
);
|
|
682
|
-
|
|
683
|
-
await screen.findByTestId('attachment-0');
|
|
684
|
-
expect(screen.getByTestId('attachment-0')).toHaveTextContent(
|
|
685
|
-
'test file content',
|
|
686
|
-
);
|
|
687
|
-
|
|
688
|
-
await screen.findByTestId('message-1');
|
|
689
|
-
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
690
|
-
'AI: Response to message with text attachment',
|
|
691
|
-
);
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
// image file
|
|
695
|
-
|
|
696
|
-
it('should handle image file attachment and submission', async () => {
|
|
697
|
-
const file = new File(['test image content'], 'test.png', {
|
|
698
|
-
type: 'image/png',
|
|
699
|
-
});
|
|
869
|
+
it(
|
|
870
|
+
'should handle text file attachment and submission',
|
|
871
|
+
withTestServer(
|
|
872
|
+
{
|
|
873
|
+
url: '/api/chat',
|
|
874
|
+
type: 'stream-values',
|
|
875
|
+
content: ['0:"Response to message with text attachment"\n'],
|
|
876
|
+
},
|
|
877
|
+
async ({ call }) => {
|
|
878
|
+
const file = new File(['test file content'], 'test.txt', {
|
|
879
|
+
type: 'text/plain',
|
|
880
|
+
});
|
|
700
881
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
chunks: ['0:"Response to message with image attachment"\n'],
|
|
704
|
-
});
|
|
882
|
+
const fileInput = screen.getByTestId('file-input');
|
|
883
|
+
await userEvent.upload(fileInput, file);
|
|
705
884
|
|
|
706
|
-
|
|
707
|
-
|
|
885
|
+
const messageInput = screen.getByTestId('message-input');
|
|
886
|
+
await userEvent.type(messageInput, 'Message with text attachment');
|
|
708
887
|
|
|
709
|
-
|
|
710
|
-
|
|
888
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
889
|
+
await userEvent.click(submitButton);
|
|
711
890
|
|
|
712
|
-
|
|
713
|
-
|
|
891
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
892
|
+
messages: [
|
|
893
|
+
{
|
|
894
|
+
role: 'user',
|
|
895
|
+
content: 'Message with text attachment',
|
|
896
|
+
experimental_attachments: [
|
|
897
|
+
{
|
|
898
|
+
name: 'test.txt',
|
|
899
|
+
contentType: 'text/plain',
|
|
900
|
+
url: 'data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=',
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
},
|
|
904
|
+
],
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
await screen.findByTestId('message-0');
|
|
908
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
909
|
+
'User: Message with text attachment',
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
await screen.findByTestId('attachment-0');
|
|
913
|
+
expect(screen.getByTestId('attachment-0')).toHaveTextContent(
|
|
914
|
+
'test file content',
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
await screen.findByTestId('message-1');
|
|
918
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
919
|
+
'AI: Response to message with text attachment',
|
|
920
|
+
);
|
|
921
|
+
},
|
|
922
|
+
),
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
it(
|
|
926
|
+
'should handle image file attachment and submission',
|
|
927
|
+
withTestServer(
|
|
928
|
+
{
|
|
929
|
+
url: '/api/chat',
|
|
930
|
+
type: 'stream-values',
|
|
931
|
+
content: ['0:"Response to message with image attachment"\n'],
|
|
932
|
+
},
|
|
933
|
+
async ({ call }) => {
|
|
934
|
+
const file = new File(['test image content'], 'test.png', {
|
|
935
|
+
type: 'image/png',
|
|
936
|
+
});
|
|
714
937
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
expect(sentBody.messages[0].experimental_attachments).toBeDefined();
|
|
718
|
-
expect(sentBody.messages[0].experimental_attachments.length).toBe(1);
|
|
719
|
-
expect(sentBody.messages[0].experimental_attachments[0].name).toBe(
|
|
720
|
-
'test.png',
|
|
721
|
-
);
|
|
938
|
+
const fileInput = screen.getByTestId('file-input');
|
|
939
|
+
await userEvent.upload(fileInput, file);
|
|
722
940
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
'User: Message with image attachment',
|
|
726
|
-
);
|
|
941
|
+
const messageInput = screen.getByTestId('message-input');
|
|
942
|
+
await userEvent.type(messageInput, 'Message with image attachment');
|
|
727
943
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
'src',
|
|
731
|
-
expect.stringContaining('data:image/png;base64'),
|
|
732
|
-
);
|
|
944
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
945
|
+
await userEvent.click(submitButton);
|
|
733
946
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
947
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
948
|
+
messages: [
|
|
949
|
+
{
|
|
950
|
+
role: 'user',
|
|
951
|
+
content: 'Message with image attachment',
|
|
952
|
+
experimental_attachments: [
|
|
953
|
+
{
|
|
954
|
+
name: 'test.png',
|
|
955
|
+
contentType: 'image/png',
|
|
956
|
+
url: 'data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50',
|
|
957
|
+
},
|
|
958
|
+
],
|
|
959
|
+
},
|
|
960
|
+
],
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
await screen.findByTestId('message-0');
|
|
964
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
965
|
+
'User: Message with image attachment',
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
await screen.findByTestId('attachment-0');
|
|
969
|
+
expect(screen.getByTestId('attachment-0')).toHaveAttribute(
|
|
970
|
+
'src',
|
|
971
|
+
expect.stringContaining('data:image/png;base64'),
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
await screen.findByTestId('message-1');
|
|
975
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
976
|
+
'AI: Response to message with image attachment',
|
|
977
|
+
);
|
|
978
|
+
},
|
|
979
|
+
),
|
|
980
|
+
);
|
|
739
981
|
});
|
|
740
982
|
|
|
741
983
|
describe('file attachments with url', () => {
|
|
742
984
|
const TestComponent = () => {
|
|
743
985
|
const { messages, handleSubmit, handleInputChange, isLoading, input } =
|
|
744
|
-
useChat(
|
|
745
|
-
api: '/api/stream-chat',
|
|
746
|
-
});
|
|
986
|
+
useChat();
|
|
747
987
|
|
|
748
988
|
return (
|
|
749
989
|
<div>
|
|
@@ -812,40 +1052,53 @@ describe('file attachments with url', () => {
|
|
|
812
1052
|
cleanup();
|
|
813
1053
|
});
|
|
814
1054
|
|
|
815
|
-
it(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1055
|
+
it(
|
|
1056
|
+
'should handle image file attachment and submission',
|
|
1057
|
+
withTestServer(
|
|
1058
|
+
{
|
|
1059
|
+
url: '/api/chat',
|
|
1060
|
+
type: 'stream-values',
|
|
1061
|
+
content: ['0:"Response to message with image attachment"\n'],
|
|
1062
|
+
},
|
|
1063
|
+
async ({ call }) => {
|
|
1064
|
+
const messageInput = screen.getByTestId('message-input');
|
|
1065
|
+
await userEvent.type(messageInput, 'Message with image attachment');
|
|
1066
|
+
|
|
1067
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
1068
|
+
await userEvent.click(submitButton);
|
|
1069
|
+
|
|
1070
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
1071
|
+
messages: [
|
|
1072
|
+
{
|
|
1073
|
+
role: 'user',
|
|
1074
|
+
content: 'Message with image attachment',
|
|
1075
|
+
experimental_attachments: [
|
|
1076
|
+
{
|
|
1077
|
+
name: 'test.png',
|
|
1078
|
+
contentType: 'image/png',
|
|
1079
|
+
url: 'https://example.com/image.png',
|
|
1080
|
+
},
|
|
1081
|
+
],
|
|
1082
|
+
},
|
|
1083
|
+
],
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
await screen.findByTestId('message-0');
|
|
1087
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
1088
|
+
'User: Message with image attachment',
|
|
1089
|
+
);
|
|
1090
|
+
|
|
1091
|
+
await screen.findByTestId('attachment-0');
|
|
1092
|
+
expect(screen.getByTestId('attachment-0')).toHaveAttribute(
|
|
1093
|
+
'src',
|
|
1094
|
+
expect.stringContaining('https://example.com/image.png'),
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
await screen.findByTestId('message-1');
|
|
1098
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
1099
|
+
'AI: Response to message with image attachment',
|
|
1100
|
+
);
|
|
1101
|
+
},
|
|
1102
|
+
),
|
|
1103
|
+
);
|
|
851
1104
|
});
|