Skip to content

Commit efc487a

Browse files
improvement(chat-file-upload): add visual indication of file upload exceeding limit (#1123)
* improvement(chat-file-upload): add visual indication of file upload exceeding limit * fix duplicate error + lint * fix lint * fix lint
1 parent 5786909 commit efc487a

File tree

4 files changed

+100
-43
lines changed

4 files changed

+100
-43
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/chat.tsx

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState }
44
import { ArrowDown, ArrowUp } from 'lucide-react'
55
import { Button } from '@/components/ui/button'
66
import { Input } from '@/components/ui/input'
7+
import { Notice } from '@/components/ui/notice'
78
import { ScrollArea } from '@/components/ui/scroll-area'
89
import { createLogger } from '@/lib/logs/console/logger'
910
import {
@@ -32,12 +33,11 @@ interface ChatFile {
3233
}
3334

3435
interface ChatProps {
35-
panelWidth: number
3636
chatMessage: string
3737
setChatMessage: (message: string) => void
3838
}
3939

40-
export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) {
40+
export function Chat({ chatMessage, setChatMessage }: ChatProps) {
4141
const { activeWorkflowId } = useWorkflowRegistry()
4242

4343
const {
@@ -63,6 +63,7 @@ export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) {
6363
// File upload state
6464
const [chatFiles, setChatFiles] = useState<ChatFile[]>([])
6565
const [isUploadingFiles, setIsUploadingFiles] = useState(false)
66+
const [uploadErrors, setUploadErrors] = useState<string[]>([])
6667
const [dragCounter, setDragCounter] = useState(0)
6768
const isDragOver = dragCounter > 0
6869
// Scroll state
@@ -280,11 +281,15 @@ export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) {
280281
type: chatFile.type,
281282
file: chatFile.file, // Pass the actual File object
282283
}))
284+
workflowInput.onUploadError = (message: string) => {
285+
setUploadErrors((prev) => [...prev, message])
286+
}
283287
}
284288

285289
// Clear input and files, refocus immediately
286290
setChatMessage('')
287291
setChatFiles([])
292+
setUploadErrors([])
288293
focusInput(10)
289294

290295
// Execute the workflow to generate a response
@@ -560,14 +565,16 @@ export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) {
560565
No messages yet
561566
</div>
562567
) : (
563-
<ScrollArea ref={scrollAreaRef} className='h-full pb-2' hideScrollbar={true}>
564-
<div>
565-
{workflowMessages.map((message) => (
566-
<ChatMessage key={message.id} message={message} />
567-
))}
568-
<div ref={messagesEndRef} />
569-
</div>
570-
</ScrollArea>
568+
<div ref={scrollAreaRef} className='h-full'>
569+
<ScrollArea className='h-full pb-2' hideScrollbar={true}>
570+
<div>
571+
{workflowMessages.map((message) => (
572+
<ChatMessage key={message.id} message={message} />
573+
))}
574+
<div ref={messagesEndRef} />
575+
</div>
576+
</ScrollArea>
577+
</div>
571578
)}
572579

573580
{/* Scroll to bottom button */}
@@ -615,26 +622,68 @@ export function Chat({ panelWidth, chatMessage, setChatMessage }: ChatProps) {
615622
if (!(!activeWorkflowId || isExecuting || isUploadingFiles)) {
616623
const droppedFiles = Array.from(e.dataTransfer.files)
617624
if (droppedFiles.length > 0) {
618-
const newFiles = droppedFiles.slice(0, 5 - chatFiles.length).map((file) => ({
619-
id: crypto.randomUUID(),
620-
name: file.name,
621-
size: file.size,
622-
type: file.type,
623-
file,
624-
}))
625-
setChatFiles([...chatFiles, ...newFiles])
625+
const remainingSlots = Math.max(0, 5 - chatFiles.length)
626+
const candidateFiles = droppedFiles.slice(0, remainingSlots)
627+
const errors: string[] = []
628+
const validNewFiles: ChatFile[] = []
629+
630+
for (const file of candidateFiles) {
631+
if (file.size > 10 * 1024 * 1024) {
632+
errors.push(`${file.name} is too large (max 10MB)`)
633+
continue
634+
}
635+
636+
const isDuplicate = chatFiles.some(
637+
(existingFile) =>
638+
existingFile.name === file.name && existingFile.size === file.size
639+
)
640+
if (isDuplicate) {
641+
errors.push(`${file.name} already added`)
642+
continue
643+
}
644+
645+
validNewFiles.push({
646+
id: crypto.randomUUID(),
647+
name: file.name,
648+
size: file.size,
649+
type: file.type,
650+
file,
651+
})
652+
}
653+
654+
if (errors.length > 0) {
655+
setUploadErrors(errors)
656+
}
657+
658+
if (validNewFiles.length > 0) {
659+
setChatFiles([...chatFiles, ...validNewFiles])
660+
}
626661
}
627662
}
628663
}}
629664
>
630665
{/* File upload section */}
631666
<div className='mb-2'>
667+
{uploadErrors.length > 0 && (
668+
<div className='mb-2'>
669+
<Notice variant='error' title='File upload error'>
670+
<ul className='list-disc pl-5'>
671+
{uploadErrors.map((err, idx) => (
672+
<li key={idx}>{err}</li>
673+
))}
674+
</ul>
675+
</Notice>
676+
</div>
677+
)}
632678
<ChatFileUpload
633679
files={chatFiles}
634-
onFilesChange={setChatFiles}
680+
onFilesChange={(files) => {
681+
setChatFiles(files)
682+
}}
635683
maxFiles={5}
636684
maxSize={10}
637685
disabled={!activeWorkflowId || isExecuting || isUploadingFiles}
686+
onError={(errors) => setUploadErrors(errors)}
638687
/>
639688
</div>
640689

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/chat/components/chat-file-upload.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ChatFileUploadProps {
2121
maxSize?: number // in MB
2222
acceptedTypes?: string[]
2323
disabled?: boolean
24+
onError?: (errors: string[]) => void
2425
}
2526

