Skip to content

Commit 4005a15

Browse files
committed
fix file upload
1 parent d05bb81 commit 4005a15

2 files changed

Lines changed: 67 additions & 62 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/user-input/user-input.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
158158
isLoading: isSending,
159159
})
160160
const hasFiles = files.attachedFiles.some((f) => !f.uploading && f.key)
161+
const hasUploadingFiles = files.attachedFiles.some((f) => f.uploading)
161162

162163
const contextManagement = useContextManagement({ message: value })
163164

@@ -232,7 +233,7 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
232233
setSelectedContexts: contextManagement.setSelectedContexts,
233234
})
234235

235-
const canSubmit = (value.trim().length > 0 || hasFiles) && !isSending
236+
const canSubmit = (value.trim().length > 0 || hasFiles) && !isSending && !hasUploadingFiles
236237

237238
const valueRef = useRef(value)
238239
valueRef.current = value
@@ -507,6 +508,8 @@ export const UserInput = forwardRef<UserInputHandle, UserInputProps>(function Us
507508

508509
if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
509510
e.preventDefault()
511+
// Mirror canSubmit's uploading guard; Enter reads refs, not rendered state.
512+
if (filesRef.current.attachedFiles.some((f) => f.uploading)) return
510513
const hasSubmitPayload =
511514
valueRef.current.trim().length > 0 ||
512515
filesRef.current.attachedFiles.some((file) => !file.uploading && file.key)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/copilot/components/user-input/hooks/use-file-attachments.ts

Lines changed: 63 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
104104
}, [])
105105

106106
/**
107-
* Processes and uploads files to S3
108-
* @param fileList - Files to process
107+
* Uploads files in parallel so a slow file does not block faster ones queued
108+
* behind it. All placeholders insert in a single state update for a stable row.
109109
*/
110110
const processFiles = useCallback(
111111
async (fileList: FileList) => {
@@ -114,67 +114,69 @@ export function useFileAttachments(props: UseFileAttachmentsProps) {
114114
return
115115
}
116116

117-
for (const file of Array.from(fileList)) {
118-
let previewUrl: string | undefined
119-
if (file.type.startsWith('image/')) {
120-
previewUrl = URL.createObjectURL(file)
121-
}
122-
123-
const resolvedType = resolveFileType(file)
124-
125-
const tempFile: AttachedFile = {
126-
id: generateId(),
127-
name: file.name,
128-
size: file.size,
129-
type: resolvedType,
130-
path: '',
131-
uploading: true,
132-
previewUrl,
133-
}
134-
135-
setAttachedFiles((prev) => [...prev, tempFile])
136-
137-
try {
138-
const formData = new FormData()
139-
formData.append('file', file)
140-
formData.append('context', 'mothership')
141-
if (workspaceId) {
142-
formData.append('workspaceId', workspaceId)
143-
}
144-
145-
const uploadResponse = await fetch('/api/files/upload', {
146-
method: 'POST',
147-
body: formData,
148-
})
149-
150-
if (!uploadResponse.ok) {
151-
const errorData = await uploadResponse.json().catch(() => ({
152-
error: `Upload failed: ${uploadResponse.status}`,
153-
}))
154-
throw new Error(errorData.error || `Failed to upload file: ${uploadResponse.status}`)
155-
}
156-
157-
const uploadData = await uploadResponse.json()
158-
159-
logger.info(`File uploaded successfully: ${uploadData.fileInfo?.path || uploadData.path}`)
117+
const files = Array.from(fileList)
118+
if (files.length === 0) return
119+
120+
const placeholders: AttachedFile[] = files.map((file) => ({
121+
id: generateId(),
122+
name: file.name,
123+
size: file.size,
124+
type: resolveFileType(file),
125+
path: '',
126+
uploading: true,
127+
previewUrl: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,
128+
}))
129+
130+
setAttachedFiles((prev) => [...prev, ...placeholders])
131+
132+
await Promise.all(
133+
files.map(async (file, i) => {
134+
const placeholder = placeholders[i]
135+
try {
136+
const formData = new FormData()
137+
formData.append('file', file)
138+
formData.append('context', 'mothership')
139+
if (workspaceId) {
140+
formData.append('workspaceId', workspaceId)
141+
}
142+
143+
const uploadResponse = await fetch('/api/files/upload', {
144+
method: 'POST',
145+
body: formData,
146+
})
147+
148+
if (!uploadResponse.ok) {
149+
const errorData = await uploadResponse.json().catch(() => ({
150+
error: `Upload failed: ${uploadResponse.status}`,
151+
}))
152+
throw new Error(errorData.error || `Failed to upload file: ${uploadResponse.status}`)
153+
}
154+
155+
const uploadData = await uploadResponse.json()
156+
157+
logger.info(
158+
`File uploaded successfully: ${uploadData.fileInfo?.path || uploadData.path}`
159+
)
160160

161-
setAttachedFiles((prev) =>
162-
prev.map((f) =>
163-
f.id === tempFile.id
164-
? {
165-
...f,
166-
path: uploadData.fileInfo?.path || uploadData.path || uploadData.url,
167-
key: uploadData.fileInfo?.key || uploadData.key,
168-
uploading: false,
169-
}
170-
: f
161+
setAttachedFiles((prev) =>
162+
prev.map((f) =>
163+
f.id === placeholder.id
164+
? {
165+
...f,
166+
path: uploadData.fileInfo?.path || uploadData.path || uploadData.url,
167+
key: uploadData.fileInfo?.key || uploadData.key,
168+
uploading: false,
169+
}
170+
: f
171+
)
171172
)
172-
)
173-
} catch (error) {
174-
logger.error(`File upload failed: ${error}`)
175-
setAttachedFiles((prev) => prev.filter((f) => f.id !== tempFile.id))
176-
}
177-
}
173+
} catch (error) {
174+
logger.error(`File upload failed: ${error}`)
175+
if (placeholder.previewUrl) URL.revokeObjectURL(placeholder.previewUrl)
176+
setAttachedFiles((prev) => prev.filter((f) => f.id !== placeholder.id))
177+
}
178+
})
179+
)
178180
},
179181
[userId, workspaceId]
180182
)

0 commit comments

Comments
 (0)