Skip to content

Commit c9ad635

Browse files
authored
feat (ai): add filename to file ui parts (vercel#6104)
## Background Attachments were moved to file ui parts in a previous pull request. File UI parts do not have filenames (file content parts do). ## Summary Add filenames to file ui parts. ## Verification Tested with `examples/next-openai` attachment uploads.
1 parent b5c47f3 commit c9ad635

File tree

10 files changed

+70
-33
lines changed

10 files changed

+70
-33
lines changed

.changeset/real-apes-lick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'ai': minor
3+
---
4+
5+
feat (ai): add filename to file ui parts

examples/next-openai/app/use-chat-attachments-append/page.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ export default function Page() {
3131
) {
3232
return (
3333
<div key={index}>
34-
<img className="rounded-md w-60" src={part.url} />
34+
<img
35+
className="rounded-md w-60"
36+
src={part.url}
37+
alt={part.filename}
38+
/>
3539
</div>
3640
);
3741
}
@@ -81,16 +85,6 @@ export default function Page() {
8185
</span>
8286
</div>
8387
);
84-
} else if (type.startsWith('text/')) {
85-
return (
86-
<div
87-
key={attachment.name}
88-
className="flex flex-col flex-shrink-0 w-24 gap-1 text-sm text-zinc-500"
89-
>
90-
<div className="w-16 h-20 rounded-md bg-zinc-100" />
91-
{attachment.name}
92-
</div>
93-
);
9488
}
9589
})
9690
: ''}

