add rename and delete improvements

This commit is contained in:
Mckay Wrigley 2023-03-18 03:57:15 -06:00
parent b6d5576227
commit a0056460ab
6 changed files with 160 additions and 53 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>
)}
</>