From df82b62e385d0c4173cbb577f560dcb1ec0f37b1 Mon Sep 17 00:00:00 2001 From: Jing Hua Date: Fri, 10 Mar 2023 17:03:46 +0800 Subject: [PATCH] feat: Import and export chats fixes #8, #26 --- src/assets/icons/ExportIcon.tsx | 23 ++++ src/components/AboutMenu/AboutMenu.tsx | 2 + .../Chat/ChatContent/ChatContent.tsx | 2 +- .../ImportExportChat/ImportExportChat.tsx | 124 ++++++++++++++++++ src/components/ImportExportChat/index.ts | 1 + .../Menu/MenuOptions/MenuOptions.tsx | 4 +- src/components/Menu/MenuOptions/Updates.tsx | 8 +- src/utils/chat.ts | 24 ++++ src/utils/date.ts | 10 ++ src/utils/downloadFile.ts | 11 ++ 10 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 src/assets/icons/ExportIcon.tsx create mode 100644 src/components/ImportExportChat/ImportExportChat.tsx create mode 100644 src/components/ImportExportChat/index.ts create mode 100644 src/utils/chat.ts create mode 100644 src/utils/date.ts create mode 100644 src/utils/downloadFile.ts diff --git a/src/assets/icons/ExportIcon.tsx b/src/assets/icons/ExportIcon.tsx new file mode 100644 index 0000000..7c6780d --- /dev/null +++ b/src/assets/icons/ExportIcon.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +const ExportIcon = (props: React.SVGProps) => { + return ( + + + + + + ); +}; + +export default ExportIcon; diff --git a/src/components/AboutMenu/AboutMenu.tsx b/src/components/AboutMenu/AboutMenu.tsx index 8ca3474..76f9117 100644 --- a/src/components/AboutMenu/AboutMenu.tsx +++ b/src/components/AboutMenu/AboutMenu.tsx @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import PopupModal from '@components/PopupModal'; import AboutIcon from '@icon/AboutIcon'; +import Updates from '@components/Menu/MenuOptions/Updates'; const AboutMenu = () => { const [isModalOpen, setIsModalOpen] = useState(false); @@ -27,6 +28,7 @@ const AboutMenu = () => {

Free ChatGPT is an amazing open-source web app that allows you to play with OpenAI's ChatGPT API for free!

+

Discord Server

We invite you to join our Discord community! Our Discord server is a great place to exchange ChatGPT ideas and tips, and submit feature requests for Free ChatGPT. You'll have the opportunity to interact with the developers behind Free ChatGPT as well as other AI enthusiasts who share your passion.

diff --git a/src/components/Chat/ChatContent/ChatContent.tsx b/src/components/Chat/ChatContent/ChatContent.tsx index ae872f0..5b9574e 100644 --- a/src/components/Chat/ChatContent/ChatContent.tsx +++ b/src/components/Chat/ChatContent/ChatContent.tsx @@ -69,7 +69,7 @@ const ChatContent = () => { {error !== '' && (
-
+
{error}
{ + const [isModalOpen, setIsModalOpen] = useState(false); + + return ( + <> + { + setIsModalOpen(true); + }} + > + + Import / Export + + {isModalOpen && ( + +
+ + +
+
+ )} + + ); +}; + +const ImportChat = () => { + const setChats = useStore.getState().setChats; + 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 (isChats(parsedData)) { + setChats(parsedData); + setAlert({ message: 'Succesfully imported!', success: true }); + } else { + setAlert({ message: 'Invalid chats data format', success: false }); + } + } catch (error: unknown) { + setAlert({ message: (error as Error).message, success: false }); + } + }; + + reader.readAsText(file); + } + }; + return ( + <> + + + + {alert && ( +
+ {alert.message} +
+ )} + + ); +}; + +const ExportChat = () => { + const chats = useStore.getState().chats; + return ( +
+
+ Export (JSON) +
+ +
+ ); +}; + +export default ImportExportChat; diff --git a/src/components/ImportExportChat/index.ts b/src/components/ImportExportChat/index.ts new file mode 100644 index 0000000..2681abe --- /dev/null +++ b/src/components/ImportExportChat/index.ts @@ -0,0 +1 @@ +export { default } from './ImportExportChat'; diff --git a/src/components/Menu/MenuOptions/MenuOptions.tsx b/src/components/Menu/MenuOptions/MenuOptions.tsx index f25e099..607ca64 100644 --- a/src/components/Menu/MenuOptions/MenuOptions.tsx +++ b/src/components/Menu/MenuOptions/MenuOptions.tsx @@ -8,16 +8,18 @@ import Me from './Me'; import ThemeSwitcher from './ThemeSwitcher'; import Updates from './Updates'; import AboutMenu from '@components/AboutMenu'; +import ImportExportChat from '@components/ImportExportChat'; const MenuOptions = () => { return ( <> + {/* */} - + {/* */} {/* */} diff --git a/src/components/Menu/MenuOptions/Updates.tsx b/src/components/Menu/MenuOptions/Updates.tsx index c4e1bb6..279dd0f 100644 --- a/src/components/Menu/MenuOptions/Updates.tsx +++ b/src/components/Menu/MenuOptions/Updates.tsx @@ -1,12 +1,16 @@ import React from 'react'; import LinkIcon from '@icon/LinkIcon'; -const Updates = () => { +const Updates = ({ isButton = false }: { isButton?: boolean }) => { return ( Source Code diff --git a/src/utils/chat.ts b/src/utils/chat.ts new file mode 100644 index 0000000..2f84a53 --- /dev/null +++ b/src/utils/chat.ts @@ -0,0 +1,24 @@ +import { ChatInterface } from '@type/chat'; +import { roles } from '@type/chat'; + +export const isChats = (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 (!(typeof chat.titleSet === 'boolean')) return false; + + if (!Array.isArray(chat.messages)) return false; + for (const message of chat.messages) { + if (!(typeof message.content === 'string')) return false; + if (!(typeof message.role === 'string')) return false; + if (!roles.includes(message.role)) return false; + } + + if (!(typeof chat.config === 'object')) return false; + if (!(typeof chat.config.temperature === 'number')) return false; + if (!(typeof chat.config.presence_penalty === 'number')) return false; + } + + return true; +}; diff --git a/src/utils/date.ts b/src/utils/date.ts new file mode 100644 index 0000000..ec1dcb1 --- /dev/null +++ b/src/utils/date.ts @@ -0,0 +1,10 @@ +export const getToday = () => { + const date = new Date(); + const dateString = + date.getFullYear() + + '-' + + ('0' + (date.getMonth() + 1)).slice(-2) + + '-' + + ('0' + date.getDate()).slice(-2); + return dateString; +}; diff --git a/src/utils/downloadFile.ts b/src/utils/downloadFile.ts new file mode 100644 index 0000000..b781737 --- /dev/null +++ b/src/utils/downloadFile.ts @@ -0,0 +1,11 @@ +const downloadFile = (data: object, filename: string) => { + const blob = new Blob([JSON.stringify(data)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + link.click(); + link.remove(); +}; + +export default downloadFile;