@@ -37,19 +37,26 @@ export function useChatExtraContext(): ChatExtraContextApi {
37
37
break ;
38
38
}
39
39
40
- if ( mimeType . startsWith ( 'image/' ) && mimeType !== 'image/svg+xml' ) {
40
+ if ( mimeType . startsWith ( 'image/' ) ) {
41
41
if ( ! serverProps ?. modalities ?. vision ) {
42
42
toast . error ( 'Multimodal is not supported by this server or model.' ) ;
43
43
break ;
44
44
}
45
45
const reader = new FileReader ( ) ;
46
- reader . onload = ( event ) => {
46
+ reader . onload = async ( event ) => {
47
47
if ( event . target ?. result ) {
48
+ let base64Url = event . target . result as string ;
49
+
50
+ if ( mimeType === 'image/svg+xml' ) {
51
+ // Convert SVG to PNG
52
+ base64Url = await svgBase64UrlToPngDataURL ( base64Url ) ;
53
+ }
54
+
48
55
addItems ( [
49
56
{
50
57
type : 'imageFile' ,
51
58
name : file . name ,
52
- base64Url : event . target . result as string ,
59
+ base64Url,
53
60
} ,
54
61
] ) ;
55
62
}
@@ -172,3 +179,71 @@ export function isLikelyNotBinary(str: string): boolean {
172
8000
179
const ratio = suspiciousCharCount / sampleLength ;
173
180
return ratio <= options . suspiciousCharThresholdRatio ;
174
181
}
182
+
183
+ // WARN: vibe code below
184
+ // Converts a Base64URL encoded SVG string to a PNG Data URL using browser Canvas API.
185
+ function svgBase64UrlToPngDataURL ( base64UrlSvg : string ) : Promise < string > {
186
+ const backgroundColor = 'white' ; // Default background color for PNG
187
+
188
+ return new Promise ( ( resolve , reject ) => {
189
+ try {
190
+ // 1. Convert Base64URL to standard Base64, then decode to SVG string
191
+ let base64 = base64UrlSvg
192
+ . split ( ',' )
193
+ . pop ( ) !
194
+ . replace ( / - / g, '+' )
195
+ . replace ( / _ / g, '/' ) ;
196
+ const padding = base64 . length % 4 ;
197
+ if ( padding ) {
198
+ base64 += '=' . repeat ( 4 - padding ) ;
199
+ }
200
+ const svgString = atob ( base64 ) ;
201
+
202
+ const img = new Image ( ) ;
203
+
204
+ img . onload = ( ) => {
205
+ const canvas = document . createElement ( 'canvas' ) ;
206
+ const ctx = canvas . getContext ( '2d' ) ;
207
+
208
+ if ( ! ctx ) {
209
+ reject ( new Error ( 'Failed to get 2D canvas context.' ) ) ;
210
+ return ;
211
+ }
212
+
213
+ // Use provided dimensions or SVG's natural dimensions, with fallbacks
214
+ // Fallbacks (e.g., 300x300) are for SVGs without explicit width/height
215
+ // or when naturalWidth/Height might be 0 before full processing.
216
+ const targetWidth = img . naturalWidth || 300 ;
217
+ const targetHeight = img . naturalHeight || 300 ;
218
+
219
+ canvas . width = targetWidth ;
220
+ canvas . height = targetHeight ;
221
+
222
+ if ( backgroundColor ) {
223
+ ctx . fillStyle = backgroundColor ;
224
+ ctx . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
225
+ }
226
+
227
+ ctx . drawImage ( img , 0 , 0 , targetWidth , targetHeight ) ;
228
+ resolve ( canvas . toDataURL ( 'image/png' ) ) ;
229
+ } ;
230
+
231
+ img . onerror = ( ) => {
232
+ reject (
233
+ new Error ( 'Failed to load SVG image. Ensure the SVG data is valid.' )
234
+ ) ;
235
+ } ;
236
+
237
+ // 2. Load SVG string into an Image element.
238
+ // The SVG string must be re-encoded to standard Base64 for the data URL.
239
+ // Alternatively, for non-Base64 SVG in data URL:
240
+ // img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgString);
241
+ img . src = 'data:image/svg+xml;base64,' + btoa ( svgString ) ;
242
+ } catch ( error ) {
243
+ const message = error instanceof Error ? error . message : String ( error ) ;
244
+ const errorMessage = `Error converting SVG to PNG: ${ message } ` ;
245
+ toast . error ( errorMessage ) ;
246
+ reject ( new Error ( errorMessage ) ) ;
247
+ }
248
+ } ) ;
249
+ }
0 commit comments