@@ -677,7 +677,9 @@ export const calculateSHA256 = async (file) => {
677677
678678export const getImportOrigin = ( _chats ) => {
679679 // Check what external service chat imports are from
680- if ( 'mapping' in _chats [ 0 ] ) {
680+ // ChatGPT exports may include folder/project metadata entries without 'mapping',
681+ // so we check if ANY item has a 'mapping' key instead of only the first one.
682+ if ( _chats . some ( ( chat ) => 'mapping' in chat ) ) {
681683 return 'openai' ;
682684 }
683685 return 'webui' ;
@@ -706,6 +708,21 @@ export const getUserPosition = async (raw = false) => {
706708 }
707709} ;
708710
711+ const extractOpenAIMessageContent = ( message ) : string => {
712+ // Extract text content from a ChatGPT message, handling various content formats
713+ // (string parts, object parts like DALL-E images, text field fallback)
714+ try {
715+ const parts = message ?. [ 'content' ] ?. [ 'parts' ] ;
716+ if ( Array . isArray ( parts ) ) {
717+ const textParts = parts . filter ( ( p ) => typeof p === 'string' ) ;
718+ if ( textParts . length > 0 ) return textParts . join ( '\n' ) ;
719+ }
720+ return message ?. [ 'content' ] ?. [ 'text' ] || '' ;
721+ } catch {
722+ return '' ;
723+ }
724+ } ;
725+
709726const convertOpenAIMessages = ( convo ) => {
710727 // Parse OpenAI chat messages and create chat dictionary for creating new chats
711728 const mapping = convo [ 'mapping' ] ;
@@ -726,15 +743,18 @@ const convertOpenAIMessages = (convo) => {
726743 // Skip chat messages with no content
727744 continue ;
728745 } else {
746+ const role = message [ 'message' ] ?. [ 'author' ] ?. [ 'role' ] ;
747+ // Skip system and tool messages — they don't map to user/assistant
748+ if ( role === 'system' || role === 'tool' ) {
749+ continue ;
750+ }
751+
729752 const new_chat = {
730753 id : message_id ,
731754 parentId : lastId ,
732755 childrenIds : message [ 'children' ] || [ ] ,
733- role : message [ 'message' ] ?. [ 'author' ] ?. [ 'role' ] !== 'user' ? 'assistant' : 'user' ,
734- content :
735- message [ 'message' ] ?. [ 'content' ] ?. [ 'parts' ] ?. [ 0 ] ||
736- message [ 'message' ] ?. [ 'content' ] ?. [ 'text' ] ||
737- '' ,
756+ role : role !== 'user' ? 'assistant' : 'user' ,
757+ content : extractOpenAIMessageContent ( message [ 'message' ] ) ,
738758 model : 'gpt-3.5-turbo' ,
739759 done : true ,
740760 context : null
@@ -747,6 +767,12 @@ const convertOpenAIMessages = (convo) => {
747767 }
748768 }
749769
770+ // Fix up the last message's childrenIds to be empty (it's the leaf node in our
771+ // linear chain regardless of what the original tree structure had)
772+ if ( messages . length > 0 ) {
773+ messages [ messages . length - 1 ] . childrenIds = [ ] ;
774+ }
775+
750776 const history : Record < PropertyKey , ( typeof messages ) [ number ] > = { } ;
751777 messages . forEach ( ( obj ) => ( history [ obj . id ] = obj ) ) ;
752778
@@ -773,18 +799,6 @@ const validateChat = (chat) => {
773799 return false ;
774800 }
775801
776- // Last message's children should be an empty array
777- const lastMessage = messages [ messages . length - 1 ] ;
778- if ( lastMessage . childrenIds . length !== 0 ) {
779- return false ;
780- }
781-
782- // First message's parent should be null
783- const firstMessage = messages [ 0 ] ;
784- if ( firstMessage . parentId !== null ) {
785- return false ;
786- }
787-
788802 // Every message's content should be a string
789803 for ( const message of messages ) {
790804 if ( typeof message . content !== 'string' ) {
@@ -799,7 +813,15 @@ export const convertOpenAIChats = (_chats) => {
799813 // Create a list of dictionaries with each conversation from import
800814 const chats = [ ] ;
801815 let failed = 0 ;
816+ let skipped = 0 ;
802817 for ( const convo of _chats ) {
818+ // Skip folder/project metadata entries that lack a 'mapping' key
819+ if ( ! ( 'mapping' in convo ) ) {
820+ skipped ++ ;
821+ console . log ( 'Skipping non-conversation entry (folder/project):' , convo [ 'title' ] ?? convo [ 'id' ] ) ;
822+ continue ;
823+ }
824+
803825 const chat = convertOpenAIMessages ( convo ) ;
804826
805827 if ( validateChat ( chat ) ) {
@@ -815,6 +837,9 @@ export const convertOpenAIChats = (_chats) => {
815837 }
816838 }
817839 console . log ( failed , 'Conversations could not be imported' ) ;
840+ if ( skipped > 0 ) {
841+ console . log ( skipped , 'Non-conversation entries (folders/projects) were skipped' ) ;
842+ }
818843 return chats ;
819844} ;
820845
0 commit comments