From 02fc5dae80634c138f7a0d6fb4d093c6089aeac0 Mon Sep 17 00:00:00 2001 From: chaxus Date: Thu, 27 Nov 2025 13:20:25 +0800 Subject: [PATCH] feat(prompt-input): add file property to attachment messages for persistent storage --- .changeset/proud-pots-report.md | 5 +++++ packages/elements/src/prompt-input.tsx | 18 ++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 .changeset/proud-pots-report.md diff --git a/.changeset/proud-pots-report.md b/.changeset/proud-pots-report.md new file mode 100644 index 00000000..e675c1e9 --- /dev/null +++ b/.changeset/proud-pots-report.md @@ -0,0 +1,5 @@ +--- +"ai-elements": minor +--- + +feat(prompt-input): Add file property to attachment messages for persistent storage diff --git a/packages/elements/src/prompt-input.tsx b/packages/elements/src/prompt-input.tsx index d0a2a364..188c1c3f 100644 --- a/packages/elements/src/prompt-input.tsx +++ b/packages/elements/src/prompt-input.tsx @@ -75,7 +75,7 @@ import { // ============================================================================ export type AttachmentsContext = { - files: (FileUIPart & { id: string })[]; + files: (FileUIPart & { id: string, file?: File })[]; add: (files: File[] | FileList) => void; remove: (id: string) => void; clear: () => void; @@ -151,7 +151,7 @@ export function PromptInputProvider({ // ----- attachments state (global when wrapped) const [attachmentFiles, setAttachmentFiles] = useState< - (FileUIPart & { id: string })[] + (FileUIPart & { id: string, file?: File })[] >([]); const fileInputRef = useRef(null); const openRef = useRef<() => void>(() => {}); @@ -170,6 +170,7 @@ export function PromptInputProvider({ url: URL.createObjectURL(file), mediaType: file.type, filename: file.name, + file, })) ) ); @@ -277,7 +278,7 @@ export const usePromptInputAttachments = () => { }; export type PromptInputAttachmentProps = HTMLAttributes & { - data: FileUIPart & { id: string }; + data: FileUIPart & { id: string; file?: File }; className?: string; }; @@ -376,7 +377,7 @@ export type PromptInputAttachmentsProps = Omit< HTMLAttributes, "children" > & { - children: (attachment: FileUIPart & { id: string }) => ReactNode; + children: (attachment: FileUIPart & { id: string; file?: File }) => ReactNode; }; export function PromptInputAttachments({ @@ -429,7 +430,7 @@ export const PromptInputActionAddAttachments = ({ export type PromptInputMessage = { text: string; - files: FileUIPart[]; + files: (FileUIPart & { file?: File })[]; }; export type PromptInputProps = Omit< @@ -477,7 +478,7 @@ export const PromptInput = ({ const formRef = useRef(null); // ----- Local attachments (only used when no provider) - const [items, setItems] = useState<(FileUIPart & { id: string })[]>([]); + const [items, setItems] = useState<(FileUIPart & { id: string; file?: File })[]>([]); const files = usingProvider ? controller.attachments.files : items; // Keep a ref to files for cleanup on unmount (avoids stale closure) @@ -537,7 +538,7 @@ export const PromptInput = ({ message: "Too many files. Some were not added.", }); } - const next: (FileUIPart & { id: string })[] = []; + const next: (FileUIPart & { id: string, file?: File })[] = []; for (const file of capped) { next.push({ id: nanoid(), @@ -545,6 +546,7 @@ export const PromptInput = ({ url: URL.createObjectURL(file), mediaType: file.type, filename: file.name, + file, }); } return prev.concat(next); @@ -729,7 +731,7 @@ export const PromptInput = ({ return item; }) ) - .then((convertedFiles: FileUIPart[]) => { + .then((convertedFiles: (FileUIPart & { file?: File })[]) => { try { const result = onSubmit({ text, files: convertedFiles }, event);