enhance ui and fix layout problems (#63)
This commit is contained in:
		
							parent
							
								
									d14268b075
								
							
						
					
					
						commit
						4055e84450
					
				|  | @ -33,7 +33,7 @@ export const Chat: FC<Props> = ({ conversation, models, messageIsStreaming, mode | ||||||
|   }, [conversation.messages]); |   }, [conversation.messages]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex-1 overflow-scroll dark:bg-[#343541]"> |     <div className="relative flex-1 overflow-none dark:bg-[#343541]"> | ||||||
|       {modelError ? ( |       {modelError ? ( | ||||||
|         <div className="flex flex-col justify-center mx-auto h-full w-[300px] sm:w-[500px] space-y-6"> |         <div className="flex flex-col justify-center mx-auto h-full w-[300px] sm:w-[500px] space-y-6"> | ||||||
|           <div className="text-center text-red-500">Error fetching models.</div> |           <div className="text-center text-red-500">Error fetching models.</div> | ||||||
|  | @ -42,11 +42,11 @@ export const Chat: FC<Props> = ({ conversation, models, messageIsStreaming, mode | ||||||
|         </div> |         </div> | ||||||
|       ) : ( |       ) : ( | ||||||
|         <> |         <> | ||||||
|           <div> |           <div className="overflow-scroll max-h-full"> | ||||||
|             {conversation.messages.length === 0 ? ( |             {conversation.messages.length === 0 ? ( | ||||||
|               <> |               <> | ||||||
|                 <div className="flex flex-col mx-auto pt-12 space-y-10 w-[350px] sm:w-[600px]"> |                 <div className="flex flex-col mx-auto pt-12 space-y-10 w-[350px] sm:w-[600px]"> | ||||||
|                   <div className="text-4xl text-center text-neutral-600 dark:text-neutral-200">{models.length === 0 ? "Loading..." : "Chatbot UI"}</div> |                   <div className="text-4xl font-semibold text-center text-gray-800 dark:text-gray-100">{models.length === 0 ? "Loading..." : "Chatbot UI"}</div> | ||||||
| 
 | 
 | ||||||
|                   {models.length > 0 && ( |                   {models.length > 0 && ( | ||||||
|                     <div className="flex flex-col h-full space-y-4 border p-4 rounded border-neutral-500"> |                     <div className="flex flex-col h-full space-y-4 border p-4 rounded border-neutral-500"> | ||||||
|  | @ -79,7 +79,7 @@ export const Chat: FC<Props> = ({ conversation, models, messageIsStreaming, mode | ||||||
|                 {loading && <ChatLoader />} |                 {loading && <ChatLoader />} | ||||||
| 
 | 
 | ||||||
|                 <div |                 <div | ||||||
|                   className="bg-white dark:bg-[#343541] h-24 sm:h-32" |                   className="bg-white dark:bg-[#343541] h-[113px] sm:h-[162px]" | ||||||
|                   ref={messagesEndRef} |                   ref={messagesEndRef} | ||||||
|                 /> |                 /> | ||||||
|               </> |               </> | ||||||
|  |  | ||||||
|  | @ -68,10 +68,12 @@ export const ChatInput: FC<Props> = ({ onSend, messageIsStreaming, model }) => { | ||||||
|   }, [content]); |   }, [content]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="fixed sm:absolute bottom-4 sm:bottom-8 w-full sm:w-1/2 px-2 left-0 sm:left-[280px] lg:left-[200px] right-0 ml-auto mr-auto"> |     <div className="absolute bottom-0 left-0 w-full border-t md:border-t-0 dark:border-white/20 md:border-transparent md:dark:border-transparent dark:bg-[#444654] md:dark:bg-gradient-to-t from-[#343541] via-[#343541] to-[#343541]/0 bg-white md:dark:!bg-transparent dark:md:bg-vert-dark-gradient pt-2"> | ||||||
|  |       <div className="stretch mx-2 md:mt-[52px] mt-4 flex flex-row gap-3 last:mb-2 md:mx-4 md:last:mb-6 lg:mx-auto lg:max-w-3xl"> | ||||||
|  |           <div className="flex flex-col w-full py-2 flex-grow md:py-3 md:pl-4 relative border border-black/10 bg-white dark:border-gray-900/50 dark:text-white dark:bg-[#40414F] rounded-md shadow-[0_0_10px_rgba(0,0,0,0.10)] dark:shadow-[0_0_15px_rgba(0,0,0,0.10)]"> | ||||||
|       <textarea |       <textarea | ||||||
|         ref={textareaRef} |         ref={textareaRef} | ||||||
|         className="rounded-lg pl-4 pr-8 py-3 w-full focus:outline-none dark:bg-[#40414F] dark:border-opacity-50 dark:border-neutral-800 dark:text-neutral-100 border border-neutral-300 shadow text-neutral-900" |         className="text-base m-0 w-full resize-none outline-none border-0 bg-transparent p-0 pr-7 focus:ring-0 focus-visible:ring-0 dark:bg-transparent pl-2 md:pl-0" | ||||||
|         style={{ |         style={{ | ||||||
|           resize: "none", |           resize: "none", | ||||||
|           bottom: `${textareaRef?.current?.scrollHeight}px`, |           bottom: `${textareaRef?.current?.scrollHeight}px`, | ||||||
|  | @ -88,11 +90,14 @@ export const ChatInput: FC<Props> = ({ onSend, messageIsStreaming, model }) => { | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|       <button |       <button | ||||||
|         className="absolute right-5 bottom-[18px] focus:outline-none text-neutral-800 hover:text-neutral-900 dark:text-neutral-100 dark:hover:text-neutral-200 dark:bg-opacity-50 hover:bg-neutral-200 p-1 rounded-sm" |         className="absolute right-5 focus:outline-none text-neutral-800 hover:text-neutral-900 dark:text-neutral-100 dark:hover:text-neutral-200 dark:bg-opacity-50 hover:bg-neutral-200 p-1 rounded-sm" | ||||||
|         onClick={handleSend} |         onClick={handleSend} | ||||||
|       > |       > | ||||||
|         <IconSend size={18} /> |         <IconSend size={16} className="opacity-60"/> | ||||||
|       </button> |       </button> | ||||||
|       </div> |       </div> | ||||||
|  |       </div> | ||||||
|  |       <div className="px-3 pt-2 pb-3 text-center text-xs text-black/50 dark:text-white/50 md:px-4 md:pt-3 md:pb-6"><a href="https://github.com/mckaywrigley/chatbot-ui" target="_blank" rel="noreferrer" className="underline">ChatBot UI</a>. Chatbot UI is an advanced chatbot kit for OpenAI's chat models aiming to mimic ChatGPT's interface and functionality.</div> | ||||||
|  |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -12,11 +12,11 @@ interface Props { | ||||||
| export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | export const ChatMessage: FC<Props> = ({ message, lightMode }) => { | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className={`flex justify-center py-[20px] sm:py-[30px] ${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={`group ${message.role === "assistant" ? "text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" : "text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 "}`} | ||||||
|       style={{ overflowWrap: "anywhere" }} |       style={{ overflowWrap: "anywhere" }} | ||||||
|     > |     > | ||||||
|       <div className="w-full sm:w-4/5 md:w-3/5 lg:w-[600px] xl:w-[800px] flex px-4"> |       <div className="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"> | ||||||
|         <div className="mr-1 sm:mr-2 font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div> |         <div className="font-bold min-w-[40px]">{message.role === "assistant" ? "AI:" : "You:"}</div> | ||||||
| 
 | 
 | ||||||
|         <div className="prose dark:prose-invert mt-[-2px]"> |         <div className="prose dark:prose-invert mt-[-2px]"> | ||||||
|           {message.role === "user" ? ( |           {message.role === "user" ? ( | ||||||
|  |  | ||||||
|  | @ -38,17 +38,17 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | ||||||
|   }, [isRenaming, isDeleting]); |   }, [isRenaming, isDeleting]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex flex-col-reverse gap-2 w-full px-2"> |     <div className="flex flex-col-reverse gap-1 w-full pt-2"> | ||||||
|       {conversations.map((conversation, index) => ( |       {conversations.map((conversation, index) => ( | ||||||
|         <button |         <button | ||||||
|           key={index} |           key={index} | ||||||
|           className={`flex items-center justify-start min-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 gap-3 items-center p-3 text-sm rounded-lg hover:bg-[#343541]/90 transition-colors duration-200 cursor-pointer ${loading ? "disabled:cursor-not-allowed" : ""} ${selectedConversation.id === conversation.id ? "bg-[#343541]/90" : ""}`} | ||||||
|           onClick={() => onSelectConversation(conversation)} |           onClick={() => onSelectConversation(conversation)} | ||||||
|           disabled={loading} |           disabled={loading} | ||||||
|         > |         > | ||||||
|           <IconMessage |           <IconMessage | ||||||
|             className="mr-2 min-w-[20px]" |             className="" | ||||||
|             size={18} |             size={16} | ||||||
|           /> |           /> | ||||||
| 
 | 
 | ||||||
|           {isRenaming && selectedConversation.id === conversation.id ? ( |           {isRenaming && selectedConversation.id === conversation.id ? ( | ||||||
|  | @ -65,10 +65,10 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | ||||||
|           )} |           )} | ||||||
| 
 | 
 | ||||||
|           {(isDeleting || isRenaming) && selectedConversation.id === conversation.id && ( |           {(isDeleting || isRenaming) && selectedConversation.id === conversation.id && ( | ||||||
|             <div className="flex w-[40px]"> |             <div className="flex gap-1 -ml-2"> | ||||||
|               <IconCheck |               <IconCheck | ||||||
|                 className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" |                 className="min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||||
|                 size={18} |                 size={16} | ||||||
|                 onClick={(e) => { |                 onClick={(e) => { | ||||||
|                   e.stopPropagation(); |                   e.stopPropagation(); | ||||||
| 
 | 
 | ||||||
|  | @ -84,8 +84,8 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | ||||||
|               /> |               /> | ||||||
| 
 | 
 | ||||||
|               <IconX |               <IconX | ||||||
|                 className="ml-auto min-w-[20px] text-neutral-400 hover:text-neutral-100" |                 className="min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||||
|                 size={18} |                 size={16} | ||||||
|                 onClick={(e) => { |                 onClick={(e) => { | ||||||
|                   e.stopPropagation(); |                   e.stopPropagation(); | ||||||
|                   setIsDeleting(false); |                   setIsDeleting(false); | ||||||
|  | @ -96,7 +96,7 @@ export const Conversations: FC<Props> = ({ loading, conversations, selectedConve | ||||||
|           )} |           )} | ||||||
| 
 | 
 | ||||||
|           {selectedConversation.id === conversation.id && !isDeleting && !isRenaming && ( |           {selectedConversation.id === conversation.id && !isDeleting && !isRenaming && ( | ||||||
|             <div className="flex w-[40px]"> |             <div className="flex gap-1 -ml-2"> | ||||||
|               <IconPencil |               <IconPencil | ||||||
|                 className="min-w-[20px] text-neutral-400 hover:text-neutral-100" |                 className="min-w-[20px] text-neutral-400 hover:text-neutral-100" | ||||||
|                 size={18} |                 size={18} | ||||||
|  |  | ||||||
|  | @ -24,11 +24,11 @@ export const Key: FC<Props> = ({ apiKey, onApiKeyChange }) => { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return isChanging ? ( |   return isChanging ? ( | ||||||
|     <div className="flex hover:bg-[#343541] py-2 px-2 rounded-md cursor-pointer w-full items-center"> |     <div className="flex transition-colors duration:200 hover:bg-gray-500/10 py-3 px-3 rounded-md cursor-pointer w-full items-center"> | ||||||
|       <IconKey size={16} /> |       <IconKey size={16} /> | ||||||
| 
 | 
 | ||||||
|       <input |       <input | ||||||
|         className="ml-2 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" |         className="ml-2 flex-1 h-[20px] bg-transparent border-b border-neutral-400 focus:border-neutral-100 text-left overflow-hidden overflow-ellipsis pr-1 outline-none text-white" | ||||||
|         type="password" |         type="password" | ||||||
|         value={newKey} |         value={newKey} | ||||||
|         onChange={(e) => setNewKey(e.target.value)} |         onChange={(e) => setNewKey(e.target.value)} | ||||||
|  |  | ||||||
|  | @ -16,9 +16,9 @@ export const Search: FC<Props> = ({ searchTerm, onSearch }) => { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className="relative flex items-center sm:pl-2 px-2 mb-2"> |     <div className="relative flex items-center"> | ||||||
|       <input |       <input | ||||||
|         className="flex-1 w-full pr-10 bg-[#202123] border border-neutral-600 text-sm rounded-lg px-4 py-2 text-white" |         className="flex-1 w-full pr-10 bg-[#202123] border border-neutral-600 text-sm rounded-md px-4 py-3 text-white" | ||||||
|         type="text" |         type="text" | ||||||
|         placeholder="Search conversations..." |         placeholder="Search conversations..." | ||||||
|         value={searchTerm} |         value={searchTerm} | ||||||
|  |  | ||||||
|  | @ -34,17 +34,17 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | ||||||
|   }, [searchTerm, conversations]); |   }, [searchTerm, conversations]); | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <div className={`flex flex-col bg-[#202123] min-w-full sm:min-w-[260px] sm:max-w-[260px] z-10 sm:relative sm:top-0 absolute top-12 bottom-0`}> |     <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="flex items-center h-[60px] sm:pl-2 px-2"> |       <div className="flex items-center"> | ||||||
|         <button |         <button | ||||||
|           className="flex items-center w-full sm:w-[200px] h-[40px] rounded-lg bg-[#202123] border border-neutral-600 text-sm hover:bg-neutral-700" |           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" | ||||||
|           onClick={() => { |           onClick={() => { | ||||||
|             onNewConversation(); |             onNewConversation(); | ||||||
|             setSearchTerm(""); |             setSearchTerm(""); | ||||||
|           }} |           }} | ||||||
|         > |         > | ||||||
|           <IconPlus |           <IconPlus | ||||||
|             className="ml-4 mr-3" |             className="" | ||||||
|             size={16} |             size={16} | ||||||
|           /> |           /> | ||||||
|           New chat |           New chat | ||||||
|  | @ -52,7 +52,7 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | ||||||
| 
 | 
 | ||||||
|         <IconArrowBarLeft |         <IconArrowBarLeft | ||||||
|           className="ml-1 p-1 text-neutral-300 cursor-pointer hover:text-neutral-400 hidden sm:flex" |           className="ml-1 p-1 text-neutral-300 cursor-pointer hover:text-neutral-400 hidden sm:flex" | ||||||
|           size={38} |           size={32} | ||||||
|           onClick={onToggleSidebar} |           onClick={onToggleSidebar} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|  | @ -64,7 +64,7 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected | ||||||
|         /> |         /> | ||||||
|       )} |       )} | ||||||
| 
 | 
 | ||||||
|       <div className="flex-1 overflow-auto"> |       <div className="flex-grow overflow-auto"> | ||||||
|         <Conversations |         <Conversations | ||||||
|           loading={loading} |           loading={loading} | ||||||
|           conversations={filteredConversations} |           conversations={filteredConversations} | ||||||
|  |  | ||||||
|  | @ -9,10 +9,10 @@ interface Props { | ||||||
| export const SidebarButton: FC<Props> = ({ text, icon, onClick }) => { | export const SidebarButton: FC<Props> = ({ text, icon, onClick }) => { | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className="flex hover:bg-[#343541] py-2 px-2 rounded-md cursor-pointer w-full items-center" |       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} |       onClick={onClick} | ||||||
|     > |     > | ||||||
|       <div className="mr-2">{icon}</div> |       <div className="">{icon}</div> | ||||||
|       <div>{text}</div> |       <div>{text}</div> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ interface Props { | ||||||
| 
 | 
 | ||||||
| export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations }) => { | export const SidebarSettings: FC<Props> = ({ lightMode, apiKey, onToggleLightMode, onApiKeyChange, onClearConversations }) => { | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex flex-col items-center border-t border-neutral-500 px-2 py-4 text-sm space-y-2"> |     <div className="flex flex-col pt-1 items-center border-t border-white/20 text-sm space-y-1"> | ||||||
|       <ClearConversations onClearConversations={onClearConversations} /> |       <ClearConversations onClearConversations={onClearConversations} /> | ||||||
| 
 | 
 | ||||||
|       <SidebarButton |       <SidebarButton | ||||||
|  |  | ||||||
|  | @ -318,7 +318,7 @@ export default function Home() { | ||||||
|         /> |         /> | ||||||
|       </Head> |       </Head> | ||||||
|       {selectedConversation && ( |       {selectedConversation && ( | ||||||
|         <div className={`flex flex-col h-screen w-screen text-white ${lightMode}`}> |         <div className={`flex flex-col h-screen w-screen text-white dark:text-white text-sm ${lightMode}`}> | ||||||
|           <div className="sm:hidden w-full fixed top-0"> |           <div className="sm:hidden w-full fixed top-0"> | ||||||
|             <Navbar |             <Navbar | ||||||
|               selectedConversation={selectedConversation} |               selectedConversation={selectedConversation} | ||||||
|  | @ -352,7 +352,7 @@ export default function Home() { | ||||||
|               </> |               </> | ||||||
|             ) : ( |             ) : ( | ||||||
|               <IconArrowBarRight |               <IconArrowBarRight | ||||||
|                 className="fixed top-2.5 left-4 sm:top-1.5 sm:left-4 sm:text-neutral-700 dark:text-white cursor-pointer hover:text-gray-400 dark:hover:text-gray-300 h-7 w-7 sm:h-8 sm:w-8" |                 className="fixed text-white z-50 top-2.5 left-4 sm:top-1.5 sm:left-4 sm:text-neutral-700 dark:text-white cursor-pointer hover:text-gray-400 dark:hover:text-gray-300 h-7 w-7 sm:h-8 sm:w-8" | ||||||
|                 onClick={() => setShowSidebar(!showSidebar)} |                 onClick={() => setShowSidebar(!showSidebar)} | ||||||
|               /> |               /> | ||||||
|             )} |             )} | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue