@@ -55,23 +55,6 @@ export class DeepnoteDataConverter {
5555 this . registry . register ( new VisualizationBlockConverter ( ) ) ;
5656 }
5757
58- /**
59- * Initialize async dependencies like vega-lite.
60- * Must be called before using output conversion methods.
61- */
62- async initialize ( ) : Promise < void > {
63- await ensureVegaLiteLoaded ( ) ;
64- }
65-
66- /**
67- * Finds a converter for the given block type.
68- * @param blockType The type of block to find a converter for
69- * @returns The converter if found, undefined otherwise
70- */
71- public findConverter ( blockType : string ) : BlockConverter | undefined {
72- return this . registry . findConverter ( blockType ) ;
73- }
74-
7558 /**
7659 * Converts Deepnote blocks to VS Code notebook cells.
7760 * Sorts blocks by sortingKey before conversion to maintain proper order.
@@ -178,135 +161,24 @@ export class DeepnoteDataConverter {
178161 return cells . map ( ( cell , index ) => this . convertCellToBlock ( cell , index ) ) ;
179162 }
180163
181- private base64ToUint8Array ( base64 : string ) : Uint8Array {
182- const binaryString = atob ( base64 ) ;
183- const bytes = new Uint8Array ( binaryString . length ) ;
184-
185- for ( let i = 0 ; i < binaryString . length ; i ++ ) {
186- bytes [ i ] = binaryString . charCodeAt ( i ) ;
187- }
188-
189- return bytes ;
190- }
191-
192- private createFallbackBlock ( cell : NotebookCellData , index : number ) : DeepnoteBlock {
193- return {
194- blockGroup : uuidUtils . generateUuid ( ) ,
195- id : generateBlockId ( ) ,
196- sortingKey : generateSortingKey ( index ) ,
197- type : cell . kind === NotebookCellKind . Code ? 'code' : 'markdown' ,
198- content : cell . value || '' ,
199- metadata : { }
200- } ;
201- }
202-
203- private createFallbackCell ( block : DeepnoteBlock ) : NotebookCellData {
204- const cell = new NotebookCellData ( NotebookCellKind . Markup , block . content || '' , 'markdown' ) ;
205-
206- cell . metadata = {
207- deepnoteBlockId : block . id ,
208- deepnoteBlockType : block . type ,
209- deepnoteSortingKey : block . sortingKey ,
210- deepnoteMetadata : block . metadata
211- } ;
212-
213- return cell ;
164+ /**
165+ * Finds a converter for the given block type.
166+ * @param blockType The type of block to find a converter for
167+ * @returns The converter if found, undefined otherwise
168+ */
169+ public findConverter ( blockType : string ) : BlockConverter | undefined {
170+ return this . registry . findConverter ( blockType ) ;
214171 }
215172
216- private transformOutputsForDeepnote ( outputs : NotebookCellOutput [ ] ) : DeepnoteOutput [ ] {
217- return outputs . map ( ( output ) => {
218- // Check if this is an error output
219- const errorItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.error' ) ;
220-
221- if ( errorItem ) {
222- try {
223- const errorData = JSON . parse ( new TextDecoder ( ) . decode ( errorItem . data ) ) ;
224-
225- return {
226- ename : errorData . name || 'Error' ,
227- evalue : errorData . message || '' ,
228- output_type : 'error' ,
229- traceback : errorData . stack ? errorData . stack . split ( '\n' ) : [ ]
230- } as DeepnoteOutput ;
231- } catch {
232- return {
233- ename : 'Error' ,
234- evalue : '' ,
235- output_type : 'error' ,
236- traceback : [ ]
237- } as DeepnoteOutput ;
238- }
239- }
240-
241- // Check if this is a stream output
242- const stdoutItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.stdout' ) ;
243- const stderrItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.stderr' ) ;
244-
245- if ( stdoutItem || stderrItem ) {
246- const item = stdoutItem || stderrItem ;
247- const text = new TextDecoder ( ) . decode ( item ! . data ) ;
248-
249- return {
250- name : stderrItem ? 'stderr' : 'stdout' ,
251- output_type : 'stream' ,
252- text
253- } as DeepnoteOutput ;
254- }
255-
256- // Rich output (execute_result or display_data)
257- const data : Record < string , unknown > = { } ;
258-
259- for ( const item of output . items ) {
260- if ( item . mime === 'text/plain' ) {
261- data [ 'text/plain' ] = new TextDecoder ( ) . decode ( item . data ) ;
262- } else if ( item . mime === 'text/markdown' ) {
263- data [ 'text/markdown' ] = new TextDecoder ( ) . decode ( item . data ) ;
264- } else if ( item . mime === 'text/html' ) {
265- data [ 'text/html' ] = new TextDecoder ( ) . decode ( item . data ) ;
266- } else if ( item . mime === 'application/json' ) {
267- data [ 'application/json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
268- } else if ( item . mime === 'image/png' ) {
269- data [ 'image/png' ] = this . uint8ArrayToBase64 ( item . data ) ;
270- } else if ( item . mime === 'image/jpeg' ) {
271- data [ 'image/jpeg' ] = this . uint8ArrayToBase64 ( item . data ) ;
272- } else if ( item . mime === 'application/vnd.deepnote.dataframe.v3+json' ) {
273- data [ 'application/vnd.deepnote.dataframe.v3+json' ] = JSON . parse (
274- new TextDecoder ( ) . decode ( item . data )
275- ) ;
276- } else if ( item . mime === 'application/vnd.vega.v6+json' ) {
277- data [ 'application/vnd.vega.v6+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
278- } else if ( item . mime === 'application/vnd.vega.v5+json' ) {
279- data [ 'application/vnd.vega.v5+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
280- } else if ( item . mime === 'application/vnd.plotly.v1+json' ) {
281- data [ 'application/vnd.plotly.v1+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
282- } else if ( item . mime === 'application/vnd.deepnote.sql-output-metadata+json' ) {
283- data [ 'application/vnd.deepnote.sql-output-metadata+json' ] = JSON . parse (
284- new TextDecoder ( ) . decode ( item . data )
285- ) ;
286- }
287- }
288-
289- const deepnoteOutput : DeepnoteOutput = {
290- data,
291- execution_count : ( output . metadata ?. executionCount as number ) || 0 ,
292- output_type : 'execute_result'
293- } ;
294-
295- // Add metadata if present (excluding executionCount which we already handled)
296- if ( output . metadata ) {
297- // eslint-disable-next-line @typescript-eslint/no-unused-vars
298- const { executionCount, ...restMetadata } = output . metadata ;
299-
300- if ( Object . keys ( restMetadata ) . length > 0 ) {
301- ( deepnoteOutput as DeepnoteOutput & { metadata ?: Record < string , unknown > } ) . metadata = restMetadata ;
302- }
303- }
304-
305- return deepnoteOutput ;
306- } ) ;
173+ /**
174+ * Initialize async dependencies like vega-lite.
175+ * Must be called before using output conversion methods.
176+ */
177+ async initialize ( ) : Promise < void > {
178+ await ensureVegaLiteLoaded ( ) ;
307179 }
308180
309- private transformOutputsForVsCode (
181+ public transformOutputsForVsCode (
310182 outputs : DeepnoteOutput [ ] ,
311183 cellIndex : number ,
312184 cellId : string ,
@@ -552,6 +424,153 @@ export class DeepnoteDataConverter {
552424 } ) ;
553425 }
554426
427+ private base64ToUint8Array ( base64 : string ) : Uint8Array {
428+ const binaryString = atob ( base64 ) ;
429+ const bytes = new Uint8Array ( binaryString . length ) ;
430+
431+ for ( let i = 0 ; i < binaryString . length ; i ++ ) {
432+ bytes [ i ] = binaryString . charCodeAt ( i ) ;
433+ }
434+
435+ return bytes ;
436+ }
437+
438+ private createFallbackBlock ( cell : NotebookCellData , index : number ) : DeepnoteBlock {
439+ const meta = cell . metadata as Record < string , unknown > | undefined ;
440+ const preservedId = ( meta ?. id ?? meta ?. __deepnoteBlockId ?? meta ?. deepnoteBlockId ) as string | undefined ;
441+ const preservedSortingKey = ( meta ?. sortingKey ?? meta ?. deepnoteSortingKey ) as string | undefined ;
442+ const preservedBlockGroup = meta ?. blockGroup as string | undefined ;
443+
444+ return {
445+ blockGroup : preservedBlockGroup ?? uuidUtils . generateUuid ( ) ,
446+ id : preservedId ?? generateBlockId ( ) ,
447+ sortingKey : preservedSortingKey ?? generateSortingKey ( index ) ,
448+ type : cell . kind === NotebookCellKind . Code ? 'code' : 'markdown' ,
449+ content : cell . value || '' ,
450+ metadata : { }
451+ } ;
452+ }
453+
454+ private createFallbackCell ( block : DeepnoteBlock ) : NotebookCellData {
455+ const cell = new NotebookCellData ( NotebookCellKind . Markup , block . content || '' , 'markdown' ) ;
456+
457+ cell . metadata = {
458+ ...( block . metadata ?? { } ) ,
459+ id : block . id ,
460+ __deepnoteBlockId : block . id ,
461+ type : block . type ,
462+ sortingKey : block . sortingKey ,
463+ deepnoteBlockId : block . id ,
464+ deepnoteBlockType : block . type ,
465+ deepnoteSortingKey : block . sortingKey ,
466+ deepnoteMetadata : block . metadata
467+ } ;
468+
469+ return cell ;
470+ }
471+
472+ private transformOutputsForDeepnote ( outputs : NotebookCellOutput [ ] ) : DeepnoteOutput [ ] {
473+ return outputs . map ( ( output ) => {
474+ // Check if this is an error output
475+ const errorItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.error' ) ;
476+
477+ if ( errorItem ) {
478+ try {
479+ const errorData = JSON . parse ( new TextDecoder ( ) . decode ( errorItem . data ) ) ;
480+
481+ return {
482+ ename : errorData . name || 'Error' ,
483+ evalue : errorData . message || '' ,
484+ output_type : 'error' ,
485+ traceback : errorData . stack ? errorData . stack . split ( '\n' ) : [ ]
486+ } as DeepnoteOutput ;
487+ } catch {
488+ return {
489+ ename : 'Error' ,
490+ evalue : '' ,
491+ output_type : 'error' ,
492+ traceback : [ ]
493+ } as DeepnoteOutput ;
494+ }
495+ }
496+
497+ // Check if this is a stream output
498+ const stdoutItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.stdout' ) ;
499+ const stderrItem = output . items . find ( ( item ) => item . mime === 'application/vnd.code.notebook.stderr' ) ;
500+
501+ if ( stdoutItem || stderrItem ) {
502+ const item = stdoutItem || stderrItem ;
503+ const text = new TextDecoder ( ) . decode ( item ! . data ) ;
504+
505+ return {
506+ name : stderrItem ? 'stderr' : 'stdout' ,
507+ output_type : 'stream' ,
508+ text
509+ } as DeepnoteOutput ;
510+ }
511+
512+ // Rich output (execute_result or display_data)
513+ const data : Record < string , unknown > = { } ;
514+
515+ for ( const item of output . items ) {
516+ try {
517+ if ( item . mime === 'text/plain' ) {
518+ data [ 'text/plain' ] = new TextDecoder ( ) . decode ( item . data ) ;
519+ } else if ( item . mime === 'text/markdown' ) {
520+ data [ 'text/markdown' ] = new TextDecoder ( ) . decode ( item . data ) ;
521+ } else if ( item . mime === 'text/html' ) {
522+ data [ 'text/html' ] = new TextDecoder ( ) . decode ( item . data ) ;
523+ } else if ( item . mime === CHART_BIG_NUMBER_MIME_TYPE ) {
524+ data [ 'text/plain' ] = new TextDecoder ( ) . decode ( item . data ) ;
525+ } else if ( item . mime === 'application/json' ) {
526+ data [ 'application/json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
527+ } else if ( item . mime === 'image/png' ) {
528+ data [ 'image/png' ] = this . uint8ArrayToBase64 ( item . data ) ;
529+ } else if ( item . mime === 'image/jpeg' ) {
530+ data [ 'image/jpeg' ] = this . uint8ArrayToBase64 ( item . data ) ;
531+ } else if ( item . mime === 'application/vnd.deepnote.dataframe.v3+json' ) {
532+ data [ 'application/vnd.deepnote.dataframe.v3+json' ] = JSON . parse (
533+ new TextDecoder ( ) . decode ( item . data )
534+ ) ;
535+ } else if ( item . mime === 'application/vnd.vega.v6+json' ) {
536+ data [ 'application/vnd.vega.v6+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
537+ } else if ( item . mime === 'application/vnd.vega.v5+json' ) {
538+ data [ 'application/vnd.vega.v5+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
539+ } else if ( item . mime === 'application/vnd.plotly.v1+json' ) {
540+ data [ 'application/vnd.plotly.v1+json' ] = JSON . parse ( new TextDecoder ( ) . decode ( item . data ) ) ;
541+ } else if ( item . mime === 'application/vnd.deepnote.sql-output-metadata+json' ) {
542+ data [ 'application/vnd.deepnote.sql-output-metadata+json' ] = JSON . parse (
543+ new TextDecoder ( ) . decode ( item . data )
544+ ) ;
545+ }
546+ } catch ( e ) {
547+ console . warn ( `Failed to convert output item mime=${ item . mime } ` , e ) ;
548+ }
549+ }
550+
551+ const deepnoteOutput : DeepnoteOutput = {
552+ data,
553+ execution_count : ( output . metadata ?. executionCount as number ) || 0 ,
554+ output_type : 'execute_result'
555+ } ;
556+
557+ // Add metadata if present (excluding executionCount which we already handled)
558+ if ( output . metadata ) {
559+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
560+ const { executionCount, cellId, cellIndex, ...restMetadata } = output . metadata as Record <
561+ string ,
562+ unknown
563+ > ;
564+
565+ if ( Object . keys ( restMetadata ) . length > 0 ) {
566+ ( deepnoteOutput as DeepnoteOutput & { metadata ?: Record < string , unknown > } ) . metadata = restMetadata ;
567+ }
568+ }
569+
570+ return deepnoteOutput ;
571+ } ) ;
572+ }
573+
555574 /**
556575 * Converts a Uint8Array to a base64 string without causing stack overflow.
557576 * Uses chunked processing to avoid call stack limits with large arrays.
0 commit comments