123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353 |
- import { useXAgent, XStream } from "@ant-design/x";
- import { useEffect, useRef, useState } from "react";
- import { useSessionStorageState } from "ahooks";
- import { GetSessionList, GetSessionMessageList } from "@/api/ai";
- import { getDateGroupString } from "@/utils";
- import type { ConversationsProps } from "@ant-design/x";
- import type { ReactNode } from "react";
- // 消息格式
- type MessageItem = {
- id: string;
- content: string | ReactNode;
- role: "user" | "assistant" | "system";
- status: "loading" | "done" | "error" | "stop";
- loading?: boolean;
- footer?: ReactNode;
- };
- // 后端返回格式
- type ResponseMessageItem = {
- answer: string;
- conversation_id: string;
- created_at: number;
- event: "message" | "message_end" | "message_error" | "ping";
- message_id: string;
- task_id: string;
- };
- type ChatParams = {
- // 应用名称
- app_name: string;
- // 会话内容
- chat_query: string;
- // 会话名称 第一次
- chat_name?: string;
- // 会话id 后续会话带入
- conversation_id?: string;
- };
- type ChatProps = {
- // 应用名称
- app_name: string;
- // 会话id 后续会话带入
- conversation_id?: string;
- // 成功获取会话内容
- onSuccess?: (data: ResponseMessageItem) => void;
- // 更新流式消息内容
- onUpdate: (data: ResponseMessageItem) => void;
- // 异常
- onError?: (error: Error) => void;
- };
- const defaultConversation = {
- // 会话id
- key: "1",
- label: "新的对话",
- group: '今日'
- };
- export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
- /**
- * 发送消息加载状态
- */
- const [loading, setLoading] = useState(false);
- /**
- * 加载会话记录列表
- */
- const [loadingSession, setLoadingSession] = useState(false);
- /**
- * 加载消息列表
- */
- const [loadingMessages, setLoadingMessages] = useState(false);
- // 用于停止对话
- const abortController = useRef<AbortController | null>(null);
- /**
- * 消息列表
- */
- const [messages, setMessages] = useState<Array<MessageItem>>([]);
- // 会话列表
- const [conversationList, setConversationList] = useState<
- ConversationsProps["items"]
- >([{ ...defaultConversation }]);
- // 活动对话
- const [activeConversation, setActiveConversation] = useState("1");
- // 当前智能体对象
- const [currentAgent, setCurrentAgent] = useSessionStorageState("agent-map");
- // 有更多对话
- const [hasMoreConversation, setHasMoreConversation] = useState(false);
- // 会话分页
- const [pageIndex, setPageIndex] = useState(1);
- const getSession = (page: number) => {
- setLoadingSession(true);
- GetSessionList({
- app_name,
- page_index: page,
- })
- .then((res) => {
- if(page === 1) {
- setConversationList([
- { ...defaultConversation },
- ...(res?.result?.model || []).map((item: any) => ({
- ...item,
- key: item.sessionId,
- label: item.name,
- group: getDateGroupString(item.updateTime)
- })),
- ]);
- } else {
- setConversationList([
- ...(conversationList || []),
- ...(res?.result?.model || []).map((item: any) => ({
- ...item,
- key: item.sessionId,
- label: item.name,
- group: getDateGroupString(item.updateTime)
- })),
- ]);
- }
- setHasMoreConversation(res?.result.totalPages > page);
- })
- .finally(() => {
- setLoadingSession(false);
- });
- }
- // 切换app时获取会话记录
- useEffect(() => {
- setPageIndex(1);
- getSession(1);
- }, [app_name]);
- /**
- * 加载更多会话
- */
- const loadMoreConversation = () => {
- getSession(pageIndex + 1);
- setPageIndex(pageIndex + 1);
- };
- /**
- * 切换会话
- * @param key 会话id
- * @returns
- */
- const changeConversation = async (key: string) => {
- setActiveConversation(key);
- if (key === "1") {
- setMessages([]);
- return;
- }
- cancel();
- setLoadingMessages(true);
- // 获取会话内容
- try {
- const res = await GetSessionMessageList({
- app_name,
- session_id: key,
- page_index: 1,
- });
- const list: MessageItem[] = [];
- (res?.result?.model || []).forEach((item: any) => {
- list.push(
- {
- id: item.id + "_query",
- content: item.query,
- role: "user",
- status: "done",
- },
- {
- id: item.id + "_query",
- content: item.answer,
- role: "assistant",
- status: "done",
- }
- );
- });
- setMessages(list);
- } finally {
- setLoadingMessages(false);
- }
- };
- /**
- * 封装智能体
- */
- const [agent] = useXAgent<ResponseMessageItem>({
- request: async (message, { onError, onSuccess, onUpdate }) => {
- abortController.current = new AbortController();
- const signal = abortController.current.signal;
- try {
- setLoading(true);
- const response = await fetch(
- "https://design.shalu.com/api/ai/chat-message",
- {
- method: "POST",
- body: JSON.stringify(message),
- headers: {
- Authorization: localStorage.getItem("token_a") || "",
- "Content-Type": "application/json",
- },
- signal,
- }
- );
- // 判断当前是否流式返回
- if(response.headers.get('content-type')?.includes('text/event-stream')) {
- if (response.body) {
- for await (const chunk of XStream({
- readableStream: response.body,
- })) {
- const data = JSON.parse(chunk.data);
- if (data?.event === "message") {
- onUpdate(data);
- } else if (data?.event === "message_end") {
- onSuccess(data);
- } else if (data?.event === "message_error") {
- onError(data);
- } else if (data?.event === "ping") {
- console.log(">>>> stream start <<<<");
- } else {
- console.log(">>>> stream error <<<<");
- onError(Error(data?.message || '请求失败'));
- }
- }
- }
- } else {
- // 接口异常处理
- response.json().then(res => {
- if(res.code === 0 ) {
- onError?.(Error(res?.error || '请求失败'));
- cancel();
- }
- });
- }
- } catch (error) {
- // 判断是不是 abort 错误
- if (signal.aborted) {
- return;
- }
- onError(error as Error);
- } finally {
- setLoading(false);
- }
- },
- });
- /**
- * 发起请求
- * @param chat_query 对话内容
- */
- const onRequest = (chat_query: string) => {
- activeConversation === '1' && setConversationList((list) => {
- return list?.map((item) => {
- return {
- ...item,
- label: item.key === "1" ? chat_query : item.label,
- };
- });
- });
- agent.request(
- {
- app_name,
- chat_query,
- chat_name: activeConversation === "1" ? chat_query : undefined,
- conversation_id:
- activeConversation === "1" ? undefined : activeConversation,
- },
- {
- onSuccess: (data) => {
- onSuccess?.(data);
- },
- onUpdate: (data) => {
- onUpdate(data);
- // 更新会话相关信息
- if (activeConversation === "1") {
- setConversationList((list) => {
- return list?.map((item) => {
- return {
- ...item,
- // 更新当前会话id
- key: item.key === "1" ? data.conversation_id : item.key,
- };
- });
- });
- setActiveConversation(data.conversation_id);
- }
- },
- onError: (error) => {
- console.log("error", error);
- onError?.(error);
- },
- }
- );
- };
- /**
- * 停止对话
- */
- const cancel = () => {
- abortController.current?.abort();
- setLoading(false);
- };
- /**
- * 新增会话
- */
- const addConversation = () => {
- cancel();
- setMessages([]);
- setActiveConversation("1");
- // 还没产生对话时 直接清除当前对话
- if (!conversationList?.find((item) => item.key === "1")) {
- setConversationList([
- {
- ...defaultConversation,
- },
- ...(conversationList || []),
- ]);
- }
- };
- return {
- agent,
- loading,
- loadingMessages,
- loadingSession,
- cancel,
- messages,
- setMessages,
- conversationList,
- setConversationList,
- activeConversation,
- setActiveConversation,
- onRequest,
- addConversation,
- changeConversation,
- loadMoreConversation,
- hasMoreConversation
- };
- }
|