Skip to content

Commit 6830c25

Browse files
committed
feat: Adds converting SVG images to PNG
Ensures that SVG images are converted to PNG format before being stored in the database and displayed in the chat interface. This improves compatibility with systems that may not fully support SVG rendering and addresses potential security concerns related to SVG files. Handles cases where SVG to PNG conversion fails gracefully, preventing application errors.
1 parent 2b1b1f8 commit 6830c25

File tree

2 files changed

+84
-3
lines changed

2 files changed

+84
-3
lines changed

tools/server/webui/src/lib/components/chat/ChatScreen.svelte

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import type { ChatUploadedFile } from '$lib/types/chat.d.ts';
1515
import type { DatabaseMessageExtra } from '$lib/types/database.d.ts';
1616
import { isLikelyTextFile, isTextFileByName, readFileAsText } from '$lib/utils/text-files';
17+
import { svgBase64UrlToPngDataURL, isSvgMimeType } from '$lib/utils/svg-to-png';
1718
1819
let { showCenteredEmpty = false } = $props();
1920
let chatScrollContainer: HTMLDivElement | undefined = $state();
@@ -63,10 +64,23 @@
6364
for (const file of files) {
6465
if (file.type.startsWith('image/')) {
6566
if (file.preview) {
67+
let base64Url = file.preview;
68+
69+
if (isSvgMimeType(file.type)) {
70+
try {
71+
base64Url = await svgBase64UrlToPngDataURL(base64Url);
72+
} catch (error) {
73+
console.error(
74+
'Failed to convert SVG to PNG for database storage:',
75+
error
76+
);
77+
}
78+
}
79+
6680
extras.push({
6781
type: 'imageFile',
6882
name: file.name,
69-
base64Url: file.preview
83+
base64Url
7084
});
7185
}
7286
} else {
@@ -141,8 +155,20 @@
141155
142156
if (file.type.startsWith('image/')) {
143157
const reader = new FileReader();
144-
reader.onload = (e) => {
145-
uploadedFile.preview = e.target?.result as string;
158+
reader.onload = async (e) => {
159+
let preview = e.target?.result as string;
160+
161+
// Convert SVG to PNG if necessary
162+
if (isSvgMimeType(file.type)) {
163+
try {
164+
preview = await svgBase64UrlToPngDataURL(preview);
165+
} catch (error) {
166+
console.error('Failed to convert SVG to PNG:', error);
167+
// Use original SVG preview if conversion fails
168+
}
169+
}
170+
171+
uploadedFile.preview = preview;
146172
uploadedFiles = [...uploadedFiles, uploadedFile];
147173
};
148174
reader.readAsDataURL(file);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
export function svgBase64UrlToPngDataURL(
2+
base64UrlSvg: string,
3+
backgroundColor: string = 'white'
4+
): Promise<string> {
5+
return new Promise((resolve, reject) => {
6+
try {
7+
const img = new Image();
8+
9+
img.onload = () => {
10+
const canvas = document.createElement('canvas');
11+
const ctx = canvas.getContext('2d');
12+
13+
if (!ctx) {
14+
reject(new Error('Failed to get 2D canvas context.'));
15+
return;
16+
}
17+
18+
const targetWidth = img.naturalWidth || 300;
19+
const targetHeight = img.naturalHeight || 300;
20+
21+
canvas.width = targetWidth;
22+
canvas.height = targetHeight;
23+
24+
if (backgroundColor) {
25+
ctx.fillStyle = backgroundColor;
26+
ctx.fillRect(0, 0, canvas.width, canvas.height);
27+
}
28+
ctx.drawImage(img, 0, 0, targetWidth, targetHeight);
29+
30+
resolve(canvas.toDataURL('image/png'));
31+
};
32+
33+
img.onerror = () => {
34+
reject(
35+
new Error('Failed to load SVG image. Ensure the SVG data is valid.')
36+
);
37+
};
38+
39+
img.src = base64UrlSvg;
40+
} catch (error) {
41+
const message = error instanceof Error ? error.message : String(error);
42+
const errorMessage = `Error converting SVG to PNG: ${message}`;
43+
console.error(errorMessage, error);
44+
reject(new Error(errorMessage));
45+
}
46+
});
47+
}
48+
49+
export function isSvgFile(file: File): boolean {
50+
return file.type === 'image/svg+xml';
51+
}
52+
53+
export function isSvgMimeType(mimeType: string): boolean {
54+
return mimeType === 'image/svg+xml';
55+
}

0 commit comments

Comments
 (0)