add import/export (#71)
This commit is contained in:
		
							parent
							
								
									972a5aff23
								
							
						
					
					
						commit
						f0c575b40d
					
				|  | @ -0,0 +1,34 @@ | |||
| import { Conversation } from "@/types"; | ||||
| import { IconFileImport } from "@tabler/icons-react"; | ||||
| import { FC } from "react"; | ||||
| 
 | ||||
| interface Props { | ||||
|   onImport: (conversations: Conversation[]) => void; | ||||
| } | ||||
| 
 | ||||
| export const Import: FC<Props> = ({ onImport }) => { | ||||
|   return ( | ||||
|     <div className="flex py-3 px-3 gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer w-full items-center"> | ||||
|       <input | ||||
|         className="opacity-0 absolute w-[200px] cursor-pointer" | ||||
|         type="file" | ||||
|         accept=".json" | ||||
|         onChange={(e) => { | ||||
|           if (!e.target.files?.length) return; | ||||
| 
 | ||||
|           const file = e.target.files[0]; | ||||
|           const reader = new FileReader(); | ||||
|           reader.onload = (e) => { | ||||
|             const conversations: Conversation[] = JSON.parse(e.target?.result as string); | ||||
|             onImport(conversations); | ||||
|           }; | ||||
|           reader.readAsText(file); | ||||
|         }} | ||||
|       /> | ||||
|       <div className="flex items-center gap-3 text-left"> | ||||
|         <IconFileImport size={16} /> | ||||
|         <div>Import conversations</div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | @ -19,9 +19,11 @@ interface Props { | |||
|   onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void; | ||||
|   onApiKeyChange: (apiKey: string) => void; | ||||
|   onClearConversations: () => void; | ||||
|   onExportConversations: () => void; | ||||
|   onImportConversations: (conversations: Conversation[]) => void; | ||||
| } | ||||
| 
 | ||||
| export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onUpdateConversation, onApiKeyChange, onClearConversations }) => { | ||||
| export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onUpdateConversation, onApiKeyChange, onClearConversations, onExportConversations, onImportConversations }) => { | ||||
|   const [searchTerm, setSearchTerm] = useState<string>(""); | ||||
|   const [filteredConversations, setFilteredConversations] = useState<Conversation[]>(conversations); | ||||
| 
 | ||||
|  | @ -34,7 +36,7 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | |||
|   }, [searchTerm, conversations]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className={`h-full flex flex-col flex-none space-y-2 p-2 flex-col bg-[#202123] w-[260px] z-10 sm:relative sm:top-0 absolute top-12 bottom-0`}> | ||||
|     <div className={`h-full flex flex-none space-y-2 p-2 flex-col bg-[#202123] w-[260px] z-10 sm:relative sm:top-0 absolute top-12 bottom-0`}> | ||||
|       <div className="flex items-center"> | ||||
|         <button | ||||
|           className="flex gap-3 p-3 items-center w-full sm:w-[200px] rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer text-sm flex-shrink-0 border border-white/20" | ||||
|  | @ -87,6 +89,8 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | |||
|         onToggleLightMode={onToggleLightMode} | ||||
|         onApiKeyChange={onApiKeyChange} | ||||
|         onClearConversations={onClearConversations} | ||||
|         onExportConversations={onExportConversations} | ||||
|         onImportConversations={onImportConversations} | ||||
|       /> | ||||
|     </div> | ||||
|   ); | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ export const SidebarButton: FC<Props> = ({ text, icon, onClick }) => { | |||
|       className="flex py-3 px-3 gap-3 rounded-md hover:bg-gray-500/10 transition-colors duration-200 text-white cursor-pointer w-full items-center" | ||||
|       onClick={onClick} | ||||
|     > | ||||
|       <div className="">{icon}</div> | ||||
|       <div>{icon}</div> | ||||
|       <div>{text}</div> | ||||
|     </div> | ||||
|   ); | ||||
|  |  | |||
|  | @ -1,6 +1,8 @@ | |||
| import { IconMoon, IconSun } from "@tabler/icons-react"; | ||||
| import { Conversation } from "@/types"; | ||||
| import { IconFileExport, IconMoon, IconSun } from "@tabler/icons-react"; | ||||
| import { FC } from "react"; | ||||
| import { ClearConversations } from "./ClearConversations"; | ||||
| import { Import } from "./Import"; | ||||
| import { Key } from "./Key"; | ||||
| import { SidebarButton } from "./SidebarButton"; | ||||
| 
 | ||||
