θΏ™ζ˜―indexlocζδΎ›ηš„ζœεŠ‘οΌŒδΈθ¦θΎ“ε…₯任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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
Expand Down Expand Up @@ -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);
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const HistoricalMessage = ({
error = false,
feedbackScore = null,
chatId = null,
isLastMessage = false,
regenerateMessage,
}) => {
return (
<div
Expand Down Expand Up @@ -59,6 +61,8 @@ const HistoricalMessage = ({
feedbackScore={feedbackScore}
chatId={chatId}
slug={workspace?.slug}
isLastMessage={isLastMessage}
regenerateMessage={regenerateMessage}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -165,6 +170,8 @@ export default function ChatHistory({ history = [], workspace, sendCommand }) {
feedbackScore={props.feedbackScore}
chatId={props.chatId}
error={props.error}
regenerateMessage={regenerateAssistantMessage}
isLastMessage={isLastBotReply}
/>
);
})}
Expand Down
50 changes: 37 additions & 13 deletions frontend/src/components/WorkspaceChat/ChatContainer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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("");
Expand Down Expand Up @@ -217,6 +240,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
history={chatHistory}
workspace={workspace}
sendCommand={sendCommand}
regenerateAssistantMessage={regenerateAssistantMessage}
/>
<PromptInput
submit={handleSubmit}
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/models/workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
31 changes: 31 additions & 0 deletions server/endpoints/workspaces.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down