char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Stores split refactor (#27240)521a64cd0

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