mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-30 10:43:06 +08:00
feat: implement edit chat title modal
This commit is contained in:
@ -58,6 +58,13 @@ const ChatView = () => {
|
||||
}, [lastMessage?.isGenerated, lastMessage?.content]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentChat?.connectionId === connectionStore.currentConnectionCtx?.connection.id &&
|
||||
currentChat?.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Auto select the first chat when the current connection changes.
|
||||
const chatList = chatStore.chatList.filter(
|
||||
(chat) =>
|
||||
@ -65,7 +72,7 @@ const ChatView = () => {
|
||||
chat.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
||||
);
|
||||
chatStore.setCurrentChat(head(chatList));
|
||||
}, [connectionStore.currentConnectionCtx]);
|
||||
}, [currentChat, connectionStore.currentConnectionCtx]);
|
||||
|
||||
const sendMessageToCurrentChat = async () => {
|
||||
const currentChat = chatStore.getState().currentChat;
|
||||
|
@ -8,11 +8,13 @@ import EngineIcon from "./EngineIcon";
|
||||
import CreateConnectionModal from "./CreateConnectionModal";
|
||||
import SettingModal from "./SettingModal";
|
||||
import ActionConfirmModal, { ActionConfirmModalProps } from "./ActionConfirmModal";
|
||||
import EditChatTitleModal from "./EditChatTitleModal";
|
||||
|
||||
interface State {
|
||||
showCreateConnectionModal: boolean;
|
||||
showSettingModal: boolean;
|
||||
showDeleteConnectionModal: boolean;
|
||||
showEditChatTitleModal: boolean;
|
||||
}
|
||||
|
||||
const ConnectionSidebar = () => {
|
||||
@ -23,8 +25,10 @@ const ConnectionSidebar = () => {
|
||||
showCreateConnectionModal: false,
|
||||
showSettingModal: false,
|
||||
showDeleteConnectionModal: false,
|
||||
showEditChatTitleModal: false,
|
||||
});
|
||||
const [deleteConnectionModalContext, setDeleteConnectionModalContext] = useState<ActionConfirmModalProps>();
|
||||
const [editChatTitleModalContext, setEditChatTitleModalContext] = useState<Chat>();
|
||||
const connectionList = connectionStore.connectionList;
|
||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||
const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id);
|
||||
@ -46,6 +50,13 @@ const ConnectionSidebar = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const toggleEditChatTitleModal = (show = true) => {
|
||||
setState({
|
||||
...state,
|
||||
showEditChatTitleModal: show,
|
||||
});
|
||||
};
|
||||
|
||||
const handleConnectionSelect = async (connection: Connection) => {
|
||||
const databaseList = await connectionStore.getOrFetchDatabaseList(connection);
|
||||
connectionStore.setCurrentConnectionCtx({
|
||||
@ -103,6 +114,14 @@ const ConnectionSidebar = () => {
|
||||
layoutStore.toggleSidebar(false);
|
||||
};
|
||||
|
||||
const handleEditChatTitle = (chat: Chat) => {
|
||||
setEditChatTitleModalContext(chat);
|
||||
setState({
|
||||
...state,
|
||||
showEditChatTitleModal: true,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteChat = (chat: Chat) => {
|
||||
chatStore.clearChat((item) => item.id !== chat.id);
|
||||
if (chatStore.currentChat?.id === chat.id) {
|
||||
@ -181,7 +200,7 @@ const ConnectionSidebar = () => {
|
||||
{chatList.map((chat) => (
|
||||
<div
|
||||
key={chat.id}
|
||||
className={`w-full mt-2 first:mt-4 py-3 px-4 pr-3 rounded-lg flex flex-row justify-start items-center cursor-pointer border border-transparent group hover:bg-gray-50 ${
|
||||
className={`w-full mt-2 first:mt-4 py-3 pl-4 pr-2 rounded-lg flex flex-row justify-start items-center cursor-pointer border border-transparent group hover:bg-gray-50 ${
|
||||
chat.id === chatStore.currentChat?.id && "!bg-white border-gray-200 font-medium"
|
||||
}`}
|
||||
onClick={() => handleChatSelect(chat)}
|
||||
@ -192,14 +211,21 @@ const ConnectionSidebar = () => {
|
||||
<Icon.IoChatbubbleOutline className="w-5 h-auto mr-1.5 opacity-80 shrink-0" />
|
||||
)}
|
||||
<span className="truncate grow">{chat.title || "SQL Chat"}</span>
|
||||
<span
|
||||
className="ml-0.5 shrink-0 opacity-60 hidden group-hover:block hover:opacity-80"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteChat(chat);
|
||||
}}
|
||||
>
|
||||
<Icon.IoClose className="w-5 h-auto" />
|
||||
<span className="ml-0.5 shrink-0 hidden group-hover:flex flex-row justify-end items-center space-x-1">
|
||||
<Icon.FiEdit3
|
||||
className="w-4 h-auto opacity-60 hover:opacity-80"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditChatTitle(chat);
|
||||
}}
|
||||
/>
|
||||
<Icon.IoClose
|
||||
className="w-5 h-auto opacity-60 hover:opacity-80"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteChat(chat);
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
@ -232,6 +258,10 @@ const ConnectionSidebar = () => {
|
||||
/>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{state.showEditChatTitleModal &&
|
||||
editChatTitleModalContext &&
|
||||
createPortal(<EditChatTitleModal close={() => toggleEditChatTitleModal(false)} chat={editChatTitleModalContext} />, document.body)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
60
components/EditChatTitleModal.tsx
Normal file
60
components/EditChatTitleModal.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useChatStore } from "@/store";
|
||||
import { Chat } from "@/types";
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props {
|
||||
chat: Chat;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
const EditMessageTitleModal = (props: Props) => {
|
||||
const { close, chat } = props;
|
||||
const chatStore = useChatStore();
|
||||
const [title, setTitle] = useState(chat.title);
|
||||
const allowSave = title !== "";
|
||||
|
||||
const handleSaveEdit = () => {
|
||||
const formatedTitle = title.trim();
|
||||
if (formatedTitle === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
chatStore.updateChat(chat.id, {
|
||||
title: formatedTitle,
|
||||
});
|
||||
toast.success("Chat title updated");
|
||||
close();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="modal modal-middle modal-open">
|
||||
<div className="modal-box relative">
|
||||
<h3 className="font-bold text-lg">Edit chat title</h3>
|
||||
<button className="btn btn-sm btn-circle absolute right-4 top-4" onClick={close}>
|
||||
<Icon.IoMdClose className="w-5 h-auto" />
|
||||
</button>
|
||||
<div className="w-full flex flex-col justify-start items-start space-y-3 pt-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Chat title"
|
||||
className="input input-bordered w-full"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button className="btn btn-outline" onClick={close}>
|
||||
Close
|
||||
</button>
|
||||
<button className="btn" disabled={!allowSave} onClick={handleSaveEdit}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditMessageTitleModal;
|
@ -2,6 +2,7 @@ import * as Ai from "react-icons/ai";
|
||||
import * as Bi from "react-icons/bi";
|
||||
import * as Bs from "react-icons/bs";
|
||||
import * as Di from "react-icons/di";
|
||||
import * as Fi from "react-icons/fi";
|
||||
import * as Io from "react-icons/io";
|
||||
import * as Io5 from "react-icons/io5";
|
||||
|
||||
@ -10,6 +11,7 @@ const Icon = {
|
||||
...Bi,
|
||||
...Bs,
|
||||
...Di,
|
||||
...Fi,
|
||||
...Io,
|
||||
...Io5,
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import dayjs from "dayjs";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import { Chat, Id } from "@/types";
|
||||
@ -19,6 +20,7 @@ interface ChatState {
|
||||
getState: () => ChatState;
|
||||
createChat: (connectionId?: Id, databaseName?: string) => Chat;
|
||||
setCurrentChat: (chat: Chat | undefined) => void;
|
||||
updateChat: (chatId: Id, chat: Partial<Chat>) => void;
|
||||
clearChat: (filter: (chat: Chat) => boolean) => void;
|
||||
}
|
||||
|
||||
@ -40,6 +42,16 @@ export const useChatStore = create<ChatState>()(
|
||||
return chat;
|
||||
},
|
||||
setCurrentChat: (chat: Chat | undefined) => set(() => ({ currentChat: chat })),
|
||||
updateChat: (chatId: Id, chat: Partial<Chat>) => {
|
||||
const rawChat = get().chatList.find((chat) => chat.id === chatId);
|
||||
if (!rawChat) {
|
||||
return;
|
||||
}
|
||||
Object.assign(rawChat, chat);
|
||||
set((state) => ({
|
||||
chatList: uniqBy([...state.chatList], (chat) => chat.id),
|
||||
}));
|
||||
},
|
||||
clearChat: (filter: (chat: Chat) => boolean) => {
|
||||
set((state) => ({
|
||||
...state,
|
||||
|
Reference in New Issue
Block a user