diff --git a/web/src/components/AskAIDialog.tsx b/web/src/components/AskAIDialog.tsx index d6e7217f..0d9f532b 100644 --- a/web/src/components/AskAIDialog.tsx +++ b/web/src/components/AskAIDialog.tsx @@ -1,5 +1,18 @@ -import { Button, Textarea } from "@mui/joy"; -import { useEffect, useState } from "react"; +import { + Button, + FormControl, + FormLabel, + Input, + Menu, + MenuItem, + Modal, + ModalClose, + ModalDialog, + Stack, + Textarea, + Typography, +} from "@mui/joy"; +import React, { useEffect, useState } from "react"; import { toast } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import * as api from "@/helpers/api"; @@ -9,6 +22,8 @@ import { useMessageStore } from "@/store/zustand/message"; import Icon from "./Icon"; import { generateDialog } from "./Dialog"; import showSettingDialog from "./SettingDialog"; +import { defaultMessageGroup, MessageGroup, useMessageGroupStore } from "@/store/zustand/message-group"; +import { PlusIcon, Trash2Icon } from "lucide-react"; type Props = DialogProps; @@ -16,7 +31,8 @@ const AskAIDialog: React.FC = (props: Props) => { const { t } = useTranslation(); const { destroy, hide } = props; const fetchingState = useLoading(false); - const messageStore = useMessageStore(); + const [messageGroup, setMessageGroup] = useState(defaultMessageGroup); + const messageStore = useMessageStore(messageGroup)(); const [isEnabled, setIsEnabled] = useState(true); const [isInIME, setIsInIME] = useState(false); const [question, setQuestion] = useState(""); @@ -41,7 +57,7 @@ const AskAIDialog: React.FC = (props: Props) => { const handleKeyDown = (event: React.KeyboardEvent) => { if (event.key === "Enter" && !event.shiftKey && !isInIME) { event.preventDefault(); - handleSendQuestionButtonClick(); + handleSendQuestionButtonClick().then(); } }; @@ -76,36 +92,127 @@ const AskAIDialog: React.FC = (props: Props) => { }); }; + const [anchorEl, setAnchorEl] = useState(null); + const handleMenuOpen = (event: React.SyntheticEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleMenuClose = () => { + setAnchorEl(null); + }; + const handleOptionSelect = (option: MessageGroup) => { + setMessageGroup(option); + setAnchorEl(null); + }; + + const [isAddMessageGroupDlgOpen, setIsAddMessageGroupDlgOpen] = useState(false); + const [groupName, setGroupName] = useState(""); + + const messageGroupStore = useMessageGroupStore(); + const messageGroupList = messageGroupStore.groupList; + + const handleOpenDialog = () => { + setIsAddMessageGroupDlgOpen(true); + }; + + const handleRemoveDialog = () => { + setMessageGroup(messageGroupStore.removeGroup(messageGroup)); + }; + + const handleCloseDialog = () => { + setIsAddMessageGroupDlgOpen(false); + setGroupName(""); + }; + + const handleAddMessageGroupDlgConfirm = () => { + const newMessageGroup: MessageGroup = { + name: groupName, + messageStorageId: "message-storage-" + groupName, + }; + messageGroupStore.addGroup(newMessageGroup); + setMessageGroup(newMessageGroup); + handleCloseDialog(); + }; + + const handleCancel = () => { + handleCloseDialog(); + }; + return ( <>

{t("ask-ai.title")} + + + + +

+ + + handleOptionSelect(defaultMessageGroup)}>{defaultMessageGroup.name} + {messageGroupList.map((messageGroup, index) => ( + handleOptionSelect(messageGroup)}> + {messageGroup.name} + + ))} + + + + + + {t("ask-ai.create-message-group-title")} + + + + {t("ask-ai.label-message-group-name-title")} + setGroupName(e.target.value)} + placeholder={t("ask-ai.label-message-group-name-title")} + /> + + + + + + + +
- {messageList.map((message, index) => ( -
- {message.role === "user" ? ( -
- - {message.content} - -
- ) : ( -
- -
-
{marked(message.content)}
+ + {messageList.map((message, index) => ( +
+ {message.role === "user" ? ( +
+ + {message.content} +
-
- )} -
- ))} + ) : ( +
+ +
+
{marked(message.content)}
+
+
+ )} +
+ ))} + {fetchingState.isLoading && (

diff --git a/web/src/css/global.css b/web/src/css/global.css index 7a2768b4..e7df6210 100644 --- a/web/src/css/global.css +++ b/web/src/css/global.css @@ -9,3 +9,30 @@ body { #root { @apply w-full h-full; } + +.button-group { + display: flex; + gap: 0; /* 按钮之间的间距 */ +} + +.button-group>button:not(:first-child):not(:last-child) { + border-radius: 0; +} + +.button-group>button:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.button-group>button:last-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; +} + + +.button-len-max-150 { + max-width: 150px; /* 按钮的最大宽度 */ + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/web/src/locales/en.json b/web/src/locales/en.json index 70656869..acbfc1af 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -380,7 +380,10 @@ "title": "Ask AI", "not-enabled": "You have not set up your OpenAI API key.", "go-to-settings": "Go to settings", - "placeholder": "Ask anything…" + "placeholder": "Ask anything…", + "default-message-group-title": "Default Session", + "create-message-group-title": "Create Session", + "label-message-group-name-title": "Session Name" }, "embed-memo": { "title": "Embed Memo", @@ -403,4 +406,4 @@ "powered-by": "Powered by", "other-projects": "Other Projects" } -} \ No newline at end of file +} diff --git a/web/src/locales/zh-Hans.json b/web/src/locales/zh-Hans.json index 5432495a..8ad269f7 100644 --- a/web/src/locales/zh-Hans.json +++ b/web/src/locales/zh-Hans.json @@ -23,7 +23,11 @@ "go-to-settings": "前往设置", "not-enabled": "您尚未设置 OpenAI API 密钥。", "placeholder": "随便问", - "title": "问 AI" + "title": "问 AI", + "default-message-group-title": "默认会话", + "create-message-group-title": "新建会话", + "label-message-group-name-title": "会话名称" + }, "auth": { "host-tip": "你正在注册为管理员用户账号。", diff --git a/web/src/store/zustand/message-group.ts b/web/src/store/zustand/message-group.ts new file mode 100644 index 00000000..19f3929c --- /dev/null +++ b/web/src/store/zustand/message-group.ts @@ -0,0 +1,41 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; +import { t } from "i18next"; + +export interface MessageGroup { + name: string; + messageStorageId: string; +} + +interface MessageGroupState { + groupList: MessageGroup[]; + getState: () => MessageGroupState; + addGroup: (group: MessageGroup) => void; + removeGroup: (group: MessageGroup) => MessageGroup; +} + +export const defaultMessageGroup: MessageGroup = { + name: t("ask-ai.default-message-group-title"), + messageStorageId: "message-storage", +}; + +export const useMessageGroupStore = create()( + persist( + (set, get) => ({ + groupList: [], + getState: () => get(), + addGroup: (group: MessageGroup) => set((state) => ({ groupList: [...state.groupList, group] })), + removeGroup: (group: MessageGroup) => { + set((state) => ({ + groupList: state.groupList.filter((i) => i.name != group.name || i.messageStorageId != group.messageStorageId), + })); + localStorage.removeItem(group.messageStorageId); + const groupList = get().groupList; + return groupList.length > 0 ? groupList[groupList.length - 1] : defaultMessageGroup; + }, + }), + { + name: "message-group-storage", + } + ) +); diff --git a/web/src/store/zustand/message.ts b/web/src/store/zustand/message.ts index 9f5fc210..47f6682a 100644 --- a/web/src/store/zustand/message.ts +++ b/web/src/store/zustand/message.ts @@ -1,5 +1,6 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; +import { MessageGroup } from "@/store/zustand/message-group"; export interface Message { role: "user" | "assistant"; @@ -12,15 +13,17 @@ interface MessageState { addMessage: (message: Message) => void; } -export const useMessageStore = create()( - persist( - (set, get) => ({ - messageList: [], - getState: () => get(), - addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })), - }), - { - name: "message-storage", - } - ) -); +export const useMessageStore = (options: MessageGroup) => { + return create()( + persist( + (set, get) => ({ + messageList: [], + getState: () => get(), + addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })), + }), + { + name: options.messageStorageId, + } + ) + ); +};