Skip to content

Commit 83545ed

Browse files
Ericonaldoclaude
andcommitted
feat: support all file types (not just images) in chat upload
- Remove image-only filter from multer and paste handler - Accept any file from clipboard or file picker (text, PDF, code, etc.) - Increase max size from 10MB to 50MB - Message format: [Image: path] for images, [File: path] for other types - Renamed uploadImage → uploadFile in API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 44eeee3 commit 83545ed

3 files changed

Lines changed: 23 additions & 31 deletions

File tree

client/src/api/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ async function uploadFile<T>(path: string, file: File, fieldName: string): Promi
135135
}
136136

137137
export const api = {
138-
// Upload
139-
uploadImage: (file: File) => uploadFile<{ path: string }>('/upload-image', file, 'image'),
138+
// Upload (images + any file type)
139+
uploadFile: (file: File) => uploadFile<{ path: string; originalName: string; size: number }>('/upload-image', file, 'file'),
140140

141141
// Agents
142142
getAgents: () => request<Agent[]>('/agents'),

client/src/pages/AgentChat.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -553,16 +553,12 @@ export function AgentChat() {
553553
}
554554
};
555555

556-
const handleImageUpload = async (files: File[]) => {
557-
const imageFiles = files.filter(f =>
558-
['image/png', 'image/jpeg', 'image/gif', 'image/webp'].includes(f.type)
559-
);
560-
if (imageFiles.length === 0) return;
561-
562-
setUploadingCount(prev => prev + imageFiles.length);
563-
for (const file of imageFiles) {
556+
const handleFileUpload = async (files: File[]) => {
557+
if (files.length === 0) return;
558+
setUploadingCount(prev => prev + files.length);
559+
for (const file of files) {
564560
try {
565-
const result = await api.uploadImage(file);
561+
const result = await api.uploadFile(file);
566562
setAttachedImages(prev => [...prev, { name: file.name, path: result.path }]);
567563
} catch (err) {
568564
addLocalMessage(`Failed to upload ${file.name}: ${err instanceof Error ? err.message : String(err)}`);
@@ -576,17 +572,18 @@ export function AgentChat() {
576572
const items = e.clipboardData?.items;
577573
if (!items) return;
578574

579-
const imageFiles: File[] = [];
575+
const pasteFiles: File[] = [];
580576
for (let i = 0; i < items.length; i++) {
581577
const item = items[i];
582-
if (item.type.startsWith('image/')) {
578+
// Accept any file type from clipboard (images, PDFs, etc.)
579+
if (item.kind === 'file') {
583580
const file = item.getAsFile();
584-
if (file) imageFiles.push(file);
581+
if (file) pasteFiles.push(file);
585582
}
586583
}
587-
if (imageFiles.length > 0) {
584+
if (pasteFiles.length > 0) {
588585
e.preventDefault();
589-
handleImageUpload(imageFiles);
586+
handleFileUpload(pasteFiles);
590587
}
591588
};
592589

@@ -628,7 +625,10 @@ export function AgentChat() {
628625
}
629626

630627
// Build message text with image paths prepended
631-
const imagePrefixes = attachedImages.map(img => `[Image: ${img.path}]`).join('\n');
628+
const imagePrefixes = attachedImages.map(img => {
629+
const isImage = /\.(png|jpe?g|gif|webp|svg|bmp)$/i.test(img.name);
630+
return isImage ? `[Image: ${img.path}]` : `[File: ${img.path}]`;
631+
}).join('\n');
632632
const userText = input.trim();
633633
const text = imagePrefixes
634634
? (userText ? `${imagePrefixes}\n\n${userText}` : imagePrefixes)
@@ -1004,12 +1004,12 @@ export function AgentChat() {
10041004
<input
10051005
ref={fileInputRef}
10061006
type="file"
1007-
accept="image/png,image/jpeg,image/gif,image/webp"
1007+
accept="*/*"
10081008
multiple
10091009
style={{ display: 'none' }}
10101010
onChange={(e) => {
10111011
if (e.target.files) {
1012-
handleImageUpload(Array.from(e.target.files));
1012+
handleFileUpload(Array.from(e.target.files));
10131013
e.target.value = '';
10141014
}
10151015
}}

server/src/routes/upload.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,18 @@ const storage = multer.diskStorage({
2323

2424
const upload = multer({
2525
storage,
26-
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB
27-
fileFilter: (_req, file, cb) => {
28-
const allowed = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
29-
if (allowed.includes(file.mimetype)) {
30-
cb(null, true);
31-
} else {
32-
cb(new Error(`Unsupported file type: ${file.mimetype}. Allowed: ${allowed.join(', ')}`));
33-
}
34-
},
26+
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB
3527
});
3628

3729
export function uploadRoutes(): Router {
3830
const router = Router();
3931

40-
router.post('/', upload.single('image'), (req, res) => {
32+
router.post('/', upload.single('file'), (req, res) => {
4133
if (!req.file) {
42-
res.status(400).json({ error: 'No image file provided' });
34+
res.status(400).json({ error: 'No file provided' });
4335
return;
4436
}
45-
res.json({ path: req.file.path });
37+
res.json({ path: req.file.path, originalName: req.file.originalname, size: req.file.size });
4638
});
4739

4840
return router;

0 commit comments

Comments
 (0)