agent-ecosystem/src/shared/constants/attachments.ts
iliya b20b69066e feat(attachments): expand file support and enhance attachment handling
- 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.
2026-03-23 17:24:48 +02:00

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);
}