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