mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
chore: update
This commit is contained in:
parent
421b2827d6
commit
cb44488fca
@ -143,7 +143,7 @@ export const useSetChatBoxContext = () => {
|
|||||||
...options,
|
...options,
|
||||||
onConversationCreate: (sessionId: string) => {
|
onConversationCreate: (sessionId: string) => {
|
||||||
setCurrentConversation(sessionId);
|
setCurrentConversation(sessionId);
|
||||||
conversationsService.refresh();
|
conversationsService.run();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const hasInfoFormValues = Object.values(infoForm?.values || []).filter(Boolean).length;
|
const hasInfoFormValues = Object.values(infoForm?.values || []).filter(Boolean).length;
|
||||||
|
@ -8,13 +8,17 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useAPIClient, useRequest } from '@nocobase/client';
|
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 { Conversation } from '../types';
|
||||||
|
import { useLoadMoreObserver } from './useLoadMoreObserver';
|
||||||
|
import { ConversationsProps } from '@ant-design/x';
|
||||||
|
|
||||||
type ChatConversationContextValue = {
|
type ChatConversationContextValue = {
|
||||||
currentConversation?: string;
|
currentConversation?: string;
|
||||||
setCurrentConversation: (sessionId?: string) => void;
|
setCurrentConversation: (sessionId?: string) => void;
|
||||||
conversationsService: any;
|
conversationsService: any;
|
||||||
|
lastConversationRef: (node: HTMLDivElement | null) => void;
|
||||||
|
conversations: ConversationsProps['items'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChatConversationsContext = createContext<ChatConversationContextValue | null>(null);
|
export const ChatConversationsContext = createContext<ChatConversationContextValue | null>(null);
|
||||||
@ -24,25 +28,57 @@ export const useChatConversations = () => useContext(ChatConversationsContext);
|
|||||||
export const ChatConversationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const ChatConversationsProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const [currentConversation, setCurrentConversation] = useState<string>();
|
const [currentConversation, setCurrentConversation] = useState<string>();
|
||||||
|
const [conversations, setConversations] = useState<ConversationsProps['items']>([]);
|
||||||
|
|
||||||
const conversationsService = useRequest<Conversation[]>(
|
const conversationsService = useRequest<Conversation[]>(
|
||||||
() =>
|
(page = 1) =>
|
||||||
api
|
api
|
||||||
.resource('aiConversations')
|
.resource('aiConversations')
|
||||||
.list({
|
.list({
|
||||||
sort: ['-updatedAt'],
|
sort: ['-updatedAt'],
|
||||||
appends: ['aiEmployee'],
|
appends: ['aiEmployee'],
|
||||||
|
page,
|
||||||
|
pageSize: 15,
|
||||||
})
|
})
|
||||||
.then((res) => res?.data?.data),
|
.then((res) => res?.data),
|
||||||
{
|
{
|
||||||
manual: true,
|
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<any>();
|
||||||
|
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 = {
|
const value = {
|
||||||
currentConversation,
|
currentConversation,
|
||||||
setCurrentConversation,
|
setCurrentConversation,
|
||||||
conversationsService,
|
conversationsService,
|
||||||
|
lastConversationRef,
|
||||||
|
conversations,
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ChatConversationsContext.Provider value={value}>{children}</ChatConversationsContext.Provider>;
|
return <ChatConversationsContext.Provider value={value}>{children}</ChatConversationsContext.Provider>;
|
||||||
|
@ -28,7 +28,7 @@ interface ChatMessagesContextValue {
|
|||||||
},
|
},
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
messagesService: any;
|
messagesService: any;
|
||||||
lastMessageRef: React.RefObject<any>;
|
lastMessageRef: (node: HTMLElement | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatMessagesContext = createContext<ChatMessagesContextValue | null>(null);
|
export const ChatMessagesContext = createContext<ChatMessagesContextValue | null>(null);
|
||||||
@ -189,18 +189,18 @@ export const ChatMessagesProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
hasMore?: boolean;
|
hasMore?: boolean;
|
||||||
};
|
};
|
||||||
}>(
|
}>(
|
||||||
(cursor?: string) =>
|
(sessionId, cursor?: string) =>
|
||||||
api
|
api
|
||||||
.resource('aiConversations')
|
.resource('aiConversations')
|
||||||
.getMessages({
|
.getMessages({
|
||||||
sessionId: currentConversation,
|
sessionId,
|
||||||
cursor,
|
cursor,
|
||||||
})
|
})
|
||||||
.then((res) => res?.data),
|
.then((res) => res?.data),
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: true,
|
||||||
onSuccess: (data, params) => {
|
onSuccess: (data, params) => {
|
||||||
const cursor = params[0];
|
const cursor = params[1];
|
||||||
if (!data?.data?.length) {
|
if (!data?.data?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -219,15 +219,9 @@ export const ChatMessagesProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||||||
if (messagesService.loading || !messagesService.data?.meta?.hasMore) {
|
if (messagesService.loading || !messagesService.data?.meta?.hasMore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await messagesService.runAsync(messagesService.data?.meta?.cursor);
|
await messagesService.runAsync(currentConversation, messagesService.data?.meta?.cursor);
|
||||||
}, []);
|
|
||||||
const { ref: lastMessageRef } = useLoadMoreObserver({ loadMore: loadMoreMessages });
|
|
||||||
useEffect(() => {
|
|
||||||
if (!currentConversation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
messagesServiceRef.current.run();
|
|
||||||
}, [currentConversation]);
|
}, [currentConversation]);
|
||||||
|
const { ref: lastMessageRef } = useLoadMoreObserver({ loadMore: loadMoreMessages });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatMessagesContext.Provider
|
<ChatMessagesContext.Provider
|
||||||
|
@ -7,15 +7,15 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Layout, Input, Empty, Spin, App } from 'antd';
|
import { Layout, Input, Empty, Spin, App } from 'antd';
|
||||||
import { Conversations as AntConversations } from '@ant-design/x';
|
import { Conversations as AntConversations } from '@ant-design/x';
|
||||||
import type { ConversationsProps } from '@ant-design/x';
|
|
||||||
import { useAPIClient, useToken } from '@nocobase/client';
|
import { useAPIClient, useToken } from '@nocobase/client';
|
||||||
import { useChatBoxContext } from './ChatBoxContext';
|
import { useChatBoxContext } from './ChatBoxContext';
|
||||||
import { useT } from '../../locale';
|
import { useT } from '../../locale';
|
||||||
import { DeleteOutlined } from '@ant-design/icons';
|
import { DeleteOutlined } from '@ant-design/icons';
|
||||||
import { useChatConversations } from './ChatConversationsProvider';
|
import { useChatConversations } from './ChatConversationsProvider';
|
||||||
|
import { useChatMessages } from './ChatMessagesProvider';
|
||||||
const { Header, Content } = Layout;
|
const { Header, Content } = Layout;
|
||||||
|
|
||||||
export const Conversations: React.FC = () => {
|
export const Conversations: React.FC = () => {
|
||||||
@ -23,17 +23,35 @@ export const Conversations: React.FC = () => {
|
|||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { modal, message } = App.useApp();
|
const { modal, message } = App.useApp();
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const { currentConversation, setCurrentConversation, conversationsService } = useChatConversations();
|
const { currentConversation, setCurrentConversation, conversationsService, conversations, lastConversationRef } =
|
||||||
|
useChatConversations();
|
||||||
|
const { messagesService } = useChatMessages();
|
||||||
const startNewConversation = useChatBoxContext('startNewConversation');
|
const startNewConversation = useChatBoxContext('startNewConversation');
|
||||||
const setCurrentEmployee = useChatBoxContext('setCurrentEmployee');
|
const setCurrentEmployee = useChatBoxContext('setCurrentEmployee');
|
||||||
const setSenderValue = useChatBoxContext('setSenderValue');
|
const setSenderValue = useChatBoxContext('setSenderValue');
|
||||||
const setSenderPlaceholder = useChatBoxContext('setSenderPlaceholder');
|
const setSenderPlaceholder = useChatBoxContext('setSenderPlaceholder');
|
||||||
const { loading: ConversationsLoading, data: conversationsRes } = conversationsService;
|
const { loading: conversationsLoading, data: conversationsRes } = conversationsService;
|
||||||
const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({
|
|
||||||
key: conversation.sessionId,
|
const items = useMemo(() => {
|
||||||
label: conversation.title,
|
const result = conversations.map((item, index) => ({
|
||||||
timestamp: new Date(conversation.updatedAt).getTime(),
|
...item,
|
||||||
}));
|
label: index === conversations.length - 1 ? <div ref={lastConversationRef}>{item.label}</div> : item.label,
|
||||||
|
}));
|
||||||
|
if (conversationsLoading) {
|
||||||
|
result.push({
|
||||||
|
key: 'loading',
|
||||||
|
label: (
|
||||||
|
<Spin
|
||||||
|
style={{
|
||||||
|
display: 'block',
|
||||||
|
margin: '8px auto',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, [conversations, conversationsLoading, lastConversationRef]);
|
||||||
|
|
||||||
const deleteConversation = async (sessionId: string) => {
|
const deleteConversation = async (sessionId: string) => {
|
||||||
await api.resource('aiConversations').destroy({
|
await api.resource('aiConversations').destroy({
|
||||||
@ -49,14 +67,19 @@ export const Conversations: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCurrentConversation(sessionId);
|
setCurrentConversation(sessionId);
|
||||||
const conversation = conversationsRes.find((item) => item.sessionId === sessionId);
|
const conversation = conversationsRes?.data?.find((item) => item.sessionId === sessionId);
|
||||||
setCurrentEmployee(conversation?.aiEmployee);
|
setCurrentEmployee(conversation?.aiEmployee);
|
||||||
setSenderValue('');
|
setSenderValue('');
|
||||||
setSenderPlaceholder(conversation?.aiEmployee?.chatSettings?.senderPlaceholder);
|
setSenderPlaceholder(conversation?.aiEmployee?.chatSettings?.senderPlaceholder);
|
||||||
|
messagesService.run(sessionId);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout
|
||||||
|
style={{
|
||||||
|
height: '100%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Header
|
<Header
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: token.colorBgContainer,
|
backgroundColor: token.colorBgContainer,
|
||||||
@ -67,38 +90,41 @@ export const Conversations: React.FC = () => {
|
|||||||
>
|
>
|
||||||
<Input.Search style={{ verticalAlign: 'middle' }} />
|
<Input.Search style={{ verticalAlign: 'middle' }} />
|
||||||
</Header>
|
</Header>
|
||||||
<Content>
|
<Content
|
||||||
<Spin spinning={ConversationsLoading}>
|
style={{
|
||||||
{conversations && conversations.length ? (
|
height: '100%',
|
||||||
<AntConversations
|
overflow: 'auto',
|
||||||
activeKey={currentConversation}
|
}}
|
||||||
onActiveChange={selectConversation}
|
>
|
||||||
items={conversations}
|
{conversations && conversations.length ? (
|
||||||
menu={(conversation) => ({
|
<AntConversations
|
||||||
items: [
|
activeKey={currentConversation}
|
||||||
{
|
onActiveChange={selectConversation}
|
||||||
label: 'Delete',
|
items={items}
|
||||||
key: 'delete',
|
menu={(conversation) => ({
|
||||||
icon: <DeleteOutlined />,
|
items: [
|
||||||
},
|
{
|
||||||
],
|
label: 'Delete',
|
||||||
onClick: ({ key }) => {
|
key: 'delete',
|
||||||
switch (key) {
|
icon: <DeleteOutlined />,
|
||||||
case 'delete':
|
|
||||||
modal.confirm({
|
|
||||||
title: t('Delete this conversation?'),
|
|
||||||
content: t('Are you sure to delete this conversation?'),
|
|
||||||
onOk: () => deleteConversation(conversation.key),
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})}
|
],
|
||||||
/>
|
onClick: ({ key }) => {
|
||||||
) : (
|
switch (key) {
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
case 'delete':
|
||||||
)}
|
modal.confirm({
|
||||||
</Spin>
|
title: t('Delete this conversation?'),
|
||||||
|
content: t('Are you sure to delete this conversation?'),
|
||||||
|
onOk: () => deleteConversation(conversation.key),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
)}
|
||||||
</Content>
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
@ -47,12 +47,15 @@ export const Messages: React.FC = () => {
|
|||||||
<div>
|
<div>
|
||||||
{messages.map((msg, index) => {
|
{messages.map((msg, index) => {
|
||||||
const role = roles[msg.role];
|
const role = roles[msg.role];
|
||||||
|
if (!role) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return index === 0 ? (
|
return index === 0 ? (
|
||||||
<div ref={lastMessageRef}>
|
<div key={msg.key} ref={lastMessageRef}>
|
||||||
<Bubble {...role} key={msg.key} content={msg.content} />
|
<Bubble {...role} loading={msg.loading} content={msg.content} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Bubble {...role} key={msg.key} content={msg.content} />
|
<Bubble {...role} key={msg.key} loading={msg.loading} content={msg.content} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user