From d990aa96418cf23a2159df15d5dfefec6da8bc37 Mon Sep 17 00:00:00 2001 From: techwithanirudh Date: Mon, 20 May 2024 07:22:55 +0000 Subject: [PATCH] =?UTF-8?q?Revert=20"=F0=9F=9A=91=20fix(export):=20Issue?= =?UTF-8?q?=20exporting=20Conversation=20with=20Assistants=20(#2769)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 13f95a4be7cd1d94dc78789b69d77cd9b3583a46. --- .../ExportConversation/ExportConversation.jsx | 44 ++ .../Nav/ExportConversation/ExportModal.jsx | 448 ++++++++++++++++++ .../Nav/ExportConversation/ExportModal.tsx | 179 ------- .../Nav/ExportConversation/index.ts | 1 + client/src/hooks/Conversations/index.ts | 1 - .../Conversations/useExportConversation.ts | 369 --------------- .../src/hooks/Messages/useBuildMessageTree.ts | 78 --- 7 files changed, 493 insertions(+), 627 deletions(-) create mode 100644 client/src/components/Nav/ExportConversation/ExportConversation.jsx create mode 100644 client/src/components/Nav/ExportConversation/ExportModal.jsx delete mode 100644 client/src/components/Nav/ExportConversation/ExportModal.tsx delete mode 100644 client/src/hooks/Conversations/useExportConversation.ts delete mode 100644 client/src/hooks/Messages/useBuildMessageTree.ts diff --git a/client/src/components/Nav/ExportConversation/ExportConversation.jsx b/client/src/components/Nav/ExportConversation/ExportConversation.jsx new file mode 100644 index 000000000000..edd935ecbc1b --- /dev/null +++ b/client/src/components/Nav/ExportConversation/ExportConversation.jsx @@ -0,0 +1,44 @@ +import { useState, forwardRef } from 'react'; +import { useRecoilValue } from 'recoil'; +import { Download } from 'lucide-react'; +import ExportModal from './ExportModal'; +import { useLocalize } from '~/hooks'; +import { cn } from '~/utils/'; +import store from '~/store'; + +const ExportConversation = forwardRef(() => { + const [open, setOpen] = useState(false); + const localize = useLocalize(); + + const conversation = useRecoilValue(store.conversation) || {}; + + const exportable = + conversation?.conversationId && + conversation?.conversationId !== 'new' && + conversation?.conversationId !== 'search'; + + const clickHandler = () => { + if (exportable) { + setOpen(true); + } + }; + + return ( + <> + + + + + ); +}); + +export default ExportConversation; diff --git a/client/src/components/Nav/ExportConversation/ExportModal.jsx b/client/src/components/Nav/ExportConversation/ExportModal.jsx new file mode 100644 index 000000000000..d7dd847f9439 --- /dev/null +++ b/client/src/components/Nav/ExportConversation/ExportModal.jsx @@ -0,0 +1,448 @@ +import download from 'downloadjs'; +import filenamify from 'filenamify'; +import { useRecoilCallback } from 'recoil'; +import { useEffect, useState } from 'react'; +import exportFromJSON from 'export-from-json'; +import DialogTemplate from '~/components/ui/DialogTemplate'; +import { useGetMessagesByConvoId } from 'librechat-data-provider/react-query'; +import { Dialog, DialogButton, Input, Label, Checkbox, Dropdown } from '~/components/ui/'; +import { cn, defaultTextProps, removeFocusOutlines, cleanupPreset } from '~/utils/'; +import { useScreenshot, useLocalize } from '~/hooks'; +import { buildTree } from '~/utils'; +import store from '~/store'; + +export default function ExportModal({ open, onOpenChange, conversation }) { + const { captureScreenshot } = useScreenshot(); + const localize = useLocalize(); + + const [filename, setFileName] = useState(''); + const [type, setType] = useState('Select a file type'); + + const [includeOptions, setIncludeOptions] = useState(true); + const [exportBranches, setExportBranches] = useState(false); + const [recursive, setRecursive] = useState(true); + + const { data: messagesTree = null } = useGetMessagesByConvoId(conversation.conversationId ?? '', { + select: (data) => { + const dataTree = buildTree({ messages: data }); + return dataTree?.length === 0 ? null : dataTree ?? null; + }, + }); + + const getSiblingIdx = useRecoilCallback( + ({ snapshot }) => + async (messageId) => + await snapshot.getPromise(store.messagesSiblingIdxFamily(messageId)), + [], + ); + + const typeOptions = [ + { value: 'screenshot', display: 'screenshot (.png)' }, + { value: 'text', display: 'text (.txt)' }, + { value: 'markdown', display: 'markdown (.md)' }, + { value: 'json', display: 'json (.json)' }, + { value: 'csv', display: 'csv (.csv)' }, + ]; //,, 'webpage']; + + useEffect(() => { + setFileName(filenamify(String(conversation?.title || 'file'))); + setType('screenshot'); + setIncludeOptions(true); + setExportBranches(false); + setRecursive(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); + + const _setType = (newType) => { + const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage'; + const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot'; + + setExportBranches(exportBranchesSupport); + setIncludeOptions(exportOptionsSupport); + setType(newType); + }; + + const exportBranchesSupport = type === 'json' || type === 'csv' || type === 'webpage'; + const exportOptionsSupport = type !== 'csv' && type !== 'screenshot'; + + // return an object or an array based on branches and recursive option + // messageId is used to get siblindIdx from recoil snapshot + const buildMessageTree = async ({ + messageId, + message, + messages, + branches = false, + recursive = false, + }) => { + let children = []; + if (messages?.length) { + if (branches) { + for (const message of messages) { + children.push( + await buildMessageTree({ + messageId: message?.messageId, + message: message, + messages: message?.children, + branches, + recursive, + }), + ); + } + } else { + let message = messages[0]; + if (messages?.length > 1) { + const siblingIdx = await getSiblingIdx(messageId); + message = messages[messages.length - siblingIdx - 1]; + } + + children = [ + await buildMessageTree({ + messageId: message?.messageId, + message: message, + messages: message?.children, + branches, + recursive, + }), + ]; + } + } + + if (recursive) { + return { ...message, children: children }; + } else { + let ret = []; + if (message) { + let _message = { ...message }; + delete _message.children; + ret = [_message]; + } + for (const child of children) { + ret = ret.concat(child); + } + return ret; + } + }; + + const exportScreenshot = async () => { + let data; + try { + data = await captureScreenshot(); + } catch (err) { + console.error('Failed to capture screenshot'); + return console.error(err); + } + download(data, `${filename}.png`, 'image/png'); + }; + + const exportCSV = async () => { + let data = []; + + const messages = await buildMessageTree({ + messageId: conversation?.conversationId, + message: null, + messages: messagesTree, + branches: exportBranches, + recursive: false, + }); + + for (const message of messages) { + data.push(message); + } + + exportFromJSON({ + data: data, + fileName: filename, + extension: 'csv', + exportType: exportFromJSON.types.csv, + beforeTableEncode: (entries) => [ + { + fieldName: 'sender', + fieldValues: entries.find((e) => e.fieldName == 'sender').fieldValues, + }, + { fieldName: 'text', fieldValues: entries.find((e) => e.fieldName == 'text').fieldValues }, + { + fieldName: 'isCreatedByUser', + fieldValues: entries.find((e) => e.fieldName == 'isCreatedByUser').fieldValues, + }, + { + fieldName: 'error', + fieldValues: entries.find((e) => e.fieldName == 'error').fieldValues, + }, + { + fieldName: 'unfinished', + fieldValues: entries.find((e) => e.fieldName == 'unfinished').fieldValues, + }, + { + fieldName: 'messageId', + fieldValues: entries.find((e) => e.fieldName == 'messageId').fieldValues, + }, + { + fieldName: 'parentMessageId', + fieldValues: entries.find((e) => e.fieldName == 'parentMessageId').fieldValues, + }, + { + fieldName: 'createdAt', + fieldValues: entries.find((e) => e.fieldName == 'createdAt').fieldValues, + }, + ], + }); + }; + + const exportMarkdown = async () => { + let data = + '# Conversation\n' + + `- conversationId: ${conversation?.conversationId}\n` + + `- endpoint: ${conversation?.endpoint}\n` + + `- title: ${conversation?.title}\n` + + `- exportAt: ${new Date().toTimeString()}\n`; + + if (includeOptions) { + data += '\n## Options\n'; + const options = cleanupPreset({ preset: conversation }); + + for (const key of Object.keys(options)) { + data += `- ${key}: ${options[key]}\n`; + } + } + + const messages = await buildMessageTree({ + messageId: conversation?.conversationId, + message: null, + messages: messagesTree, + branches: false, + recursive: false, + }); + + data += '\n## History\n'; + for (const message of messages) { + data += `**${message?.sender}:**\n${message?.text}\n`; + if (message.error) { + data += '*(This is an error message)*\n'; + } + if (message.unfinished) { + data += '*(This is an unfinished message)*\n'; + } + data += '\n\n'; + } + + exportFromJSON({ + data: data, + fileName: filename, + extension: 'md', + exportType: exportFromJSON.types.text, + }); + }; + + const exportText = async () => { + let data = + 'Conversation\n' + + '########################\n' + + `conversationId: ${conversation?.conversationId}\n` + + `endpoint: ${conversation?.endpoint}\n` + + `title: ${conversation?.title}\n` + + `exportAt: ${new Date().toTimeString()}\n`; + + if (includeOptions) { + data += '\nOptions\n########################\n'; + const options = cleanupPreset({ preset: conversation }); + + for (const key of Object.keys(options)) { + data += `${key}: ${options[key]}\n`; + } + } + + const messages = await buildMessageTree({ + messageId: conversation?.conversationId, + message: null, + messages: messagesTree, + branches: false, + recursive: false, + }); + + data += '\nHistory\n########################\n'; + for (const message of messages) { + data += `>> ${message?.sender}:\n${message?.text}\n`; + if (message.error) { + data += '(This is an error message)\n'; + } + if (message.unfinished) { + data += '(This is an unfinished message)\n'; + } + data += '\n\n'; + } + + exportFromJSON({ + data: data, + fileName: filename, + extension: 'txt', + exportType: exportFromJSON.types.text, + }); + }; + + const exportJSON = async () => { + let data = { + conversationId: conversation?.conversationId, + endpoint: conversation?.endpoint, + title: conversation?.title, + exportAt: new Date().toTimeString(), + branches: exportBranches, + recursive: recursive, + }; + + if (includeOptions) { + data.options = cleanupPreset({ preset: conversation }); + } + + const messages = await buildMessageTree({ + messageId: conversation?.conversationId, + message: null, + messages: messagesTree, + branches: exportBranches, + recursive: recursive, + }); + + if (recursive) { + data.messagesTree = messages.children; + } else { + data.messages = messages; + } + + exportFromJSON({ + data: data, + fileName: filename, + extension: 'json', + exportType: exportFromJSON.types.json, + }); + }; + + const exportConversation = () => { + if (type === 'json') { + exportJSON(); + } else if (type == 'text') { + exportText(); + } else if (type == 'markdown') { + exportMarkdown(); + } else if (type == 'csv') { + exportCSV(); + } else if (type == 'screenshot') { + exportScreenshot(); + } + }; + + return ( + + +
+
+ + setFileName(filenamify(e.target.value || ''))} + placeholder={localize('com_nav_export_filename_placeholder')} + className={cn( + defaultTextProps, + 'flex h-10 max-h-10 w-full resize-none px-3 py-2', + removeFocusOutlines, + )} + /> +
+
+ + +
+
+
+
+
+ +
+ + +
+
+
+
+ +
+ + +
+
+ {type === 'json' ? ( +
+ +
+ + +
+
+ ) : null} +
+ + } + buttons={ + <> + + {localize('com_endpoint_export')} + + + } + selection={null} + /> +
+ ); +} diff --git a/client/src/components/Nav/ExportConversation/ExportModal.tsx b/client/src/components/Nav/ExportConversation/ExportModal.tsx deleted file mode 100644 index f63a39c3cab3..000000000000 --- a/client/src/components/Nav/ExportConversation/ExportModal.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import filenamify from 'filenamify'; -import { useEffect, useState } from 'react'; -import type { TConversation } from 'librechat-data-provider'; -import { Dialog, DialogButton, Input, Label, Checkbox, Dropdown } from '~/components/ui'; -import { useLocalize, useExportConversation } from '~/hooks'; -import DialogTemplate from '~/components/ui/DialogTemplate'; -import { cn, defaultTextProps } from '~/utils'; - -export default function ExportModal({ - open, - onOpenChange, - conversation, -}: { - open: boolean; - onOpenChange: (open: boolean) => void; - conversation: TConversation | null; -}) { - const localize = useLocalize(); - - const [filename, setFileName] = useState(''); - const [type, setType] = useState('Select a file type'); - - const [includeOptions, setIncludeOptions] = useState(true); - const [exportBranches, setExportBranches] = useState(false); - const [recursive, setRecursive] = useState(true); - - const typeOptions = [ - { value: 'screenshot', display: 'screenshot (.png)' }, - { value: 'text', display: 'text (.txt)' }, - { value: 'markdown', display: 'markdown (.md)' }, - { value: 'json', display: 'json (.json)' }, - { value: 'csv', display: 'csv (.csv)' }, - ]; - - useEffect(() => { - setFileName(filenamify(String(conversation?.title || 'file'))); - setType('screenshot'); - setIncludeOptions(true); - setExportBranches(false); - setRecursive(true); - }, [conversation?.title, open]); - - const _setType = (newType: string) => { - const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage'; - const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot'; - - setExportBranches(exportBranchesSupport); - setIncludeOptions(exportOptionsSupport); - setType(newType); - }; - - const exportBranchesSupport = type === 'json' || type === 'csv' || type === 'webpage'; - const exportOptionsSupport = type !== 'csv' && type !== 'screenshot'; - - const { exportConversation } = useExportConversation({ - conversation, - filename, - type, - includeOptions, - exportBranches, - recursive, - }); - - return ( - - -
-
- - setFileName(filenamify(e.target.value || ''))} - placeholder={localize('com_nav_export_filename_placeholder')} - className={cn( - defaultTextProps, - 'flex h-10 max-h-10 w-full resize-none px-3 py-2', - )} - /> -
-
- - -
-
-
-
-
- -
- - -
-
-
-
- -
- - -
-
- {type === 'json' ? ( -
- -
- - -
-
- ) : null} -
- - } - buttons={ - <> - - {localize('com_endpoint_export')} - - - } - selection={undefined} - /> -
- ); -} diff --git a/client/src/components/Nav/ExportConversation/index.ts b/client/src/components/Nav/ExportConversation/index.ts index b5a5a2c89ce5..a4321d9e9454 100644 --- a/client/src/components/Nav/ExportConversation/index.ts +++ b/client/src/components/Nav/ExportConversation/index.ts @@ -1 +1,2 @@ +export { default as ExportConversation } from './ExportConversation'; export { default as ExportModal } from './ExportModal'; diff --git a/client/src/hooks/Conversations/index.ts b/client/src/hooks/Conversations/index.ts index 5e5cabb81c0b..be63e73a646b 100644 --- a/client/src/hooks/Conversations/index.ts +++ b/client/src/hooks/Conversations/index.ts @@ -8,4 +8,3 @@ export { default as useDebouncedInput } from './useDebouncedInput'; export { default as useNavigateToConvo } from './useNavigateToConvo'; export { default as useSetIndexOptions } from './useSetIndexOptions'; export { default as useParameterEffects } from './useParameterEffects'; -export { default as useExportConversation } from './useExportConversation'; diff --git a/client/src/hooks/Conversations/useExportConversation.ts b/client/src/hooks/Conversations/useExportConversation.ts deleted file mode 100644 index 5c9750e4dac5..000000000000 --- a/client/src/hooks/Conversations/useExportConversation.ts +++ /dev/null @@ -1,369 +0,0 @@ -import download from 'downloadjs'; -import exportFromJSON from 'export-from-json'; -import { useGetMessagesByConvoId } from 'librechat-data-provider/react-query'; -import { - ContentTypes, - ToolCallTypes, - imageGenTools, - isImageVisionTool, -} from 'librechat-data-provider'; -import type { - TMessage, - TPreset, - TConversation, - TMessageContentParts, -} from 'librechat-data-provider'; -import useBuildMessageTree from '~/hooks/Messages/useBuildMessageTree'; -import { useScreenshot } from '~/hooks/ScreenshotContext'; -import { cleanupPreset, buildTree } from '~/utils'; - -export default function useExportConversation({ - conversation, - filename, - type, - includeOptions, - exportBranches, - recursive, -}: { - conversation: TConversation | null; - filename: string; - type: string; - includeOptions: boolean | 'indeterminate'; - exportBranches: boolean | 'indeterminate'; - recursive: boolean | 'indeterminate'; -}) { - const { captureScreenshot } = useScreenshot(); - const buildMessageTree = useBuildMessageTree(); - const { data: messagesTree = null } = useGetMessagesByConvoId( - conversation?.conversationId ?? '', - { - select: (data) => { - const dataTree = buildTree({ messages: data }); - return dataTree?.length === 0 ? null : dataTree ?? null; - }, - }, - ); - - const getMessageText = (message: TMessage, format = 'text') => { - if (!message) { - return ''; - } - - const formatText = (sender, text) => { - if (format === 'text') { - return `>> ${sender}:\n${text}`; - } - return `**${sender}**\n${text}`; - }; - - if (!message.content) { - return formatText(message.sender, message.text); - } - - return message.content - .map((content) => getMessageContent(message.sender, content)) - .map((text) => { - return formatText(text[0], text[1]); - }) - .join('\n\n\n'); - }; - - /** - * Format and return message texts according to the type of content. - * Currently, content whose type is `TOOL_CALL` basically returns JSON as is. - * In the future, different formatted text may be returned for each type. - */ - const getMessageContent = (sender: string, content: TMessageContentParts): string[] => { - if (!content) { - return []; - } - - if (content.type === ContentTypes.ERROR) { - // ERROR - return [sender, content[ContentTypes.TEXT].value]; - } - - if (content.type === ContentTypes.TEXT) { - // TEXT - return [sender, content[ContentTypes.TEXT].value]; - } - - if (content.type === ContentTypes.TOOL_CALL) { - const type = content[ContentTypes.TOOL_CALL].type; - - if (type === ToolCallTypes.CODE_INTERPRETER) { - // CODE_INTERPRETER - const toolCall = content[ContentTypes.TOOL_CALL]; - const code_interpreter = toolCall[ToolCallTypes.CODE_INTERPRETER]; - return ['Code Interpreter', JSON.stringify(code_interpreter)]; - } - - if (type === ToolCallTypes.RETRIEVAL) { - // RETRIEVAL - const toolCall = content[ContentTypes.TOOL_CALL]; - return ['Retrieval', JSON.stringify(toolCall)]; - } - - if ( - type === ToolCallTypes.FUNCTION && - imageGenTools.has(content[ContentTypes.TOOL_CALL].function.name) - ) { - // IMAGE_GENERATION - const toolCall = content[ContentTypes.TOOL_CALL]; - return ['Tool', JSON.stringify(toolCall)]; - } - - if (type === ToolCallTypes.FUNCTION) { - // IMAGE_VISION - const toolCall = content[ContentTypes.TOOL_CALL]; - if (isImageVisionTool(toolCall)) { - return ['Tool', JSON.stringify(toolCall)]; - } - return ['Tool', JSON.stringify(toolCall)]; - } - } - - if (content.type === ContentTypes.IMAGE_FILE) { - // IMAGE - const imageFile = content[ContentTypes.IMAGE_FILE]; - return ['Image', JSON.stringify(imageFile)]; - } - - return [sender, JSON.stringify(content)]; - }; - - const exportScreenshot = async () => { - let data; - try { - data = await captureScreenshot(); - } catch (err) { - console.error('Failed to capture screenshot'); - return console.error(err); - } - download(data, `${filename}.png`, 'image/png'); - }; - - const exportCSV = async () => { - const data: TMessage[] = []; - - const messages = await buildMessageTree({ - messageId: conversation?.conversationId, - message: null, - messages: messagesTree, - branches: !!exportBranches, - recursive: false, - }); - - if (Array.isArray(messages)) { - for (const message of messages) { - data.push(message); - } - } else { - data.push(messages); - } - - exportFromJSON({ - data: data, - fileName: filename, - extension: 'csv', - exportType: exportFromJSON.types.csv, - beforeTableEncode: (entries) => [ - { - fieldName: 'sender', - fieldValues: entries?.find((e) => e.fieldName == 'sender')?.fieldValues ?? [], - }, - { - fieldName: 'text', - fieldValues: entries?.find((e) => e.fieldName == 'text')?.fieldValues ?? [], - }, - { - fieldName: 'isCreatedByUser', - fieldValues: entries?.find((e) => e.fieldName == 'isCreatedByUser')?.fieldValues ?? [], - }, - { - fieldName: 'error', - fieldValues: entries?.find((e) => e.fieldName == 'error')?.fieldValues ?? [], - }, - { - fieldName: 'unfinished', - fieldValues: entries?.find((e) => e.fieldName == 'unfinished')?.fieldValues ?? [], - }, - { - fieldName: 'messageId', - fieldValues: entries?.find((e) => e.fieldName == 'messageId')?.fieldValues ?? [], - }, - { - fieldName: 'parentMessageId', - fieldValues: entries?.find((e) => e.fieldName == 'parentMessageId')?.fieldValues ?? [], - }, - { - fieldName: 'createdAt', - fieldValues: entries?.find((e) => e.fieldName == 'createdAt')?.fieldValues ?? [], - }, - ], - }); - }; - - const exportMarkdown = async () => { - let data = - '# Conversation\n' + - `- conversationId: ${conversation?.conversationId}\n` + - `- endpoint: ${conversation?.endpoint}\n` + - `- title: ${conversation?.title}\n` + - `- exportAt: ${new Date().toTimeString()}\n`; - - if (includeOptions) { - data += '\n## Options\n'; - const options = cleanupPreset({ preset: conversation as TPreset }); - - for (const key of Object.keys(options)) { - data += `- ${key}: ${options[key]}\n`; - } - } - - const messages = await buildMessageTree({ - messageId: conversation?.conversationId, - message: null, - messages: messagesTree, - branches: false, - recursive: false, - }); - - data += '\n## History\n'; - if (Array.isArray(messages)) { - for (const message of messages) { - data += `${getMessageText(message, 'md')}\n`; - if (message.error) { - data += '*(This is an error message)*\n'; - } - if (message.unfinished) { - data += '*(This is an unfinished message)*\n'; - } - data += '\n\n'; - } - } else { - data += `${getMessageText(messages, 'md')}\n`; - if (messages.error) { - data += '*(This is an error message)*\n'; - } - if (messages.unfinished) { - data += '*(This is an unfinished message)*\n'; - } - } - - exportFromJSON({ - data: data, - fileName: filename, - extension: 'md', - exportType: exportFromJSON.types.txt, - }); - }; - - const exportText = async () => { - let data = - 'Conversation\n' + - '########################\n' + - `conversationId: ${conversation?.conversationId}\n` + - `endpoint: ${conversation?.endpoint}\n` + - `title: ${conversation?.title}\n` + - `exportAt: ${new Date().toTimeString()}\n`; - - if (includeOptions) { - data += '\nOptions\n########################\n'; - const options = cleanupPreset({ preset: conversation as TPreset }); - - for (const key of Object.keys(options)) { - data += `${key}: ${options[key]}\n`; - } - } - - const messages = await buildMessageTree({ - messageId: conversation?.conversationId, - message: null, - messages: messagesTree, - branches: false, - recursive: false, - }); - - data += '\nHistory\n########################\n'; - if (Array.isArray(messages)) { - for (const message of messages) { - data += `${getMessageText(message)}\n`; - if (message.error) { - data += '(This is an error message)\n'; - } - if (message.unfinished) { - data += '(This is an unfinished message)\n'; - } - data += '\n\n'; - } - } else { - data += `${getMessageText(messages)}\n`; - if (messages.error) { - data += '(This is an error message)\n'; - } - if (messages.unfinished) { - data += '(This is an unfinished message)\n'; - } - } - - exportFromJSON({ - data: data, - fileName: filename, - extension: 'txt', - exportType: exportFromJSON.types.txt, - }); - }; - - const exportJSON = async () => { - const data = { - conversationId: conversation?.conversationId, - endpoint: conversation?.endpoint, - title: conversation?.title, - exportAt: new Date().toTimeString(), - branches: exportBranches, - recursive: recursive, - }; - - if (includeOptions) { - data['options'] = cleanupPreset({ preset: conversation as TPreset }); - } - - const messages = await buildMessageTree({ - messageId: conversation?.conversationId, - message: null, - messages: messagesTree, - branches: !!exportBranches, - recursive: !!recursive, - }); - - if (recursive && !Array.isArray(messages)) { - data['messagesTree'] = messages.children; - } else { - data['messages'] = messages; - } - - exportFromJSON({ - data: data, - fileName: filename, - extension: 'json', - exportType: exportFromJSON.types.json, - }); - }; - - const exportConversation = () => { - if (type === 'json') { - exportJSON(); - } else if (type == 'text') { - exportText(); - } else if (type == 'markdown') { - exportMarkdown(); - } else if (type == 'csv') { - exportCSV(); - } else if (type == 'screenshot') { - exportScreenshot(); - } - }; - - return { exportConversation }; -} diff --git a/client/src/hooks/Messages/useBuildMessageTree.ts b/client/src/hooks/Messages/useBuildMessageTree.ts deleted file mode 100644 index d1e40318c109..000000000000 --- a/client/src/hooks/Messages/useBuildMessageTree.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { useRecoilCallback } from 'recoil'; -import type { TMessage } from 'librechat-data-provider'; -import store from '~/store'; - -export default function useBuildMessageTree() { - const getSiblingIdx = useRecoilCallback( - ({ snapshot }) => - async (messageId: string | null | undefined) => - await snapshot.getPromise(store.messagesSiblingIdxFamily(messageId)), - [], - ); - - // return an object or an array based on branches and recursive option - // messageId is used to get siblindIdx from recoil snapshot - const buildMessageTree = async ({ - messageId, - message, - messages, - branches = false, - recursive = false, - }: { - messageId: string | null | undefined; - message: TMessage | null; - messages: TMessage[] | null; - branches?: boolean; - recursive?: boolean; - }): Promise => { - let children: TMessage[] = []; - if (messages?.length) { - if (branches) { - for (const message of messages) { - children.push( - (await buildMessageTree({ - messageId: message?.messageId, - message: message, - messages: message?.children || [], - branches, - recursive, - })) as TMessage, - ); - } - } else { - let message = messages[0]; - if (messages?.length > 1) { - const siblingIdx = await getSiblingIdx(messageId); - message = messages[messages.length - siblingIdx - 1]; - } - - children = [ - (await buildMessageTree({ - messageId: message?.messageId, - message: message, - messages: message?.children || [], - branches, - recursive, - })) as TMessage, - ]; - } - } - - if (recursive && message) { - return { ...message, children: children }; - } else { - let ret: TMessage[] = []; - if (message) { - const _message = { ...message }; - delete _message.children; - ret = [_message]; - } - for (const child of children) { - ret = ret.concat(child); - } - return ret; - } - }; - - return buildMessageTree; -}