mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
* 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
This commit is contained in:
@ -1,5 +1,18 @@
|
|||||||
import { Button, Textarea } from "@mui/joy";
|
import {
|
||||||
import { useEffect, useState } from "react";
|
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 { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import * as api from "@/helpers/api";
|
import * as api from "@/helpers/api";
|
||||||
@ -9,6 +22,8 @@ import { useMessageStore } from "@/store/zustand/message";
|
|||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
import showSettingDialog from "./SettingDialog";
|
import showSettingDialog from "./SettingDialog";
|
||||||
|
import { defaultMessageGroup, MessageGroup, useMessageGroupStore } from "@/store/zustand/message-group";
|
||||||
|
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||||
|
|
||||||
type Props = DialogProps;
|
type Props = DialogProps;
|
||||||
|
|
||||||
@ -16,7 +31,8 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { destroy, hide } = props;
|
const { destroy, hide } = props;
|
||||||
const fetchingState = useLoading(false);
|
const fetchingState = useLoading(false);
|
||||||
const messageStore = useMessageStore();
|
const [messageGroup, setMessageGroup] = useState<MessageGroup>(defaultMessageGroup);
|
||||||
|
const messageStore = useMessageStore(messageGroup)();
|
||||||
const [isEnabled, setIsEnabled] = useState<boolean>(true);
|
const [isEnabled, setIsEnabled] = useState<boolean>(true);
|
||||||
const [isInIME, setIsInIME] = useState(false);
|
const [isInIME, setIsInIME] = useState(false);
|
||||||
const [question, setQuestion] = useState<string>("");
|
const [question, setQuestion] = useState<string>("");
|
||||||
@ -41,7 +57,7 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
if (event.key === "Enter" && !event.shiftKey && !isInIME) {
|
if (event.key === "Enter" && !event.shiftKey && !isInIME) {
|
||||||
event.preventDefault();
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="dialog-header-container">
|
<div className="dialog-header-container">
|
||||||
<p className="title-text flex flex-row items-center">
|
<p className="title-text flex flex-row items-center">
|
||||||
<Icon.Bot className="mr-1 w-5 h-auto opacity-80" />
|
<Icon.Bot className="mr-1 w-5 h-auto opacity-80" />
|
||||||
{t("ask-ai.title")}
|
{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>
|
</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()}>
|
<button className="btn close-btn" onClick={() => hide()}>
|
||||||
<Icon.X />
|
<Icon.X />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="dialog-content-container !w-112 max-w-full">
|
<div className="dialog-content-container !w-112 max-w-full">
|
||||||
{messageList.map((message, index) => (
|
<Stack spacing={2} style={{ width: "100%" }}>
|
||||||
<div key={index} className="w-full flex flex-col justify-start items-start space-y-2">
|
{messageList.map((message, index) => (
|
||||||
{message.role === "user" ? (
|
<div key={index} className="w-full flex flex-col justify-start items-start space-y-2">
|
||||||
<div className="w-full flex flex-row justify-end items-start pl-6">
|
{message.role === "user" ? (
|
||||||
<span className="word-break shadow rounded-lg rounded-tr-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700">
|
<div className="w-full flex flex-row justify-end items-start pl-6">
|
||||||
{message.content}
|
<span className="word-break shadow rounded-lg rounded-tr-none px-3 py-2 opacity-80 bg-gray-100 dark:bg-zinc-700">
|
||||||
</span>
|
{message.content}
|
||||||
</div>
|
</span>
|
||||||
) : (
|
|
||||||
<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>
|
) : (
|
||||||
)}
|
<div className="w-full flex flex-row justify-start items-start pr-8 space-x-2">
|
||||||
</div>
|
<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 && (
|
{fetchingState.isLoading && (
|
||||||
<p className="w-full py-2 mt-4 flex flex-row justify-center items-center">
|
<p className="w-full py-2 mt-4 flex flex-row justify-center items-center">
|
||||||
<Icon.Loader className="w-5 h-auto animate-spin" />
|
<Icon.Loader className="w-5 h-auto animate-spin" />
|
||||||
|
@ -9,3 +9,30 @@ body {
|
|||||||
#root {
|
#root {
|
||||||
@apply w-full h-full;
|
@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",
|
"title": "Ask AI",
|
||||||
"not-enabled": "You have not set up your OpenAI API key.",
|
"not-enabled": "You have not set up your OpenAI API key.",
|
||||||
"go-to-settings": "Go to settings",
|
"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": {
|
"embed-memo": {
|
||||||
"title": "Embed Memo",
|
"title": "Embed Memo",
|
||||||
@ -403,4 +406,4 @@
|
|||||||
"powered-by": "Powered by",
|
"powered-by": "Powered by",
|
||||||
"other-projects": "Other Projects"
|
"other-projects": "Other Projects"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,11 @@
|
|||||||
"go-to-settings": "前往设置",
|
"go-to-settings": "前往设置",
|
||||||
"not-enabled": "您尚未设置 OpenAI API 密钥。",
|
"not-enabled": "您尚未设置 OpenAI API 密钥。",
|
||||||
"placeholder": "随便问",
|
"placeholder": "随便问",
|
||||||
"title": "问 AI"
|
"title": "问 AI",
|
||||||
|
"default-message-group-title": "默认会话",
|
||||||
|
"create-message-group-title": "新建会话",
|
||||||
|
"label-message-group-name-title": "会话名称"
|
||||||
|
|
||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"host-tip": "你正在注册为管理员用户账号。",
|
"host-tip": "你正在注册为管理员用户账号。",
|
||||||
|
41
web/src/store/zustand/message-group.ts
Normal file
41
web/src/store/zustand/message-group.ts
Normal file
@ -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 { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
import { MessageGroup } from "@/store/zustand/message-group";
|
||||||
|
|
||||||
export interface Message {
|
export interface Message {
|
||||||
role: "user" | "assistant";
|
role: "user" | "assistant";
|
||||||
@ -12,15 +13,17 @@ interface MessageState {
|
|||||||
addMessage: (message: Message) => void;
|
addMessage: (message: Message) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMessageStore = create<MessageState>()(
|
export const useMessageStore = (options: MessageGroup) => {
|
||||||
persist(
|
return create<MessageState>()(
|
||||||
(set, get) => ({
|
persist(
|
||||||
messageList: [],
|
(set, get) => ({
|
||||||
getState: () => get(),
|
messageList: [],
|
||||||
addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })),
|
getState: () => get(),
|
||||||
}),
|
addMessage: (message: Message) => set((state) => ({ messageList: [...state.messageList, message] })),
|
||||||
{
|
}),
|
||||||
name: "message-storage",
|
{
|
||||||
}
|
name: options.messageStorageId,
|
||||||
)
|
}
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Reference in New Issue
Block a user