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]); |   }, [messages]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="h-full flex flex-col"> |     <div className="h-full w-full flex flex-col dark:bg-[#343541]"> | ||||||
|       {messages.length === 0 ? ( |       <div className="flex-1 overflow-y-auto"> | ||||||
|         <> |         {messages.length === 0 ? ( | ||||||
|           <div className="flex justify-center pt-8"> |           <> | ||||||
|             <ModelSelect |             <div className="flex justify-center pt-8 overflow-auto"> | ||||||
|               model={model} |               <ModelSelect | ||||||
|               onSelect={onSelect} |                 model={model} | ||||||
|             /> |                 onSelect={onSelect} | ||||||
|           </div> |               /> | ||||||
|  |             </div> | ||||||
| 
 | 
 | ||||||
|           <div className="flex-1 text-4xl text-center text-neutral-300 pt-[280px]">Chatbot UI Pro</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="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> |             <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) => ( |             {messages.map((message, index) => ( | ||||||
|  | @ -53,11 +53,11 @@ export const Chat: FC<Props> = ({ model, messages, loading, lightMode, onSend, o | ||||||
|             ))} |             ))} | ||||||
|             {loading && <ChatLoader />} |             {loading && <ChatLoader />} | ||||||
|             <div ref={messagesEndRef} /> |             <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} /> |         <ChatInput onSend={onSend} /> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -6,11 +6,11 @@ interface Props {} | ||||||
| export const ChatLoader: FC<Props> = () => { | export const ChatLoader: FC<Props> = () => { | ||||||
|   return ( |   return ( | ||||||
|     <div |     <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" }} |       style={{ overflowWrap: "anywhere" }} | ||||||
|     > |     > | ||||||
|       <div className="w-[650px] flex"> |       <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-[30px]">AI:</div> |         <div className="mr-4 font-bold min-w-[40px]">AI:</div> | ||||||
|         <IconDots className="animate-pulse" /> |         <IconDots className="animate-pulse" /> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -11,10 +11,10 @@ interface Props { | ||||||
| export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | ||||||
|   return ( |   return ( | ||||||
|     <div |     <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" }} |       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="mr-4 font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div> | ||||||
| 
 | 
 | ||||||
|         <div className="prose dark:prose-invert"> |         <div className="prose dark:prose-invert"> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Conversation } from "@/types"; | import { Conversation } from "@/types"; | ||||||
| import { IconMessage, IconTrash } from "@tabler/icons-react"; | import { IconCheck, IconMessage, IconPencil, IconTrash, IconX } from "@tabler/icons-react"; | ||||||
| import { FC } from "react"; | import { FC, KeyboardEvent, useEffect, useState } from "react"; | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   loading: boolean; |   loading: boolean; | ||||||
|  | @ -8,15 +8,41 @@ interface Props { | ||||||
|   selectedConversation: Conversation; |   selectedConversation: Conversation; | ||||||
|   onSelectConversation: (conversation: Conversation) => void; |   onSelectConversation: (conversation: Conversation) => void; | ||||||
|   onDeleteConversation: (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 ( |   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) => ( |       {conversations.map((conversation, index) => ( | ||||||
|         <button |         <button | ||||||
|           key={index} |           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)} |           onClick={() => onSelectConversation(conversation)} | ||||||
|           disabled={loading} |           disabled={loading} | ||||||
|         > |         > | ||||||
|  | @ -24,16 +50,73 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | ||||||
|             className="mr-2 min-w-[20px]" |             className="mr-2 min-w-[20px]" | ||||||
|             size={18} |             size={18} | ||||||
|           /> |           /> | ||||||
|           <div className="overflow-hidden whitespace-nowrap overflow-ellipsis pr-1">{conversation.messages[0] ? conversation.messages[0].content : "Empty conversation"}</div> |  | ||||||
| 
 | 
 | ||||||
|           <IconTrash |           {isRenaming && selectedConversation.id === conversation.id ? ( | ||||||
|             className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" |             <input | ||||||
|             size={18} |               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" | ||||||
|             onClick={(e) => { |               type="text" | ||||||
|               e.stopPropagation(); |               value={renameValue} | ||||||
|               onDeleteConversation(conversation); |               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> |         </button> | ||||||
|       ))} |       ))} | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -14,14 +14,15 @@ interface Props { | ||||||
|   onSelectConversation: (conversation: Conversation) => void; |   onSelectConversation: (conversation: Conversation) => void; | ||||||
|   onDeleteConversation: (conversation: Conversation) => void; |   onDeleteConversation: (conversation: Conversation) => void; | ||||||
|   onToggleSidebar: () => 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 ( |   return ( | ||||||
|     <div className="flex flex-col bg-[#202123] min-w-[260px]"> |     <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 |         <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} |           onClick={onNewConversation} | ||||||
|         > |         > | ||||||
|           <IconPlus |           <IconPlus | ||||||
|  | @ -32,18 +33,19 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | ||||||
|         </button> |         </button> | ||||||
| 
 | 
 | ||||||
|         <IconArrowBarLeft |         <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} |           onClick={onToggleSidebar} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|       <div className="flex-1 mx-auto pb-2 overflow-auto"> |       <div className="flex flex-1 justify-center pb-2 overflow-y-auto"> | ||||||
|         <Conversations |         <Conversations | ||||||
|           loading={loading} |           loading={loading} | ||||||
|           conversations={conversations} |           conversations={conversations} | ||||||
|           selectedConversation={selectedConversation} |           selectedConversation={selectedConversation} | ||||||
|           onSelectConversation={onSelectConversation} |           onSelectConversation={onSelectConversation} | ||||||
|           onDeleteConversation={onDeleteConversation} |           onDeleteConversation={onDeleteConversation} | ||||||
|  |           onRenameConversation={onRenameConversation} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -68,6 +68,7 @@ export default function Home() { | ||||||
| 
 | 
 | ||||||
|           updatedConversation = { |           updatedConversation = { | ||||||
|             ...updatedConversation, |             ...updatedConversation, | ||||||
|  |             name: message.content, | ||||||
|             messages: updatedMessages |             messages: updatedMessages | ||||||
|           }; |           }; | ||||||
| 
 | 
 | ||||||
|  | @ -120,12 +121,34 @@ export default function Home() { | ||||||
|     localStorage.setItem("theme", mode); |     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 handleNewConversation = () => { | ||||||
|     const lastConversation = conversations[conversations.length - 1]; |     const lastConversation = conversations[conversations.length - 1]; | ||||||
| 
 | 
 | ||||||
|     const newConversation: Conversation = { |     const newConversation: Conversation = { | ||||||
|       id: lastConversation ? lastConversation.id + 1 : 1, |       id: lastConversation ? lastConversation.id + 1 : 1, | ||||||
|       name: "", |       name: "New conversation", | ||||||
|       messages: [] |       messages: [] | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -218,6 +241,7 @@ export default function Home() { | ||||||
|               onSelectConversation={handleSelectConversation} |               onSelectConversation={handleSelectConversation} | ||||||
|               onDeleteConversation={handleDeleteConversation} |               onDeleteConversation={handleDeleteConversation} | ||||||
|               onToggleSidebar={() => setShowSidebar(!showSidebar)} |               onToggleSidebar={() => setShowSidebar(!showSidebar)} | ||||||
|  |               onRenameConversation={handleRenameConversation} | ||||||
|             /> |             /> | ||||||
|           ) : ( |           ) : ( | ||||||
|             <IconArrowBarRight |             <IconArrowBarRight | ||||||
|  | @ -226,16 +250,14 @@ export default function Home() { | ||||||
|             /> |             /> | ||||||
|           )} |           )} | ||||||
| 
 | 
 | ||||||
|           <div className="flex flex-col w-full h-full dark:bg-[#343541]"> |           <Chat | ||||||
|             <Chat |             model={model} | ||||||
|               model={model} |             messages={selectedConversation.messages} | ||||||
|               messages={selectedConversation.messages} |             loading={loading} | ||||||
|               loading={loading} |             lightMode={lightMode} | ||||||
|               lightMode={lightMode} |             onSend={handleSend} | ||||||
|               onSend={handleSend} |             onSelect={setModel} | ||||||
|               onSelect={setModel} |           /> | ||||||
|             /> |  | ||||||
|           </div> |  | ||||||
|         </div> |         </div> | ||||||
|       )} |       )} | ||||||
|     </> |     </> | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue