diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBoxContext.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBoxContext.tsx index eae34fd0fb..209971b5eb 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBoxContext.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBoxContext.tsx @@ -143,7 +143,7 @@ export const useSetChatBoxContext = () => { ...options, onConversationCreate: (sessionId: string) => { setCurrentConversation(sessionId); - conversationsService.refresh(); + conversationsService.run(); }, }; const hasInfoFormValues = Object.values(infoForm?.values || []).filter(Boolean).length; diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatConversationsProvider.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatConversationsProvider.tsx index 970d045d6e..84cc2bf422 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatConversationsProvider.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatConversationsProvider.tsx @@ -8,13 +8,17 @@ */ import { useAPIClient, useRequest } from '@nocobase/client'; -import React, { createContext, useContext, useState } from 'react'; +import React, { createContext, useCallback, useContext, useRef, useState } from 'react'; import { Conversation } from '../types'; +import { useLoadMoreObserver } from './useLoadMoreObserver'; +import { ConversationsProps } from '@ant-design/x'; type ChatConversationContextValue = { currentConversation?: string; setCurrentConversation: (sessionId?: string) => void; conversationsService: any; + lastConversationRef: (node: HTMLDivElement | null) => void; + conversations: ConversationsProps['items']; }; export const ChatConversationsContext = createContext(null); @@ -24,25 +28,57 @@ export const useChatConversations = () => useContext(ChatConversationsContext); export const ChatConversationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const api = useAPIClient(); const [currentConversation, setCurrentConversation] = useState(); + const [conversations, setConversations] = useState([]); const conversationsService = useRequest( - () => + (page = 1) => api .resource('aiConversations') .list({ sort: ['-updatedAt'], appends: ['aiEmployee'], + page, + pageSize: 15, }) - .then((res) => res?.data?.data), + .then((res) => res?.data), { manual: true, + onSuccess: (data, params) => { + const page = params[0]; + if (!data?.data?.length) { + return; + } + const conversations: ConversationsProps['items'] = data.data.map((conversation) => ({ + key: conversation.sessionId, + label: conversation.title, + timestamp: new Date(conversation.updatedAt).getTime(), + })); + if (!page || page === 1) { + setConversations(conversations); + } else { + setConversations((prev) => [...prev, ...conversations]); + } + }, }, ); + const conversationsServiceRef = useRef(); + conversationsServiceRef.current = conversationsService; + const loadMoreConversations = useCallback(async () => { + const conversationsService = conversationsServiceRef.current; + const meta = conversationsService.data?.meta; + if (conversationsService.loading || (meta && meta.page >= meta.totalPage)) { + return; + } + await conversationsService.runAsync(meta.page + 1); + }, []); + const { ref: lastConversationRef } = useLoadMoreObserver({ loadMore: loadMoreConversations }); const value = { currentConversation, setCurrentConversation, conversationsService, + lastConversationRef, + conversations, }; return {children}; diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatMessagesProvider.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatMessagesProvider.tsx index 97e036b185..ffab3ad903 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatMessagesProvider.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatMessagesProvider.tsx @@ -28,7 +28,7 @@ interface ChatMessagesContextValue { }, ) => Promise; messagesService: any; - lastMessageRef: React.RefObject; + lastMessageRef: (node: HTMLElement | null) => void; } export const ChatMessagesContext = createContext(null); @@ -189,18 +189,18 @@ export const ChatMessagesProvider: React.FC<{ children: React.ReactNode }> = ({ hasMore?: boolean; }; }>( - (cursor?: string) => + (sessionId, cursor?: string) => api .resource('aiConversations') .getMessages({ - sessionId: currentConversation, + sessionId, cursor, }) .then((res) => res?.data), { manual: true, onSuccess: (data, params) => { - const cursor = params[0]; + const cursor = params[1]; if (!data?.data?.length) { return; } @@ -219,15 +219,9 @@ export const ChatMessagesProvider: React.FC<{ children: React.ReactNode }> = ({ if (messagesService.loading || !messagesService.data?.meta?.hasMore) { return; } - await messagesService.runAsync(messagesService.data?.meta?.cursor); - }, []); - const { ref: lastMessageRef } = useLoadMoreObserver({ loadMore: loadMoreMessages }); - useEffect(() => { - if (!currentConversation) { - return; - } - messagesServiceRef.current.run(); + await messagesService.runAsync(currentConversation, messagesService.data?.meta?.cursor); }, [currentConversation]); + const { ref: lastMessageRef } = useLoadMoreObserver({ loadMore: loadMoreMessages }); return ( { @@ -23,17 +23,35 @@ export const Conversations: React.FC = () => { const api = useAPIClient(); const { modal, message } = App.useApp(); const { token } = useToken(); - const { currentConversation, setCurrentConversation, conversationsService } = useChatConversations(); + const { currentConversation, setCurrentConversation, conversationsService, conversations, lastConversationRef } = + useChatConversations(); + const { messagesService } = useChatMessages(); const startNewConversation = useChatBoxContext('startNewConversation'); const setCurrentEmployee = useChatBoxContext('setCurrentEmployee'); const setSenderValue = useChatBoxContext('setSenderValue'); const setSenderPlaceholder = useChatBoxContext('setSenderPlaceholder'); - const { loading: ConversationsLoading, data: conversationsRes } = conversationsService; - const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({ - key: conversation.sessionId, - label: conversation.title, - timestamp: new Date(conversation.updatedAt).getTime(), - })); + const { loading: conversationsLoading, data: conversationsRes } = conversationsService; + + const items = useMemo(() => { + const result = conversations.map((item, index) => ({ + ...item, + label: index === conversations.length - 1 ?
{item.label}
: item.label, + })); + if (conversationsLoading) { + result.push({ + key: 'loading', + label: ( + + ), + }); + } + return result; + }, [conversations, conversationsLoading, lastConversationRef]); const deleteConversation = async (sessionId: string) => { await api.resource('aiConversations').destroy({ @@ -49,14 +67,19 @@ export const Conversations: React.FC = () => { return; } setCurrentConversation(sessionId); - const conversation = conversationsRes.find((item) => item.sessionId === sessionId); + const conversation = conversationsRes?.data?.find((item) => item.sessionId === sessionId); setCurrentEmployee(conversation?.aiEmployee); setSenderValue(''); setSenderPlaceholder(conversation?.aiEmployee?.chatSettings?.senderPlaceholder); + messagesService.run(sessionId); }; return ( - +
{ >
- - - {conversations && conversations.length ? ( - ({ - items: [ - { - label: 'Delete', - key: 'delete', - icon: , - }, - ], - onClick: ({ key }) => { - switch (key) { - case 'delete': - modal.confirm({ - title: t('Delete this conversation?'), - content: t('Are you sure to delete this conversation?'), - onOk: () => deleteConversation(conversation.key), - }); - break; - } + + {conversations && conversations.length ? ( + ({ + items: [ + { + label: 'Delete', + key: 'delete', + icon: , }, - })} - /> - ) : ( - - )} - + ], + onClick: ({ key }) => { + switch (key) { + case 'delete': + modal.confirm({ + title: t('Delete this conversation?'), + content: t('Are you sure to delete this conversation?'), + onOk: () => deleteConversation(conversation.key), + }); + break; + } + }, + })} + /> + ) : ( + + )}
); diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Messages.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Messages.tsx index bdad0fc4fb..9994eb599b 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Messages.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/Messages.tsx @@ -47,12 +47,15 @@ export const Messages: React.FC = () => {
{messages.map((msg, index) => { const role = roles[msg.role]; + if (!role) { + return null; + } return index === 0 ? ( -
- +
+
) : ( - + ); })}