2627
export function ChatFileUpload({
@@ -30,6 +31,7 @@ export function ChatFileUpload({
3031
maxSize = 10,
3132
acceptedTypes = ['*'],
3233
disabled = false,
34+
onError,
3335
}: ChatFileUploadProps) {
3436
const [isDragOver, setIsDragOver] = useState(false)
3537
const fileInputRef = useRef<HTMLInputElement>(null)
@@ -91,7 +93,7 @@ export function ChatFileUpload({
9193

9294
if (errors.length > 0) {
9395
logger.warn('File upload errors:', errors)
94-
// You could show these errors in a toast or alert
96+
onError?.(errors)
9597
}
9698

9799
if (newFiles.length > 0) {
@@ -168,7 +170,12 @@ export function ChatFileUpload({
168170
ref={fileInputRef}
169171
type='file'
170172
multiple
171-
onChange={(e) => handleFileSelect(e.target.files)}
173+
onChange={(e) => {
174+
handleFileSelect(e.target.files)
175+
if (fileInputRef.current) {
176+
fileInputRef.current.value = ''
177+
}
178+
}}
172179
className='hidden'
173180
accept={acceptedTypes.join(',')}
174181
disabled={disabled}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -492,11 +492,7 @@ export function Panel() {
492492
<div className='flex-1 overflow-hidden px-3'>
493493
{/* Keep all tabs mounted but hidden to preserve state and animations */}
494494
<div style={{ display: activeTab === 'chat' ? 'block' : 'none', height: '100%' }}>
495-
<Chat
496-
panelWidth={panelWidth}
497-
chatMessage={chatMessage}
498-
setChatMessage={setChatMessage}
499-
/>
495+
<Chat chatMessage={chatMessage} setChatMessage={setChatMessage} />
500496
</div>
501497
<div style={{ display: activeTab === 'console' ? 'block' : 'none', height: '100%' }}>
502498
<Console panelWidth={panelWidth} />

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -279,15 +279,17 @@ export function useWorkflowExecution() {
279279

280280
// Handle file uploads if present
281281
const uploadedFiles: any[] = []
282-
console.log('Checking for files to upload:', workflowInput.files)
282+
interface UploadErrorCapableInput {
283+
onUploadError: (message: string) => void
284+
}
285+
const isUploadErrorCapable = (value: unknown): value is UploadErrorCapableInput =>
286+
!!value &&
287+
typeof value === 'object' &&
288+
'onUploadError' in (value as any) &&
289+
typeof (value as any).onUploadError === 'function'
283290
if (workflowInput.files && Array.isArray(workflowInput.files)) {
284291
try {
285-
console.log('Processing files for upload:', workflowInput.files.length)
286-
287292
for (const fileData of workflowInput.files) {
288-
console.log('Uploading file:', fileData.name, fileData.size)
289-
console.log('File data:', fileData)
290-
291293
// Create FormData for upload
292294
const formData = new FormData()
293295
formData.append('file', fileData.file)
@@ -303,8 +305,6 @@ export function useWorkflowExecution() {
303305

304306
if (response.ok) {
305307
const uploadResult = await response.json()
306-
console.log('Upload successful:', uploadResult)
307-
308308
// Convert upload result to clean UserFile format
309309
const processUploadResult = (result: any) => ({
310310
id:
@@ -327,23 +327,28 @@ export function useWorkflowExecution() {
327327
// Single file upload - the result IS the file object
328328
uploadedFiles.push(processUploadResult(uploadResult))
329329
} else {
330-
console.error('Unexpected upload response format:', uploadResult)
330+
logger.error('Unexpected upload response format:', uploadResult)
331331
}
332332
} else {
333333
const errorText = await response.text()
334-
console.error(
335-
`Failed to upload file ${fileData.name}:`,
336-
response.status,
337-
errorText
338-
)
334+
const message = `Failed to upload ${fileData.name}: ${response.status} ${errorText}`
335+
logger.error(message)
336+
if (isUploadErrorCapable(workflowInput)) {
337+
try {
338+
workflowInput.onUploadError(message)
339+
} catch {}
340+
}
339341
}
340342
}
341-
342-
console.log('All files processed. Uploaded files:', uploadedFiles)
343343
// Update workflow input with uploaded files
344344
workflowInput.files = uploadedFiles
345345
} catch (error) {
346-
console.error('Error uploading files:', error)
346+
logger.error('Error uploading files:', error)
347+
if (isUploadErrorCapable(workflowInput)) {
348+
try {
349+
workflowInput.onUploadError('Unexpected error uploading files')
350+
} catch {}
351+
}
347352
// Continue execution even if file upload fails
348353
workflowInput.files = []
349354
}

0 commit comments

Comments
 (0)