- Added support for additional attachment types: PDF and plain text. - Updated the TeamProvisioningService to handle new attachment types with appropriate content blocks. - Enhanced AttachmentDisplay and AttachmentPreview components to differentiate between image and non-image files. - Modified DropZoneOverlay and SendMessageDialog to reflect changes in file handling and messaging. - Improved user experience by allowing file previews for non-image attachments and updating error messages accordingly. - Refactored attachment validation logic to categorize unsupported files and handle them gracefully.
175 lines
4.4 KiB
TypeScript
175 lines
4.4 KiB
TypeScript
/**
|
|
* Attachment file categorization and MIME type helpers.
|
|
*
|
|
* Browser MIME types are unreliable:
|
|
* .ts → "video/mp2t", .json → "application/json", .go/.rs/.yaml → ""
|
|
* So categorization is ALWAYS by file extension (primary), with browser MIME
|
|
* used only as a fallback for images.
|
|
*/
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Constants
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export const IMAGE_MIME_TYPES = new Set(['image/png', 'image/jpeg', 'image/gif', 'image/webp']);
|
|
|
|
/** Extensions recognized as image files (fallback when browser MIME is empty). */
|
|
const IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp']);
|
|
|
|
/** Extensions recognized as text-based files → sent as `document` block with `text/plain`. */
|
|
export const TEXT_FILE_EXTENSIONS = new Set([
|
|
// Data
|
|
'json',
|
|
'jsonl',
|
|
'txt',
|
|
'md',
|
|
'mdx',
|
|
'csv',
|
|
'tsv',
|
|
// JavaScript / TypeScript
|
|
'ts',
|
|
'tsx',
|
|
'js',
|
|
'jsx',
|
|
'mjs',
|
|
'cjs',
|
|
// Other languages
|
|
'py',
|
|
'go',
|
|
'rs',
|
|
'java',
|
|
'kt',
|
|
'rb',
|
|
'c',
|
|
'h',
|
|
'cpp',
|
|
'hpp',
|
|
'cs',
|
|
'swift',
|
|
'dart',
|
|
'php',
|
|
'lua',
|
|
'scala',
|
|
'ex',
|
|
'exs',
|
|
// Web
|
|
'html',
|
|
'css',
|
|
'scss',
|
|
'less',
|
|
'vue',
|
|
'svelte',
|
|
// Config / markup
|
|
'xml',
|
|
'yaml',
|
|
'yml',
|
|
'toml',
|
|
'ini',
|
|
'cfg',
|
|
'conf',
|
|
// Shell
|
|
'sh',
|
|
'bash',
|
|
'zsh',
|
|
'fish',
|
|
// Query / schema
|
|
'sql',
|
|
'graphql',
|
|
'gql',
|
|
'proto',
|
|
// Misc text
|
|
'env',
|
|
'log',
|
|
'rst',
|
|
'diff',
|
|
'patch',
|
|
// Known filenames that happen to equal their "extension" when split on '.'
|
|
'dockerfile',
|
|
'makefile',
|
|
'gitignore',
|
|
'dockerignore',
|
|
'editorconfig',
|
|
]);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type FileCategory = 'image' | 'pdf' | 'text' | 'unsupported';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Categorization
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Categorize a `File` by its **extension** (primary) — browser MIME is
|
|
* unreliable for anything other than images.
|
|
*/
|
|
export function categorizeFile(file: File): FileCategory {
|
|
// 1. Browser MIME is reliable for images
|
|
if (IMAGE_MIME_TYPES.has(file.type)) return 'image';
|
|
|
|
const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
|
|
|
|
// 2. Extension-based checks
|
|
if (IMAGE_EXTENSIONS.has(ext)) return 'image'; // fallback for empty MIME
|
|
if (ext === 'pdf') return 'pdf';
|
|
if (TEXT_FILE_EXTENSIONS.has(ext)) return 'text';
|
|
|
|
// 3. Special filenames / patterns
|
|
const baseName = file.name.toLowerCase();
|
|
if (baseName.startsWith('.env')) return 'text'; // .env.local, .env.production, etc.
|
|
|
|
return 'unsupported';
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MIME helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const IMAGE_EXT_TO_MIME: Record<string, string> = {
|
|
png: 'image/png',
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
gif: 'image/gif',
|
|
webp: 'image/webp',
|
|
};
|
|
|
|
/**
|
|
* Return the MIME type that should be stored in `AttachmentPayload.mimeType`
|
|
* (used by the backend to choose the correct content block type).
|
|
*/
|
|
export function getEffectiveMimeType(file: File): string {
|
|
const cat = categorizeFile(file);
|
|
|
|
if (cat === 'image') {
|
|
if (file.type && IMAGE_MIME_TYPES.has(file.type)) return file.type;
|
|
// Fallback when browser returns empty MIME for an image extension
|
|
const ext = file.name.split('.').pop()?.toLowerCase() ?? '';
|
|
return IMAGE_EXT_TO_MIME[ext] ?? 'image/png';
|
|
}
|
|
if (cat === 'pdf') return 'application/pdf';
|
|
if (cat === 'text') return 'text/plain';
|
|
|
|
return file.type || 'application/octet-stream';
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MIME type guards (used by backend routing & preview components)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function isImageMime(mime: string): boolean {
|
|
return IMAGE_MIME_TYPES.has(mime);
|
|
}
|
|
|
|
export function isPdfMime(mime: string): boolean {
|
|
return mime === 'application/pdf';
|
|
}
|
|
|
|
export function isTextDocMime(mime: string): boolean {
|
|
return mime === 'text/plain';
|
|
}
|
|
|
|
export function isNativeAttachmentMime(mime: string): boolean {
|
|
return isImageMime(mime) || isPdfMime(mime) || isTextDocMime(mime);
|
|
}
|