char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
521a64cd0
char/aliceai-t5-moe
1import { convertPDFToImage , convertPDFToText } from './pdf-processing' ; 2import { isSvgMimeType , svgBase64UrlToPngDataURL } from './svg-to-png' ; 3import { isLikelyTextFile , readFileAsText } from './text-files' ; 4import { isWebpMimeType , webpBase64UrlToPngDataURL } from './webp-to-png' ; 5import { SETTINGS_KEYS } from '$lib/constants' ; 6import { AttachmentType , FileTypeCategory , SpecialFileType } from '$lib/enums' ; 7import { modelsStore } from '$lib/stores/models/index.svelte' ; 8import { settingsStore } from '$lib/stores/settings/index.svelte' ; 9import type { ChatUploadedFile , DatabaseMessageExtra , FileProcessingResult } from '$lib/types' ; 10import { getFileTypeCategory } from '$lib/utils' ; 11import { toast } from 'svelte-sonner' ; 12 13function readFileAsBase64 ( file :File ) :Promise < string > { 14return new Promise (( resolve , reject ) => { 15const reader = new FileReader (); 16 17reader . onload = () => { 18// Extract base64 data without the data URL prefix 19const dataUrl = reader . result as string ; 20const base64 = dataUrl . split ( ',' )[ 1 ]; 21 22resolve ( base64 ); 23}; 24 25reader . onerror = () => reject ( reader . error ); 26 27reader . readAsDataURL ( file ); 28}); 29} 30 31export async function parseFilesToMessageExtras ( 32files :ChatUploadedFile [], 33activeModelId ?:string 34) :Promise < FileProcessingResult > { 35const extras :DatabaseMessageExtra [] = []; 36const emptyFiles :string [] = []; 37 38for ( const file of files ) { 39if ( file . type === SpecialFileType . MCP_PROMPT && file . mcpPrompt ) { 40extras . push ({ 41arguments :file . mcpPrompt . arguments , 42content :file . textContent ?? '' , 43name :file . name , 44promptName :file . mcpPrompt . promptName , 45serverName :file . mcpPrompt . serverName , 46size :file . size , 47type :AttachmentType . MCP_PROMPT 48}); 49 50continue ; 51} 52 53if ( getFileTypeCategory ( file . type ) === FileTypeCategory . IMAGE ) { 54if ( file . preview ) { 55let base64Url = file . preview ; 56 57if ( isSvgMimeType ( file . type )) { 58try { 59base64Url = await svgBase64UrlToPngDataURL ( base64Url ); 60} catch ( error ) { 61console . error ( 'Failed to convert SVG to PNG for database storage:' , error ); 62} 63} else if ( isWebpMimeType ( file . type )) { 64try { 65base64Url = await webpBase64UrlToPngDataURL ( base64Url ); 66} catch ( error ) { 67console . error ( 'Failed to convert WebP to PNG for database storage:' , error ); 68} 69} 70 71extras . push ({ 72 base64Url, 73name :file . name , 74size :file . size , 75type :AttachmentType . IMAGE 76}); 77} 78} else if ( getFileTypeCategory ( file . type ) === FileTypeCategory . AUDIO ) { 79// Process audio files (MP3 and WAV) 80try { 81const base64Data = await readFileAsBase64 ( file . file ); 82 83extras . push ({ 84base64Data :base64Data , 85mimeType :file . type , 86name :file . name , 87size :file . size , 88type :AttachmentType . AUDIO 89}); 90} catch ( error ) { 91console . error ( `Failed to process audio file ${ file . name } :` , error ); 92} 93} else if ( getFileTypeCategory ( file . type ) === FileTypeCategory . VIDEO ) { 94// Process video files (MP4, etc) 95try { 96const base64Data = await readFileAsBase64 ( file . file ); 97 98extras . push ({ 99base64Data :base64Data , 100mimeType :file . type , 101name :file . name , 102size :file . size , 103type :AttachmentType . VIDEO 104}); 105} catch ( error ) { 106console . error ( `Failed to process video file ${ file . name } :` , error ); 107} 108} else if ( getFileTypeCategory ( file . type ) === FileTypeCategory . ) { 109try { 110// Always get base64 data for preview functionality 111const base64Data = await readFileAsBase64 ( file . file ); 112const currentConfig = settingsStore . config ; 113// Use per-model vision check for router mode 114const hasVisionSupport = activeModelId 115 ?modelsStore . props . modelSupportsVision ( activeModelId ) 116 :false ; 117 118// Force PDF-to-text for non-vision models 119let shouldProcessAsImages = Boolean ( currentConfig . pdfAsImage ) && hasVisionSupport ; 120 121// If user had pdfAsImage enabled but model doesn't support vision, update setting and notify 122if ( currentConfig . pdfAsImage && ! hasVisionSupport ) { 123console . log ( 'Non-vision model detected: forcing PDF-to-text mode and updating settings' ); 124 125// Update the setting in localStorage 126settingsStore . updateConfig ( SETTINGS_KEYS . PDF_AS_IMAGE , false ); 127 128// Show toast notification to user 129toast . warning ( 130'PDF setting changed: Non-vision model detected, PDFs will be processed as text instead of images.' , 131{ 132duration :5000 133} 134); 135 136shouldProcessAsImages = false ; 137} 138 139if ( shouldProcessAsImages ) { 140// Process PDF as images (only for vision models) 141try { 142const images = await convertPDFToImage ( file . file ); 143 144// Show success toast for PDF image processing 145toast . success ( 146`PDF " ${ file . name } " processed as ${ images . length } images for vision model.` , 147{ 148duration :3000 149} 150); 151 152extras . push ({ 153base64Data :base64Data , 154content :`PDF file with ${ images . length } pages` , 155images :images , 156name :file . name , 157processedAsImages :true , 158size :file . size , 159type :AttachmentType . }); 161} catch ( imageError ) { 162console . warn ( 163`Failed to process PDF ${ file . name } as images, falling back to text:` , 164imageError 165); 166 167// Fallback to text processing 168const content = await convertPDFToText ( file . file ); 169 170extras . push ({ 171base64Data :base64Data , 172content :content , 173name :file . name , 174processedAsImages :false , 175size :file . size , 176type :AttachmentType . }); 178} 179} else { 180// Process PDF as text (default or forced for non-vision models) 181const content = await convertPDFToText ( file . file ); 182 183// Show success toast for PDF text processing 184toast . success ( `PDF " ${ file . name } " processed as text content.` , { 185duration :3000 186}); 187 188extras . push ({ 189base64Data :base64Data , 190content :content , 191name :file . name , 192processedAsImages :false , 193size :file . size , 194type :AttachmentType . }); 196} 197} catch ( error ) { 198console . error ( `Failed to process PDF file ${ file . name } :` , error ); 199} 200} else { 201try { 202const content = await readFileAsText ( file . file ); 203 204// Check if file is empty 205if ( content . trim () === '' ) { 206console . warn ( `File ${ file . name } is empty and will be skipped` ); 207emptyFiles . push ( file . name ); 208} else if ( isLikelyTextFile ( content )) { 209extras . push ({ 210content :content , 211name :file . name , 212size :file . size , 213type :AttachmentType . TEXT 214}); 215} else { 216console . warn ( `File ${ file . name } appears to be binary and will be skipped` ); 217} 218} catch ( error ) { 219console . error ( `Failed to read file ${ file . name } :` , error ); 220} 221} 222} 223 224return { emptyFiles, extras}; 225}