mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 21:23:58 +01:00
feat: import openai chatgpt export
This commit is contained in:
parent
b3f421cde9
commit
ece4778f88
35
src/components/ImportExportChat/ExportChat.tsx
Normal file
35
src/components/ImportExportChat/ExportChat.tsx
Normal file
|
@ -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 (
|
||||||
|
<div className='mt-6'>
|
||||||
|
<div className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||||
|
{t('export')} (JSON)
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className='btn btn-small btn-primary'
|
||||||
|
onClick={() => {
|
||||||
|
const fileData: Export = {
|
||||||
|
chats: useStore.getState().chats,
|
||||||
|
folders: useStore.getState().folders,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
downloadFile(fileData, getToday());
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('export')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ExportChat;
|
173
src/components/ImportExportChat/ImportChat.tsx
Normal file
173
src/components/ImportExportChat/ImportChat.tsx
Normal file
|
@ -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<HTMLInputElement>(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<string, string> = {};
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<label className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||||
|
{t('import')} (JSON)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full text-sm file:p-2 text-gray-800 file:text-gray-700 dark:text-gray-300 dark:file:text-gray-200 rounded-md cursor-pointer focus:outline-none bg-gray-50 file:bg-gray-100 dark:bg-gray-800 dark:file:bg-gray-700 file:border-0 border border-gray-300 dark:border-gray-600 placeholder-gray-900 dark:placeholder-gray-300 file:cursor-pointer'
|
||||||
|
type='file'
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='btn btn-small btn-primary mt-3'
|
||||||
|
onClick={handleFileUpload}
|
||||||
|
>
|
||||||
|
{t('import')}
|
||||||
|
</button>
|
||||||
|
{alert && (
|
||||||
|
<div
|
||||||
|
className={`relative py-2 px-3 w-full mt-3 border rounded-md text-gray-600 dark:text-gray-100 text-sm whitespace-pre-wrap ${
|
||||||
|
alert.success
|
||||||
|
? 'border-green-500 bg-green-500/10'
|
||||||
|
: 'border-red-500 bg-red-500/10'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{alert.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImportChat;
|
78
src/components/ImportExportChat/ImportChatOpenAI.tsx
Normal file
78
src/components/ImportExportChat/ImportChatOpenAI.tsx
Normal file
|
@ -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<React.SetStateAction<boolean>>;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const inputRef = useRef<HTMLInputElement>(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 (
|
||||||
|
<>
|
||||||
|
<div className='text-lg font-bold text-gray-900 dark:text-gray-300 text-center mb-3'>
|
||||||
|
{t('import')} OpenAI ChatGPT {t('export')}
|
||||||
|
</div>
|
||||||
|
<label className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||||
|
{t('import')} (JSON)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
className='w-full text-sm file:p-2 text-gray-800 file:text-gray-700 dark:text-gray-300 dark:file:text-gray-200 rounded-md cursor-pointer focus:outline-none bg-gray-50 file:bg-gray-100 dark:bg-gray-800 dark:file:bg-gray-700 file:border-0 border border-gray-300 dark:border-gray-600 placeholder-gray-900 dark:placeholder-gray-300 file:cursor-pointer'
|
||||||
|
type='file'
|
||||||
|
ref={inputRef}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className='btn btn-small btn-primary mt-3'
|
||||||
|
onClick={handleFileUpload}
|
||||||
|
>
|
||||||
|
{t('import')}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImportChatOpenAI;
|
|
@ -1,19 +1,12 @@
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import useStore from '@store/store';
|
|
||||||
|
|
||||||
import ExportIcon from '@icon/ExportIcon';
|
import ExportIcon from '@icon/ExportIcon';
|
||||||
import downloadFile from '@utils/downloadFile';
|
|
||||||
import { getToday } from '@utils/date';
|
|
||||||
import PopupModal from '@components/PopupModal';
|
import PopupModal from '@components/PopupModal';
|
||||||
import {
|
|
||||||
isLegacyImport,
|
import ImportChat from './ImportChat';
|
||||||
validateAndFixChats,
|
import ExportChat from './ExportChat';
|
||||||
validateExportV1,
|
import ImportChatOpenAI from './ImportChatOpenAI';
|
||||||
} from '@utils/import';
|
|
||||||
import { ChatInterface, Folder, FolderCollection } from '@type/chat';
|
|
||||||
import Export, { ExportBase, ExportV1 } from '@type/export';
|
|
||||||
|
|
||||||
const ImportExportChat = () => {
|
const ImportExportChat = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -39,6 +32,8 @@ const ImportExportChat = () => {
|
||||||
<div className='p-6 border-b border-gray-200 dark:border-gray-600'>
|
<div className='p-6 border-b border-gray-200 dark:border-gray-600'>
|
||||||
<ImportChat />
|
<ImportChat />
|
||||||
<ExportChat />
|
<ExportChat />
|
||||||
|
<div className='border-t my-3 border-gray-200 dark:border-gray-600' />
|
||||||
|
<ImportChatOpenAI setIsModalOpen={setIsModalOpen} />
|
||||||
</div>
|
</div>
|
||||||
</PopupModal>
|
</PopupModal>
|
||||||
)}
|
)}
|
||||||
|
@ -46,185 +41,4 @@ const ImportExportChat = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImportChat = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const setChats = useStore.getState().setChats;
|
|
||||||
const setFolders = useStore.getState().setFolders;
|
|
||||||
const inputRef = useRef<HTMLInputElement>(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<string, string> = {};
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<label className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
|
||||||
{t('import')} (JSON)
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
className='w-full text-sm file:p-2 text-gray-800 file:text-gray-700 dark:text-gray-300 dark:file:text-gray-200 rounded-md cursor-pointer focus:outline-none bg-gray-50 file:bg-gray-100 dark:bg-gray-800 dark:file:bg-gray-700 file:border-0 border border-gray-300 dark:border-gray-600 placeholder-gray-900 dark:placeholder-gray-300 file:cursor-pointer'
|
|
||||||
type='file'
|
|
||||||
ref={inputRef}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className='btn btn-small btn-primary mt-3'
|
|
||||||
onClick={handleFileUpload}
|
|
||||||
>
|
|
||||||
{t('import')}
|
|
||||||
</button>
|
|
||||||
{alert && (
|
|
||||||
<div
|
|
||||||
className={`relative py-2 px-3 w-full mt-3 border rounded-md text-gray-600 dark:text-gray-100 text-sm whitespace-pre-wrap ${
|
|
||||||
alert.success
|
|
||||||
? 'border-green-500 bg-green-500/10'
|
|
||||||
: 'border-red-500 bg-red-500/10'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{alert.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ExportChat = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='mt-6'>
|
|
||||||
<div className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
|
||||||
{t('export')} (JSON)
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className='btn btn-small btn-primary'
|
|
||||||
onClick={() => {
|
|
||||||
const fileData: Export = {
|
|
||||||
chats: useStore.getState().chats,
|
|
||||||
folders: useStore.getState().folders,
|
|
||||||
version: 1,
|
|
||||||
};
|
|
||||||
downloadFile(fileData, getToday());
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('export')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ImportExportChat;
|
export default ImportExportChat;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChatInterface, FolderCollection } from './chat';
|
import { ChatInterface, FolderCollection, Role } from './chat';
|
||||||
|
|
||||||
export interface ExportBase {
|
export interface ExportBase {
|
||||||
version: number;
|
version: number;
|
||||||
|
@ -9,4 +9,24 @@ export interface ExportV1 extends ExportBase {
|
||||||
folders: FolderCollection;
|
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;
|
export default ExportV1;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
modelOptions,
|
modelOptions,
|
||||||
_defaultChatConfig,
|
_defaultChatConfig,
|
||||||
} from '@constants/chat';
|
} from '@constants/chat';
|
||||||
import { ExportV1 } from '@type/export';
|
import { ExportV1, OpenAIChat } from '@type/export';
|
||||||
|
|
||||||
export const validateAndFixChats = (chats: any): chats is ChatInterface[] => {
|
export const validateAndFixChats = (chats: any): chats is ChatInterface[] => {
|
||||||
if (!Array.isArray(chats)) return false;
|
if (!Array.isArray(chats)) return false;
|
||||||
|
@ -88,3 +88,45 @@ export const validateFolders = (
|
||||||
export const validateExportV1 = (data: ExportV1): data is ExportV1 => {
|
export const validateExportV1 = (data: ExportV1): data is ExportV1 => {
|
||||||
return validateAndFixChats(data.chats) && validateFolders(data.folders);
|
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);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue