1import { isSvgMimeType, svgBase64UrlToPngDataURL } from './svg-to-png';
2import { isWebpMimeType, webpBase64UrlToPngDataURL } from './webp-to-png';
3import { FileTypeCategory } from '$lib/enums';
4import { modelsStore } from '$lib/stores/models.svelte';
5import { settingsStore } from '$lib/stores/settings.svelte';
6import { toast } from 'svelte-sonner';
7import { getFileTypeCategory } from '$lib/utils';
8import { convertPDFToText } from './pdf-processing';
9
10/**
11 * Read a file as a data URL (base64 encoded)
12 * @param file - The file to read
13 * @returns Promise resolving to the data URL string
14 */
15function readFileAsDataURL(file: File): Promise<string> {
16 return new Promise((resolve, reject) => {
17 const reader = new FileReader();
18 reader.onload = () => resolve(reader.result as string);
19 reader.onerror = () => reject(reader.error);
20 reader.readAsDataURL(file);
21 });
22}
23
24/**
25 * Read a file as UTF-8 text
26 * @param file - The file to read
27 * @returns Promise resolving to the text content
28 */
29function readFileAsUTF8(file: File): Promise<string> {
30 return new Promise((resolve, reject) => {
31 const reader = new FileReader();
32 reader.onload = () => resolve(reader.result as string);
33 reader.onerror = () => reject(reader.error);
34 reader.readAsText(file);
35 });
36}
37
38/**
39 * Process uploaded files into ChatUploadedFile format with previews and content
40 *
41 * This function processes various file types and generates appropriate previews:
42 * - Images: Base64 data URLs with format normalization (SVG/WebP → PNG)
43 * - Text files: UTF-8 content extraction
44 * - PDFs: Metadata only (processed later in conversion pipeline)
45 * - Audio: Base64 data URLs for preview
46 *
47 * @param files - Array of File objects to process
48 * @returns Promise resolving to array of ChatUploadedFile objects
49 */
50export async function processFilesToChatUploaded(
51 files: File[],
52 activeModelId?: string
53): Promise<ChatUploadedFile[]> {
54 const results: ChatUploadedFile[] = [];
55
56 for (const file of files) {
57 const id = Date.now().toString() + Math.random().toString(36).substr(2, 9);
58 const base: ChatUploadedFile = {
59 id,
60 name: file.name,
61 size: file.size,
62 type: file.type,
63 file
64 };
65
66 try {
67 if (getFileTypeCategory(file.type) === FileTypeCategory.IMAGE) {
68 let preview = await readFileAsDataURL(file);
69
70 // Normalize SVG and WebP to PNG in previews
71 if (isSvgMimeType(file.type)) {
72 try {
73 preview = await svgBase64UrlToPngDataURL(preview);
74 } catch (err) {
75 console.error('Failed to convert SVG to PNG:', err);
76 }
77 } else if (isWebpMimeType(file.type)) {
78 try {
79 preview = await webpBase64UrlToPngDataURL(preview);
80 } catch (err) {
81 console.error('Failed to convert WebP to PNG:', err);
82 }
83 }
84
85 results.push({ ...base, preview });
86 } else if (getFileTypeCategory(file.type) === FileTypeCategory.PDF) {
87 // Extract text content from PDF for preview
88 try {
89 const textContent = await convertPDFToText(file);
90 results.push({ ...base, textContent });
91 } catch (err) {
92 console.warn('Failed to extract text from PDF, adding without content:', err);
93 results.push(base);
94 }
95
96 // Show suggestion toast if vision model is available but PDF as image is disabled
97 const hasVisionSupport = activeModelId
98 ? modelsStore.modelSupportsVision(activeModelId)
99 : false;
100 const currentConfig = settingsStore.config;
101 if (hasVisionSupport && !currentConfig.pdfAsImage) {
102 toast.info(`You can enable parsing PDF as images with vision models.`, {
103 duration: 8000,
104 action: {
105 label: 'Enable PDF as Images',
106 onClick: () => {
107 settingsStore.updateConfig('pdfAsImage', true);
108 toast.success('PDF parsing as images enabled!', {
109 duration: 3000
110 });
111 }
112 }
113 });
114 }
115 } else if (getFileTypeCategory(file.type) === FileTypeCategory.AUDIO) {
116 // Generate preview URL for audio files
117 const preview = await readFileAsDataURL(file);
118 results.push({ ...base, preview });
119 } else {
120 // Fallback: treat unknown files as text
121 try {
122 const textContent = await readFileAsUTF8(file);
123 results.push({ ...base, textContent });
124 } catch (err) {
125 console.warn('Failed to read file as text, adding without content:', err);
126 results.push(base);
127 }
128 }
129 } catch (error) {
130 console.error('Error processing file', file.name, error);
131 results.push(base);
132 }
133 }
134
135 return results;
136}