This commit is contained in:
Timothy Jaeryang Baek
2026-04-21 16:16:48 +09:00
parent 116eb7fc55
commit 085d3cb1c9
+43 -18
View File
@@ -677,7 +677,9 @@ export const calculateSHA256 = async (file) => {
export const getImportOrigin = (_chats) => {
// Check what external service chat imports are from
if ('mapping' in _chats[0]) {
// ChatGPT exports may include folder/project metadata entries without 'mapping',
// so we check if ANY item has a 'mapping' key instead of only the first one.
if (_chats.some((chat) => 'mapping' in chat)) {
return 'openai';
}
return 'webui';
@@ -706,6 +708,21 @@ export const getUserPosition = async (raw = false) => {
}
};
const extractOpenAIMessageContent = (message): string => {
// Extract text content from a ChatGPT message, handling various content formats
// (string parts, object parts like DALL-E images, text field fallback)
try {
const parts = message?.['content']?.['parts'];
if (Array.isArray(parts)) {
const textParts = parts.filter((p) => typeof p === 'string');
if (textParts.length > 0) return textParts.join('\n');
}
return message?.['content']?.['text'] || '';
} catch {
return '';
}
};
const convertOpenAIMessages = (convo) => {
// Parse OpenAI chat messages and create chat dictionary for creating new chats
const mapping = convo['mapping'];
@@ -726,15 +743,18 @@ const convertOpenAIMessages = (convo) => {
// Skip chat messages with no content
continue;
} else {
const role = message['message']?.['author']?.['role'];
// Skip system and tool messages — they don't map to user/assistant
if (role === 'system' || role === 'tool') {
continue;
}
const new_chat = {
id: message_id,
parentId: lastId,
childrenIds: message['children'] || [],
role: message['message']?.['author']?.['role'] !== 'user' ? 'assistant' : 'user',
content:
message['message']?.['content']?.['parts']?.[0] ||
message['message']?.['content']?.['text'] ||
'',
role: role !== 'user' ? 'assistant' : 'user',
content: extractOpenAIMessageContent(message['message']),
model: 'gpt-3.5-turbo',
done: true,
context: null
@@ -747,6 +767,12 @@ const convertOpenAIMessages = (convo) => {
}
}
// Fix up the last message's childrenIds to be empty (it's the leaf node in our
// linear chain regardless of what the original tree structure had)
if (messages.length > 0) {
messages[messages.length - 1].childrenIds = [];
}
const history: Record<PropertyKey, (typeof messages)[number]> = {};
messages.forEach((obj) => (history[obj.id] = obj));
@@ -773,18 +799,6 @@ const validateChat = (chat) => {
return false;
}
// Last message's children should be an empty array
const lastMessage = messages[messages.length - 1];
if (lastMessage.childrenIds.length !== 0) {
return false;
}
// First message's parent should be null
const firstMessage = messages[0];
if (firstMessage.parentId !== null) {
return false;
}
// Every message's content should be a string
for (const message of messages) {
if (typeof message.content !== 'string') {
@@ -799,7 +813,15 @@ export const convertOpenAIChats = (_chats) => {
// Create a list of dictionaries with each conversation from import
const chats = [];
let failed = 0;
let skipped = 0;
for (const convo of _chats) {
// Skip folder/project metadata entries that lack a 'mapping' key
if (!('mapping' in convo)) {
skipped++;
console.log('Skipping non-conversation entry (folder/project):', convo['title'] ?? convo['id']);
continue;
}
const chat = convertOpenAIMessages(convo);
if (validateChat(chat)) {
@@ -815,6 +837,9 @@ export const convertOpenAIChats = (_chats) => {
}
}
console.log(failed, 'Conversations could not be imported');
if (skipped > 0) {
console.log(skipped, 'Non-conversation entries (folders/projects) were skipped');
}
return chats;
};