feat(#1568): add "ask ai" section session splitting function (#1711)

* feat(#1568): Added "ask ai" section session splitting function

Added "ask ai" section session splitting function
Optimize the "ask ai" dialogue style

* fix(#1568): Fix wrong attribute "appearance"

* fix(#1568): Add ts type define

* fix(#1568): Add ts type define

* fix(#1568): Resolve the issue of components not being stretched when only user input is available

* feat(#1568): New session automatic switching function

* refactor(#1729): remove unused code

* feat(#1568): New Remove Session Function

New Remove Session Function
Rename some methods
pull/1754/head
GodMeowIceSun 2 years ago committed by GitHub
parent ec26a9702d
commit 93d608f050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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: Props) => {
const { t } = useTranslation();
const { destroy, hide } = props;
const fetchingState = useLoading(false);
const messageStore = useMessageStore();
const [messageGroup, setMessageGroup] = useState<MessageGroup>(defaultMessageGroup);
const messageStore = useMessageStore(messageGroup)();
const [isEnabled, setIsEnabled] = useState<boolean>(true);
const [isInIME, setIsInIME] = useState(false);
const [question, setQuestion] = useState<string>("");
@ -41,7 +57,7 @@ const AskAIDialog: React.FC<Props> = (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: Props) => {
});
};
const [anchorEl, setAnchorEl] = useState<null | (EventTarget & Element)>(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<boolean>(false);
const [groupName, setGroupName] = useState<string>("");
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 (
<>
<div className="dialog-header-container">
<p className="title-text flex flex-row items-center">
<Icon.Bot className="mr-1 w-5 h-auto opacity-80" />
{t("ask-ai.title")}
<span className="button-group" style={{ marginLeft: "10px" }}>
<Button color={"primary"} onClick={handleMenuOpen}>
<div className="button-len-max-150">{messageGroup.name}</div>
</Button>
<Button color={"success"} onClick={handleOpenDialog}>
<PlusIcon size={"13px"} />
</Button>
<Button color={"danger"} onClick={handleRemoveDialog}>
<Trash2Icon size={"13px"} />
</Button>
</span>
</p>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
<MenuItem onClick={() => handleOptionSelect(defaultMessageGroup)}>{defaultMessageGroup.name}</MenuItem>
{messageGroupList.map((messageGroup, index) => (
<MenuItem key={index} onClick={() => handleOptionSelect(messageGroup)}>
{messageGroup.name}
</MenuItem>
))}
</Menu>
<Modal open={isAddMessageGroupDlgOpen} onClose={handleCloseDialog}>
<ModalDialog aria-labelledby="basic-modal-dialog-title" sx={{ maxWidth: 500 }}>
<ModalClose />
<Typography id="basic-modal-dialog-title" component="h2">
{t("ask-ai.create-message-group-title")}
</Typography>
<Stack spacing={2}>
<FormControl>
<FormLabel>{t("ask-ai.label-message-group-name-title")}</FormLabel>
<Input
value={groupName}
onChange={(e) => setGroupName(e.target.value)}
placeholder={t("ask-ai.label-message-group-name-title")}
/>
</FormControl>
<Typography>
<Button onClick={handleCancel} style={{ marginRight: "10px" }}>
{t("common.cancel")}
</Button>
<Button onClick={handleAddMessageGroupDlgConfirm}>{t("common.confirm")}</Button>
</Typography>
</Stack>
</ModalDialog>
</Modal>
<button className="btn close-btn" onClick={() => hide()}>
<Icon.X />
</button>
</div>
<div className="dialog-content-container !w-112 max-w-full">
{messageList.map((message, index) => (
<div key={index} className="w-full flex flex-col justify-start items-start space-y-2">
{message.role === "user" ? (
<div className="w-full flex flex-row justify-end items-start pl-6">
<span className="word-break shadow rounded-lg rounded-tr-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700">
{message.content}
</span>
</div>
) : (
<div className="w-full flex flex-row justify-start items-start pr-8 space-x-2">
<Icon.Bot className="mt-2 shrink-0 mr-1 w-6 h-auto opacity-80" />
<div className="memo-content-wrapper !w-auto flex flex-col justify-start items-start shadow rounded-lg rounded-tl-none px-3 py-2 bg-gray-100 dark:bg-zinc-700">
<div className="memo-content-text">{marked(message.content)}</div>
<Stack spacing={2} style={{ width: "100%" }}>
{messageList.map((message, index) => (
<div key={index} className="w-full flex flex-col justify-start items-start space-y-2">
{message.role === "user" ? (
<div className="w-full flex flex-row justify-end items-start pl-6">
<span className="word-break shadow rounded-lg rounded-tr-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700">
{message.content}
</span>
</div>
</div>
)}
</div>
))}
) : (
<div className="w-full flex flex-row justify-start items-start pr-8 space-x-2">
<Icon.Bot className="mt-2 shrink-0 mr-1 w-6 h-auto opacity-80" />
<div className="memo-content-wrapper !w-auto flex flex-col justify-start items-start shadow rounded-lg rounded-tl-none px-3 py-2 bg-gray-100 dark:bg-zinc-700">
<div className="memo-content-text">{marked(message.content)}</div>
</div>
</div>
)}
</div>
))}
</Stack>
{fetchingState.isLoading && (
<p className="w-full py-2 mt-4 flex flex-row justify-center items-center">
<Icon.Loader className="w-5 h-auto animate-spin" />

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

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

@ -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": "你正在注册为管理员用户账号。",

@ -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<MessageGroupState>()(
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",
}
)
);

@ -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<MessageState>()(
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<MessageState>()(
persist(
(set, get) => ({
messageList: [],
getState: () => get(),
addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })),
}),
{
name: options.messageStorageId,
}
)
);
};

Loading…
Cancel
Save