/** * File validation utilities based on model modalities * Ensures only compatible file types are processed based on model capabilities */ import { getFileTypeCategory } from '$lib/utils/file-type'; import { supportsVision, supportsAudio } from '$lib/stores/server.svelte'; import { FileExtensionAudio, FileExtensionImage, FileExtensionPdf, FileExtensionText, MimeTypeAudio, MimeTypeImage, MimeTypeApplication, MimeTypeText, FileTypeCategory } from '$lib/enums/files'; /** * Check if a file type is supported by the current model's modalities * @param filename - The filename to check * @param mimeType - The MIME type of the file * @returns true if the file type is supported by the current model */ export function isFileTypeSupportedByModel(filename: string, mimeType?: string): boolean { const category = mimeType ? getFileTypeCategory(mimeType) : null; // If we can't determine the category from MIME type, fall back to general support check if (!category) { // For unknown types, only allow if they might be text files // This is a conservative approach for edge cases return true; // Let the existing isFileTypeSupported handle this } switch (category) { case FileTypeCategory.TEXT: // Text files are always supported return true; case FileTypeCategory.PDF: // PDFs are always supported (will be processed as text for non-vision models) return true; case FileTypeCategory.IMAGE: // Images require vision support return supportsVision(); case FileTypeCategory.AUDIO: // Audio files require audio support return supportsAudio(); default: // Unknown categories - be conservative and allow return true; } } /** * Filter files based on model modalities and return supported/unsupported lists * @param files - Array of files to filter * @returns Object with supportedFiles and unsupportedFiles arrays */ export function filterFilesByModalities(files: File[]): { supportedFiles: File[]; unsupportedFiles: File[]; modalityReasons: Record; } { const supportedFiles: File[] = []; const unsupportedFiles: File[] = []; const modalityReasons: Record = {}; const hasVision = supportsVision(); const hasAudio = supportsAudio(); for (const file of files) { const category = getFileTypeCategory(file.type); let isSupported = true; let reason = ''; switch (category) { case FileTypeCategory.IMAGE: if (!hasVision) { isSupported = false; reason = 'Images require a vision-capable model'; } break; case FileTypeCategory.AUDIO: if (!hasAudio) { isSupported = false; reason = 'Audio files require an audio-capable model'; } break; case FileTypeCategory.TEXT: case FileTypeCategory.PDF: // Always supported break; default: // For unknown types, check if it's a generally supported file type // This handles edge cases and maintains backward compatibility break; } if (isSupported) { supportedFiles.push(file); } else { unsupportedFiles.push(file); modalityReasons[file.name] = reason; } } return { supportedFiles, unsupportedFiles, modalityReasons }; } /** * Generate a user-friendly error message for unsupported files * @param unsupportedFiles - Array of unsupported files * @param modalityReasons - Reasons why files are unsupported * @returns Formatted error message */ export function generateModalityErrorMessage( unsupportedFiles: File[], modalityReasons: Record ): string { if (unsupportedFiles.length === 0) return ''; const hasVision = supportsVision(); const hasAudio = supportsAudio(); let message = ''; if (unsupportedFiles.length === 1) { const file = unsupportedFiles[0]; const reason = modalityReasons[file.name]; message = `The file "${file.name}" cannot be uploaded: ${reason}.`; } else { const fileNames = unsupportedFiles.map((f) => f.name).join(', '); message = `The following files cannot be uploaded: ${fileNames}.`; } // Add helpful information about what is supported const supportedTypes: string[] = ['text files', 'PDFs']; if (hasVision) supportedTypes.push('images'); if (hasAudio) supportedTypes.push('audio files'); message += ` This model supports: ${supportedTypes.join(', ')}.`; return message; } /** * Generate file input accept string based on current model modalities * @returns Accept string for HTML file input element */ export function generateModalityAwareAcceptString(): string { const hasVision = supportsVision(); const hasAudio = supportsAudio(); const acceptedExtensions: string[] = []; const acceptedMimeTypes: string[] = []; // Always include text files and PDFs acceptedExtensions.push(...Object.values(FileExtensionText)); acceptedMimeTypes.push(...Object.values(MimeTypeText)); acceptedExtensions.push(...Object.values(FileExtensionPdf)); acceptedMimeTypes.push(...Object.values(MimeTypeApplication)); // Include images only if vision is supported if (hasVision) { acceptedExtensions.push(...Object.values(FileExtensionImage)); acceptedMimeTypes.push(...Object.values(MimeTypeImage)); } // Include audio only if audio is supported if (hasAudio) { acceptedExtensions.push(...Object.values(FileExtensionAudio)); acceptedMimeTypes.push(...Object.values(MimeTypeAudio)); } return [...acceptedExtensions, ...acceptedMimeTypes].join(','); }