Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 8 commits into from
May 26, 2023
139 changes: 118 additions & 21 deletions web/src/components/AskAIDialog.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -9,14 +22,19 @@ import { useMessageStore } from "@/store/zustand/message";
import Icon from "./Icon";
import { generateDialog } from "./Dialog";
import showSettingDialog from "./SettingDialog";
import { MessageGroup, useMessageGroupStore } from "@/store/zustand/message-group";
import { PlusIcon } from "lucide-react";
// import { useMessageGroupStore } from "@/store/zustand/message-group";
GodMeowIceSun marked this conversation as resolved.
Show resolved Hide resolved

type Props = DialogProps;

const AskAIDialog: React.FC<Props> = (props: Props) => {
const { t } = useTranslation();
const { destroy, hide } = props;
const fetchingState = useLoading(false);
const messageStore = useMessageStore();
const defaultMessageGroup: MessageGroup = { name: t("ask-ai.default-message-group-title"), messageStorageId: "message-storage" };
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>("");
Expand All @@ -41,7 +59,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();
GodMeowIceSun marked this conversation as resolved.
Show resolved Hide resolved
}
};

Expand Down Expand Up @@ -76,36 +94,115 @@ 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 [isOpen, setIsOpen] = useState<boolean>(false);
const [groupName, setGroupName] = useState<string>("");

const messageGroupStore = useMessageGroupStore();
const messageGroupList = messageGroupStore.groupList;

const handleOpenDialog = () => {
setIsOpen(true);
};

const handleCloseDialog = () => {
setIsOpen(false);
setGroupName("");
};

const handleConfirm = () => {
messageGroupStore.addGroup({ name: groupName, messageStorageId: "message-storage-" + groupName });
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 onClick={handleOpenDialog}>
<PlusIcon 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={isOpen} 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={handleConfirm}>{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}>
{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" />
Expand Down
27 changes: 27 additions & 0 deletions web/src/css/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
GodMeowIceSun marked this conversation as resolved.
Show resolved Hide resolved
boojack marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 5 additions & 2 deletions web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,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",
Expand All @@ -398,4 +401,4 @@
"powered-by": "Powered by",
"other-projects": "Other Projects"
}
}
}
6 changes: 5 additions & 1 deletion web/src/locales/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "你正在注册为管理员用户账号。",
Expand Down
26 changes: 26 additions & 0 deletions web/src/store/zustand/message-group.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";

export interface MessageGroup {
name: string;
messageStorageId: string;
}

interface MessageGroupState {
groupList: MessageGroup[];
getState: () => MessageGroupState;
addGroup: (group: MessageGroup) => void;
}

export const useMessageGroupStore = create<MessageGroupState>()(
persist(
(set, get) => ({
groupList: [],
getState: () => get(),
addGroup: (group: MessageGroup) => set((state) => ({ groupList: [...state.groupList, group] })),
}),
{
name: "message-group-storage",
}
)
);
27 changes: 15 additions & 12 deletions web/src/store/zustand/message.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
}
)
);
};