mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 19:33:59 +01:00
parent
f6c2976cbd
commit
9956d254f4
|
@ -1,13 +1,19 @@
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, 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 useStore from '@store/store';
|
||||||
|
|
||||||
import ExportIcon from '@icon/ExportIcon';
|
import ExportIcon from '@icon/ExportIcon';
|
||||||
import downloadFile from '@utils/downloadFile';
|
import downloadFile from '@utils/downloadFile';
|
||||||
import { getToday } from '@utils/date';
|
import { getToday } from '@utils/date';
|
||||||
import PopupModal from '@components/PopupModal';
|
import PopupModal from '@components/PopupModal';
|
||||||
import { validateAndFixChats } from '@utils/chat';
|
import {
|
||||||
import { ChatInterface } from '@type/chat';
|
isLegacyImport,
|
||||||
|
validateAndFixChats,
|
||||||
|
validateExportV1,
|
||||||
|
} 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();
|
||||||
|
@ -43,8 +49,7 @@ const ImportExportChat = () => {
|
||||||
const ImportChat = () => {
|
const ImportChat = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const setChats = useStore.getState().setChats;
|
const setChats = useStore.getState().setChats;
|
||||||
const setFoldersName = useStore.getState().setFoldersName;
|
const setFolders = useStore.getState().setFolders;
|
||||||
const setFoldersExpanded = useStore.getState().setFoldersExpanded;
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [alert, setAlert] = useState<{
|
const [alert, setAlert] = useState<{
|
||||||
message: string;
|
message: string;
|
||||||
|
@ -63,33 +68,100 @@ const ImportChat = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedData = JSON.parse(data);
|
const parsedData = JSON.parse(data);
|
||||||
if (validateAndFixChats(parsedData)) {
|
if (isLegacyImport(parsedData)) {
|
||||||
const parsedFolders: string[] = [];
|
if (validateAndFixChats(parsedData)) {
|
||||||
parsedData.forEach((data) => {
|
// import new folders
|
||||||
if (data.folder && !parsedFolders.includes(data.folder))
|
const folderNameToIdMap: Record<string, string> = {};
|
||||||
parsedFolders.push(data.folder);
|
const parsedFolders: string[] = [];
|
||||||
});
|
|
||||||
setFoldersName([
|
|
||||||
...parsedFolders,
|
|
||||||
...useStore.getState().foldersName,
|
|
||||||
]);
|
|
||||||
setFoldersExpanded([
|
|
||||||
...new Array(parsedFolders.length).fill(false),
|
|
||||||
...useStore.getState().foldersExpanded,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const prevChats = useStore.getState().chats;
|
parsedData.forEach((data) => {
|
||||||
if (prevChats) {
|
const folder = data.folder;
|
||||||
const updatedChats: ChatInterface[] = JSON.parse(
|
if (folder) {
|
||||||
JSON.stringify(prevChats)
|
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 };
|
||||||
|
},
|
||||||
|
{}
|
||||||
);
|
);
|
||||||
setChats(parsedData.concat(updatedChats));
|
|
||||||
|
// 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 {
|
} else {
|
||||||
setChats(parsedData);
|
setAlert({
|
||||||
|
message: 'Invalid chats data format',
|
||||||
|
success: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setAlert({ message: 'Succesfully imported!', success: true });
|
|
||||||
} else {
|
} else {
|
||||||
setAlert({ message: 'Invalid chats data format', success: false });
|
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) {
|
} catch (error: unknown) {
|
||||||
setAlert({ message: (error as Error).message, success: false });
|
setAlert({ message: (error as Error).message, success: false });
|
||||||
|
@ -132,7 +204,7 @@ const ImportChat = () => {
|
||||||
|
|
||||||
const ExportChat = () => {
|
const ExportChat = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const chats = useStore.getState().chats;
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-6'>
|
<div className='mt-6'>
|
||||||
<div className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
<div className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||||
|
@ -141,7 +213,12 @@ const ExportChat = () => {
|
||||||
<button
|
<button
|
||||||
className='btn btn-small btn-primary'
|
className='btn btn-small btn-primary'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (chats) downloadFile(chats, getToday());
|
const fileData: Export = {
|
||||||
|
chats: useStore.getState().chats,
|
||||||
|
folders: useStore.getState().folders,
|
||||||
|
version: 1,
|
||||||
|
};
|
||||||
|
downloadFile(fileData, getToday());
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('export')}
|
{t('export')}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import useStore from '@store/store';
|
import useStore from '@store/store';
|
||||||
|
|
||||||
import DownChevronArrow from '@icon/DownChevronArrow';
|
import DownChevronArrow from '@icon/DownChevronArrow';
|
||||||
import FolderIcon from '@icon/FolderIcon';
|
import FolderIcon from '@icon/FolderIcon';
|
||||||
import { ChatHistoryInterface, ChatInterface } from '@type/chat';
|
import {
|
||||||
|
ChatHistoryInterface,
|
||||||
|
ChatInterface,
|
||||||
|
FolderCollection,
|
||||||
|
} from '@type/chat';
|
||||||
|
|
||||||
import ChatHistory from './ChatHistory';
|
import ChatHistory from './ChatHistory';
|
||||||
import EditIcon from '@icon/EditIcon';
|
import EditIcon from '@icon/EditIcon';
|
||||||
|
@ -12,18 +16,17 @@ import CrossIcon from '@icon/CrossIcon';
|
||||||
import TickIcon from '@icon/TickIcon';
|
import TickIcon from '@icon/TickIcon';
|
||||||
|
|
||||||
const ChatFolder = ({
|
const ChatFolder = ({
|
||||||
folderName,
|
|
||||||
folderChats,
|
folderChats,
|
||||||
folderIndex,
|
folderId,
|
||||||
}: {
|
}: {
|
||||||
folderName: string;
|
|
||||||
folderChats: ChatHistoryInterface[];
|
folderChats: ChatHistoryInterface[];
|
||||||
folderIndex: number;
|
folderId: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
const folderName = useStore((state) => state.folders[folderId].name);
|
||||||
|
const isExpanded = useStore((state) => state.folders[folderId].expanded);
|
||||||
|
|
||||||
const setChats = useStore((state) => state.setChats);
|
const setChats = useStore((state) => state.setChats);
|
||||||
const setFoldersName = useStore((state) => state.setFoldersName);
|
const setFolders = useStore((state) => state.setFolders);
|
||||||
const setFoldersExpanded = useStore((state) => state.setFoldersExpanded);
|
|
||||||
const foldersExpanded = useStore((state) => state.foldersExpanded);
|
|
||||||
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
@ -33,20 +36,11 @@ const ChatFolder = ({
|
||||||
const [isHover, setIsHover] = useState<boolean>(false);
|
const [isHover, setIsHover] = useState<boolean>(false);
|
||||||
|
|
||||||
const editTitle = () => {
|
const editTitle = () => {
|
||||||
const updatedChats: ChatInterface[] = JSON.parse(
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
JSON.stringify(useStore.getState().chats)
|
JSON.stringify(useStore.getState().folders)
|
||||||
);
|
);
|
||||||
|
updatedFolders[folderId].name = _folderName;
|
||||||
updatedChats.forEach((chat) => {
|
setFolders(updatedFolders);
|
||||||
if (chat.folder === folderName) chat.folder = _folderName;
|
|
||||||
});
|
|
||||||
setChats(updatedChats);
|
|
||||||
|
|
||||||
const updatedFolderNames = [...useStore.getState().foldersName];
|
|
||||||
const pos = updatedFolderNames.indexOf(folderName);
|
|
||||||
if (pos !== -1) updatedFolderNames[pos] = _folderName;
|
|
||||||
setFoldersName(updatedFolderNames);
|
|
||||||
|
|
||||||
setIsEdit(false);
|
setIsEdit(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,19 +49,16 @@ const ChatFolder = ({
|
||||||
JSON.stringify(useStore.getState().chats)
|
JSON.stringify(useStore.getState().chats)
|
||||||
);
|
);
|
||||||
updatedChats.forEach((chat) => {
|
updatedChats.forEach((chat) => {
|
||||||
if (chat.folder === folderName) delete chat.folder;
|
if (chat.folder === folderId) delete chat.folder;
|
||||||
});
|
});
|
||||||
setChats(updatedChats);
|
setChats(updatedChats);
|
||||||
|
|
||||||
const updatedFoldersName = [...useStore.getState().foldersName];
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
const updatedFoldersExpanded = [...useStore.getState().foldersExpanded];
|
JSON.stringify(useStore.getState().folders)
|
||||||
|
);
|
||||||
|
delete updatedFolders[folderId];
|
||||||
|
setFolders(updatedFolders);
|
||||||
|
|
||||||
const i = updatedFoldersName.findIndex((name) => name === folderName);
|
|
||||||
updatedFoldersName.splice(i, 1);
|
|
||||||
updatedFoldersExpanded.splice(i, 1);
|
|
||||||
|
|
||||||
setFoldersName(updatedFoldersName);
|
|
||||||
setFoldersExpanded(updatedFoldersExpanded);
|
|
||||||
setIsDelete(false);
|
setIsDelete(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,15 +86,19 @@ const ChatFolder = ({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsHover(false);
|
setIsHover(false);
|
||||||
|
|
||||||
const updatedFoldersExpanded = [...foldersExpanded];
|
// expand folder on drop
|
||||||
updatedFoldersExpanded[folderIndex] = true;
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
setFoldersExpanded(updatedFoldersExpanded);
|
JSON.stringify(useStore.getState().folders)
|
||||||
|
);
|
||||||
|
updatedFolders[folderId].expanded = true;
|
||||||
|
setFolders(updatedFolders);
|
||||||
|
|
||||||
|
// update chat folderId to new folderId
|
||||||
const chatIndex = Number(e.dataTransfer.getData('chatIndex'));
|
const chatIndex = Number(e.dataTransfer.getData('chatIndex'));
|
||||||
const updatedChats: ChatInterface[] = JSON.parse(
|
const updatedChats: ChatInterface[] = JSON.parse(
|
||||||
JSON.stringify(useStore.getState().chats)
|
JSON.stringify(useStore.getState().chats)
|
||||||
);
|
);
|
||||||
updatedChats[chatIndex].folder = folderName;
|
updatedChats[chatIndex].folder = folderId;
|
||||||
setChats(updatedChats);
|
setChats(updatedChats);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -119,11 +114,17 @@ const ChatFolder = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleExpanded = () => {
|
const toggleExpanded = () => {
|
||||||
const updatedFoldersExpanded = [...foldersExpanded];
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
updatedFoldersExpanded[folderIndex] = !updatedFoldersExpanded[folderIndex];
|
JSON.stringify(useStore.getState().folders)
|
||||||
setFoldersExpanded(updatedFoldersExpanded);
|
);
|
||||||
|
updatedFolders[folderId].expanded = !updatedFolders[folderId].expanded;
|
||||||
|
setFolders(updatedFolders);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef && inputRef.current) inputRef.current.focus();
|
||||||
|
}, [isEdit]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full transition-colors ${isHover ? 'bg-gray-800/40' : ''}`}
|
className={`w-full transition-colors ${isHover ? 'bg-gray-800/40' : ''}`}
|
||||||
|
@ -154,7 +155,7 @@ const ChatFolder = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className='absolute flex right-1 z-10 text-gray-300 visible'
|
className='flex text-gray-300'
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{isDelete || isEdit ? (
|
{isDelete || isEdit ? (
|
||||||
|
@ -183,7 +184,7 @@ const ChatFolder = ({
|
||||||
<button className='p-1 hover:text-white' onClick={toggleExpanded}>
|
<button className='p-1 hover:text-white' onClick={toggleExpanded}>
|
||||||
<DownChevronArrow
|
<DownChevronArrow
|
||||||
className={`${
|
className={`${
|
||||||
foldersExpanded[folderIndex] ? 'rotate-180' : ''
|
isExpanded ? 'rotate-180' : ''
|
||||||
} transition-transform`}
|
} transition-transform`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
@ -192,7 +193,7 @@ const ChatFolder = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='ml-3 pl-1 border-l-2 border-gray-700 flex flex-col gap-1'>
|
<div className='ml-3 pl-1 border-l-2 border-gray-700 flex flex-col gap-1'>
|
||||||
{foldersExpanded[folderIndex] &&
|
{isExpanded &&
|
||||||
folderChats.map((chat) => (
|
folderChats.map((chat) => (
|
||||||
<ChatHistory
|
<ChatHistory
|
||||||
title={chat.title}
|
title={chat.title}
|
||||||
|
|
|
@ -11,54 +11,71 @@ import {
|
||||||
ChatHistoryInterface,
|
ChatHistoryInterface,
|
||||||
ChatHistoryFolderInterface,
|
ChatHistoryFolderInterface,
|
||||||
ChatInterface,
|
ChatInterface,
|
||||||
|
FolderCollection,
|
||||||
} from '@type/chat';
|
} from '@type/chat';
|
||||||
|
|
||||||
const ChatHistoryList = () => {
|
const ChatHistoryList = () => {
|
||||||
const currentChatIndex = useStore((state) => state.currentChatIndex);
|
const currentChatIndex = useStore((state) => state.currentChatIndex);
|
||||||
const setChats = useStore((state) => state.setChats);
|
const setChats = useStore((state) => state.setChats);
|
||||||
|
const setFolders = useStore((state) => state.setFolders);
|
||||||
const chatTitles = useStore(
|
const chatTitles = useStore(
|
||||||
(state) => state.chats?.map((chat) => chat.title),
|
(state) => state.chats?.map((chat) => chat.title),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isHover, setIsHover] = useState<boolean>(false);
|
const [isHover, setIsHover] = useState<boolean>(false);
|
||||||
const [folders, setFolders] = useState<ChatHistoryFolderInterface>({});
|
const [chatFolders, setChatFolders] = useState<ChatHistoryFolderInterface>(
|
||||||
const [noFolders, setNoFolders] = useState<ChatHistoryInterface[]>([]);
|
{}
|
||||||
|
);
|
||||||
|
const [noChatFolders, setNoChatFolders] = useState<ChatHistoryInterface[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
const [filter, setFilter] = useState<string>('');
|
const [filter, setFilter] = useState<string>('');
|
||||||
|
|
||||||
const chatsRef = useRef<ChatInterface[]>(useStore.getState().chats || []);
|
const chatsRef = useRef<ChatInterface[]>(useStore.getState().chats || []);
|
||||||
const foldersNameRef = useRef<string[]>(useStore.getState().foldersName);
|
const foldersRef = useRef<FolderCollection>(useStore.getState().folders);
|
||||||
const filterRef = useRef<string>(filter);
|
const filterRef = useRef<string>(filter);
|
||||||
|
|
||||||
const updateFolders = useRef(() => {
|
const updateFolders = useRef(() => {
|
||||||
const _folders: ChatHistoryFolderInterface = {};
|
const _folders: ChatHistoryFolderInterface = {};
|
||||||
const _noFolders: ChatHistoryInterface[] = [];
|
const _noFolders: ChatHistoryInterface[] = [];
|
||||||
const chats = useStore.getState().chats;
|
const chats = useStore.getState().chats;
|
||||||
const foldersName = useStore.getState().foldersName;
|
const folders = useStore.getState().folders;
|
||||||
|
|
||||||
foldersName.forEach((f) => (_folders[f] = []));
|
Object.values(folders)
|
||||||
|
.sort((a, b) => a.order - b.order)
|
||||||
|
.forEach((f) => (_folders[f.id] = []));
|
||||||
|
|
||||||
if (chats) {
|
if (chats) {
|
||||||
chats.forEach((chat, index) => {
|
chats.forEach((chat, index) => {
|
||||||
const filterLowerCase = filterRef.current.toLowerCase();
|
const _filterLowerCase = filterRef.current.toLowerCase();
|
||||||
|
const _chatTitle = chat.title.toLowerCase();
|
||||||
|
const _chatFolderName = chat.folder
|
||||||
|
? folders[chat.folder].name.toLowerCase()
|
||||||
|
: '';
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!chat.title.toLocaleLowerCase().includes(filterLowerCase) &&
|
!_chatTitle.includes(_filterLowerCase) &&
|
||||||
!chat.folder?.toLowerCase().includes(filterLowerCase) &&
|
!_chatFolderName.includes(_filterLowerCase) &&
|
||||||
index !== useStore.getState().currentChatIndex
|
index !== useStore.getState().currentChatIndex
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!chat.folder) {
|
if (!chat.folder) {
|
||||||
_noFolders.push({ title: chat.title, index: index });
|
_noFolders.push({ title: chat.title, index: index, id: chat.id });
|
||||||
} else {
|
} else {
|
||||||
if (!_folders[chat.folder]) _folders[chat.folder] = [];
|
if (!_folders[chat.folder]) _folders[_chatFolderName] = [];
|
||||||
_folders[chat.folder].push({ title: chat.title, index: index });
|
_folders[chat.folder].push({
|
||||||
|
title: chat.title,
|
||||||
|
index: index,
|
||||||
|
id: chat.id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setFolders(_folders);
|
setChatFolders(_folders);
|
||||||
setNoFolders(_noFolders);
|
setNoChatFolders(_noFolders);
|
||||||
}).current;
|
}).current;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -72,9 +89,9 @@ const ChatHistoryList = () => {
|
||||||
) {
|
) {
|
||||||
updateFolders();
|
updateFolders();
|
||||||
chatsRef.current = state.chats;
|
chatsRef.current = state.chats;
|
||||||
} else if (state.foldersName !== foldersNameRef.current) {
|
} else if (state.folders !== foldersRef.current) {
|
||||||
updateFolders();
|
updateFolders();
|
||||||
foldersNameRef.current = state.foldersName;
|
foldersRef.current = state.folders;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -85,20 +102,21 @@ const ChatHistoryList = () => {
|
||||||
currentChatIndex >= 0 &&
|
currentChatIndex >= 0 &&
|
||||||
currentChatIndex < chatTitles.length
|
currentChatIndex < chatTitles.length
|
||||||
) {
|
) {
|
||||||
|
// set title
|
||||||
document.title = chatTitles[currentChatIndex];
|
document.title = chatTitles[currentChatIndex];
|
||||||
|
|
||||||
|
// expand folder of current chat
|
||||||
const chats = useStore.getState().chats;
|
const chats = useStore.getState().chats;
|
||||||
if (chats) {
|
if (chats) {
|
||||||
const folderIndex = useStore
|
const folderId = chats[currentChatIndex].folder;
|
||||||
.getState()
|
|
||||||
.foldersName.findIndex((f) => f === chats[currentChatIndex].folder);
|
|
||||||
|
|
||||||
if (folderIndex) {
|
if (folderId) {
|
||||||
const updatedFolderExpanded = [
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
...useStore.getState().foldersExpanded,
|
JSON.stringify(useStore.getState().folders)
|
||||||
];
|
);
|
||||||
updatedFolderExpanded[folderIndex] = true;
|
|
||||||
useStore.getState().setFoldersExpanded(updatedFolderExpanded);
|
updatedFolders[folderId].expanded = true;
|
||||||
|
setFolders(updatedFolders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,20 +167,15 @@ const ChatHistoryList = () => {
|
||||||
<NewFolder />
|
<NewFolder />
|
||||||
<ChatSearch filter={filter} setFilter={setFilter} />
|
<ChatSearch filter={filter} setFilter={setFilter} />
|
||||||
<div className='flex flex-col gap-2 text-gray-100 text-sm'>
|
<div className='flex flex-col gap-2 text-gray-100 text-sm'>
|
||||||
{Object.keys(folders).map((folderName, folderIndex) => (
|
{Object.keys(chatFolders).map((folderId) => (
|
||||||
<ChatFolder
|
<ChatFolder
|
||||||
folderName={folderName}
|
folderChats={chatFolders[folderId]}
|
||||||
folderChats={folders[folderName]}
|
folderId={folderId}
|
||||||
folderIndex={folderIndex}
|
key={folderId}
|
||||||
key={folderName}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{noFolders.map(({ title, index }) => (
|
{noChatFolders.map(({ title, index, id }) => (
|
||||||
<ChatHistory
|
<ChatHistory title={title} key={`${title}-${id}`} chatIndex={index} />
|
||||||
title={title}
|
|
||||||
key={`${title}-${index}`}
|
|
||||||
chatIndex={index}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full h-10' />
|
<div className='w-full h-10' />
|
||||||
|
|
|
@ -10,15 +10,13 @@ const ClearConversation = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const initialiseNewChat = useInitialiseNewChat();
|
const initialiseNewChat = useInitialiseNewChat();
|
||||||
const setFoldersName = useStore((state) => state.setFoldersName);
|
const setFolders = useStore((state) => state.setFolders);
|
||||||
const setFoldersExpanded = useStore((state) => state.setFoldersExpanded);
|
|
||||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setFoldersName([]);
|
|
||||||
setFoldersExpanded([]);
|
|
||||||
initialiseNewChat();
|
initialiseNewChat();
|
||||||
|
setFolders({});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,30 +1,44 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import useStore from '@store/store';
|
import useStore from '@store/store';
|
||||||
|
|
||||||
import NewFolderIcon from '@icon/NewFolderIcon';
|
import NewFolderIcon from '@icon/NewFolderIcon';
|
||||||
|
import { Folder, FolderCollection } from '@type/chat';
|
||||||
|
|
||||||
const NewFolder = () => {
|
const NewFolder = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const generating = useStore((state) => state.generating);
|
const generating = useStore((state) => state.generating);
|
||||||
const setFoldersName = useStore((state) => state.setFoldersName);
|
const setFolders = useStore((state) => state.setFolders);
|
||||||
const setFoldersExpanded = useStore((state) => state.setFoldersExpanded);
|
|
||||||
|
|
||||||
const addFolder = () => {
|
const addFolder = () => {
|
||||||
let folderIndex = 1;
|
let folderIndex = 1;
|
||||||
let name = `New Folder ${folderIndex}`;
|
let name = `New Folder ${folderIndex}`;
|
||||||
|
|
||||||
while (
|
const folders = useStore.getState().folders;
|
||||||
useStore
|
|
||||||
.getState()
|
while (Object.values(folders).some((folder) => folder.name === name)) {
|
||||||
.foldersName.some((_folderName) => _folderName === name)
|
|
||||||
) {
|
|
||||||
folderIndex += 1;
|
folderIndex += 1;
|
||||||
name = `New Folder ${folderIndex}`;
|
name = `New Folder ${folderIndex}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFoldersName([name, ...useStore.getState().foldersName]);
|
const updatedFolders: FolderCollection = JSON.parse(
|
||||||
setFoldersExpanded([false, ...useStore.getState().foldersExpanded]);
|
JSON.stringify(folders)
|
||||||
|
);
|
||||||
|
|
||||||
|
const id = uuidv4();
|
||||||
|
const newFolder: Folder = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
expanded: false,
|
||||||
|
order: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.values(updatedFolders).forEach((folder) => {
|
||||||
|
folder.order += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
setFolders({ [id]: newFolder, ...updatedFolders });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { ChatInterface, ConfigInterface, ModelOptions } from '@type/chat';
|
import { ChatInterface, ConfigInterface, ModelOptions } from '@type/chat';
|
||||||
import useStore from '@store/store';
|
import useStore from '@store/store';
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ export const _defaultChatConfig: ConfigInterface = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateDefaultChat = (title?: string): ChatInterface => ({
|
export const generateDefaultChat = (title?: string): ChatInterface => ({
|
||||||
|
id: uuidv4(),
|
||||||
title: title ? title : 'New Chat',
|
title: title ? title : 'New Chat',
|
||||||
messages:
|
messages:
|
||||||
useStore.getState().defaultSystemMessage.length > 0
|
useStore.getState().defaultSystemMessage.length > 0
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { StoreSlice } from './store';
|
import { StoreSlice } from './store';
|
||||||
import { ChatInterface, MessageInterface } from '@type/chat';
|
import { ChatInterface, FolderCollection, MessageInterface } from '@type/chat';
|
||||||
|
|
||||||
export interface ChatSlice {
|
export interface ChatSlice {
|
||||||
messages: MessageInterface[];
|
messages: MessageInterface[];
|
||||||
|
@ -7,15 +7,13 @@ export interface ChatSlice {
|
||||||
currentChatIndex: number;
|
currentChatIndex: number;
|
||||||
generating: boolean;
|
generating: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
foldersName: string[];
|
folders: FolderCollection;
|
||||||
foldersExpanded: boolean[];
|
|
||||||
setMessages: (messages: MessageInterface[]) => void;
|
setMessages: (messages: MessageInterface[]) => void;
|
||||||
setChats: (chats: ChatInterface[]) => void;
|
setChats: (chats: ChatInterface[]) => void;
|
||||||
setCurrentChatIndex: (currentChatIndex: number) => void;
|
setCurrentChatIndex: (currentChatIndex: number) => void;
|
||||||
setGenerating: (generating: boolean) => void;
|
setGenerating: (generating: boolean) => void;
|
||||||
setError: (error: string) => void;
|
setError: (error: string) => void;
|
||||||
setFoldersName: (foldersName: string[]) => void;
|
setFolders: (folders: FolderCollection) => void;
|
||||||
setFoldersExpanded: (foldersExpanded: boolean[]) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createChatSlice: StoreSlice<ChatSlice> = (set, get) => ({
|
export const createChatSlice: StoreSlice<ChatSlice> = (set, get) => ({
|
||||||
|
@ -23,8 +21,7 @@ export const createChatSlice: StoreSlice<ChatSlice> = (set, get) => ({
|
||||||
currentChatIndex: -1,
|
currentChatIndex: -1,
|
||||||
generating: false,
|
generating: false,
|
||||||
error: '',
|
error: '',
|
||||||
foldersName: [],
|
folders: {},
|
||||||
foldersExpanded: [],
|
|
||||||
setMessages: (messages: MessageInterface[]) => {
|
setMessages: (messages: MessageInterface[]) => {
|
||||||
set((prev: ChatSlice) => ({
|
set((prev: ChatSlice) => ({
|
||||||
...prev,
|
...prev,
|
||||||
|
@ -55,16 +52,10 @@ export const createChatSlice: StoreSlice<ChatSlice> = (set, get) => ({
|
||||||
error: error,
|
error: error,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
setFoldersName: (foldersName: string[]) => {
|
setFolders: (folders: FolderCollection) => {
|
||||||
set((prev: ChatSlice) => ({
|
set((prev: ChatSlice) => ({
|
||||||
...prev,
|
...prev,
|
||||||
foldersName: foldersName,
|
folders: folders,
|
||||||
}));
|
|
||||||
},
|
|
||||||
setFoldersExpanded: (foldersExpanded: boolean[]) => {
|
|
||||||
set((prev: ChatSlice) => ({
|
|
||||||
...prev,
|
|
||||||
foldersExpanded: foldersExpanded,
|
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Folder,
|
||||||
|
FolderCollection,
|
||||||
LocalStorageInterfaceV0ToV1,
|
LocalStorageInterfaceV0ToV1,
|
||||||
LocalStorageInterfaceV1ToV2,
|
LocalStorageInterfaceV1ToV2,
|
||||||
LocalStorageInterfaceV2ToV3,
|
LocalStorageInterfaceV2ToV3,
|
||||||
|
@ -6,6 +10,7 @@ import {
|
||||||
LocalStorageInterfaceV4ToV5,
|
LocalStorageInterfaceV4ToV5,
|
||||||
LocalStorageInterfaceV5ToV6,
|
LocalStorageInterfaceV5ToV6,
|
||||||
LocalStorageInterfaceV6ToV7,
|
LocalStorageInterfaceV6ToV7,
|
||||||
|
LocalStorageInterfaceV7oV8,
|
||||||
} from '@type/chat';
|
} from '@type/chat';
|
||||||
import {
|
import {
|
||||||
_defaultChatConfig,
|
_defaultChatConfig,
|
||||||
|
@ -73,3 +78,29 @@ export const migrateV6 = (persistedState: LocalStorageInterfaceV6ToV7) => {
|
||||||
if (!persistedState.apiKey || persistedState.apiKey.length === 0)
|
if (!persistedState.apiKey || persistedState.apiKey.length === 0)
|
||||||
persistedState.apiKey = '';
|
persistedState.apiKey = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const migrateV7 = (persistedState: LocalStorageInterfaceV7oV8) => {
|
||||||
|
let folders: FolderCollection = {};
|
||||||
|
const folderNameToIdMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
// convert foldersExpanded and foldersName to folders
|
||||||
|
persistedState.foldersName.forEach((name, index) => {
|
||||||
|
const id = uuidv4();
|
||||||
|
const folder: Folder = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
expanded: persistedState.foldersExpanded[index],
|
||||||
|
order: index,
|
||||||
|
};
|
||||||
|
|
||||||
|
folders = { [id]: folder, ...folders };
|
||||||
|
folderNameToIdMap[name] = id;
|
||||||
|
});
|
||||||
|
persistedState.folders = folders;
|
||||||
|
|
||||||
|
// change the chat.folder from name to id
|
||||||
|
persistedState.chats.forEach((chat) => {
|
||||||
|
if (chat.folder) chat.folder = folderNameToIdMap[chat.folder];
|
||||||
|
chat.id = uuidv4();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
LocalStorageInterfaceV4ToV5,
|
LocalStorageInterfaceV4ToV5,
|
||||||
LocalStorageInterfaceV5ToV6,
|
LocalStorageInterfaceV5ToV6,
|
||||||
LocalStorageInterfaceV6ToV7,
|
LocalStorageInterfaceV6ToV7,
|
||||||
|
LocalStorageInterfaceV7oV8,
|
||||||
} from '@type/chat';
|
} from '@type/chat';
|
||||||
import {
|
import {
|
||||||
migrateV0,
|
migrateV0,
|
||||||
|
@ -22,6 +23,7 @@ import {
|
||||||
migrateV4,
|
migrateV4,
|
||||||
migrateV5,
|
migrateV5,
|
||||||
migrateV6,
|
migrateV6,
|
||||||
|
migrateV7,
|
||||||
} from './migrate';
|
} from './migrate';
|
||||||
|
|
||||||
export type StoreState = ChatSlice &
|
export type StoreState = ChatSlice &
|
||||||
|
@ -59,11 +61,10 @@ const useStore = create<StoreState>()(
|
||||||
hideMenuOptions: state.hideMenuOptions,
|
hideMenuOptions: state.hideMenuOptions,
|
||||||
firstVisit: state.firstVisit,
|
firstVisit: state.firstVisit,
|
||||||
hideSideMenu: state.hideSideMenu,
|
hideSideMenu: state.hideSideMenu,
|
||||||
foldersName: state.foldersName,
|
folders: state.folders,
|
||||||
foldersExpanded: state.foldersExpanded,
|
|
||||||
enterToSubmit: state.enterToSubmit,
|
enterToSubmit: state.enterToSubmit,
|
||||||
}),
|
}),
|
||||||
version: 7,
|
version: 8,
|
||||||
migrate: (persistedState, version) => {
|
migrate: (persistedState, version) => {
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -80,6 +81,8 @@ const useStore = create<StoreState>()(
|
||||||
migrateV5(persistedState as LocalStorageInterfaceV5ToV6);
|
migrateV5(persistedState as LocalStorageInterfaceV5ToV6);
|
||||||
case 6:
|
case 6:
|
||||||
migrateV6(persistedState as LocalStorageInterfaceV6ToV7);
|
migrateV6(persistedState as LocalStorageInterfaceV6ToV7);
|
||||||
|
case 7:
|
||||||
|
migrateV7(persistedState as LocalStorageInterfaceV7oV8);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return persistedState as StoreState;
|
return persistedState as StoreState;
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface MessageInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatInterface {
|
export interface ChatInterface {
|
||||||
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
folder?: string;
|
folder?: string;
|
||||||
messages: MessageInterface[];
|
messages: MessageInterface[];
|
||||||
|
@ -29,10 +30,23 @@ export interface ConfigInterface {
|
||||||
export interface ChatHistoryInterface {
|
export interface ChatHistoryInterface {
|
||||||
title: string;
|
title: string;
|
||||||
index: number;
|
index: number;
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatHistoryFolderInterface {
|
export interface ChatHistoryFolderInterface {
|
||||||
[folderName: string]: ChatHistoryInterface[];
|
[folderId: string]: ChatHistoryInterface[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FolderCollection {
|
||||||
|
[folderId: string]: Folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Folder {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
expanded: boolean;
|
||||||
|
order: number;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelOptions = 'gpt-4' | 'gpt-4-32k' | 'gpt-3.5-turbo';
|
export type ModelOptions = 'gpt-4' | 'gpt-4-32k' | 'gpt-3.5-turbo';
|
||||||
|
@ -120,3 +134,10 @@ export interface LocalStorageInterfaceV6ToV7 {
|
||||||
firstVisit: boolean;
|
firstVisit: boolean;
|
||||||
hideSideMenu: boolean;
|
hideSideMenu: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LocalStorageInterfaceV7oV8
|
||||||
|
extends LocalStorageInterfaceV6ToV7 {
|
||||||
|
foldersName: string[];
|
||||||
|
foldersExpanded: boolean[];
|
||||||
|
folders: FolderCollection;
|
||||||
|
}
|
||||||
|
|
12
src/types/export.ts
Normal file
12
src/types/export.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { ChatInterface, FolderCollection } from './chat';
|
||||||
|
|
||||||
|
export interface ExportBase {
|
||||||
|
version: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportV1 extends ExportBase {
|
||||||
|
chats?: ChatInterface[];
|
||||||
|
folders: FolderCollection;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExportV1;
|
|
@ -1,63 +1,7 @@
|
||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
// import jsPDF from 'jspdf';
|
// import jsPDF from 'jspdf';
|
||||||
import { ChatInterface, ConfigInterface, MessageInterface } from '@type/chat';
|
import { ChatInterface } from '@type/chat';
|
||||||
import { roles } from '@type/chat';
|
|
||||||
import { Theme } from '@type/theme';
|
import { Theme } from '@type/theme';
|
||||||
import {
|
|
||||||
defaultModel,
|
|
||||||
modelOptions,
|
|
||||||
_defaultChatConfig,
|
|
||||||
} from '@constants/chat';
|
|
||||||
|
|
||||||
export const validateAndFixChats = (chats: any): chats is ChatInterface[] => {
|
|
||||||
if (!Array.isArray(chats)) return false;
|
|
||||||
|
|
||||||
for (const chat of chats) {
|
|
||||||
if (!(typeof chat.title === 'string') || chat.title === '') return false;
|
|
||||||
|
|
||||||
if (chat.titleSet === undefined) chat.titleSet = false;
|
|
||||||
if (!(typeof chat.titleSet === 'boolean')) return false;
|
|
||||||
|
|
||||||
if (!validateMessage(chat.messages)) return false;
|
|
||||||
if (!validateAndFixChatConfig(chat.config)) return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateMessage = (messages: MessageInterface[]) => {
|
|
||||||
if (!Array.isArray(messages)) return false;
|
|
||||||
for (const message of messages) {
|
|
||||||
if (!(typeof message.content === 'string')) return false;
|
|
||||||
if (!(typeof message.role === 'string')) return false;
|
|
||||||
if (!roles.includes(message.role)) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateAndFixChatConfig = (config: ConfigInterface) => {
|
|
||||||
if (config === undefined) config = _defaultChatConfig;
|
|
||||||
if (!(typeof config === 'object')) return false;
|
|
||||||
|
|
||||||
if (!config.temperature) config.temperature = _defaultChatConfig.temperature;
|
|
||||||
if (!(typeof config.temperature === 'number')) return false;
|
|
||||||
|
|
||||||
if (!config.presence_penalty)
|
|
||||||
config.presence_penalty = _defaultChatConfig.presence_penalty;
|
|
||||||
if (!(typeof config.presence_penalty === 'number')) return false;
|
|
||||||
|
|
||||||
if (!config.top_p) config.top_p = _defaultChatConfig.top_p;
|
|
||||||
if (!(typeof config.top_p === 'number')) return false;
|
|
||||||
|
|
||||||
if (!config.frequency_penalty)
|
|
||||||
config.frequency_penalty = _defaultChatConfig.frequency_penalty;
|
|
||||||
if (!(typeof config.frequency_penalty === 'number')) return false;
|
|
||||||
|
|
||||||
if (!config.model) config.model = defaultModel;
|
|
||||||
if (!modelOptions.includes(config.model)) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const htmlToImg = async (html: HTMLDivElement) => {
|
export const htmlToImg = async (html: HTMLDivElement) => {
|
||||||
const needResize = window.innerWidth >= 1024;
|
const needResize = window.innerWidth >= 1024;
|
||||||
|
|
90
src/utils/import.ts
Normal file
90
src/utils/import.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ChatInterface,
|
||||||
|
ConfigInterface,
|
||||||
|
FolderCollection,
|
||||||
|
MessageInterface,
|
||||||
|
} from '@type/chat';
|
||||||
|
import { roles } from '@type/chat';
|
||||||
|
import {
|
||||||
|
defaultModel,
|
||||||
|
modelOptions,
|
||||||
|
_defaultChatConfig,
|
||||||
|
} from '@constants/chat';
|
||||||
|
import { ExportV1 } from '@type/export';
|
||||||
|
|
||||||
|
export const validateAndFixChats = (chats: any): chats is ChatInterface[] => {
|
||||||
|
if (!Array.isArray(chats)) return false;
|
||||||
|
|
||||||
|
for (const chat of chats) {
|
||||||
|
if (!(typeof chat.id === 'string')) chat.id = uuidv4();
|
||||||
|
if (!(typeof chat.title === 'string') || chat.title === '') return false;
|
||||||
|
|
||||||
|
if (chat.titleSet === undefined) chat.titleSet = false;
|
||||||
|
if (!(typeof chat.titleSet === 'boolean')) return false;
|
||||||
|
|
||||||
|
if (!validateMessage(chat.messages)) return false;
|
||||||
|
if (!validateAndFixChatConfig(chat.config)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateMessage = (messages: MessageInterface[]) => {
|
||||||
|
if (!Array.isArray(messages)) return false;
|
||||||
|
for (const message of messages) {
|
||||||
|
if (!(typeof message.content === 'string')) return false;
|
||||||
|
if (!(typeof message.role === 'string')) return false;
|
||||||
|
if (!roles.includes(message.role)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateAndFixChatConfig = (config: ConfigInterface) => {
|
||||||
|
if (config === undefined) config = _defaultChatConfig;
|
||||||
|
if (!(typeof config === 'object')) return false;
|
||||||
|
|
||||||
|
if (!config.temperature) config.temperature = _defaultChatConfig.temperature;
|
||||||
|
if (!(typeof config.temperature === 'number')) return false;
|
||||||
|
|
||||||
|
if (!config.presence_penalty)
|
||||||
|
config.presence_penalty = _defaultChatConfig.presence_penalty;
|
||||||
|
if (!(typeof config.presence_penalty === 'number')) return false;
|
||||||
|
|
||||||
|
if (!config.top_p) config.top_p = _defaultChatConfig.top_p;
|
||||||
|
if (!(typeof config.top_p === 'number')) return false;
|
||||||
|
|
||||||
|
if (!config.frequency_penalty)
|
||||||
|
config.frequency_penalty = _defaultChatConfig.frequency_penalty;
|
||||||
|
if (!(typeof config.frequency_penalty === 'number')) return false;
|
||||||
|
|
||||||
|
if (!config.model) config.model = defaultModel;
|
||||||
|
if (!modelOptions.includes(config.model)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLegacyImport = (importedData: any) => {
|
||||||
|
if (Array.isArray(importedData)) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateFolders = (
|
||||||
|
folders: FolderCollection
|
||||||
|
): folders is FolderCollection => {
|
||||||
|
if (typeof folders !== 'object') return false;
|
||||||
|
|
||||||
|
for (const folderId in folders) {
|
||||||
|
if (typeof folders[folderId].id !== 'string') return false;
|
||||||
|
if (typeof folders[folderId].name !== 'string') return false;
|
||||||
|
if (typeof folders[folderId].order !== 'number') return false;
|
||||||
|
if (typeof folders[folderId].expanded !== 'boolean') return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateExportV1 = (data: ExportV1): data is ExportV1 => {
|
||||||
|
return validateAndFixChats(data.chats) && validateFolders(data.folders);
|
||||||
|
};
|
Loading…
Reference in a new issue