mirror of
https://github.com/sqlchat/sqlchat.git
synced 2025-07-30 02:32:03 +08:00
feat: add dropdown to message view
This commit is contained in:
@ -1,7 +1,9 @@
|
|||||||
import { ReactElement } from "react";
|
import { Menu, MenuItem } from "@mui/material";
|
||||||
|
import { ReactElement, useState } from "react";
|
||||||
|
import { toast } from "react-hot-toast";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { useUserStore } from "@/store";
|
import { useMessageStore, useUserStore } from "@/store";
|
||||||
import { Message } from "@/types";
|
import { Message } from "@/types";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import { CodeBlock } from "../CodeBlock";
|
import { CodeBlock } from "../CodeBlock";
|
||||||
@ -13,11 +15,33 @@ interface Props {
|
|||||||
const MessageView = (props: Props) => {
|
const MessageView = (props: Props) => {
|
||||||
const message = props.message;
|
const message = props.message;
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const messageStore = useMessageStore();
|
||||||
|
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLButtonElement | null>(null);
|
||||||
const isCurrentUser = message.creatorId === userStore.currentUser.id;
|
const isCurrentUser = message.creatorId === userStore.currentUser.id;
|
||||||
|
const showMenu = Boolean(menuAnchorEl);
|
||||||
|
|
||||||
|
const handleMoreMenuClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (menuAnchorEl) {
|
||||||
|
setMenuAnchorEl(null);
|
||||||
|
} else {
|
||||||
|
setMenuAnchorEl(event.currentTarget);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyMessage = () => {
|
||||||
|
navigator.clipboard.writeText(message.content);
|
||||||
|
toast.success("Copied to clipboard");
|
||||||
|
setMenuAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMessage = (message: Message) => {
|
||||||
|
messageStore.clearMessage((item) => item.id !== message.id);
|
||||||
|
setMenuAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`w-full max-w-full flex flex-row justify-start items-start my-4 ${
|
className={`w-full max-w-full flex flex-row justify-start items-start my-4 group ${
|
||||||
isCurrentUser ? "justify-end pl-8 sm:pl-24" : "pr-8 sm:pr-24"
|
isCurrentUser ? "justify-end pl-8 sm:pl-24" : "pr-8 sm:pr-24"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -36,7 +60,7 @@ const MessageView = (props: Props) => {
|
|||||||
<Icon.AiOutlineRobot className="w-6 h-6" />
|
<Icon.AiOutlineRobot className="w-6 h-6" />
|
||||||
</div>
|
</div>
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
className="mt-0.5 w-auto max-w-[calc(100%-3rem)] bg-gray-100 px-4 py-2 rounded-lg prose prose-neutral"
|
className="mt-0.5 w-auto max-w-[calc(100%-4rem)] bg-gray-100 px-4 py-2 rounded-lg prose prose-neutral"
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
components={{
|
components={{
|
||||||
pre({ node, className, children, ...props }) {
|
pre({ node, className, children, ...props }) {
|
||||||
@ -44,7 +68,7 @@ const MessageView = (props: Props) => {
|
|||||||
const match = /language-(\w+)/.exec(child.props.className || "");
|
const match = /language-(\w+)/.exec(child.props.className || "");
|
||||||
const language = match ? match[1] : "text";
|
const language = match ? match[1] : "text";
|
||||||
return (
|
return (
|
||||||
<pre className={`${className || ""} p-0 w-full`} {...props}>
|
<pre className={`${className || ""} w-full p-0 my-1`} {...props}>
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
key={Math.random()}
|
key={Math.random()}
|
||||||
language={language || "text"}
|
language={language || "text"}
|
||||||
@ -61,6 +85,32 @@ const MessageView = (props: Props) => {
|
|||||||
>
|
>
|
||||||
{message.content}
|
{message.content}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
|
<div className={`invisible group-hover:visible ${showMenu && "!visible"}`}>
|
||||||
|
<button
|
||||||
|
className="w-6 h-6 ml-1 mt-2 flex justify-center items-center text-gray-400 hover:text-gray-500"
|
||||||
|
onClick={handleMoreMenuClick}
|
||||||
|
>
|
||||||
|
<Icon.IoMdMore className="w-5 h-auto" />
|
||||||
|
</button>
|
||||||
|
<Menu
|
||||||
|
className="mt-1"
|
||||||
|
anchorEl={menuAnchorEl}
|
||||||
|
open={showMenu}
|
||||||
|
onClose={() => setMenuAnchorEl(null)}
|
||||||
|
MenuListProps={{
|
||||||
|
"aria-labelledby": "basic-button",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem onClick={copyMessage}>
|
||||||
|
<Icon.BiClipboard className="w-4 h-auto mr-1 opacity-70" />
|
||||||
|
Copy
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => deleteMessage(message)}>
|
||||||
|
<Icon.BiTrash className="w-4 h-auto mr-1 opacity-70" />
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -150,7 +150,7 @@ const ChatView = () => {
|
|||||||
className="drawer-content relative w-full h-full max-h-full flex flex-col justify-start items-start overflow-y-auto bg-white"
|
className="drawer-content relative w-full h-full max-h-full flex flex-col justify-start items-start overflow-y-auto bg-white"
|
||||||
>
|
>
|
||||||
<Header className={showHeaderShadow ? "shadow" : ""} />
|
<Header className={showHeaderShadow ? "shadow" : ""} />
|
||||||
<div className="p-2 w-full h-auto grow max-w-3xl py-1 px-4 sm:px-8 mx-auto">
|
<div className="p-2 w-full h-auto grow max-w-4xl py-1 px-4 sm:px-8 mx-auto">
|
||||||
{messageList.length === 0 ? (
|
{messageList.length === 0 ? (
|
||||||
<EmptyView className="mt-16" sendMessage={sendMessageToCurrentChat} />
|
<EmptyView className="mt-16" sendMessage={sendMessageToCurrentChat} />
|
||||||
) : (
|
) : (
|
||||||
@ -158,7 +158,7 @@ const ChatView = () => {
|
|||||||
)}
|
)}
|
||||||
{isRequesting && <MessageLoader />}
|
{isRequesting && <MessageLoader />}
|
||||||
</div>
|
</div>
|
||||||
<div className="sticky bottom-0 w-full max-w-3xl py-2 px-4 sm:px-8 mx-auto bg-white bg-opacity-80 backdrop-blur">
|
<div className="sticky bottom-0 w-full max-w-4xl py-2 px-4 sm:px-8 mx-auto bg-white bg-opacity-80 backdrop-blur">
|
||||||
<MessageTextarea disabled={isRequesting} sendMessage={sendMessageToCurrentChat} />
|
<MessageTextarea disabled={isRequesting} sendMessage={sendMessageToCurrentChat} />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
@ -17,7 +17,7 @@ const handler = async (req: NextRequest) => {
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model: "gpt-3.5-turbo",
|
model: "gpt-3.5-turbo",
|
||||||
messages: reqBody.messages,
|
messages: reqBody.messages,
|
||||||
max_tokens: 2000,
|
max_tokens: 1000,
|
||||||
temperature: 0,
|
temperature: 0,
|
||||||
frequency_penalty: 0.0,
|
frequency_penalty: 0.0,
|
||||||
presence_penalty: 0.0,
|
presence_penalty: 0.0,
|
||||||
|
Reference in New Issue
Block a user