mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-31 11:13:02 +08:00
feat: implement edit chat title modal
This commit is contained in:
@ -58,6 +58,13 @@ const ChatView = () => {
|
|||||||
}, [lastMessage?.isGenerated, lastMessage?.content]);
|
}, [lastMessage?.isGenerated, lastMessage?.content]);
|
||||||
|
|
||||||
useEffect(() => {
|
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.
|
// Auto select the first chat when the current connection changes.
|
||||||
const chatList = chatStore.chatList.filter(
|
const chatList = chatStore.chatList.filter(
|
||||||
(chat) =>
|
(chat) =>
|
||||||
@ -65,7 +72,7 @@ const ChatView = () => {
|
|||||||
chat.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
chat.databaseName === connectionStore.currentConnectionCtx?.database?.name
|
||||||
);
|
);
|
||||||
chatStore.setCurrentChat(head(chatList));
|
chatStore.setCurrentChat(head(chatList));
|
||||||
}, [connectionStore.currentConnectionCtx]);
|
}, [currentChat, connectionStore.currentConnectionCtx]);
|
||||||
|
|
||||||
const sendMessageToCurrentChat = async () => {
|
const sendMessageToCurrentChat = async () => {
|
||||||
const currentChat = chatStore.getState().currentChat;
|
const currentChat = chatStore.getState().currentChat;
|
||||||
|
@ -8,11 +8,13 @@ import EngineIcon from "./EngineIcon";
|
|||||||
import CreateConnectionModal from "./CreateConnectionModal";
|
import CreateConnectionModal from "./CreateConnectionModal";
|
||||||
import SettingModal from "./SettingModal";
|
import SettingModal from "./SettingModal";
|
||||||
import ActionConfirmModal, { ActionConfirmModalProps } from "./ActionConfirmModal";
|
import ActionConfirmModal, { ActionConfirmModalProps } from "./ActionConfirmModal";
|
||||||
|
import EditChatTitleModal from "./EditChatTitleModal";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
showCreateConnectionModal: boolean;
|
showCreateConnectionModal: boolean;
|
||||||
showSettingModal: boolean;
|
showSettingModal: boolean;
|
||||||
showDeleteConnectionModal: boolean;
|
showDeleteConnectionModal: boolean;
|
||||||
|
showEditChatTitleModal: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ConnectionSidebar = () => {
|
const ConnectionSidebar = () => {
|
||||||
@ -23,8 +25,10 @@ const ConnectionSidebar = () => {
|
|||||||
showCreateConnectionModal: false,
|
showCreateConnectionModal: false,
|
||||||
showSettingModal: false,
|
showSettingModal: false,
|
||||||
showDeleteConnectionModal: false,
|
showDeleteConnectionModal: false,
|
||||||
|
showEditChatTitleModal: false,
|
||||||
});
|
});
|
||||||
const [deleteConnectionModalContext, setDeleteConnectionModalContext] = useState<ActionConfirmModalProps>();
|
const [deleteConnectionModalContext, setDeleteConnectionModalContext] = useState<ActionConfirmModalProps>();
|
||||||
|
const [editChatTitleModalContext, setEditChatTitleModalContext] = useState<Chat>();
|
||||||
const connectionList = connectionStore.connectionList;
|
const connectionList = connectionStore.connectionList;
|
||||||
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
const currentConnectionCtx = connectionStore.currentConnectionCtx;
|
||||||
const databaseList = connectionStore.databaseList.filter((database) => database.connectionId === currentConnectionCtx?.connection.id);
|
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 handleConnectionSelect = async (connection: Connection) => {
|
||||||
const databaseList = await connectionStore.getOrFetchDatabaseList(connection);
|
const databaseList = await connectionStore.getOrFetchDatabaseList(connection);
|
||||||
connectionStore.setCurrentConnectionCtx({
|
connectionStore.setCurrentConnectionCtx({
|
||||||
@ -103,6 +114,14 @@ const ConnectionSidebar = () => {
|
|||||||
layoutStore.toggleSidebar(false);
|
layoutStore.toggleSidebar(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditChatTitle = (chat: Chat) => {
|
||||||
|
setEditChatTitleModalContext(chat);
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
showEditChatTitleModal: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleDeleteChat = (chat: Chat) => {
|
const handleDeleteChat = (chat: Chat) => {
|
||||||
chatStore.clearChat((item) => item.id !== chat.id);
|
chatStore.clearChat((item) => item.id !== chat.id);
|
||||||
if (chatStore.currentChat?.id === chat.id) {
|
if (chatStore.currentChat?.id === chat.id) {
|
||||||
@ -181,7 +200,7 @@ const ConnectionSidebar = () => {
|
|||||||
{chatList.map((chat) => (
|
{chatList.map((chat) => (
|
||||||
<div
|
<div
|
||||||
key={chat.id}
|
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"
|
chat.id === chatStore.currentChat?.id && "!bg-white border-gray-200 font-medium"
|
||||||
}`}
|
}`}
|
||||||
onClick={() => handleChatSelect(chat)}
|
onClick={() => handleChatSelect(chat)}
|
||||||
@ -192,14 +211,21 @@ const ConnectionSidebar = () => {
|
|||||||
<Icon.IoChatbubbleOutline className="w-5 h-auto mr-1.5 opacity-80 shrink-0" />
|
<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="truncate grow">{chat.title || "SQL Chat"}</span>
|
||||||
<span
|
<span className="ml-0.5 shrink-0 hidden group-hover:flex flex-row justify-end items-center space-x-1">
|
||||||
className="ml-0.5 shrink-0 opacity-60 hidden group-hover:block hover:opacity-80"
|
<Icon.FiEdit3
|
||||||
onClick={(e) => {
|
className="w-4 h-auto opacity-60 hover:opacity-80"
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
handleDeleteChat(chat);
|
e.stopPropagation();
|
||||||
}}
|
handleEditChatTitle(chat);
|
||||||
>
|
}}
|
||||||
<Icon.IoClose className="w-5 h-auto" />
|
/>
|
||||||
|
<Icon.IoClose
|
||||||
|
className="w-5 h-auto opacity-60 hover:opacity-80"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleDeleteChat(chat);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -232,6 +258,10 @@ const ConnectionSidebar = () => {
|
|||||||
/>,
|
/>,
|
||||||
document.body
|
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 Bi from "react-icons/bi";
|
||||||
import * as Bs from "react-icons/bs";
|
import * as Bs from "react-icons/bs";
|
||||||
import * as Di from "react-icons/di";
|
import * as Di from "react-icons/di";
|
||||||
|
import * as Fi from "react-icons/fi";
|
||||||
import * as Io from "react-icons/io";
|
import * as Io from "react-icons/io";
|
||||||
import * as Io5 from "react-icons/io5";
|
import * as Io5 from "react-icons/io5";
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ const Icon = {
|
|||||||
...Bi,
|
...Bi,
|
||||||
...Bs,
|
...Bs,
|
||||||
...Di,
|
...Di,
|
||||||
|
...Fi,
|
||||||
...Io,
|
...Io,
|
||||||
...Io5,
|
...Io5,
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { uniqBy } from "lodash-es";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { Chat, Id } from "@/types";
|
import { Chat, Id } from "@/types";
|
||||||
@ -19,6 +20,7 @@ interface ChatState {
|
|||||||
getState: () => ChatState;
|
getState: () => ChatState;
|
||||||
createChat: (connectionId?: Id, databaseName?: string) => Chat;
|
createChat: (connectionId?: Id, databaseName?: string) => Chat;
|
||||||
setCurrentChat: (chat: Chat | undefined) => void;
|
setCurrentChat: (chat: Chat | undefined) => void;
|
||||||
|
updateChat: (chatId: Id, chat: Partial<Chat>) => void;
|
||||||
clearChat: (filter: (chat: Chat) => boolean) => void;
|
clearChat: (filter: (chat: Chat) => boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,6 +42,16 @@ export const useChatStore = create<ChatState>()(
|
|||||||
return chat;
|
return chat;
|
||||||
},
|
},
|
||||||
setCurrentChat: (chat: Chat | undefined) => set(() => ({ currentChat: 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) => {
|
clearChat: (filter: (chat: Chat) => boolean) => {
|
||||||
set((state) => ({
|
set((state) => ({
|
||||||
...state,
|
...state,
|
||||||
|
Reference in New Issue
Block a user