| const { v4: uuidv4 } = require('uuid'); |
| const { logger } = require('@librechat/data-schemas'); |
| const { EModelEndpoint, Constants, openAISettings, CacheKeys } = require('librechat-data-provider'); |
| const { createImportBatchBuilder } = require('./importBatchBuilder'); |
| const { cloneMessagesWithTimestamps } = require('./fork'); |
| const getLogStores = require('~/cache/getLogStores'); |
|
|
| |
| |
| |
| |
| |
| |
| |
| function getImporter(jsonData) { |
| |
| if (Array.isArray(jsonData)) { |
| logger.info('Importing ChatGPT conversation'); |
| return importChatGptConvo; |
| } |
|
|
| |
| if (jsonData.version && Array.isArray(jsonData.history)) { |
| logger.info('Importing ChatbotUI conversation'); |
| return importChatBotUiConvo; |
| } |
|
|
| |
| if (jsonData.conversationId && (jsonData.messagesTree || jsonData.messages)) { |
| logger.info('Importing LibreChat conversation'); |
| return importLibreChatConvo; |
| } |
|
|
| throw new Error('Unsupported import type'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function importChatBotUiConvo( |
| jsonData, |
| requestUserId, |
| builderFactory = createImportBatchBuilder, |
| ) { |
| |
| try { |
| |
| const importBatchBuilder = builderFactory(requestUserId); |
|
|
| for (const historyItem of jsonData.history) { |
| importBatchBuilder.startConversation(EModelEndpoint.openAI); |
| for (const message of historyItem.messages) { |
| if (message.role === 'assistant') { |
| importBatchBuilder.addGptMessage(message.content, historyItem.model.id); |
| } else if (message.role === 'user') { |
| importBatchBuilder.addUserMessage(message.content); |
| } |
| } |
| importBatchBuilder.finishConversation(historyItem.name, new Date()); |
| } |
| await importBatchBuilder.saveBatch(); |
| logger.info(`user: ${requestUserId} | ChatbotUI conversation imported`); |
| } catch (error) { |
| logger.error(`user: ${requestUserId} | Error creating conversation from ChatbotUI file`, error); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| async function importLibreChatConvo( |
| jsonData, |
| requestUserId, |
| builderFactory = createImportBatchBuilder, |
| ) { |
| try { |
| |
| const importBatchBuilder = builderFactory(requestUserId); |
| const options = jsonData.options || {}; |
|
|
| |
| let endpoint = jsonData.endpoint ?? options.endpoint ?? EModelEndpoint.openAI; |
| const cache = getLogStores(CacheKeys.CONFIG_STORE); |
| const endpointsConfig = await cache.get(CacheKeys.ENDPOINT_CONFIG); |
| const endpointConfig = endpointsConfig?.[endpoint]; |
| if (!endpointConfig && endpointsConfig) { |
| endpoint = Object.keys(endpointsConfig)[0]; |
| } else if (!endpointConfig) { |
| endpoint = EModelEndpoint.openAI; |
| } |
|
|
| importBatchBuilder.startConversation(endpoint); |
|
|
| let firstMessageDate = null; |
|
|
| const messagesToImport = jsonData.messagesTree || jsonData.messages; |
|
|
| if (jsonData.recursive) { |
| |
| |
| |
| |
| |
| |
| const flattenMessages = ( |
| messages, |
| parentMessageId = Constants.NO_PARENT, |
| flatMessages = [], |
| ) => { |
| for (const message of messages) { |
| if (!message.text && !message.content) { |
| continue; |
| } |
|
|
| const flatMessage = { |
| ...message, |
| parentMessageId: parentMessageId, |
| children: undefined, |
| }; |
| flatMessages.push(flatMessage); |
|
|
| if (!firstMessageDate && message.createdAt) { |
| firstMessageDate = new Date(message.createdAt); |
| } |
|
|
| if (message.children && message.children.length > 0) { |
| flattenMessages(message.children, message.messageId, flatMessages); |
| } |
| } |
| return flatMessages; |
| }; |
|
|
| const flatMessages = flattenMessages(messagesToImport); |
| cloneMessagesWithTimestamps(flatMessages, importBatchBuilder); |
| } else if (messagesToImport) { |
| cloneMessagesWithTimestamps(messagesToImport, importBatchBuilder); |
| for (const message of messagesToImport) { |
| if (!firstMessageDate && message.createdAt) { |
| firstMessageDate = new Date(message.createdAt); |
| } |
| } |
| } else { |
| throw new Error('Invalid LibreChat file format'); |
| } |
|
|
| if (firstMessageDate === 'Invalid Date') { |
| firstMessageDate = null; |
| } |
|
|
| importBatchBuilder.finishConversation(jsonData.title, firstMessageDate ?? new Date(), options); |
| await importBatchBuilder.saveBatch(); |
| logger.debug(`user: ${requestUserId} | Conversation "${jsonData.title}" imported`); |
| } catch (error) { |
| logger.error(`user: ${requestUserId} | Error creating conversation from LibreChat file`, error); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function importChatGptConvo( |
| jsonData, |
| requestUserId, |
| builderFactory = createImportBatchBuilder, |
| ) { |
| try { |
| const importBatchBuilder = builderFactory(requestUserId); |
| for (const conv of jsonData) { |
| processConversation(conv, importBatchBuilder, requestUserId); |
| } |
| await importBatchBuilder.saveBatch(); |
| } catch (error) { |
| logger.error(`user: ${requestUserId} | Error creating conversation from imported file`, error); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function processConversation(conv, importBatchBuilder, requestUserId) { |
| importBatchBuilder.startConversation(EModelEndpoint.openAI); |
|
|
| |
| const messageMap = new Map(); |
| for (const [id, mapping] of Object.entries(conv.mapping)) { |
| if (mapping.message && mapping.message.content.content_type) { |
| const newMessageId = uuidv4(); |
| messageMap.set(id, newMessageId); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| const findNonSystemParent = (parentId) => { |
| if (!parentId || !messageMap.has(parentId)) { |
| return Constants.NO_PARENT; |
| } |
|
|
| const parentMapping = conv.mapping[parentId]; |
| if (!parentMapping?.message) { |
| return Constants.NO_PARENT; |
| } |
|
|
| |
| if (parentMapping.message.author?.role === 'system') { |
| return findNonSystemParent(parentMapping.parent); |
| } |
|
|
| return messageMap.get(parentId); |
| }; |
|
|
| |
| const messages = []; |
| for (const [id, mapping] of Object.entries(conv.mapping)) { |
| const role = mapping.message?.author?.role; |
| if (!mapping.message) { |
| messageMap.delete(id); |
| continue; |
| } else if (role === 'system') { |
| |
| continue; |
| } |
|
|
| const newMessageId = messageMap.get(id); |
| const parentMessageId = findNonSystemParent(mapping.parent); |
|
|
| const messageText = formatMessageText(mapping.message); |
|
|
| const isCreatedByUser = role === 'user'; |
| let sender = isCreatedByUser ? 'user' : 'assistant'; |
| const model = mapping.message.metadata.model_slug || openAISettings.model.default; |
|
|
| if (!isCreatedByUser) { |
| |
| const gptMatch = model.match(/gpt-(.+)/i); |
| if (gptMatch) { |
| sender = `GPT-${gptMatch[1]}`; |
| } else { |
| sender = model || 'assistant'; |
| } |
| } |
|
|
| messages.push({ |
| messageId: newMessageId, |
| parentMessageId, |
| text: messageText, |
| sender, |
| isCreatedByUser, |
| model, |
| user: requestUserId, |
| endpoint: EModelEndpoint.openAI, |
| }); |
| } |
|
|
| for (const message of messages) { |
| importBatchBuilder.saveMessage(message); |
| } |
|
|
| importBatchBuilder.finishConversation(conv.title, new Date(conv.create_time * 1000)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function processAssistantMessage(messageData, messageText) { |
| if (!messageText) { |
| return messageText; |
| } |
|
|
| const citations = messageData.metadata?.citations ?? []; |
|
|
| const sortedCitations = [...citations].sort((a, b) => b.start_ix - a.start_ix); |
|
|
| let result = messageText; |
| for (const citation of sortedCitations) { |
| if ( |
| !citation.metadata?.type || |
| citation.metadata.type !== 'webpage' || |
| typeof citation.start_ix !== 'number' || |
| typeof citation.end_ix !== 'number' || |
| citation.start_ix >= citation.end_ix |
| ) { |
| continue; |
| } |
|
|
| const replacement = ` ([${citation.metadata.title}](${citation.metadata.url}))`; |
|
|
| result = result.slice(0, citation.start_ix) + replacement + result.slice(citation.end_ix); |
| } |
|
|
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| function formatMessageText(messageData) { |
| const isText = messageData.content.content_type === 'text'; |
| let messageText = ''; |
|
|
| if (isText && messageData.content.parts) { |
| messageText = messageData.content.parts.join(' '); |
| } else if (messageData.content.content_type === 'code') { |
| messageText = `\`\`\`${messageData.content.language}\n${messageData.content.text}\n\`\`\``; |
| } else if (messageData.content.content_type === 'execution_output') { |
| messageText = `Execution Output:\n> ${messageData.content.text}`; |
| } else if (messageData.content.parts) { |
| for (const part of messageData.content.parts) { |
| if (typeof part === 'string') { |
| messageText += part + ' '; |
| } else if (typeof part === 'object') { |
| messageText = `\`\`\`json\n${JSON.stringify(part, null, 2)}\n\`\`\`\n`; |
| } |
| } |
| messageText = messageText.trim(); |
| } else { |
| messageText = `\`\`\`json\n${JSON.stringify(messageData.content, null, 2)}\n\`\`\``; |
| } |
|
|
| if (isText && messageData.author.role !== 'user') { |
| messageText = processAssistantMessage(messageData, messageText); |
| } |
|
|
| return messageText; |
| } |
|
|
| module.exports = { getImporter, processAssistantMessage }; |
|
|