import { Bubble, Conversations, Prompts, Sender, Suggestion, XProvider, Welcome, Attachments, AttachmentsProps, } from "@ant-design/x"; import { Conversation } from "@ant-design/x/lib/conversations"; import { useChat } from "@/hooks/useChat"; import { Card, Divider, Flex, message, Button, Space, Spin, Typography, Modal, Input, } from "antd"; import { useEffect, useRef, useState } from "react"; import { BulbOutlined, SmileOutlined, UserOutlined, EditOutlined, DeleteOutlined, PlusOutlined, CloudUploadOutlined, LinkOutlined, CopyOutlined, RedoOutlined, ReadOutlined, } from "@ant-design/icons"; import type { GetProp, GetRef } from "antd"; import type { ConversationsProps } from "@ant-design/x"; import type { AgentItem } from "./data"; import MarkdownViewer from "@/components/ai/MarkdownViewer"; import { ChangeSessionName, DeleteSession } from "@/api/ai"; type AssistantProps = { agent?: AgentItem; }; // bubbles角色配置 const roles: GetProp = { assistant: { placement: "start", avatar: { icon: , style: { background: "#fde3cf" }, }, // typing: { step: 5, interval: 20 }, loadingRender: () => ( 思考中... ), messageRender: (content) => { return typeof content === "string" ? ( ) : ( content ); }, header: "易码工坊AI助手", }, user: { placement: "end", avatar: { icon: , style: { background: "#87d068" } }, }, }; export default (props: AssistantProps) => { const [senderVal, setSenderVal] = useState(""); const { messages, setMessages, activeConversation, changeConversation, conversationList, onRequest, cancel, loading, loadingSession, addConversation, setConversationList, } = useChat({ app_name: props.agent?.key || "", onSuccess: (msg) => { setMessages((messages) => { const arr = [...messages]; const query = arr[messages.length - 2].content as string; arr[messages.length - 1].status = "done"; arr[messages.length - 1].footer = ( ); return arr; }); }, onUpdate: (msg) => { setMessages((messages) => { const arr = [...messages]; arr[messages.length - 1].content += msg.answer; arr[messages.length - 1].id = msg.message_id; arr[messages.length - 1].loading = false; arr[messages.length - 1].status = "error"; return arr; }); }, onError: (error) => { message.error(error.message); setMessages((messages) => { const arr = [...messages]; arr[messages.length - 1].content = ( {error.message} ); arr[messages.length - 1].status = "error"; arr[messages.length - 1].loading = false; return arr; }); }, }); const handleChangeConversationName = (conversation: Conversation) => { let new_name: string; Modal.info({ title: "修改对话名称", okText: "提交", closable: true, content: (
{ new_name = e.target.value; }} />
), onOk: () => { return ChangeSessionName({ app_name: props.agent?.key || "", 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: props.agent?.key || "", session_id: conversation.key, }).then(() => { message.success("删除成功"); setConversationList((list) => list?.filter((item) => item.key !== conversation.key) ); addConversation(); }); }, }); }; const menuConfig: ConversationsProps["menu"] = (conversation) => { if (conversation.key === "1") return undefined; return { items: [ { label: "修改对话名称", key: "edit", icon: , }, { label: "删除对话", key: "del", icon: , danger: true, }, ], onClick: (menuInfo) => { // 修改对话名称 if (menuInfo.key === "edit") { handleChangeConversationName(conversation); } // 删除对话 if (menuInfo.key === "del") { handleDeleteConversation(conversation); } }, }; }; const [openAttachment, setOpenAttachment] = useState(false); const attachmentsRef = useRef>(null); const senderRef = useRef>(null); const [attachmentItems, setAttachmentItems] = useState< GetProp >([]); const contentRef = useRef(null); const [contentHeight, setContentHeight] = useState(0); const setHeight = () => { setContentHeight(contentRef.current?.clientHeight || 0); }; useEffect(() => { setHeight(); window.addEventListener("resize", setHeight); return () => { window.removeEventListener("resize", setHeight); }; }, []); // 附件组件 const senderHeader = ( false} items={attachmentItems} onChange={({ fileList }) => setAttachmentItems(fileList)} placeholder={(type) => type === "drop" ? { title: "拖拽文件到这里", } : { icon: , title: "文件列表", description: "点击或者拖拽文件到这里上传", } } getDropContainer={() => senderRef.current?.nativeElement} /> ); // 底部组件 const BubbleFooter = (props: { content: string; query: string }) => { const handleCopy = () => { navigator.clipboard.writeText(props.content); message.success("复制成功"); }; const handleRedo = () => { submitMessage(props.query); }; return ( ); }; // 提交消息 const submitMessage = (msg: string) => { setSenderVal(""); setMessages((arr) => { const index = arr.length; return [ ...arr, { id: index + "", content: msg, status: "done", role: "user" }, { id: index + 1 + "", content: "", status: "loading", role: "assistant", loading: true, }, ]; }); onRequest(msg); }; // 点击提示词 const handlePromptItem = (item: any) => { const msg = item.data.description || item.data.label; const index = messages.length; setMessages([ ...messages, { id: index + "", content: msg, status: "done", role: "user" }, { id: index + 1 + "", content: "", status: "loading", role: "assistant", loading: true, }, ]); onRequest(msg); }; // 停止对话 const handleStop = () => { cancel(); setMessages((messages) => { const arr = [...messages]; arr[messages.length - 1].status = "stop"; arr[messages.length - 1].loading = false; arr[messages.length - 1].footer = (
(已停止思考)
); return arr; }); }; return ( <> {props.agent?.name} } >
{!messages.length ? ( <>
} title={`你好!我是易码工坊${props.agent?.name || "AI"}助手`} description={props.agent?.description} />
) : ( )}
, label: "写需求", }, { key: "2", icon: , label: "生成代码", }, { key: "3", icon: , label: "问题解答", }, ]} onItemClick={handlePromptItem} /> {({ onTrigger, onKeyDown }) => { return ( } onClick={() => { setOpenAttachment(!openAttachment); }} /> } value={senderVal} onPasteFile={(file) => { attachmentsRef.current?.upload(file); setOpenAttachment(true); }} onChange={(nextVal) => { if (nextVal === "/") { onTrigger(); } else if (!nextVal) { onTrigger(false); } setSenderVal(nextVal); }} onKeyDown={onKeyDown} placeholder="输入/获取快捷提示" onSubmit={submitMessage} onCancel={handleStop} /> ); }}
); };