feat(FN-2911): improve chat attachment compose and preview UX

- Add attachment-first chat compose flow with paperclip picker, drag-and-drop, and paste support in ChatView input
- Render pending attachment preview chips with per-file remove actions and drag-over visual affordance
- Display sent message attachments with image/file presentation and accessible focus/interaction styling
- Update ChatView tests and icon mocks to cover attachment-only sends, file selection, and preview behavior

Fusion-Task-Id: FN-2911
This commit is contained in:
Fusion
2026-04-28 23:11:16 -07:00
committed by gsxdsm
parent 81cf7dcbab
commit d155b8964a
7 changed files with 594 additions and 81 deletions

View File

@@ -7305,6 +7305,7 @@ export function cancelChatResponse(
*
* Since `EventSource` only supports GET requests, this function uses `fetch()`
* with a ReadableStream to parse SSE events from the POST response body.
* When attachments are provided, the request body is sent as multipart form data.
*/
export function streamChatResponse(
sessionId: string,
@@ -7318,6 +7319,7 @@ export function streamChatResponse(
onError?: (data: string) => void;
onConnectionStateChange?: (state: StreamConnectionState) => void;
},
attachments?: File[],
projectId?: string,
options?: { maxReconnectAttempts?: number },
): { close: () => void; isConnected: () => boolean } {
@@ -7383,10 +7385,20 @@ export function streamChatResponse(
// Start streaming via POST
(async () => {
try {
const hasAttachments = Array.isArray(attachments) && attachments.length > 0;
const body = hasAttachments
? (() => {
const formData = new FormData();
formData.append("content", content);
attachments.forEach((file) => formData.append("attachments", file));
return formData;
})()
: JSON.stringify({ content });
const res = await fetch(url, {
method: "POST",
headers: withTokenHeader({ "Content-Type": "application/json" }),
body: JSON.stringify({ content }),
headers: hasAttachments ? withTokenHeader() : withTokenHeader({ "Content-Type": "application/json" }),
body,
signal: abortController.signal,
});