diff --git a/src/components/ImportExportChat/ExportChat.tsx b/src/components/ImportExportChat/ExportChat.tsx
new file mode 100644
index 0000000..c4ef881
--- /dev/null
+++ b/src/components/ImportExportChat/ExportChat.tsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import useStore from '@store/store';
+
+import downloadFile from '@utils/downloadFile';
+import { getToday } from '@utils/date';
+
+import Export from '@type/export';
+
+const ExportChat = () => {
+ const { t } = useTranslation();
+
+ return (
+
+
+ {t('export')} (JSON)
+
+
+
+ );
+};
+export default ExportChat;
diff --git a/src/components/ImportExportChat/ImportChat.tsx b/src/components/ImportExportChat/ImportChat.tsx
new file mode 100644
index 0000000..6562dd5
--- /dev/null
+++ b/src/components/ImportExportChat/ImportChat.tsx
@@ -0,0 +1,173 @@
+import React, { useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { v4 as uuidv4 } from 'uuid';
+
+import useStore from '@store/store';
+
+import {
+ isLegacyImport,
+ validateAndFixChats,
+ validateExportV1,
+} from '@utils/import';
+
+import { ChatInterface, Folder, FolderCollection } from '@type/chat';
+import { ExportBase } from '@type/export';
+
+const ImportChat = () => {
+ const { t } = useTranslation();
+ const setChats = useStore.getState().setChats;
+ const setFolders = useStore.getState().setFolders;
+ const inputRef = useRef(null);
+ const [alert, setAlert] = useState<{
+ message: string;
+ success: boolean;
+ } | null>(null);
+
+ const handleFileUpload = () => {
+ if (!inputRef || !inputRef.current) return;
+ const file = inputRef.current.files?.[0];
+
+ if (file) {
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ const data = event.target?.result as string;
+
+ try {
+ const parsedData = JSON.parse(data);
+ if (isLegacyImport(parsedData)) {
+ if (validateAndFixChats(parsedData)) {
+ // import new folders
+ const folderNameToIdMap: Record = {};
+ const parsedFolders: string[] = [];
+
+ parsedData.forEach((data) => {
+ const folder = data.folder;
+ if (folder) {
+ if (!parsedFolders.includes(folder)) {
+ parsedFolders.push(folder);
+ folderNameToIdMap[folder] = uuidv4();
+ }
+ data.folder = folderNameToIdMap[folder];
+ }
+ });
+
+ const newFolders: FolderCollection = parsedFolders.reduce(
+ (acc, curr, index) => {
+ const id = folderNameToIdMap[curr];
+ const _newFolder: Folder = {
+ id,
+ name: curr,
+ expanded: false,
+ order: index,
+ };
+ return { [id]: _newFolder, ...acc };
+ },
+ {}
+ );
+
+ // increment the order of existing folders
+ const offset = parsedFolders.length;
+
+ const updatedFolders = useStore.getState().folders;
+ Object.values(updatedFolders).forEach((f) => (f.order += offset));
+
+ setFolders({ ...newFolders, ...updatedFolders });
+
+ // import chats
+ const prevChats = useStore.getState().chats;
+ if (prevChats) {
+ const updatedChats: ChatInterface[] = JSON.parse(
+ JSON.stringify(prevChats)
+ );
+ setChats(parsedData.concat(updatedChats));
+ } else {
+ setChats(parsedData);
+ }
+ setAlert({ message: 'Succesfully imported!', success: true });
+ } else {
+ setAlert({
+ message: 'Invalid chats data format',
+ success: false,
+ });
+ }
+ } else {
+ switch ((parsedData as ExportBase).version) {
+ case 1:
+ if (validateExportV1(parsedData)) {
+ // import folders
+ parsedData.folders;
+ // increment the order of existing folders
+ const offset = Object.keys(parsedData.folders).length;
+
+ const updatedFolders = useStore.getState().folders;
+ Object.values(updatedFolders).forEach(
+ (f) => (f.order += offset)
+ );
+
+ setFolders({ ...parsedData.folders, ...updatedFolders });
+
+ // import chats
+ const prevChats = useStore.getState().chats;
+ if (parsedData.chats) {
+ if (prevChats) {
+ const updatedChats: ChatInterface[] = JSON.parse(
+ JSON.stringify(prevChats)
+ );
+ setChats(parsedData.chats.concat(updatedChats));
+ } else {
+ setChats(parsedData.chats);
+ }
+ }
+
+ setAlert({ message: 'Succesfully imported!', success: true });
+ } else {
+ setAlert({
+ message: 'Invalid format',
+ success: false,
+ });
+ }
+ break;
+ }
+ }
+ } catch (error: unknown) {
+ setAlert({ message: (error as Error).message, success: false });
+ }
+ };
+
+ reader.readAsText(file);
+ }
+ };
+
+ return (
+ <>
+
+
+
+ {alert && (
+
+ {alert.message}
+
+ )}
+ >
+ );
+};
+
+export default ImportChat;
diff --git a/src/components/ImportExportChat/ImportChatOpenAI.tsx b/src/components/ImportExportChat/ImportChatOpenAI.tsx
new file mode 100644
index 0000000..734aadb
--- /dev/null
+++ b/src/components/ImportExportChat/ImportChatOpenAI.tsx
@@ -0,0 +1,78 @@
+import React, { useRef } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import useStore from '@store/store';
+
+import { importOpenAIChatExport } from '@utils/import';
+
+import { ChatInterface } from '@type/chat';
+
+const ImportChatOpenAI = ({
+ setIsModalOpen,
+}: {
+ setIsModalOpen: React.Dispatch>;
+}) => {
+ const { t } = useTranslation();
+
+ const inputRef = useRef(null);
+
+ const setToastStatus = useStore((state) => state.setToastStatus);
+ const setToastMessage = useStore((state) => state.setToastMessage);
+ const setToastShow = useStore((state) => state.setToastShow);
+ const setChats = useStore.getState().setChats;
+
+ const handleFileUpload = () => {
+ if (!inputRef || !inputRef.current) return;
+ const file = inputRef.current.files?.[0];
+ if (!file) return;
+
+ const reader = new FileReader();
+
+ reader.onload = (event) => {
+ const data = event.target?.result as string;
+
+ try {
+ const parsedData = JSON.parse(data);
+ const chats = importOpenAIChatExport(parsedData);
+ const prevChats: ChatInterface[] = JSON.parse(
+ JSON.stringify(useStore.getState().chats)
+ );
+ setChats(chats.concat(prevChats));
+
+ setToastStatus('success');
+ setToastMessage('Imported successfully!');
+ setIsModalOpen(false);
+ } catch (error: unknown) {
+ setToastStatus('error');
+ setToastMessage(`Invalid format! ${(error as Error).message}`);
+ }
+ setToastShow(true);
+ };
+
+ reader.readAsText(file);
+ };
+
+ return (
+ <>
+
+ {t('import')} OpenAI ChatGPT {t('export')}
+
+
+
+
+ >
+ );
+};
+
+export default ImportChatOpenAI;
diff --git a/src/components/ImportExportChat/ImportExportChat.tsx b/src/components/ImportExportChat/ImportExportChat.tsx
index bdad4cd..8426fe6 100644
--- a/src/components/ImportExportChat/ImportExportChat.tsx
+++ b/src/components/ImportExportChat/ImportExportChat.tsx
@@ -1,19 +1,12 @@
-import React, { useRef, useState } from 'react';
+import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
-import { v4 as uuidv4 } from 'uuid';
-import useStore from '@store/store';
import ExportIcon from '@icon/ExportIcon';
-import downloadFile from '@utils/downloadFile';
-import { getToday } from '@utils/date';
import PopupModal from '@components/PopupModal';
-import {
- isLegacyImport,
- validateAndFixChats,
- validateExportV1,
-} from '@utils/import';
-import { ChatInterface, Folder, FolderCollection } from '@type/chat';
-import Export, { ExportBase, ExportV1 } from '@type/export';
+
+import ImportChat from './ImportChat';
+import ExportChat from './ExportChat';
+import ImportChatOpenAI from './ImportChatOpenAI';
const ImportExportChat = () => {
const { t } = useTranslation();
@@ -39,6 +32,8 @@ const ImportExportChat = () => {
)}
@@ -46,185 +41,4 @@ const ImportExportChat = () => {
);
};
-const ImportChat = () => {
- const { t } = useTranslation();
- const setChats = useStore.getState().setChats;
- const setFolders = useStore.getState().setFolders;
- const inputRef = useRef(null);
- const [alert, setAlert] = useState<{
- message: string;
- success: boolean;
- } | null>(null);
-
- const handleFileUpload = () => {
- if (!inputRef || !inputRef.current) return;
- const file = inputRef.current.files?.[0];
-
- if (file) {
- const reader = new FileReader();
-
- reader.onload = (event) => {
- const data = event.target?.result as string;
-
- try {
- const parsedData = JSON.parse(data);
- if (isLegacyImport(parsedData)) {
- if (validateAndFixChats(parsedData)) {
- // import new folders
- const folderNameToIdMap: Record = {};
- const parsedFolders: string[] = [];
-
- parsedData.forEach((data) => {
- const folder = data.folder;
- if (folder) {
- if (!parsedFolders.includes(folder)) {
- parsedFolders.push(folder);
- folderNameToIdMap[folder] = uuidv4();
- }
- data.folder = folderNameToIdMap[folder];
- }
- });
-
- const newFolders: FolderCollection = parsedFolders.reduce(
- (acc, curr, index) => {
- const id = folderNameToIdMap[curr];
- const _newFolder: Folder = {
- id,
- name: curr,
- expanded: false,
- order: index,
- };
- return { [id]: _newFolder, ...acc };
- },
- {}
- );
-
- // increment the order of existing folders
- const offset = parsedFolders.length;
-
- const updatedFolders = useStore.getState().folders;
- Object.values(updatedFolders).forEach((f) => (f.order += offset));
-
- setFolders({ ...newFolders, ...updatedFolders });
-
- // import chats
- const prevChats = useStore.getState().chats;
- if (prevChats) {
- const updatedChats: ChatInterface[] = JSON.parse(
- JSON.stringify(prevChats)
- );
- setChats(parsedData.concat(updatedChats));
- } else {
- setChats(parsedData);
- }
- setAlert({ message: 'Succesfully imported!', success: true });
- } else {
- setAlert({
- message: 'Invalid chats data format',
- success: false,
- });
- }
- } else {
- switch ((parsedData as ExportBase).version) {
- case 1:
- if (validateExportV1(parsedData)) {
- // import folders
- parsedData.folders;
- // increment the order of existing folders
- const offset = Object.keys(parsedData.folders).length;
-
- const updatedFolders = useStore.getState().folders;
- Object.values(updatedFolders).forEach(
- (f) => (f.order += offset)
- );
-
- setFolders({ ...parsedData.folders, ...updatedFolders });
-
- // import chats
- const prevChats = useStore.getState().chats;
- if (parsedData.chats) {
- if (prevChats) {
- const updatedChats: ChatInterface[] = JSON.parse(
- JSON.stringify(prevChats)
- );
- setChats(parsedData.chats.concat(updatedChats));
- } else {
- setChats(parsedData.chats);
- }
- }
-
- setAlert({ message: 'Succesfully imported!', success: true });
- } else {
- setAlert({
- message: 'Invalid format',
- success: false,
- });
- }
- break;
- }
- }
- } catch (error: unknown) {
- setAlert({ message: (error as Error).message, success: false });
- }
- };
-
- reader.readAsText(file);
- }
- };
- return (
- <>
-
-
-
- {alert && (
-
- {alert.message}
-
- )}
- >
- );
-};
-
-const ExportChat = () => {
- const { t } = useTranslation();
-
- return (
-
-
- {t('export')} (JSON)
-
-
-
- );
-};
-
export default ImportExportChat;
diff --git a/src/types/export.ts b/src/types/export.ts
index 0c93b74..e32b75f 100644
--- a/src/types/export.ts
+++ b/src/types/export.ts
@@ -1,4 +1,4 @@
-import { ChatInterface, FolderCollection } from './chat';
+import { ChatInterface, FolderCollection, Role } from './chat';
export interface ExportBase {
version: number;
@@ -9,4 +9,24 @@ export interface ExportV1 extends ExportBase {
folders: FolderCollection;
}
+export type OpenAIChat = {
+ title: string;
+ mapping: {
+ [key: string]: {
+ id: string;
+ message: {
+ author: {
+ role: Role;
+ };
+ content: {
+ parts: string[];
+ };
+ } | null;
+ parent: string | null;
+ children: string[];
+ };
+ };
+ current_node: string;
+};
+
export default ExportV1;
diff --git a/src/utils/import.ts b/src/utils/import.ts
index c5dc8d2..8a43bf1 100644
--- a/src/utils/import.ts
+++ b/src/utils/import.ts
@@ -12,7 +12,7 @@ import {
modelOptions,
_defaultChatConfig,
} from '@constants/chat';
-import { ExportV1 } from '@type/export';
+import { ExportV1, OpenAIChat } from '@type/export';
export const validateAndFixChats = (chats: any): chats is ChatInterface[] => {
if (!Array.isArray(chats)) return false;
@@ -88,3 +88,45 @@ export const validateFolders = (
export const validateExportV1 = (data: ExportV1): data is ExportV1 => {
return validateAndFixChats(data.chats) && validateFolders(data.folders);
};
+
+// Convert OpenAI chat format to BetterChatGPT format
+export const convertOpenAIToBetterChatGPTFormat = (
+ openAIChat: OpenAIChat
+): ChatInterface => {
+ const messages: MessageInterface[] = [];
+
+ // Traverse the chat tree and collect messages
+ const traverseTree = (id: string) => {
+ const node = openAIChat.mapping[id];
+
+ // Extract message if it exists
+ if (node.message) {
+ const { role } = node.message.author;
+ const content = node.message.content.parts.join('');
+ if (content.length > 0) messages.push({ role, content });
+ }
+
+ // Traverse the last child node if any children exist
+ if (node.children.length > 0) {
+ traverseTree(node.children[node.children.length - 1]);
+ }
+ };
+
+ // Start traversing the tree from the root node
+ const rootNode = openAIChat.mapping[Object.keys(openAIChat.mapping)[0]].id;
+ traverseTree(rootNode);
+
+ // Return the chat interface object
+ return {
+ id: uuidv4(),
+ title: openAIChat.title,
+ messages,
+ config: _defaultChatConfig,
+ titleSet: true,
+ };
+};
+
+// Import OpenAI chat data and convert it to BetterChatGPT format
+export const importOpenAIChatExport = (openAIChatExport: OpenAIChat[]) => {
+ return openAIChatExport.map(convertOpenAIToBetterChatGPTFormat);
+};