Manages the upload state machine for chat file attachments, handling multi-file staging, magic-byte MIME validation, progress tracking, and per-file abort control before sending messages. ## Key Components ### `StagedAttachment` Internal state shape for each file in the upload pipeline. Tracks `id`, `file`, `status` (`sniffing | uploading | ready | error`), `progress` (0–100), `storagePath`, `viewToken`, and `errorMessage`. ### `uploadWithProgress(url, file, token, onProgress?, signal?)` Inline XHR helper that PUTs file bytes to a Supabase signed URL. Maps raw network progress to the 10–95 range, supports `AbortSignal` cancellation, and handles timeout/error/abort events with proper cleanup. ### `useChatAttachments()` Primary hook exposing `UseChatAttachmentsApi`: | Member | Description | |---|---| | `attachments` | Full staged array with state-machine metadata | | `readyAttachments` | Wire-format `ChatAttachment[]` for `sendMessage` | | `hasInflightUploads` | `true` while any upload is in-progress (disables Send) | | `addFiles(files)` | Stages and begins uploading files; deduplicates by `(name, size, lastModified)` | | `removeAttachment(id)` | Aborts in-flight XHR and removes the file | | `clear()` | Aborts all controllers and resets state | ## Usage Example ```typescript const { attachments, readyAttachments, hasInflightUploads, addFiles, removeAttachment, clear, } = useChatAttachments() // Stage files from an input element const handleChange = (e: React.ChangeEvent) => { if (e.target.files) addFiles(e.target.files) } // Pass ready attachments when sending await sendMessage(text, { pendingAttachments: readyAttachments }) // Abort a specific upload mid-flight removeAttachment(attachmentId) ``` > **Security note:** Files are validated via magic-byte sniffing (`file-type`) before upload, rejecting extension spoofs (e.g. `evil.exe` renamed `screenshot.png`). The sniffed MIME — not `file.type` — is sent to the server for URL minting.