add rename and delete improvements
This commit is contained in:
		
							parent
							
								
									b6d5576227
								
							
						
					
					
						commit
						a0056460ab
					
				|  | @ -26,21 +26,21 @@ export const Chat: FC<Props> = ({ model, messages, loading, lightMode, onSend, o | |||
|   }, [messages]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="h-full flex flex-col"> | ||||
|       {messages.length === 0 ? ( | ||||
|         <> | ||||
|           <div className="flex justify-center pt-8"> | ||||
|             <ModelSelect | ||||
|               model={model} | ||||
|               onSelect={onSelect} | ||||
|             /> | ||||
|           </div> | ||||
|     <div className="h-full w-full flex flex-col dark:bg-[#343541]"> | ||||
|       <div className="flex-1 overflow-y-auto"> | ||||
|         {messages.length === 0 ? ( | ||||
|           <> | ||||
|             <div className="flex justify-center pt-8 overflow-auto"> | ||||
|               <ModelSelect | ||||
|                 model={model} | ||||
|                 onSelect={onSelect} | ||||
|               /> | ||||
|             </div> | ||||
| 
 | ||||
|           <div className="flex-1 text-4xl text-center text-neutral-300 pt-[280px]">Chatbot UI Pro</div> | ||||
|         </> | ||||
|       ) : ( | ||||
|         <> | ||||
|           <div className="flex-1 overflow-auto"> | ||||
|             <div className="flex-1 text-4xl text-center text-neutral-300 pt-[280px]">Chatbot UI Pro</div> | ||||
|           </> | ||||
|         ) : ( | ||||
|           <> | ||||
|             <div className="text-center py-3 dark:bg-[#444654] dark:text-neutral-300 text-neutral-500 text-sm border border-b-neutral-300 dark:border-none">Model: {OpenAIModelNames[model]}</div> | ||||
| 
 | ||||
|             {messages.map((message, index) => ( | ||||
|  | @ -53,11 +53,11 @@ export const Chat: FC<Props> = ({ model, messages, loading, lightMode, onSend, o | |||
|             ))} | ||||
|             {loading && <ChatLoader />} | ||||
|             <div ref={messagesEndRef} /> | ||||
|           </div> | ||||
|         </> | ||||
|       )} | ||||
|           </> | ||||
|         )} | ||||
|       </div> | ||||
| 
 | ||||
|       <div className="h-[140px] w-[800px] mx-auto"> | ||||
|       <div className="h-[140px] w-[300px] sm:w-[400px] md:w-[500px] lg:w-[700px] xl:w-[800px] mx-auto"> | ||||
|         <ChatInput onSend={onSend} /> | ||||
|       </div> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -6,11 +6,11 @@ interface Props {} | |||
| export const ChatLoader: FC<Props> = () => { | ||||
|   return ( | ||||
|     <div | ||||
|       className={`flex justify-center px-[120px] py-[30px] whitespace-pre-wrap dark:bg-[#444654] dark:text-neutral-100 bg-neutral-100 text-neutral-900 dark:border-none"`} | ||||
|       className={`flex justify-center py-[30px] whitespace-pre-wrap dark:bg-[#444654] dark:text-neutral-100 bg-neutral-100 text-neutral-900 dark:border-none"`} | ||||
|       style={{ overflowWrap: "anywhere" }} | ||||
|     > | ||||
|       <div className="w-[650px] flex"> | ||||
|         <div className="mr-4 font-bold min-w-[30px]">AI:</div> | ||||
|       <div className="w-full px-4 sm:px-0 sm:w-2/3 md:w-1/2 flex"> | ||||
|         <div className="mr-4 font-bold min-w-[40px]">AI:</div> | ||||
|         <IconDots className="animate-pulse" /> | ||||
|       </div> | ||||
|     </div> | ||||
|  |  | |||
|  | @ -11,10 +11,10 @@ interface Props { | |||
| export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | ||||
|   return ( | ||||
|     <div | ||||
|       className={`flex justify-center px-[120px] py-[30px] whitespace-pre-wrap] ${message.role === "assistant" ? "dark:bg-[#444654] dark:text-neutral-100 bg-neutral-100 text-neutral-900 border border-neutral-300 dark:border-none" : "dark:bg-[#343541] dark:text-white text-neutral-900"}`} | ||||
|       className={`flex justify-center py-[30px] whitespace-pre-wrap ${message.role === "assistant" ? "dark:bg-[#444654] dark:text-neutral-100 bg-neutral-100 text-neutral-900 border border-neutral-300 dark:border-none" : "dark:bg-[#343541] dark:text-white text-neutral-900"}`} | ||||
|       style={{ overflowWrap: "anywhere" }} | ||||
|     > | ||||
|       <div className="w-[650px] flex align-middle"> | ||||
|       <div className="w-full px-4 sm:px-0 sm:w-2/3 md:w-1/2 flex"> | ||||
|         <div className="mr-4 font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div> | ||||
| 
 | ||||
|         <div className="prose dark:prose-invert"> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import { Conversation } from "@/types"; | ||||
| import { IconMessage, IconTrash } from "@tabler/icons-react"; | ||||
| import { FC } from "react"; | ||||
| import { IconCheck, IconMessage, IconPencil, IconTrash, IconX } from "@tabler/icons-react"; | ||||
| import { FC, KeyboardEvent, useEffect, useState } from "react"; | ||||
| 
 | ||||
| interface Props { | ||||
|   loading: boolean; | ||||
|  | @ -8,15 +8,41 @@ interface Props { | |||
|   selectedConversation: Conversation; | ||||
|   onSelectConversation: (conversation: Conversation) => void; | ||||
|   onDeleteConversation: (conversation: Conversation) => void; | ||||
|   onRenameConversation: (conversation: Conversation, name: string) => void; | ||||
| } | ||||
| 
 | ||||
| export const Conversations: FC<Props> = ({ loading, conversations, selectedConversation, onSelectConversation, onDeleteConversation }) => { | ||||
| export const Conversations: FC<Props> = ({ loading, conversations, selectedConversation, onSelectConversation, onDeleteConversation, onRenameConversation }) => { | ||||
|   const [isDeleting, setIsDeleting] = useState(false); | ||||
|   const [isRenaming, setIsRenaming] = useState(false); | ||||
|   const [renameValue, setRenameValue] = useState(""); | ||||
| 
 | ||||
|   const handleEnterDown = (e: KeyboardEvent<HTMLDivElement>) => { | ||||
|     if (e.key === "Enter") { | ||||
|       e.preventDefault(); | ||||
|       handleRename(selectedConversation); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   const handleRename = (conversation: Conversation) => { | ||||
|     onRenameConversation(conversation, renameValue); | ||||
|     setRenameValue(""); | ||||
|     setIsRenaming(false); | ||||
|   }; | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     if (isRenaming) { | ||||
|       setIsDeleting(false); | ||||
|     } else if (isDeleting) { | ||||
|       setIsRenaming(false); | ||||
|     } | ||||
|   }, [isRenaming, isDeleting]); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col space-y-2"> | ||||
|     <div className="flex flex-col space-y-2 w-full px-2"> | ||||
|       {conversations.map((conversation, index) => ( | ||||
|         <button | ||||
|           key={index} | ||||
|           className={`flex items-center justify-start w-[240px] h-[40px] px-2 text-sm rounded-lg hover:bg-neutral-700 cursor-pointer ${loading ? "disabled:cursor-not-allowed" : ""} ${selectedConversation.id === conversation.id ? "bg-slate-600" : ""}`} | ||||
|           className={`flex items-center justify-start h-[40px] px-2 text-sm rounded-lg hover:bg-neutral-700 cursor-pointer ${loading ? "disabled:cursor-not-allowed" : ""} ${selectedConversation.id === conversation.id ? "bg-slate-600" : ""}`} | ||||
|           onClick={() => onSelectConversation(conversation)} | ||||
|           disabled={loading} | ||||
|         > | ||||
|  | @ -24,16 +50,73 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | |||
|             className="mr-2 min-w-[20px]" | ||||
|             size={18} | ||||
|           /> | ||||
|           <div className="overflow-hidden whitespace-nowrap overflow-ellipsis pr-1">{conversation.messages[0] ? conversation.messages[0].content : "Empty conversation"}</div> | ||||
| 
 | ||||
|           <IconTrash | ||||
|             className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||
|             size={18} | ||||
|             onClick={(e) => { | ||||
|               e.stopPropagation(); | ||||
|               onDeleteConversation(conversation); | ||||
|             }} | ||||
|           /> | ||||
|           {isRenaming && selectedConversation.id === conversation.id ? ( | ||||
|             <input | ||||
|               className="flex-1 bg-transparent border-b border-neutral-400 focus:border-neutral-100 text-left overflow-hidden overflow-ellipsis pr-1 outline-none text-white" | ||||
|               type="text" | ||||
|               value={renameValue} | ||||
|               onChange={(e) => setRenameValue(e.target.value)} | ||||
|               onKeyDown={handleEnterDown} | ||||
|               autoFocus | ||||
|             /> | ||||
|           ) : ( | ||||
|             <div className="overflow-hidden whitespace-nowrap overflow-ellipsis pr-1 flex-1 text-left">{conversation.name}</div> | ||||
|           )} | ||||
| 
 | ||||
|           {(isDeleting || isRenaming) && selectedConversation.id === conversation.id && ( | ||||
|             <div className="flex w-[40px]"> | ||||
|               <IconCheck | ||||
|                 className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||
|                 size={18} | ||||
|                 onClick={(e) => { | ||||
|                   e.stopPropagation(); | ||||
| 
 | ||||
|                   if (isDeleting) { | ||||
|                     onDeleteConversation(conversation); | ||||
|                   } else if (isRenaming) { | ||||
|                     handleRename(conversation); | ||||
|                   } | ||||
| 
 | ||||
|                   setIsDeleting(false); | ||||
|                   setIsRenaming(false); | ||||
|                 }} | ||||
|               /> | ||||
| 
 | ||||
|               <IconX | ||||
|                 className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||
|                 size={18} | ||||
|                 onClick={(e) => { | ||||
|                   e.stopPropagation(); | ||||
|                   setIsDeleting(false); | ||||
|                   setIsRenaming(false); | ||||
|                 }} | ||||
|               /> | ||||
|             </div> | ||||
|           )} | ||||
| 
 | ||||
|           {selectedConversation.id === conversation.id && !isDeleting && !isRenaming && ( | ||||
|             <div className="flex w-[40px]"> | ||||
|               <IconPencil | ||||
|                 className="min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||
|                 size={18} | ||||
|                 onClick={(e) => { | ||||
|                   e.stopPropagation(); | ||||
|                   setIsRenaming(true); | ||||
|                   setRenameValue(selectedConversation.name); | ||||
|                 }} | ||||
|               /> | ||||
| 
 | ||||
|               <IconTrash | ||||
|                 className=" min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||
|                 size={18} | ||||
|                 onClick={(e) => { | ||||
|                   e.stopPropagation(); | ||||
|                   setIsDeleting(true); | ||||
|                 }} | ||||
|               /> | ||||
|             </div> | ||||
|           )} | ||||
|         </button> | ||||
|       ))} | ||||
|     </div> | ||||
|  |  | |||
|  | @ -14,14 +14,15 @@ interface Props { | |||
|   onSelectConversation: (conversation: Conversation) => void; | ||||
|   onDeleteConversation: (conversation: Conversation) => void; | ||||
|   onToggleSidebar: () => void; | ||||
|   onRenameConversation: (conversation: Conversation, name: string) => void; | ||||
| } | ||||
| 
 | ||||
| export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar }) => { | ||||
| export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation }) => { | ||||
|   return ( | ||||
|     <div className="flex flex-col bg-[#202123] min-w-[260px]"> | ||||
|       <div className="flex items-center h-[60px] pl-4"> | ||||
|       <div className="flex items-center h-[60px] pl-2"> | ||||
|         <button | ||||
|           className="flex items-center w-[190px] h-[40px] rounded-lg bg-[#202123] border border-neutral-600 text-sm hover:bg-neutral-700" | ||||
|           className="flex items-center w-[220px] h-[40px] rounded-lg bg-[#202123] border border-neutral-600 text-sm hover:bg-neutral-700" | ||||
|           onClick={onNewConversation} | ||||
|         > | ||||
|           <IconPlus | ||||
|  | @ -32,18 +33,19 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | |||
|         </button> | ||||
| 
 | ||||
|         <IconArrowBarLeft | ||||
|           className="ml-auto mr-4 text-neutral-300 cursor-pointer hover:text-neutral-400" | ||||
|           className="ml-1 mr-2 text-neutral-300 cursor-pointer hover:text-neutral-400" | ||||
|           onClick={onToggleSidebar} | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|       <div className="flex-1 mx-auto pb-2 overflow-auto"> | ||||
|       <div className="flex flex-1 justify-center pb-2 overflow-y-auto"> | ||||
|         <Conversations | ||||
|           loading={loading} | ||||
|           conversations={conversations} | ||||
|           selectedConversation={selectedConversation} | ||||
|           onSelectConversation={onSelectConversation} | ||||
|           onDeleteConversation={onDeleteConversation} | ||||
|           onRenameConversation={onRenameConversation} | ||||
|         /> | ||||
|       </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -68,6 +68,7 @@ export default function Home() { | |||
| 
 | ||||
|           updatedConversation = { | ||||
|             ...updatedConversation, | ||||
|             name: message.content, | ||||
|             messages: updatedMessages | ||||
|           }; | ||||
| 
 | ||||
|  | @ -120,12 +121,34 @@ export default function Home() { | |||
|     localStorage.setItem("theme", mode); | ||||
|   }; | ||||
| 
 | ||||
|   const handleRenameConversation = (conversation: Conversation, name: string) => { | ||||
|     const updatedConversations = conversations.map((c) => { | ||||
|       if (c.id === conversation.id) { | ||||
|         return { | ||||
|           ...c, | ||||
|           name | ||||
|         }; | ||||
|       } | ||||
| 
 | ||||
|       return c; | ||||
|     }); | ||||
| 
 | ||||
|     setConversations(updatedConversations); | ||||
|     localStorage.setItem("conversationHistory", JSON.stringify(updatedConversations)); | ||||
| 
 | ||||
|     setSelectedConversation({ | ||||
|       ...conversation, | ||||
|       name | ||||
|     }); | ||||
|     localStorage.setItem("selectedConversation", JSON.stringify(selectedConversation)); | ||||
|   }; | ||||
| 
 | ||||
|   const handleNewConversation = () => { | ||||
|     const lastConversation = conversations[conversations.length - 1]; | ||||
| 
 | ||||
|     const newConversation: Conversation = { | ||||
|       id: lastConversation ? lastConversation.id + 1 : 1, | ||||
|       name: "", | ||||
|       name: "New conversation", | ||||
|       messages: [] | ||||
|     }; | ||||
| 
 | ||||
|  | @ -218,6 +241,7 @@ export default function Home() { | |||
|               onSelectConversation={handleSelectConversation} | ||||
|               onDeleteConversation={handleDeleteConversation} | ||||
|               onToggleSidebar={() => setShowSidebar(!showSidebar)} | ||||
|               onRenameConversation={handleRenameConversation} | ||||
|             /> | ||||
|           ) : ( | ||||
|             <IconArrowBarRight | ||||
|  | @ -226,16 +250,14 @@ export default function Home() { | |||
|             /> | ||||
|           )} | ||||
| 
 | ||||
|           <div className="flex flex-col w-full h-full dark:bg-[#343541]"> | ||||
|             <Chat | ||||
|               model={model} | ||||
|               messages={selectedConversation.messages} | ||||
|               loading={loading} | ||||
|               lightMode={lightMode} | ||||
|               onSend={handleSend} | ||||
|               onSelect={setModel} | ||||
|             /> | ||||
|           </div> | ||||
|           <Chat | ||||
|             model={model} | ||||
|             messages={selectedConversation.messages} | ||||
|             loading={loading} | ||||
|             lightMode={lightMode} | ||||
|             onSend={handleSend} | ||||
|             onSelect={setModel} | ||||
|           /> | ||||
|         </div> | ||||
|       )} | ||||
|     </> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue