mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-15 02:50:10 +01:00
[FEAT] Ability to delete messages from conversation history (#1812)
* implement delete message feature * add new delete chat endpoint that just hides messages from ui and fix perms * refactor: add delete-event hooks for delete so we dont need to pass so much refactor: implmentation of delete endpoint PUT * update animation linting remove element on animiation to prevent popin * linting and cleanup imports --------- Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
parent
8658b1e7c7
commit
ab56bae1bb
@ -0,0 +1,67 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Trash } from "@phosphor-icons/react";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import Workspace from "@/models/workspace";
|
||||
const DELETE_EVENT = "delete-message";
|
||||
|
||||
export function useWatchDeleteMessage({ chatId = null, role = "user" }) {
|
||||
const [isDeleted, setIsDeleted] = useState(false);
|
||||
const [completeDelete, setCompleteDelete] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function listenForEvent() {
|
||||
if (!chatId) return;
|
||||
window.addEventListener(DELETE_EVENT, onDeleteEvent);
|
||||
}
|
||||
listenForEvent();
|
||||
return () => {
|
||||
window.removeEventListener(DELETE_EVENT, onDeleteEvent);
|
||||
};
|
||||
}, [chatId]);
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
export function DeleteMessage({ chatId, isEditing, role }) {
|
||||
if (!chatId || isEditing || role === "user") return null;
|
||||
|
||||
function emitDeleteEvent() {
|
||||
window.dispatchEvent(new CustomEvent(DELETE_EVENT, { detail: { chatId } }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-3 relative">
|
||||
<button
|
||||
onClick={emitDeleteEvent}
|
||||
data-tooltip-id={`delete-message-${chatId}`}
|
||||
data-tooltip-content="Delete message"
|
||||
className="border-none text-zinc-300"
|
||||
aria-label="Delete"
|
||||
>
|
||||
<Trash size={18} className="mb-1" />
|
||||
</button>
|
||||
<Tooltip
|
||||
id={`delete-message-${chatId}`}
|
||||
place="bottom"
|
||||
delayShow={300}
|
||||
className="tooltip !text-xs"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ import { Tooltip } from "react-tooltip";
|
||||
import Workspace from "@/models/workspace";
|
||||
import TTSMessage from "./TTSButton";
|
||||
import { EditMessageAction } from "./EditMessage";
|
||||
import { DeleteMessage } from "./DeleteMessage";
|
||||
|
||||
const Actions = ({
|
||||
message,
|
||||
@ -50,6 +51,7 @@ const Actions = ({
|
||||
chatId={chatId}
|
||||
/>
|
||||
)}
|
||||
<DeleteMessage chatId={chatId} role={role} isEditing={isEditing} />
|
||||
{chatId && role !== "user" && !isEditing && (
|
||||
<>
|
||||
<FeedbackButton
|
||||
|
@ -9,6 +9,7 @@ import { AI_BACKGROUND_COLOR, USER_BACKGROUND_COLOR } from "@/utils/constants";
|
||||
import { v4 } from "uuid";
|
||||
import createDOMPurify from "dompurify";
|
||||
import { EditMessageForm, useEditMessage } from "./Actions/EditMessage";
|
||||
import { useWatchDeleteMessage } from "./Actions/DeleteMessage";
|
||||
|
||||
const DOMPurify = createDOMPurify(window);
|
||||
const HistoricalMessage = ({
|
||||
@ -26,6 +27,10 @@ const HistoricalMessage = ({
|
||||
forkThread,
|
||||
}) => {
|
||||
const { isEditing } = useEditMessage({ chatId, role });
|
||||
const { isDeleted, completeDelete, onEndAnimation } = useWatchDeleteMessage({
|
||||
chatId,
|
||||
role,
|
||||
});
|
||||
const adjustTextArea = (event) => {
|
||||
const element = event.target;
|
||||
element.style.height = "auto";
|
||||
@ -58,10 +63,14 @@ const HistoricalMessage = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (completeDelete) return null;
|
||||
return (
|
||||
<div
|
||||
key={uuid}
|
||||
className={`flex justify-center items-end w-full group ${
|
||||
onAnimationEnd={onEndAnimation}
|
||||
className={`${
|
||||
isDeleted ? "animate-remove" : ""
|
||||
} flex justify-center items-end w-full group ${
|
||||
role === "user" ? USER_BACKGROUND_COLOR : AI_BACKGROUND_COLOR
|
||||
}`}
|
||||
>
|
||||
|
@ -746,3 +746,23 @@ does not extend the close button beyond the viewport. */
|
||||
.search-input::-webkit-search-cancel-button {
|
||||
filter: grayscale(100%) invert(1) brightness(100) opacity(0.5);
|
||||
}
|
||||
|
||||
.animate-remove {
|
||||
animation: fadeAndShrink 800ms forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeAndShrink {
|
||||
50% {
|
||||
opacity: 25%;
|
||||
}
|
||||
|
||||
75% {
|
||||
opacity: 10%;
|
||||
}
|
||||
|
||||
100% {
|
||||
height: 0px;
|
||||
opacity: 0%;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
@ -384,6 +384,17 @@ const Workspace = {
|
||||
return false;
|
||||
});
|
||||
},
|
||||
deleteChat: async (chatId) => {
|
||||
return await fetch(`${API_BASE}/workspace/workspace-chats/${chatId}`, {
|
||||
method: "PUT",
|
||||
headers: baseHeaders(),
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return { success: false, error: e.message };
|
||||
});
|
||||
},
|
||||
forkThread: async function (slug = "", threadSlug = null, chatId = null) {
|
||||
return await fetch(`${API_BASE}/workspace/${slug}/thread/fork`, {
|
||||
method: "POST",
|
||||
|
@ -833,11 +833,36 @@ function workspaceEndpoints(app) {
|
||||
);
|
||||
response.status(200).json({ newThreadSlug: newThread.slug });
|
||||
} catch (e) {
|
||||
console.log(e.message, e);
|
||||
console.error(e.message, e);
|
||||
response.status(500).json({ message: "Internal server error" });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.put(
|
||||
"/workspace/workspace-chats/:id",
|
||||
[validatedRequest, flexUserRoleValid([ROLES.all])],
|
||||
async (request, response) => {
|
||||
try {
|
||||
const { id } = request.params;
|
||||
const user = await userFromSession(request, response);
|
||||
const validChat = await WorkspaceChats.get({
|
||||
id: Number(id),
|
||||
user_id: user?.id ?? null,
|
||||
});
|
||||
if (!validChat)
|
||||
return response
|
||||
.status(404)
|
||||
.json({ success: false, error: "Chat not found." });
|
||||
|
||||
await WorkspaceChats._update(validChat.id, { include: false });
|
||||
response.json({ success: true, error: null });
|
||||
} catch (e) {
|
||||
console.error(e.message, e);
|
||||
response.status(500).json({ success: false, error: "Server error" });
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { workspaceEndpoints };
|
||||
|
Loading…
Reference in New Issue
Block a user