|
@@ -3,13 +3,37 @@ import {
|
|
|
PlusOutlined,
|
|
|
CloseOutlined,
|
|
|
FieldTimeOutlined,
|
|
|
+ RedoOutlined,
|
|
|
+ EditOutlined,
|
|
|
+ DeleteOutlined,
|
|
|
} from "@ant-design/icons";
|
|
|
-import { Button, Tooltip, Dropdown, Typography, Space, Spin } from "antd";
|
|
|
+import {
|
|
|
+ Button,
|
|
|
+ Tooltip,
|
|
|
+ Typography,
|
|
|
+ Space,
|
|
|
+ Spin,
|
|
|
+ Drawer,
|
|
|
+ Divider,
|
|
|
+ Modal,
|
|
|
+ Input,
|
|
|
+ message,
|
|
|
+} from "antd";
|
|
|
import { GetProp } from "antd";
|
|
|
-import { Bubble, Sender, Suggestion, Prompts } from "@ant-design/x";
|
|
|
+import {
|
|
|
+ Bubble,
|
|
|
+ Sender,
|
|
|
+ Suggestion,
|
|
|
+ Prompts,
|
|
|
+ Conversations,
|
|
|
+} from "@ant-design/x";
|
|
|
import MarkdownViewer from "./MarkdownViewer";
|
|
|
import { useChat } from "@/hooks/useChat";
|
|
|
import RenderGraph from "./RenderGraph";
|
|
|
+import InfiniteScroll from "react-infinite-scroll-component";
|
|
|
+import type { ConversationsProps } from "@ant-design/x";
|
|
|
+import { ChangeSessionName, DeleteSession } from "@/api/ai";
|
|
|
+import { Conversation } from "@ant-design/x/lib/conversations";
|
|
|
|
|
|
export default function AIChat(props: {
|
|
|
onClose?: () => void;
|
|
@@ -21,6 +45,7 @@ export default function AIChat(props: {
|
|
|
const [inputVal, setInputVal] = useState("");
|
|
|
const contentRef = useRef<HTMLDivElement>(null);
|
|
|
const [contentHeight, setContentHeight] = useState(0);
|
|
|
+ const [openHistory, setOpenHistory] = useState(false);
|
|
|
|
|
|
const setHeight = () => {
|
|
|
setContentHeight(contentRef.current?.clientHeight || 0);
|
|
@@ -42,9 +67,15 @@ export default function AIChat(props: {
|
|
|
cancel,
|
|
|
messages,
|
|
|
setMessages,
|
|
|
- addConversation,
|
|
|
conversationList,
|
|
|
changeConversation,
|
|
|
+ loadingSession,
|
|
|
+ addConversation,
|
|
|
+ setConversationList,
|
|
|
+ loadMoreConversation,
|
|
|
+ hasMoreConversation,
|
|
|
+ refreshConversationList,
|
|
|
+ activeConversation,
|
|
|
} = useChat({
|
|
|
app_name: "system_design",
|
|
|
onSuccess: (msg) => {
|
|
@@ -160,6 +191,91 @@ export default function AIChat(props: {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
+ const handleChangeConversationName = (conversation: Conversation) => {
|
|
|
+ let new_name: string;
|
|
|
+ Modal.info({
|
|
|
+ title: "修改对话名称",
|
|
|
+ okText: "提交",
|
|
|
+ closable: true,
|
|
|
+ content: (
|
|
|
+ <div>
|
|
|
+ <Input
|
|
|
+ type="text"
|
|
|
+ defaultValue={conversation.label + ""}
|
|
|
+ onChange={(e) => {
|
|
|
+ new_name = e.target.value;
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ onOk: () => {
|
|
|
+ return ChangeSessionName({
|
|
|
+ app_name: "system_design",
|
|
|
+ session_id: conversation.key,
|
|
|
+ new_name,
|
|
|
+ }).then(() => {
|
|
|
+ message.success("修改成功");
|
|
|
+ setConversationList((list) =>
|
|
|
+ list?.map((item) =>
|
|
|
+ item.key === conversation.key
|
|
|
+ ? { ...item, label: new_name }
|
|
|
+ : item
|
|
|
+ )
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleDeleteConversation = (conversation: Conversation) => {
|
|
|
+ Modal.confirm({
|
|
|
+ title: "删除对话",
|
|
|
+ content: "是否删除对话?",
|
|
|
+ okText: "删除",
|
|
|
+ cancelText: "取消",
|
|
|
+ onOk: () => {
|
|
|
+ return DeleteSession({
|
|
|
+ app_name: "system_design",
|
|
|
+ session_id: conversation.key,
|
|
|
+ }).then(() => {
|
|
|
+ message.success("删除成功");
|
|
|
+ refreshConversationList();
|
|
|
+
|
|
|
+ addConversation();
|
|
|
+ });
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const menuConfig: ConversationsProps["menu"] = (conversation) => {
|
|
|
+ if (conversation.key === "1") return undefined;
|
|
|
+ return {
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ label: "修改对话名称",
|
|
|
+ key: "edit",
|
|
|
+ icon: <EditOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ label: "删除对话",
|
|
|
+ key: "del",
|
|
|
+ icon: <DeleteOutlined />,
|
|
|
+ danger: true,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ onClick: (menuInfo) => {
|
|
|
+ // 修改对话名称
|
|
|
+ if (menuInfo.key === "edit") {
|
|
|
+ handleChangeConversationName(conversation);
|
|
|
+ }
|
|
|
+ // 删除对话
|
|
|
+ if (menuInfo.key === "del") {
|
|
|
+ handleDeleteConversation(conversation);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
// 处理提交
|
|
|
const onSubmit = (val: string) => {
|
|
|
setInputVal("");
|
|
@@ -198,19 +314,6 @@ export default function AIChat(props: {
|
|
|
cancel();
|
|
|
};
|
|
|
|
|
|
- const historyItems = useMemo(() => {
|
|
|
- return (conversationList || []).map((item) => {
|
|
|
- return {
|
|
|
- key: item.id,
|
|
|
- label: item.label,
|
|
|
- onClick: () => {
|
|
|
- console.log("changeConversation", item);
|
|
|
- changeConversation(item.sessionId);
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
- }, [conversationList]);
|
|
|
-
|
|
|
const bubble = useMemo(() => {
|
|
|
return (
|
|
|
<Bubble.List
|
|
@@ -245,21 +348,14 @@ export default function AIChat(props: {
|
|
|
onClick={onNewChat}
|
|
|
></Button>
|
|
|
</Tooltip>
|
|
|
- <Dropdown
|
|
|
- menu={{ items: historyItems }}
|
|
|
- trigger={["click"]}
|
|
|
- placement="bottomLeft"
|
|
|
- className="max-h-200px"
|
|
|
- arrow
|
|
|
- >
|
|
|
- <Tooltip title="历史记录">
|
|
|
- <Button
|
|
|
- type="text"
|
|
|
- size="small"
|
|
|
- icon={<FieldTimeOutlined />}
|
|
|
- ></Button>
|
|
|
- </Tooltip>
|
|
|
- </Dropdown>
|
|
|
+ <Tooltip title="历史记录">
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ icon={<FieldTimeOutlined />}
|
|
|
+ onClick={() => setOpenHistory(true)}
|
|
|
+ ></Button>
|
|
|
+ </Tooltip>
|
|
|
<Button
|
|
|
type="text"
|
|
|
size="small"
|
|
@@ -299,6 +395,48 @@ export default function AIChat(props: {
|
|
|
onCancel={onStop}
|
|
|
/>
|
|
|
</div>
|
|
|
+ <Drawer
|
|
|
+ title="历史记录"
|
|
|
+ open={openHistory}
|
|
|
+ onClose={() => setOpenHistory(false)}
|
|
|
+ placement="right"
|
|
|
+ getContainer={false}
|
|
|
+ width={280}
|
|
|
+ styles={{ body: { padding: '0 12px' } }}
|
|
|
+ >
|
|
|
+ <div id="scrollableDiv" className="w-full h-full overflow-auto">
|
|
|
+ <InfiniteScroll
|
|
|
+ dataLength={conversationList?.length || 0}
|
|
|
+ next={loadMoreConversation}
|
|
|
+ hasMore={hasMoreConversation}
|
|
|
+ loader={
|
|
|
+ <div style={{ textAlign: "center" }}>
|
|
|
+ <Spin indicator={<RedoOutlined spin />} size="small" />
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ endMessage={
|
|
|
+ <Divider plain>
|
|
|
+ <span className="text-12px text-text-secondary">
|
|
|
+ 无更多会话~
|
|
|
+ </span>
|
|
|
+ </Divider>
|
|
|
+ }
|
|
|
+ scrollableTarget="scrollableDiv"
|
|
|
+ style={{ overflow: "hidden" }}
|
|
|
+ >
|
|
|
+ <Spin spinning={loadingSession} className="flex-col">
|
|
|
+ <Conversations
|
|
|
+ groupable
|
|
|
+ style={{ width: 200, padding: 0, margin: 0 }}
|
|
|
+ activeKey={activeConversation}
|
|
|
+ onActiveChange={changeConversation}
|
|
|
+ menu={menuConfig}
|
|
|
+ items={conversationList}
|
|
|
+ />
|
|
|
+ </Spin>
|
|
|
+ </InfiniteScroll>
|
|
|
+ </div>
|
|
|
+ </Drawer>
|
|
|
</div>
|
|
|
);
|
|
|
}
|