examples/next-openai/app/use-chat-attachments-url/page.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,27 @@ export default function Page() {
2222
<div key={message.id} className="flex flex-row gap-2">
2323
<div className="flex-shrink-0 w-24 text-zinc-500">{`${message.role}: `}</div>
2424

25-
{message.parts.map((part, index) => {
26-
if (part.type === 'text') {
27-
return <div key={index}>{part.text}</div>;
28-
}
29-
if (
30-
part.type === 'file' &&
31-
part.mediaType?.startsWith('image/')
32-
) {
33-
return <img key={index} src={part.url} />;
34-
}
35-
})}
25+
<div className="flex flex-col gap-2">
26+
{message.parts.map((part, index) => {
27+
if (part.type === 'text') {
28+
return <div key={index}>{part.text}</div>;
29+
}
30+
if (
31+
part.type === 'file' &&
32+
part.mediaType?.startsWith('image/')
33+
) {
34+
return (
35+
<div key={index}>
36+
<img
37+
className="rounded-md w-60"
38+
src={part.url}
39+
alt={part.filename}
40+
/>
41+
</div>
42+
);
43+
}
44+
})}
45+
</div>
3646
</div>
3747
))}
3848
</div>
@@ -62,11 +72,9 @@ export default function Page() {
6272
<img
6373
className="w-24 rounded-md"
6474
src={file.url}
65-
// alt={file.name} TODO filename support
75+
alt={file.filename}
6676
/>
67-
<span className="text-sm text-zinc-500">
68-
{'name' /* TODO file.name*/}
69-
</span>
77+
<span className="text-sm text-zinc-500">{file.filename}</span>
7078
</div>
7179
))}
7280
</div>
@@ -86,7 +94,7 @@ export default function Page() {
8694
...prevFiles,
8795
{
8896
type: 'file' as const,
89-
// name: file.name, TODO filename support
97+
filename: file.name,
9098
mediaType: blob.contentType ?? '*/*',
9199
url: blob.url,
92100
},

examples/next-openai/app/use-chat-attachments/page.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ export default function Page() {
3030
) {
3131
return (
3232
<div key={index}>
33-
<img className="rounded-md w-60" src={part.url} />
33+
<img
34+
className="rounded-md w-60"
35+
src={part.url}
36+
alt={part.filename}
37+
/>
38+
<span className="text-sm text-zinc-500">
39+
{part.filename}
40+
</span>
3441
</div>
3542
);
3643
}

packages/ai/core/prompt/convert-to-core-messages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export function convertToCoreMessages<TOOLS extends ToolSet = never>(
4848
? {
4949
type: 'file' as const,
5050
mediaType: part.mediaType,
51+
filename: part.filename,
5152
data: part.url,
5253
}
5354
: part,

packages/ai/core/types/ui-messages.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ export type FileUIPart = {
141141
*/
142142
mediaType: string;
143143

144+
/**
145+
* Optional filename of the file.
146+
*/
147+
filename?: string;
148+
144149
/**
145150
* The URL of the file.
146151
* It can either be a URL to a hosted file or a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs).

packages/ai/core/util/convert-file-list-to-file-ui-parts.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,22 @@ export async function convertFileListToFileUIParts(
1313
}
1414

1515
return Promise.all(
16-
Array.from(files).map(async attachment => {
17-
// TODO add filename once supported by file ui parts
18-
const { name, type } = attachment;
16+
Array.from(files).map(async file => {
17+
const { name, type } = file;
1918

2019
const dataUrl = await new Promise<string>((resolve, reject) => {
2120
const reader = new FileReader();
2221
reader.onload = readerEvent => {
2322
resolve(readerEvent.target?.result as string);
2423
};
2524
reader.onerror = error => reject(error);
26-
reader.readAsDataURL(attachment);
25+
reader.readAsDataURL(file);
2726
});
2827

2928
return {
3029
type: 'file',
3130
mediaType: type,
31+
filename: name,
3232
url: dataUrl,
3333
};
3434
}),

packages/react/src/use-chat.ui.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,7 @@ describe('file attachments with data url', () => {
13381338
{
13391339
type: 'file',
13401340
mediaType: 'text/plain',
1341+
filename: 'test.txt',
13411342
url: 'data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=',
13421343
},
13431344
{
@@ -1370,6 +1371,7 @@ describe('file attachments with data url', () => {
13701371
"content": "Message with text attachment",
13711372
"parts": [
13721373
{
1374+
"filename": "test.txt",
13731375
"mediaType": "text/plain",
13741376
"type": "file",
13751377
"url": "data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=",
@@ -1418,6 +1420,7 @@ describe('file attachments with data url', () => {
14181420
{
14191421
type: 'file',
14201422
mediaType: 'image/png',
1423+
filename: 'test.png',
14211424
url: 'data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50',
14221425
},
14231426
{
@@ -1450,6 +1453,7 @@ describe('file attachments with data url', () => {
14501453
"content": "Message with image attachment",
14511454
"parts": [
14521455
{
1456+
"filename": "test.png",
14531457
"mediaType": "image/png",
14541458
"type": "file",
14551459
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1603,7 +1607,7 @@ describe('attachments with empty submit', () => {
16031607
files: [
16041608
{
16051609
type: 'file',
1606-
// name: 'test.png', TODO enable file name
1610+
filename: 'test.png',
16071611
mediaType: 'image/png',
16081612
url: 'https://example.com/image.png',
16091613
},
@@ -1642,6 +1646,7 @@ describe('attachments with empty submit', () => {
16421646
{
16431647
type: 'file',
16441648
mediaType: 'image/png',
1649+
filename: 'test.png',
16451650
url: 'https://example.com/image.png',
16461651
},
16471652
{
@@ -1674,6 +1679,7 @@ describe('attachments with empty submit', () => {
16741679
"content": "",
16751680
"parts": [
16761681
{
1682+
"filename": "test.png",
16771683
"mediaType": "image/png",
16781684
"type": "file",
16791685
"url": "https://example.com/image.png",

packages/svelte/src/chat.svelte.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,7 @@ describe('file attachments with data url', () => {
10551055
"id": "id-1",
10561056
"parts": [
10571057
{
1058+
"filename": "test.txt",
10581059
"mediaType": "text/plain",
10591060
"type": "file",
10601061
"url": "data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=",
@@ -1090,6 +1091,7 @@ describe('file attachments with data url', () => {
10901091
"content": "Message with text attachment",
10911092
"parts": [
10921093
{
1094+
"filename": "test.txt",
10931095
"mediaType": "text/plain",
10941096
"type": "file",
10951097
"url": "data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=",
@@ -1130,6 +1132,7 @@ describe('file attachments with data url', () => {
11301132
"id": "id-1",
11311133
"parts": [
11321134
{
1135+
"filename": "test.png",
11331136
"mediaType": "image/png",
11341137
"type": "file",
11351138
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1165,6 +1168,7 @@ describe('file attachments with data url', () => {
11651168
"content": "Message with image attachment",
11661169
"parts": [
11671170
{
1171+
"filename": "test.png",
11681172
"mediaType": "image/png",
11691173
"type": "file",
11701174
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1218,6 +1222,7 @@ describe('file attachments with url', () => {
12181222
"id": "id-1",
12191223
"parts": [
12201224
{
1225+
"filename": "test.png",
12211226
"mediaType": "image/png",
12221227
"type": "file",
12231228
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1253,6 +1258,7 @@ describe('file attachments with url', () => {
12531258
"content": "Message with image attachment",
12541259
"parts": [
12551260
{
1261+
"filename": "test.png",
12561262
"mediaType": "image/png",
12571263
"type": "file",
12581264
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1305,6 +1311,7 @@ describe('file attachments with empty text content', () => {
13051311
"id": "id-1",
13061312
"parts": [
13071313
{
1314+
"filename": "test.png",
13081315
"mediaType": "image/png",
13091316
"type": "file",
13101317
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",
@@ -1340,6 +1347,7 @@ describe('file attachments with empty text content', () => {
13401347
"content": "",
13411348
"parts": [
13421349
{
1350+
"filename": "test.png",
13431351
"mediaType": "image/png",
13441352
"type": "file",
13451353
"url": "data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50",

packages/vue/src/use-chat.ui.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ describe('file attachments with data url', () => {
783783
{
784784
type: 'file',
785785
mediaType: 'text/plain',
786+
filename: 'test.txt',
786787
url: 'data:text/plain;base64,dGVzdCBmaWxlIGNvbnRlbnQ=',
787788
},
788789
{
@@ -840,6 +841,7 @@ describe('file attachments with data url', () => {
840841
{
841842
type: 'file',
842843
mediaType: 'image/png',
844+
filename: 'test.png',
843845
url: 'data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50',
844846
},
845847
{
@@ -952,6 +954,7 @@ describe('attachments with empty submit', () => {
952954
{
953955
type: 'file',
954956
mediaType: 'image/png',
957+
filename: 'test.png',
955958
url: 'data:image/png;base64,dGVzdCBpbWFnZSBjb250ZW50',
956959
},
957960
{

0 commit comments

Comments
 (0)