@@ -37,19 +37,26 @@ export function useChatExtraContext(): ChatExtraContextApi {
3737 break ;
3838 }
3939
40- if ( mimeType . startsWith ( 'image/' ) && mimeType !== 'image/svg+xml' ) {
40+ if ( mimeType . startsWith ( 'image/' ) ) {
4141 if ( ! serverProps ?. modalities ?. vision ) {
4242 toast . error ( 'Multimodal is not supported by this server or model.' ) ;
4343 break ;
4444 }
4545 const reader = new FileReader ( ) ;
46- reader . onload = ( event ) => {
46+ reader . onload = async ( event ) => {
4747 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+
4855 addItems ( [
4956 {
5057 type : 'imageFile' ,
5158 name : file . name ,
52- base64Url : event . target . result as string ,
59+ base64Url,
5360 } ,
5461 ] ) ;
5562 }
@@ -172,3 +179,71 @@ export function isLikelyNotBinary(str: string): boolean {
172179 const ratio = suspiciousCharCount / sampleLength ;
173180 return ratio <= options . suspiciousCharThresholdRatio ;
174181}
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