Merge branch 'master' of github.com:Mintplex-Labs/anything-llm

This commit is contained in:
timothycarambat 2024-07-03 18:29:29 -07:00
commit ddf268771d
6 changed files with 136 additions and 2 deletions

View File

@ -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>
);
}

View File

@ -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
isSelected={selectedFeedback === true}

View File

@ -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
}`}
>

View File

@ -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;
}
}

View File

@ -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",

View File

@ -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 };