[FEAT] Implement regenerate response button (#1341)

* implement regenerate response button

* wip on rerenders

* remove function that was duplicate

* update delete-chats function

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2024-05-10 14:47:29 -07:00 committed by GitHub
parent 2345424b03
commit 734c5a9e96
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 135 additions and 15 deletions

View File

@ -5,11 +5,19 @@ import {
ClipboardText,
ThumbsUp,
ThumbsDown,
ArrowsClockwise,
} from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import Workspace from "@/models/workspace";
const Actions = ({ message, feedbackScore, chatId, slug }) => {
const Actions = ({
message,
feedbackScore,
chatId,
slug,
isLastMessage,
regenerateMessage,
}) => {
const [selectedFeedback, setSelectedFeedback] = useState(feedbackScore);
const handleFeedback = async (newFeedback) => {
@ -22,6 +30,14 @@ const Actions = ({ message, feedbackScore, chatId, slug }) => {
return (
<div className="flex justify-start items-center gap-x-4">
<CopyMessage message={message} />
{isLastMessage &&
!message?.includes("Workspace chat memory was reset!") && (
<RegenerateMessage
regenerateMessage={regenerateMessage}
slug={slug}
chatId={chatId}
/>
)}
{chatId && (
<>
<FeedbackButton
@ -106,4 +122,26 @@ function CopyMessage({ message }) {
);
}
function RegenerateMessage({ regenerateMessage, chatId }) {
return (
<div className="mt-3 relative">
<button
onClick={() => regenerateMessage(chatId)}
data-tooltip-id="regenerate-assistant-text"
data-tooltip-content="Regenerate response"
className="border-none text-zinc-300"
aria-label="Regenerate"
>
<ArrowsClockwise size={18} className="mb-1" weight="fill" />
</button>
<Tooltip
id="regenerate-assistant-text"
place="bottom"
delayShow={300}
className="tooltip !text-xs"
/>
</div>
);
}
export default memo(Actions);

View File

@ -19,6 +19,8 @@ const HistoricalMessage = ({
error = false,
feedbackScore = null,
chatId = null,
isLastMessage = false,
regenerateMessage,
}) => {
return (
<div
@ -59,6 +61,8 @@ const HistoricalMessage = ({
feedbackScore={feedbackScore}
chatId={chatId}
slug={workspace?.slug}
isLastMessage={isLastMessage}
regenerateMessage={regenerateMessage}
/>
</div>
)}

View File

@ -8,7 +8,12 @@ import debounce from "lodash.debounce";
import useUser from "@/hooks/useUser";
import Chartable from "./Chartable";
export default function ChatHistory({ history = [], workspace, sendCommand }) {
export default function ChatHistory({
history = [],
workspace,
sendCommand,
regenerateAssistantMessage,
}) {
const { user } = useUser();
const { showing, showModal, hideModal } = useManageWorkspaceModal();
const [isAtBottom, setIsAtBottom] = useState(true);
@ -165,6 +170,8 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) {
feedbackScore={props.feedbackScore}
chatId={props.chatId}
error={props.error}
regenerateMessage={regenerateAssistantMessage}
isLastMessage={isLastBotReply}
/>
);
})}

View File

@ -26,7 +26,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
setMessage(event.target.value);
};
// Emit an update to the sate of the prompt input without directly
// Emit an update to the state of the prompt input without directly
// passing a prop in so that it does not re-render constantly.
function setMessageEmit(messageContent = "") {
setMessage(messageContent);
@ -56,24 +56,47 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
setLoadingResponse(true);
};
const sendCommand = async (command, submit = false) => {
const regenerateAssistantMessage = (chatId) => {
const updatedHistory = chatHistory.slice(0, -1);
const lastUserMessage = updatedHistory.slice(-1)[0];
Workspace.deleteChats(workspace.slug, [chatId])
.then(() => sendCommand(lastUserMessage.content, true, updatedHistory))
.catch((e) => console.error(e));
};
const sendCommand = async (command, submit = false, history = []) => {
if (!command || command === "") return false;
if (!submit) {
setMessageEmit(command);
return;
}
const prevChatHistory = [
...chatHistory,
{ content: command, role: "user" },
{
content: "",
role: "assistant",
pending: true,
userMessage: command,
animate: true,
},
];
let prevChatHistory;
if (history.length > 0) {
// use pre-determined history chain.
prevChatHistory = [
...history,
{
content: "",
role: "assistant",
pending: true,
userMessage: command,
animate: true,
},
];
} else {
prevChatHistory = [
...chatHistory,
{ content: command, role: "user" },
{
content: "",
role: "assistant",
pending: true,
userMessage: command,
animate: true,
},
];
}
setChatHistory(prevChatHistory);
setMessageEmit("");
@ -217,6 +240,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
history={chatHistory}
workspace={workspace}
sendCommand={sendCommand}
regenerateAssistantMessage={regenerateAssistantMessage}
/>
<PromptInput
submit={handleSubmit}

View File

@ -74,6 +74,22 @@ const Workspace = {
.catch(() => false);
return result;
},
deleteChats: async function (slug = "", chatIds = []) {
return await fetch(`${API_BASE}/workspace/${slug}/delete-chats`, {
method: "DELETE",
headers: baseHeaders(),
body: JSON.stringify({ chatIds }),
})
.then((res) => {
if (res.ok) return true;
throw new Error("Failed to delete chats.");
})
.catch((e) => {
console.log(e);
return false;
});
},
streamChat: async function ({ slug }, message, handleChat) {
const ctrl = new AbortController();

View File

@ -372,6 +372,37 @@ function workspaceEndpoints(app) {
}
);
app.delete(
"/workspace/:slug/delete-chats",
[validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
async (request, response) => {
try {
const { chatIds = [] } = reqBody(request);
const user = await userFromSession(request, response);
const workspace = response.locals.workspace;
if (!workspace || !Array.isArray(chatIds)) {
response.sendStatus(400).end();
return;
}
// This works for both workspace and threads.
// we simplify this by just looking at workspace<>user overlap
// since they are all on the same table.
await WorkspaceChats.delete({
id: { in: chatIds.map((id) => Number(id)) },
user_id: user?.id ?? null,
workspaceId: workspace.id,
});
response.sendStatus(200).end();
} catch (e) {
console.log(e.message, e);
response.sendStatus(500).end();
}
}
);
app.post(
"/workspace/:slug/chat-feedback/:chatId",
[validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],