Fix chat UI event listener bloat (#5323)
This commit is contained in:
parent
88ea47b9f4
commit
5a2393e632
@ -22,6 +22,7 @@ function ActionMenu({ chatId, forkThread, isEditing, role }) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handleClickOutside = (event) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target)) {
|
||||
setOpen(false);
|
||||
@ -32,7 +33,7 @@ function ActionMenu({ chatId, forkThread, isEditing, role }) {
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
}, [open]);
|
||||
|
||||
if (!chatId || isEditing || role === "user") return null;
|
||||
|
||||
|
||||
@ -1,40 +1,31 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Trash } from "@phosphor-icons/react";
|
||||
import Workspace from "@/models/workspace";
|
||||
|
||||
const DELETE_EVENT = "delete-message";
|
||||
import {
|
||||
useMessageActionsContext,
|
||||
DELETE_EVENT,
|
||||
} from "@/components/WorkspaceChat/ChatContainer/ChatHistory/MessageActionsContext";
|
||||
|
||||
export function useWatchDeleteMessage({ chatId = null, role = "user" }) {
|
||||
const [isDeleted, setIsDeleted] = useState(false);
|
||||
const context = useMessageActionsContext();
|
||||
const [completeDelete, setCompleteDelete] = useState(false);
|
||||
const deleteCalled = useRef(false);
|
||||
const isDeleted = context?.isDeleted(chatId) ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
function listenForEvent() {
|
||||
if (!chatId) return;
|
||||
window.addEventListener(DELETE_EVENT, onDeleteEvent);
|
||||
if (isDeleted && !deleteCalled.current) {
|
||||
deleteCalled.current = true;
|
||||
if (role === "assistant") {
|
||||
Workspace.deleteChat(chatId);
|
||||
}
|
||||
listenForEvent();
|
||||
return () => {
|
||||
window.removeEventListener(DELETE_EVENT, onDeleteEvent);
|
||||
};
|
||||
}, [chatId]);
|
||||
}
|
||||
}, [isDeleted, chatId, role]);
|
||||
|
||||
function onEndAnimation() {
|
||||
if (!isDeleted) return;
|
||||
setCompleteDelete(true);
|
||||
}
|
||||
|
||||
async function onDeleteEvent(e) {
|
||||
if (e.detail.chatId === chatId) {
|
||||
setIsDeleted(true);
|
||||
// Do this to prevent double-emission of the PUT/DELETE api call
|
||||
// because then there will be a race condition and it will make an error log for nothing
|
||||
// as one call will complete and the other will fail.
|
||||
if (role === "assistant") await Workspace.deleteChat(chatId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return { isDeleted, completeDelete, onEndAnimation };
|
||||
}
|
||||
|
||||
|
||||
@ -1,33 +1,16 @@
|
||||
import { Info, Pencil } from "@phosphor-icons/react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useRef, useEffect } from "react";
|
||||
import Appearance from "@/models/appearance";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const EDIT_EVENT = "toggle-message-edit";
|
||||
import {
|
||||
useMessageActionsContext,
|
||||
EDIT_EVENT,
|
||||
} from "@/components/WorkspaceChat/ChatContainer/ChatHistory/MessageActionsContext";
|
||||
|
||||
export function useEditMessage({ chatId, role }) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
function onEditEvent(e) {
|
||||
if (e.detail.chatId !== chatId || e.detail.role !== role) {
|
||||
setIsEditing(false);
|
||||
return false;
|
||||
}
|
||||
setIsEditing((prev) => !prev);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function listenForEdits() {
|
||||
if (!chatId || !role) return;
|
||||
window.addEventListener(EDIT_EVENT, onEditEvent);
|
||||
}
|
||||
listenForEdits();
|
||||
return () => {
|
||||
window.removeEventListener(EDIT_EVENT, onEditEvent);
|
||||
};
|
||||
}, [chatId, role]);
|
||||
|
||||
return { isEditing, setIsEditing };
|
||||
const context = useMessageActionsContext();
|
||||
const isEditing = context?.isEditing(chatId, role) ?? false;
|
||||
return { isEditing };
|
||||
}
|
||||
|
||||
export function EditMessageAction({ chatId = null, role, isEditing }) {
|
||||
@ -53,7 +36,7 @@ export function EditMessageAction({ chatId = null, role, isEditing }) {
|
||||
? t("chat_window.edit_prompt")
|
||||
: t("chat_window.edit_response")
|
||||
} `}
|
||||
className="border-none text-zinc-300 light:text-slate-500"
|
||||
className="border-none text-zinc-300 light:text-slate-500 px-0"
|
||||
aria-label={`Edit ${role === "user" ? t("chat_window.edit_prompt") : t("chat_window.edit_response")}`}
|
||||
>
|
||||
<Pencil size={21} className="mb-1" />
|
||||
|
||||
@ -0,0 +1,87 @@
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
} from "react";
|
||||
|
||||
const EDIT_EVENT = "toggle-message-edit";
|
||||
const DELETE_EVENT = "delete-message";
|
||||
|
||||
const MessageActionsContext = createContext(null);
|
||||
|
||||
/**
|
||||
* Provider that centralizes edit/delete event listeners for all messages.
|
||||
* Instead of each message registering its own window listener (O(n) listeners),
|
||||
* this provider registers just 2 listeners total and dispatches to messages via context.
|
||||
*/
|
||||
export function MessageActionsProvider({ children }) {
|
||||
const [editingMessage, setEditingMessage] = useState(null);
|
||||
const [deletedMessages, setDeletedMessages] = useState(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
function handleEditEvent(e) {
|
||||
const { chatId, role } = e.detail;
|
||||
if (!chatId || !role) return;
|
||||
|
||||
setEditingMessage((prev) => {
|
||||
if (prev?.chatId === chatId && prev?.role === role) {
|
||||
return null;
|
||||
}
|
||||
return { chatId, role };
|
||||
});
|
||||
}
|
||||
|
||||
function handleDeleteEvent(e) {
|
||||
const { chatId } = e.detail;
|
||||
if (!chatId) return;
|
||||
|
||||
setDeletedMessages((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(chatId);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener(EDIT_EVENT, handleEditEvent);
|
||||
window.addEventListener(DELETE_EVENT, handleDeleteEvent);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(EDIT_EVENT, handleEditEvent);
|
||||
window.removeEventListener(DELETE_EVENT, handleDeleteEvent);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isEditing = useCallback(
|
||||
(chatId, role) => {
|
||||
return editingMessage?.chatId === chatId && editingMessage?.role === role;
|
||||
},
|
||||
[editingMessage]
|
||||
);
|
||||
|
||||
const isDeleted = useCallback(
|
||||
(chatId) => {
|
||||
return deletedMessages.has(chatId);
|
||||
},
|
||||
[deletedMessages]
|
||||
);
|
||||
|
||||
const clearEditing = useCallback(() => {
|
||||
setEditingMessage(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MessageActionsContext.Provider
|
||||
value={{ editingMessage, isEditing, isDeleted, clearEditing }}
|
||||
>
|
||||
{children}
|
||||
</MessageActionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useMessageActionsContext() {
|
||||
return useContext(MessageActionsContext);
|
||||
}
|
||||
|
||||
export { EDIT_EVENT, DELETE_EVENT };
|
||||
@ -23,6 +23,7 @@ import Appearance from "@/models/appearance";
|
||||
import useTextSize from "@/hooks/useTextSize";
|
||||
import useChatHistoryScrollHandle from "@/hooks/useChatHistoryScrollHandle";
|
||||
import { ThoughtExpansionProvider } from "./ThoughtContainer";
|
||||
import { MessageActionsProvider } from "./MessageActionsContext";
|
||||
|
||||
export default forwardRef(function (
|
||||
{
|
||||
@ -209,6 +210,7 @@ export default forwardRef(function (
|
||||
);
|
||||
|
||||
return (
|
||||
<MessageActionsProvider>
|
||||
<ThoughtExpansionProvider>
|
||||
<div
|
||||
className={`markdown text-white/80 light:text-theme-text-primary font-light ${textSizeClass} h-full md:h-[83%] pb-[100px] pt-6 md:pt-0 md:pb-20 md:mx-0 overflow-y-scroll flex flex-col items-center justify-start ${showScrollbar ? "show-scrollbar" : "no-scroll"}`}
|
||||
@ -244,6 +246,7 @@ export default forwardRef(function (
|
||||
</div>
|
||||
)}
|
||||
</ThoughtExpansionProvider>
|
||||
</MessageActionsProvider>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user