diff --git a/packages/plugins/@nocobase/plugin-ai/package.json b/packages/plugins/@nocobase/plugin-ai/package.json index 20853a6328..fa999e2040 100644 --- a/packages/plugins/@nocobase/plugin-ai/package.json +++ b/packages/plugins/@nocobase/plugin-ai/package.json @@ -13,7 +13,7 @@ "@nocobase/test": "1.x" }, "devDependencies": { - "@ant-design/x": "^1.0.5", + "@ant-design/x": "^1.1.0", "@langchain/core": "^0.3.39", "@langchain/deepseek": "^0.0.1", "@langchain/openai": "^0.4.3", diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/AIEmployeesProvider.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/AIEmployeesProvider.tsx index 2b5ee907eb..e9c60d7758 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/AIEmployeesProvider.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/AIEmployeesProvider.tsx @@ -12,6 +12,7 @@ import { createContext } from 'react'; import { ChatBoxProvider } from './chatbox/ChatBoxProvider'; import { useAPIClient, useRequest } from '@nocobase/client'; import { AIEmployee } from './types'; +import { AISelectionProvider } from './selector/AISelectorProvider'; export const AIEmployeesContext = createContext<{ aiEmployees: AIEmployee[]; @@ -24,9 +25,11 @@ export const AIEmployeesProvider: React.FC<{ const [aiEmployees, setAIEmployees] = React.useState(null); return ( - - {props.children} - + + + {props.children} + + ); }; diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/ProfileCard.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/ProfileCard.tsx new file mode 100644 index 0000000000..f4f8452a65 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/ProfileCard.tsx @@ -0,0 +1,85 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { Avatar, Divider } from 'antd'; +import { useToken } from '@nocobase/client'; +import { AIEmployee } from './types'; +import { avatars } from './avatars'; +import { useT } from '../locale'; + +export const ProfileCard: React.FC<{ + aiEmployee: AIEmployee; + taskDesc?: string; +}> = (props) => { + const { aiEmployee, taskDesc } = props; + const { token } = useToken(); + const t = useT(); + + return ( +
+ {aiEmployee ? ( + <> +
+ +
+ {aiEmployee.nickname} +
+
+ + {t('Bio')} + +

{aiEmployee.bio}

+ {taskDesc && ( + <> + + {t('Task description')} + +

{taskDesc}

+ + )} + + ) : null} +
+ ); +}; diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/AIEmployeeHeader.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/AIEmployeeHeader.tsx new file mode 100644 index 0000000000..2eeedbb8bf --- /dev/null +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/AIEmployeeHeader.tsx @@ -0,0 +1,58 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { List, Popover, Button, Avatar, Divider } from 'antd'; +import { useToken } from '@nocobase/client'; +import { useAIEmployeesContext } from '../AIEmployeesProvider'; +import { useT } from '../../locale'; +import { useChatBoxContext } from './ChatBoxContext'; +import { avatars } from '../avatars'; +import { css } from '@emotion/css'; +import { Sender } from '@ant-design/x'; +import { ProfileCard } from '../ProfileCard'; + +export const AIEmployeeHeader: React.FC = () => { + const t = useT(); + const { token } = useToken(); + const { + service: { loading }, + aiEmployees, + } = useAIEmployeesContext(); + const { switchAIEmployee } = useChatBoxContext(); + return ( + + { + return ( + }> + + + ); + }} + /> + + ); +}; diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBox.tsx b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBox.tsx index ed501efb65..b00f78b117 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBox.tsx +++ b/packages/plugins/@nocobase/plugin-ai/src/client/ai-employees/chatbox/ChatBox.tsx @@ -8,77 +8,21 @@ */ import React, { useContext, useState } from 'react'; -import { Layout, Card, Divider, Button, Avatar, List, Input, Popover, Empty, Spin, App } from 'antd'; -import { Conversations, Sender, Bubble } from '@ant-design/x'; -import type { ConversationsProps } from '@ant-design/x'; -import { CloseOutlined, ExpandOutlined, EditOutlined, LayoutOutlined, DeleteOutlined } from '@ant-design/icons'; -import { useAPIClient, useToken } from '@nocobase/client'; -import { useT } from '../../locale'; +import { Layout, Card, Button } from 'antd'; +import { CloseOutlined, ExpandOutlined, EditOutlined, LayoutOutlined } from '@ant-design/icons'; +import { useToken } from '@nocobase/client'; const { Header, Footer, Sider, Content } = Layout; -import { avatars } from '../avatars'; -import { useAIEmployeesContext } from '../AIEmployeesProvider'; -import { css } from '@emotion/css'; -import { ReactComponent as EmptyIcon } from '../empty-icon.svg'; -import { Attachment } from './Attachment'; import { ChatBoxContext } from './ChatBoxContext'; -import { AIEmployee } from '../types'; +import { Conversations } from './Conversations'; +import { Messages } from './Messages'; +import { Sender } from './Sender'; +import { useAISelectionContext } from '../selector/AISelectorProvider'; export const ChatBox: React.FC = () => { - const { modal, message } = App.useApp(); - const api = useAPIClient(); - const { - send, - setOpen, - filterEmployee, - setFilterEmployee, - conversations: conversationsService, - currentConversation, - setCurrentConversation, - messages, - setMessages, - roles, - attachments, - setAttachments, - responseLoading, - senderRef, - senderValue, - setSenderValue, - clear, - } = useContext(ChatBoxContext); - const { loading: ConversationsLoading, data: conversationsRes } = conversationsService; - const { - aiEmployees, - service: { loading }, - } = useAIEmployeesContext(); - const t = useT(); + const { setOpen, startNewConversation, currentEmployee } = useContext(ChatBoxContext); const { token } = useToken(); const [showConversations, setShowConversations] = useState(false); - const aiEmployeesList = [{ username: 'all' } as AIEmployee, ...(aiEmployees || [])]; - const conversations: ConversationsProps['items'] = (conversationsRes || []).map((conversation) => ({ - key: conversation.sessionId, - label: conversation.title, - timestamp: new Date(conversation.updatedAt).getTime(), - })); - - const deleteConversation = async (sessionId: string) => { - await api.resource('aiConversations').destroy({ - filterByTk: sessionId, - }); - message.success(t('Deleted successfully')); - conversationsService.refresh(); - clear(); - }; - - const getMessages = async (sessionId: string) => { - const res = await api.resource('aiConversations').getMessages({ - sessionId, - }); - const messages = res?.data?.data; - if (!messages) { - return; - } - setMessages(messages.reverse()); - }; + const { selectable } = useAISelectionContext(); return (
{ maxWidth: '760px', height: '90%', maxHeight: '560px', - zIndex: 1000, + zIndex: selectable ? -1 : 1000, }} > - + - - { - const highlight = - aiEmployee.username === filterEmployee - ? `color: ${token.colorPrimary}; - border-color: ${token.colorPrimary};` - : ''; - return aiEmployee.username === 'all' ? ( - - ) : ( - -
- -
- {aiEmployee.nickname} -
-
- - {t('Bio')} - -

{aiEmployee.bio}

-
- } - > - - - ); - }} - /> - { marginRight: '5px', }} > - -
- -
- - - {conversations && conversations.length ? ( - { - if (sessionId === currentConversation) { - return; - } - setCurrentConversation(sessionId); - getMessages(sessionId); - }} - items={conversations} - menu={(conversation) => ({ - 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; - } - }, - })} - /> - ) : ( - - )} - - -
+
{ type="text" onClick={() => setShowConversations(!showConversations)} /> - {filterEmployee !== 'all' ? ( -