mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(files): align upload route image extensions with picker (#4423)
* fix(files): align upload route image extensions with picker * fix(files): widen image mime mapping for chat/profile/logo uploads * fix(files): fall back to extension when MIME is generic for image-only uploads Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): add new image extensions to EXTENSION_TO_MIME map Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): skip non-Claude image MIMEs in createFileContent Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): resolve content type for image-only contexts when MIME is generic Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(files): cover image/jpg in Claude gate, reverse MIME map, audit metadata Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
14c93f6305
commit
0f09310f90
@@ -13,11 +13,12 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import type { StorageContext } from '@/lib/uploads/config'
|
||||
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { isImageFileType } from '@/lib/uploads/utils/file-utils'
|
||||
import { isImageFileType, resolveFileType } from '@/lib/uploads/utils/file-utils'
|
||||
import {
|
||||
SUPPORTED_AUDIO_EXTENSIONS,
|
||||
SUPPORTED_CODE_EXTENSIONS,
|
||||
SUPPORTED_DOCUMENT_EXTENSIONS,
|
||||
SUPPORTED_IMAGE_EXTENSIONS,
|
||||
SUPPORTED_VIDEO_EXTENSIONS,
|
||||
validateFileType,
|
||||
} from '@/lib/uploads/utils/validation'
|
||||
@@ -28,12 +29,10 @@ import {
|
||||
InvalidRequestError,
|
||||
} from '@/app/api/files/utils'
|
||||
|
||||
const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'] as const
|
||||
|
||||
const ALLOWED_EXTENSIONS = new Set<string>([
|
||||
...SUPPORTED_DOCUMENT_EXTENSIONS,
|
||||
...SUPPORTED_CODE_EXTENSIONS,
|
||||
...IMAGE_EXTENSIONS,
|
||||
...SUPPORTED_IMAGE_EXTENSIONS,
|
||||
...SUPPORTED_AUDIO_EXTENSIONS,
|
||||
...SUPPORTED_VIDEO_EXTENSIONS,
|
||||
])
|
||||
@@ -305,10 +304,17 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
context === 'profile-pictures' ||
|
||||
context === 'workspace-logos'
|
||||
) {
|
||||
if (context !== 'copilot' && !isImageFileType(file.type)) {
|
||||
throw new InvalidRequestError(
|
||||
`Only image files (JPEG, PNG, GIF, WebP, SVG) are allowed for ${context} uploads`
|
||||
if (context !== 'copilot') {
|
||||
const mimeType = file.type
|
||||
const isGenericMime = !mimeType || mimeType === 'application/octet-stream'
|
||||
const extension = originalName.split('.').pop()?.toLowerCase() ?? ''
|
||||
const extensionIsImage = (SUPPORTED_IMAGE_EXTENSIONS as readonly string[]).includes(
|
||||
extension
|
||||
)
|
||||
const isImage = isGenericMime ? extensionIsImage : isImageFileType(mimeType)
|
||||
if (!isImage) {
|
||||
throw new InvalidRequestError(`Only image files are allowed for ${context} uploads`)
|
||||
}
|
||||
}
|
||||
|
||||
if (context === 'workspace-logos') {
|
||||
@@ -344,6 +350,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
|
||||
logger.info(`Uploading ${context} file: ${originalName}`)
|
||||
|
||||
const resolvedContentType = resolveFileType({ type: file.type, name: originalName })
|
||||
|
||||
const timestamp = Date.now()
|
||||
const safeFileName = sanitizeFileName(originalName)
|
||||
const storageKey = `${context}/${timestamp}-${safeFileName}`
|
||||
@@ -362,7 +370,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
const fileInfo = await storageService.uploadFile({
|
||||
file: buffer,
|
||||
fileName: storageKey,
|
||||
contentType: file.type,
|
||||
contentType: resolvedContentType,
|
||||
context,
|
||||
preserveKey: true,
|
||||
customKey: storageKey,
|
||||
@@ -379,7 +387,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
key: fileInfo.key,
|
||||
name: originalName,
|
||||
size: buffer.length,
|
||||
type: file.type,
|
||||
type: resolvedContentType,
|
||||
},
|
||||
directUploadSupported: false,
|
||||
}
|
||||
@@ -400,7 +408,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
fileName: originalName,
|
||||
fileKey: fileInfo.key,
|
||||
fileSize: buffer.length,
|
||||
fileType: file.type,
|
||||
fileType: resolvedContentType,
|
||||
},
|
||||
request,
|
||||
})
|
||||
|
||||
@@ -33,6 +33,13 @@ export const MIME_TYPE_MAPPING: Record<string, 'image' | 'document' | 'audio' |
|
||||
'image/gif': 'image',
|
||||
'image/webp': 'image',
|
||||
'image/svg+xml': 'image', // SVG upload is allowed; createFileContent handles it separately for Claude API
|
||||
'image/bmp': 'image',
|
||||
'image/tiff': 'image',
|
||||
'image/heic': 'image',
|
||||
'image/heif': 'image',
|
||||
'image/avif': 'image',
|
||||
'image/x-icon': 'image',
|
||||
'image/vnd.microsoft.icon': 'image',
|
||||
|
||||
// Documents
|
||||
'application/pdf': 'document',
|
||||
@@ -158,6 +165,10 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
|
||||
return null
|
||||
}
|
||||
|
||||
if (contentType === 'image' && !CLAUDE_SUPPORTED_IMAGE_MIME_TYPES.has(mimeType.toLowerCase())) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
type: contentType,
|
||||
source: {
|
||||
@@ -168,6 +179,14 @@ export function createFileContent(fileBuffer: Buffer, mimeType: string): Message
|
||||
}
|
||||
}
|
||||
|
||||
const CLAUDE_SUPPORTED_IMAGE_MIME_TYPES = new Set([
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
])
|
||||
|
||||
/**
|
||||
* Extract file extension from filename
|
||||
*/
|
||||
@@ -184,6 +203,13 @@ const EXTENSION_TO_MIME: Record<string, string> = {
|
||||
gif: 'image/gif',
|
||||
webp: 'image/webp',
|
||||
svg: 'image/svg+xml',
|
||||
bmp: 'image/bmp',
|
||||
tif: 'image/tiff',
|
||||
tiff: 'image/tiff',
|
||||
heic: 'image/heic',
|
||||
heif: 'image/heif',
|
||||
avif: 'image/avif',
|
||||
ico: 'image/x-icon',
|
||||
|
||||
// Documents
|
||||
pdf: 'application/pdf',
|
||||
@@ -339,6 +365,13 @@ const MIME_TO_EXTENSION: Record<string, string> = {
|
||||
'image/gif': 'gif',
|
||||
'image/webp': 'webp',
|
||||
'image/svg+xml': 'svg',
|
||||
'image/bmp': 'bmp',
|
||||
'image/tiff': 'tiff',
|
||||
'image/heic': 'heic',
|
||||
'image/heif': 'heif',
|
||||
'image/avif': 'avif',
|
||||
'image/x-icon': 'ico',
|
||||
'image/vnd.microsoft.icon': 'ico',
|
||||
|
||||
// Documents
|
||||
'application/pdf': 'pdf',
|
||||
|
||||
Reference in New Issue
Block a user