From 43f50ba7efb7e5ecd4d9061db5edd58e48e43147 Mon Sep 17 00:00:00 2001 From: masudahiroto <96814344+masudahiroto@users.noreply.github.com> Date: Fri, 11 Sep 2026 18:20:48 +0900 Subject: [PATCH] feat: give the Composer slot a file intake, and paste-to-attach The Composer slot only received onOpenFilePicker, which is an action rather than the ability to take files in, so no path that produces a File without the OS picker could be wired up from a slot. Add `onAddFiles` next to the existing flat props, so the change is additive and a type published in 1.18.0 keeps working. The built-in composer now attaches files pasted into it. The decision to claim the paste is made synchronously from the clipboard types, so text is never lost, and files are handed over unfiltered so a rejection surfaces its reason. Files arriving with an empty name (screenshots in some browsers) get one filled in from their MIME type; a name the browser did provide is left alone. `fileUploadEnabled` keeps meaning "file upload is configured" rather than folding in "no run in flight", which would make the attach button disappear for the length of every run instead of greying out. The composer slot already receives `loading`, so a composer that wants both facts can combine them. AILAB-370 Co-Authored-By: Claude Opus 5 (1M context) --- .../example/src/pages/CustomSlotsDemoPage.tsx | 12 ++ apps/example/src/pages/MultimodalPage.tsx | 7 +- apps/example/test/file-paste.e2e.test.ts | 105 +++++++++++++++++ .../components/UseAIChatPanel.paste.test.tsx | 107 ++++++++++++++++++ .../client/src/components/UseAIChatPanel.tsx | 2 + .../src/components/UseAIChatSlots.test.tsx | 41 ++++++- .../components/chatSlots/DefaultComposer.tsx | 10 ++ .../client/src/components/chatSlots/types.ts | 13 +++ .../client/src/hooks/useFileUpload.test.tsx | 33 ++++++ packages/client/src/hooks/useFileUpload.tsx | 26 ++++- 10 files changed, 351 insertions(+), 5 deletions(-) create mode 100644 apps/example/test/file-paste.e2e.test.ts create mode 100644 packages/client/src/components/UseAIChatPanel.paste.test.tsx diff --git a/apps/example/src/pages/CustomSlotsDemoPage.tsx b/apps/example/src/pages/CustomSlotsDemoPage.tsx index 7ff8a0adc..5c10d0c17 100644 --- a/apps/example/src/pages/CustomSlotsDemoPage.tsx +++ b/apps/example/src/pages/CustomSlotsDemoPage.tsx @@ -238,11 +238,22 @@ function OrbitComposer({ onAbort, onOpenFilePicker, onRemoveAttachment, + onAddFiles, }: ChatComposerSlotProps) { // ToolApproval is rendered independently by use-ai. Hide the composer while // an approval is pending to match the built-in interaction pattern. if (pendingApprovals.length > 0) return null; + // Files pasted into a replacement composer go through the chat's own file + // handling, so they end up on the message the panel sends. + const handlePaste = (event: React.ClipboardEvent) => { + if (!fileUploadEnabled || event.clipboardData.files.length === 0) return; + if (!event.clipboardData.types.includes('text/plain')) { + event.preventDefault(); + } + onAddFiles(event.clipboardData.files); + }; + return (