|  | @ -10,18 +12,29 @@ interface Props { | |||
|   onToggleLightMode: (mode: "light" | "dark") => void; | ||||
|   onApiKeyChange: (apiKey: string) => void; | ||||
|   onClearConversations: () => void; | ||||
|   onExportConversations: () => void; | ||||
|   onImportConversations: (conversations: Conversation[]) => void; | ||||
| } | ||||
| 
 | ||||
| export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations }) => { | ||||
| export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations, onExportConversations, onImportConversations }) => { | ||||
|   return ( | ||||
|     <div className="flex flex-col pt-1 items-center border-t border-white/20 text-sm space-y-1"> | ||||
|       <ClearConversations onClearConversations={onClearConversations} /> | ||||
| 
 | ||||
|       <Import onImport={onImportConversations} /> | ||||
| 
 | ||||
|       <SidebarButton | ||||
|         text="Export conversations" | ||||
|         icon={<IconFileExport size={16} />} | ||||
|         onClick={() => onExportConversations()} | ||||
|       /> | ||||
| 
 | ||||
|       <SidebarButton | ||||
|         text={lightMode === "light" ? "Dark mode" : "Light mode"} | ||||
|         icon={lightMode === "light" ? <IconMoon size={16} /> : <IconSun size={16} />} | ||||
|         onClick={() => onToggleLightMode(lightMode === "light" ? "dark" : "light")} | ||||
|       /> | ||||
| 
 | ||||
|       <Key | ||||
|         apiKey={apiKey} | ||||
|         onApiKeyChange={onApiKeyChange} | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import { ChatBody, Conversation, KeyValuePair, Message, OpenAIModel, OpenAIModel | |||
| import { cleanConversationHistory, cleanSelectedConversation } from "@/utils/app/clean"; | ||||
| import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const"; | ||||
| import { saveConversation, saveConversations, updateConversation } from "@/utils/app/conversation"; | ||||
| import { exportConversations, importConversations } from "@/utils/app/data"; | ||||
| import { IconArrowBarLeft, IconArrowBarRight } from "@tabler/icons-react"; | ||||
| import Head from "next/head"; | ||||
| import { useEffect, useState } from "react"; | ||||
|  | @ -181,6 +182,16 @@ export default function Home() { | |||
|     localStorage.setItem("apiKey", apiKey); | ||||
|   }; | ||||
| 
 | ||||
|   const handleExportConversations = () => { | ||||
|     exportConversations(); | ||||
|   }; | ||||
| 
 | ||||
|   const handleImportConversations = (conversations: Conversation[]) => { | ||||
|     importConversations(conversations); | ||||
|     setConversations(conversations); | ||||
|     setSelectedConversation(conversations[conversations.length - 1]); | ||||
|   }; | ||||
| 
 | ||||
|   const handleSelectConversation = (conversation: Conversation) => { | ||||
|     setSelectedConversation(conversation); | ||||
|     saveConversation(conversation); | ||||
|  | @ -343,6 +354,8 @@ export default function Home() { | |||
|                   onUpdateConversation={handleUpdateConversation} | ||||
|                   onApiKeyChange={handleApiKeyChange} | ||||
|                   onClearConversations={handleClearConversations} | ||||
|                   onExportConversations={handleExportConversations} | ||||
|                   onImportConversations={handleImportConversations} | ||||
|                 /> | ||||
| 
 | ||||
|                 <IconArrowBarLeft | ||||
|  |  | |||
|  | @ -0,0 +1,22 @@ | |||
| import { Conversation } from "@/types"; | ||||
| 
 | ||||
| export const exportConversations = () => { | ||||
|   const history = localStorage.getItem("conversationHistory"); | ||||
| 
 | ||||
|   if (!history) return; | ||||
| 
 | ||||
|   const blob = new Blob([history], { type: "application/json" }); | ||||
|   const url = URL.createObjectURL(blob); | ||||
|   const link = document.createElement("a"); | ||||
|   link.download = "chatbot_ui_history.json"; | ||||
|   link.href = url; | ||||
|   link.style.display = "none"; | ||||
|   document.body.appendChild(link); | ||||
|   link.click(); | ||||
|   document.body.removeChild(link); | ||||
|   URL.revokeObjectURL(url); | ||||
| }; | ||||
| 
 | ||||
| export const importConversations = (conversations: Conversation[]) => { | ||||
|   localStorage.setItem("conversationHistory", JSON.stringify(conversations)); | ||||
| }; | ||||
		Loading…
	
		Reference in New Issue