@ai-sdk/react 0.0.40 → 0.0.41
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 +8 -0
- package/package.json +5 -5
- package/src/use-chat.ui.test.tsx +339 -322
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @ai-sdk/react@0.0.
|
|
2
|
+
> @ai-sdk/react@0.0.41 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
|
[32mESM[39m [1mdist/index.mjs [22m[32m25.67 KB[39m
|
|
13
13
|
[32mESM[39m [1mdist/index.mjs.map [22m[32m55.94 KB[39m
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 52ms
|
|
15
15
|
[32mCJS[39m [1mdist/index.js [22m[32m28.14 KB[39m
|
|
16
16
|
[32mCJS[39m [1mdist/index.js.map [22m[32m56.02 KB[39m
|
|
17
|
-
[32mCJS[39m ⚡️ Build success in
|
|
17
|
+
[32mCJS[39m ⚡️ Build success in 53ms
|
|
18
18
|
[34mDTS[39m Build start
|
|
19
|
-
[32mDTS[39m ⚡️ Build success in
|
|
19
|
+
[32mDTS[39m ⚡️ Build success in 5278ms
|
|
20
20
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m10.99 KB[39m
|
|
21
21
|
[32mDTS[39m [1mdist/index.d.mts [22m[32m10.99 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.41",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,13 +16,13 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@ai-sdk/provider-utils": "1.0.9",
|
|
19
|
-
"@ai-sdk/ui-utils": "0.0.
|
|
19
|
+
"@ai-sdk/ui-utils": "0.0.29",
|
|
20
20
|
"swr": "2.2.5"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@testing-library/jest-dom": "^6.4.
|
|
24
|
-
"@testing-library/user-event": "^14.5.
|
|
25
|
-
"@testing-library/react": "^
|
|
23
|
+
"@testing-library/jest-dom": "^6.4.8",
|
|
24
|
+
"@testing-library/user-event": "^14.5.2",
|
|
25
|
+
"@testing-library/react": "^16.0.0",
|
|
26
26
|
"@types/node": "^18",
|
|
27
27
|
"@types/react": "^18",
|
|
28
28
|
"@types/react-dom": "^18",
|
package/src/use-chat.ui.test.tsx
CHANGED
|
@@ -121,7 +121,7 @@ describe('stream data stream', () => {
|
|
|
121
121
|
);
|
|
122
122
|
|
|
123
123
|
it(
|
|
124
|
-
'should show error response',
|
|
124
|
+
'should show error response when there is a server error',
|
|
125
125
|
withTestServer(
|
|
126
126
|
{ type: 'error', url: '/api/chat', status: 404, content: 'Not found' },
|
|
127
127
|
async () => {
|
|
@@ -135,6 +135,25 @@ describe('stream data stream', () => {
|
|
|
135
135
|
),
|
|
136
136
|
);
|
|
137
137
|
|
|
138
|
+
it(
|
|
139
|
+
'should show error response when there is a streaming error',
|
|
140
|
+
withTestServer(
|
|
141
|
+
{
|
|
142
|
+
type: 'stream-values',
|
|
143
|
+
url: '/api/chat',
|
|
144
|
+
content: ['3:"custom error message"\n'],
|
|
145
|
+
},
|
|
146
|
+
async () => {
|
|
147
|
+
await userEvent.click(screen.getByTestId('do-append'));
|
|
148
|
+
|
|
149
|
+
await screen.findByTestId('error');
|
|
150
|
+
expect(screen.getByTestId('error')).toHaveTextContent(
|
|
151
|
+
'Error: custom error message',
|
|
152
|
+
);
|
|
153
|
+
},
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
|
|
138
157
|
describe('loading state', () => {
|
|
139
158
|
it(
|
|
140
159
|
'should show loading state',
|
|
@@ -1016,327 +1035,325 @@ describe('maxToolRoundtrips', () => {
|
|
|
1016
1035
|
});
|
|
1017
1036
|
});
|
|
1018
1037
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
// );
|
|
1339
|
-
// });
|
|
1038
|
+
describe('file attachments with data url', () => {
|
|
1039
|
+
const TestComponent = () => {
|
|
1040
|
+
const { messages, handleSubmit, handleInputChange, isLoading, input } =
|
|
1041
|
+
useChat();
|
|
1042
|
+
|
|
1043
|
+
const [attachments, setAttachments] = useState<FileList | undefined>(
|
|
1044
|
+
undefined,
|
|
1045
|
+
);
|
|
1046
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
1047
|
+
|
|
1048
|
+
return (
|
|
1049
|
+
<div>
|
|
1050
|
+
{messages.map((m, idx) => (
|
|
1051
|
+
<div data-testid={`message-${idx}`} key={m.id}>
|
|
1052
|
+
{m.role === 'user' ? 'User: ' : 'AI: '}
|
|
1053
|
+
{m.content}
|
|
1054
|
+
{m.experimental_attachments?.map(attachment => {
|
|
1055
|
+
if (attachment.contentType?.startsWith('image/')) {
|
|
1056
|
+
return (
|
|
1057
|
+
<img
|
|
1058
|
+
key={attachment.name}
|
|
1059
|
+
src={attachment.url}
|
|
1060
|
+
alt={attachment.name}
|
|
1061
|
+
data-testid={`attachment-${idx}`}
|
|
1062
|
+
/>
|
|
1063
|
+
);
|
|
1064
|
+
} else if (attachment.contentType?.startsWith('text/')) {
|
|
1065
|
+
return (
|
|
1066
|
+
<div key={attachment.name} data-testid={`attachment-${idx}`}>
|
|
1067
|
+
{getTextFromDataUrl(attachment.url)}
|
|
1068
|
+
</div>
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
})}
|
|
1072
|
+
</div>
|
|
1073
|
+
))}
|
|
1074
|
+
|
|
1075
|
+
<form
|
|
1076
|
+
onSubmit={event => {
|
|
1077
|
+
handleSubmit(event, {
|
|
1078
|
+
experimental_attachments: attachments,
|
|
1079
|
+
});
|
|
1080
|
+
setAttachments(undefined);
|
|
1081
|
+
if (fileInputRef.current) {
|
|
1082
|
+
fileInputRef.current.value = '';
|
|
1083
|
+
}
|
|
1084
|
+
}}
|
|
1085
|
+
data-testid="chat-form"
|
|
1086
|
+
>
|
|
1087
|
+
<input
|
|
1088
|
+
type="file"
|
|
1089
|
+
onChange={event => {
|
|
1090
|
+
if (event.target.files) {
|
|
1091
|
+
setAttachments(event.target.files);
|
|
1092
|
+
}
|
|
1093
|
+
}}
|
|
1094
|
+
multiple
|
|
1095
|
+
ref={fileInputRef}
|
|
1096
|
+
data-testid="file-input"
|
|
1097
|
+
/>
|
|
1098
|
+
<input
|
|
1099
|
+
value={input}
|
|
1100
|
+
onChange={handleInputChange}
|
|
1101
|
+
disabled={isLoading}
|
|
1102
|
+
data-testid="message-input"
|
|
1103
|
+
/>
|
|
1104
|
+
<button type="submit" data-testid="submit-button">
|
|
1105
|
+
Send
|
|
1106
|
+
</button>
|
|
1107
|
+
</form>
|
|
1108
|
+
</div>
|
|
1109
|
+
);
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
beforeEach(() => {
|
|
1113
|
+
render(<TestComponent />);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
afterEach(() => {
|
|
1117
|
+
vi.restoreAllMocks();
|
|
1118
|
+
cleanup();
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
it(
|
|
1122
|
+
'should handle text file attachment and submission',
|
|
1123
|
+
withTestServer(
|
|
1124
|
+
{
|
|
1125
|
+
url: '/api/chat',
|
|
1126
|
+
type: 'stream-values',
|
|
1127
|
+
content: ['0:"Response to message with text attachment"\n'],
|
|
1128
|
+
},
|
|
1129
|
+
async ({ call }) => {
|
|
1130
|
+
const file = new File(['test file content'], 'test.txt', {
|
|
1131
|
+
type: 'text/plain',
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
const fileInput = screen.getByTestId('file-input');
|
|
1135
|
+
await userEvent.upload(fileInput, file);
|
|
1136
|
+
|
|
1137
|
+
const messageInput = screen.getByTestId('message-input');
|
|
1138
|
+
await userEvent.type(messageInput, 'Message with text attachment');
|
|
1139
|
+
|
|
1140
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
1141
|
+
await userEvent.click(submitButton);
|
|
1142
|
+
|
|
1143
|
+
await screen.findByTestId('message-0');
|
|
1144
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
1145
|
+
'User: Message with text attachment',
|
|
1146
|
+
);
|
|
1147
|
+
|
|
1148
|
+
await screen.findByTestId('attachment-0');
|
|
1149
|
+
expect(screen.getByTestId('attachment-0')).toHaveTextContent(
|
|
1150
|
+
'test file content',
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
await screen.findByTestId('message-1');
|
|
1154
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
1155
|
+
'AI: Response to message with text attachment',
|
|
1156
|
+
);
|
|
1157
|
+
|
|
1158
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
1159
|
+
messages: [
|
|
1160
|
+
{
|
|
1161
|
+
role: 'user',
|
|
1162
|
+
content: 'Message with text attachment',
|
|
1163
|
+
experimental_attachments: [
|
|
1164
|
+
{
|
|
1165
|
+
name: 'test.txt',
|
|
1166
|
+
contentType: 'text/plain',
|
|
1167
|
+
url: 'data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=',
|
|
1168
|
+
},
|
|
1169
|
+
],
|
|
1170
|
+
},
|
|
1171
|
+
],
|
|
1172
|
+
});
|
|
1173
|
+
},
|
|
1174
|
+
),
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
it(
|
|
1178
|
+
'should handle image file attachment and submission',
|
|
1179
|
+
withTestServer(
|
|
1180
|
+
{
|
|
1181
|
+
url: '/api/chat',
|
|
1182
|
+
type: 'stream-values',
|
|
1183
|
+
content: ['0:"Response to message with image attachment"\n'],
|
|
1184
|
+
},
|
|
1185
|
+
async ({ call }) => {
|
|
1186
|
+
const file = new File(['test image content'], 'test.png', {
|
|
1187
|
+
type: 'image/png',
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
const fileInput = screen.getByTestId('file-input');
|
|
1191
|
+
await userEvent.upload(fileInput, file);
|
|
1192
|
+
|
|
1193
|
+
const messageInput = screen.getByTestId('message-input');
|
|
1194
|
+
await userEvent.type(messageInput, 'Message with image attachment');
|
|
1195
|
+
|
|
1196
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
1197
|
+
await userEvent.click(submitButton);
|
|
1198
|
+
|
|
1199
|
+
await screen.findByTestId('message-0');
|
|
1200
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
1201
|
+
'User: Message with image attachment',
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
await screen.findByTestId('attachment-0');
|
|
1205
|
+
expect(screen.getByTestId('attachment-0')).toHaveAttribute(
|
|
1206
|
+
'src',
|
|
1207
|
+
expect.stringContaining('data:image/png;base64'),
|
|
1208
|
+
);
|
|
1209
|
+
|
|
1210
|
+
await screen.findByTestId('message-1');
|
|
1211
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
1212
|
+
'AI: Response to message with image attachment',
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1215
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
1216
|
+
messages: [
|
|
1217
|
+
{
|
|
1218
|
+
role: 'user',
|
|
1219
|
+
content: 'Message with image attachment',
|
|
1220
|
+
experimental_attachments: [
|
|
1221
|
+
{
|
|
1222
|
+
name: 'test.png',
|
|
1223
|
+
contentType: 'image/png',
|
|
1224
|
+
url: 'data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50',
|
|
1225
|
+
},
|
|
1226
|
+
],
|
|
1227
|
+
},
|
|
1228
|
+
],
|
|
1229
|
+
});
|
|
1230
|
+
},
|
|
1231
|
+
),
|
|
1232
|
+
);
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
describe('file attachments with url', () => {
|
|
1236
|
+
const TestComponent = () => {
|
|
1237
|
+
const { messages, handleSubmit, handleInputChange, isLoading, input } =
|
|
1238
|
+
useChat();
|
|
1239
|
+
|
|
1240
|
+
return (
|
|
1241
|
+
<div>
|
|
1242
|
+
{messages.map((m, idx) => (
|
|
1243
|
+
<div data-testid={`message-${idx}`} key={m.id}>
|
|
1244
|
+
{m.role === 'user' ? 'User: ' : 'AI: '}
|
|
1245
|
+
{m.content}
|
|
1246
|
+
{m.experimental_attachments?.map(attachment => {
|
|
1247
|
+
if (attachment.contentType?.startsWith('image/')) {
|
|
1248
|
+
return (
|
|
1249
|
+
<img
|
|
1250
|
+
key={attachment.name}
|
|
1251
|
+
src={attachment.url}
|
|
1252
|
+
alt={attachment.name}
|
|
1253
|
+
data-testid={`attachment-${idx}`}
|
|
1254
|
+
/>
|
|
1255
|
+
);
|
|
1256
|
+
} else if (attachment.contentType?.startsWith('text/')) {
|
|
1257
|
+
return (
|
|
1258
|
+
<div key={attachment.name} data-testid={`attachment-${idx}`}>
|
|
1259
|
+
{Buffer.from(
|
|
1260
|
+
attachment.url.split(',')[1],
|
|
1261
|
+
'base64',
|
|
1262
|
+
).toString('utf-8')}
|
|
1263
|
+
</div>
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
})}
|
|
1267
|
+
</div>
|
|
1268
|
+
))}
|
|
1269
|
+
|
|
1270
|
+
<form
|
|
1271
|
+
onSubmit={event => {
|
|
1272
|
+
handleSubmit(event, {
|
|
1273
|
+
experimental_attachments: [
|
|
1274
|
+
{
|
|
1275
|
+
name: 'test.png',
|
|
1276
|
+
contentType: 'image/png',
|
|
1277
|
+
url: 'https://example.com/image.png',
|
|
1278
|
+
},
|
|
1279
|
+
],
|
|
1280
|
+
});
|
|
1281
|
+
}}
|
|
1282
|
+
data-testid="chat-form"
|
|
1283
|
+
>
|
|
1284
|
+
<input
|
|
1285
|
+
value={input}
|
|
1286
|
+
onChange={handleInputChange}
|
|
1287
|
+
disabled={isLoading}
|
|
1288
|
+
data-testid="message-input"
|
|
1289
|
+
/>
|
|
1290
|
+
<button type="submit" data-testid="submit-button">
|
|
1291
|
+
Send
|
|
1292
|
+
</button>
|
|
1293
|
+
</form>
|
|
1294
|
+
</div>
|
|
1295
|
+
);
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
beforeEach(() => {
|
|
1299
|
+
render(<TestComponent />);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
afterEach(() => {
|
|
1303
|
+
vi.restoreAllMocks();
|
|
1304
|
+
cleanup();
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
it(
|
|
1308
|
+
'should handle image file attachment and submission',
|
|
1309
|
+
withTestServer(
|
|
1310
|
+
{
|
|
1311
|
+
url: '/api/chat',
|
|
1312
|
+
type: 'stream-values',
|
|
1313
|
+
content: ['0:"Response to message with image attachment"\n'],
|
|
1314
|
+
},
|
|
1315
|
+
async ({ call }) => {
|
|
1316
|
+
const messageInput = screen.getByTestId('message-input');
|
|
1317
|
+
await userEvent.type(messageInput, 'Message with image attachment');
|
|
1318
|
+
|
|
1319
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
1320
|
+
await userEvent.click(submitButton);
|
|
1321
|
+
|
|
1322
|
+
await screen.findByTestId('message-0');
|
|
1323
|
+
expect(screen.getByTestId('message-0')).toHaveTextContent(
|
|
1324
|
+
'User: Message with image attachment',
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
await screen.findByTestId('attachment-0');
|
|
1328
|
+
expect(screen.getByTestId('attachment-0')).toHaveAttribute(
|
|
1329
|
+
'src',
|
|
1330
|
+
expect.stringContaining('https://example.com/image.png'),
|
|
1331
|
+
);
|
|
1332
|
+
|
|
1333
|
+
await screen.findByTestId('message-1');
|
|
1334
|
+
expect(screen.getByTestId('message-1')).toHaveTextContent(
|
|
1335
|
+
'AI: Response to message with image attachment',
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
expect(await call(0).getRequestBodyJson()).toStrictEqual({
|
|
1339
|
+
messages: [
|
|
1340
|
+
{
|
|
1341
|
+
role: 'user',
|
|
1342
|
+
content: 'Message with image attachment',
|
|
1343
|
+
experimental_attachments: [
|
|
1344
|
+
{
|
|
1345
|
+
name: 'test.png',
|
|
1346
|
+
contentType: 'image/png',
|
|
1347
|
+
url: 'https://example.com/image.png',
|
|
1348
|
+
},
|
|
1349
|
+
],
|
|
1350
|
+
},
|
|
1351
|
+
],
|
|
1352
|
+
});
|
|
1353
|
+
},
|
|
1354
|
+
),
|
|
1355
|
+
);
|
|
1356
|
+
});
|
|
1340
1357
|
|
|
1341
1358
|
describe('reload', () => {
|
|
1342
1359
|
const TestComponent = () => {
|