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
861f0e5f02
commit
c4f5ff9a29
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { ChatBoxProvider } from './chatbox/ChatBoxProvider';
|
import { ChatBoxProvider } from './chatbox/ChatBoxProvider';
|
||||||
import { useAPIClient, useRequest } from '@nocobase/client';
|
import { useAPIClient, useRequest } from '@nocobase/client';
|
||||||
@ -46,12 +46,18 @@ export const useAIEmployeesContext = () => {
|
|||||||
() =>
|
() =>
|
||||||
api
|
api
|
||||||
.resource('aiEmployees')
|
.resource('aiEmployees')
|
||||||
.list()
|
.listByUser()
|
||||||
.then((res) => res?.data?.data),
|
.then((res) => res?.data?.data),
|
||||||
{
|
{
|
||||||
ready: !aiEmployees,
|
ready: !aiEmployees,
|
||||||
onSuccess: (aiEmployees) => setAIEmployees(aiEmployees),
|
onSuccess: (aiEmployees) => setAIEmployees(aiEmployees),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return { aiEmployees, service };
|
const aiEmployeesMap = useMemo(() => {
|
||||||
|
return (aiEmployees || []).reduce((acc, aiEmployee) => {
|
||||||
|
acc[aiEmployee.username] = aiEmployee;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}, [aiEmployees]);
|
||||||
|
return { aiEmployees, aiEmployeesMap, service };
|
||||||
};
|
};
|
||||||
|
@ -7,9 +7,8 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { List, Popover, Button, Avatar, Divider } from 'antd';
|
import { List, Popover, Button, Avatar } from 'antd';
|
||||||
import { useToken } from '@nocobase/client';
|
|
||||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||||
import { useT } from '../../locale';
|
import { useT } from '../../locale';
|
||||||
import { useChatBoxContext } from './ChatBoxContext';
|
import { useChatBoxContext } from './ChatBoxContext';
|
||||||
@ -17,10 +16,6 @@ import { avatars } from '../avatars';
|
|||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Sender } from '@ant-design/x';
|
import { Sender } from '@ant-design/x';
|
||||||
import { ProfileCard } from '../ProfileCard';
|
import { ProfileCard } from '../ProfileCard';
|
||||||
import { AIEmployee } from '../types';
|
|
||||||
import { uid } from '@formily/shared';
|
|
||||||
import { useChatMessages } from './ChatMessagesProvider';
|
|
||||||
import { useChatConversations } from './ChatConversationsProvider';
|
|
||||||
|
|
||||||
export const AIEmployeeHeader: React.FC = () => {
|
export const AIEmployeeHeader: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
@ -28,36 +23,7 @@ export const AIEmployeeHeader: React.FC = () => {
|
|||||||
aiEmployees,
|
aiEmployees,
|
||||||
} = useAIEmployeesContext();
|
} = useAIEmployeesContext();
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { setMessages, addMessage } = useChatMessages();
|
const switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||||
const { currentConversation } = useChatConversations();
|
|
||||||
const setCurrentEmployee = useChatBoxContext('setCurrentEmployee');
|
|
||||||
const setSenderPlaceholder = useChatBoxContext('setSenderPlaceholder');
|
|
||||||
const setSenderValue = useChatBoxContext('setSenderValue');
|
|
||||||
const senderRef = useChatBoxContext('senderRef');
|
|
||||||
const infoForm = useChatBoxContext('infoForm');
|
|
||||||
const switchAIEmployee = useCallback(
|
|
||||||
(aiEmployee: AIEmployee) => {
|
|
||||||
const greetingMsg = {
|
|
||||||
key: uid(),
|
|
||||||
role: aiEmployee.username,
|
|
||||||
content: {
|
|
||||||
type: 'greeting' as const,
|
|
||||||
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
setCurrentEmployee(aiEmployee);
|
|
||||||
setSenderPlaceholder(aiEmployee.chatSettings?.senderPlaceholder);
|
|
||||||
infoForm.reset();
|
|
||||||
senderRef.current?.focus();
|
|
||||||
if (!currentConversation) {
|
|
||||||
setMessages([greetingMsg]);
|
|
||||||
} else {
|
|
||||||
addMessage(greetingMsg);
|
|
||||||
setSenderValue('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentConversation, infoForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sender.Header closable={false}>
|
<Sender.Header closable={false}>
|
||||||
|
@ -42,6 +42,7 @@ type ChatBoxContextValues = {
|
|||||||
setSenderPlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
setSenderPlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
||||||
startNewConversation: () => void;
|
startNewConversation: () => void;
|
||||||
triggerShortcut: (options: ShortcutOptions) => void;
|
triggerShortcut: (options: ShortcutOptions) => void;
|
||||||
|
switchAIEmployee: (aiEmployee: AIEmployee) => void;
|
||||||
send(opts: SendOptions): void;
|
send(opts: SendOptions): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ export const useSetChatBoxContext = () => {
|
|||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [showConversations, setShowConversations] = useState(false);
|
const [showConversations, setShowConversations] = useState(false);
|
||||||
const [currentEmployee, setCurrentEmployee] = useState<AIEmployee>(null);
|
const [currentEmployee, setCurrentEmployee] = useState<AIEmployee>(null);
|
||||||
const { setMessages, sendMessages } = useChatMessages();
|
const { setMessages, sendMessages, addMessage } = useChatMessages();
|
||||||
const { currentConversation, setCurrentConversation, conversationsService } = useChatConversations();
|
const { currentConversation, setCurrentConversation, conversationsService } = useChatConversations();
|
||||||
const [senderValue, setSenderValue] = useState<string>('');
|
const [senderValue, setSenderValue] = useState<string>('');
|
||||||
const [senderPlaceholder, setSenderPlaceholder] = useState<string>('');
|
const [senderPlaceholder, setSenderPlaceholder] = useState<string>('');
|
||||||
@ -156,6 +157,33 @@ export const useSetChatBoxContext = () => {
|
|||||||
senderRef.current?.focus();
|
senderRef.current?.focus();
|
||||||
}, [currentEmployee]);
|
}, [currentEmployee]);
|
||||||
|
|
||||||
|
const switchAIEmployee = useCallback(
|
||||||
|
(aiEmployee: AIEmployee) => {
|
||||||
|
setCurrentEmployee(aiEmployee);
|
||||||
|
setSenderValue('');
|
||||||
|
if (aiEmployee) {
|
||||||
|
const greetingMsg = {
|
||||||
|
key: uid(),
|
||||||
|
role: aiEmployee.username,
|
||||||
|
content: {
|
||||||
|
type: 'greeting' as const,
|
||||||
|
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
setSenderPlaceholder(aiEmployee.chatSettings?.senderPlaceholder);
|
||||||
|
senderRef.current?.focus();
|
||||||
|
if (!currentConversation) {
|
||||||
|
setMessages([greetingMsg]);
|
||||||
|
} else {
|
||||||
|
addMessage(greetingMsg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setMessages([]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentConversation],
|
||||||
|
);
|
||||||
|
|
||||||
const triggerShortcut = useCallback(
|
const triggerShortcut = useCallback(
|
||||||
(options: ShortcutOptions) => {
|
(options: ShortcutOptions) => {
|
||||||
const { aiEmployee, message, autoSend } = options;
|
const { aiEmployee, message, autoSend } = options;
|
||||||
@ -238,6 +266,7 @@ export const useSetChatBoxContext = () => {
|
|||||||
setSenderPlaceholder,
|
setSenderPlaceholder,
|
||||||
startNewConversation,
|
startNewConversation,
|
||||||
triggerShortcut,
|
triggerShortcut,
|
||||||
|
switchAIEmployee,
|
||||||
send,
|
send,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -7,51 +7,20 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { FloatButton, Avatar, Dropdown } from 'antd';
|
import { FloatButton, Avatar, Dropdown } from 'antd';
|
||||||
import icon from '../icon.svg';
|
import icon from '../icon.svg';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useChatBoxContext } from './ChatBoxContext';
|
import { useChatBoxContext } from './ChatBoxContext';
|
||||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||||
import { avatars } from '../avatars';
|
import { avatars } from '../avatars';
|
||||||
import { useT } from '../../locale';
|
|
||||||
import { useChatMessages } from './ChatMessagesProvider';
|
|
||||||
import { useChatConversations } from './ChatConversationsProvider';
|
|
||||||
import { AIEmployee } from '../types';
|
|
||||||
import { uid } from '@formily/shared';
|
|
||||||
|
|
||||||
export const ChatButton: React.FC = () => {
|
export const ChatButton: React.FC = () => {
|
||||||
const t = useT();
|
|
||||||
const { aiEmployees } = useAIEmployeesContext();
|
const { aiEmployees } = useAIEmployeesContext();
|
||||||
|
const open = useChatBoxContext('open');
|
||||||
const setOpen = useChatBoxContext('setOpen');
|
const setOpen = useChatBoxContext('setOpen');
|
||||||
const setCurrentEmployee = useChatBoxContext('setCurrentEmployee');
|
const switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||||
const { setMessages, addMessage } = useChatMessages();
|
|
||||||
const { currentConversation } = useChatConversations();
|
|
||||||
const setSenderPlaceholder = useChatBoxContext('setSenderPlaceholder');
|
|
||||||
const setSenderValue = useChatBoxContext('setSenderValue');
|
|
||||||
const senderRef = useChatBoxContext('senderRef');
|
|
||||||
const switchAIEmployee = useCallback(
|
|
||||||
(aiEmployee: AIEmployee) => {
|
|
||||||
const greetingMsg = {
|
|
||||||
key: uid(),
|
|
||||||
role: aiEmployee.username,
|
|
||||||
content: {
|
|
||||||
type: 'greeting' as const,
|
|
||||||
content: aiEmployee.greeting || t('Default greeting message', { nickname: aiEmployee.nickname }),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
setCurrentEmployee(aiEmployee);
|
|
||||||
setSenderPlaceholder(aiEmployee.chatSettings?.senderPlaceholder);
|
|
||||||
senderRef.current?.focus();
|
|
||||||
if (!currentConversation) {
|
|
||||||
setMessages([greetingMsg]);
|
|
||||||
} else {
|
|
||||||
addMessage(greetingMsg);
|
|
||||||
setSenderValue('');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentConversation],
|
|
||||||
);
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
return aiEmployees?.map((employee) => ({
|
return aiEmployees?.map((employee) => ({
|
||||||
key: employee.username,
|
key: employee.username,
|
||||||
@ -89,7 +58,7 @@ export const ChatButton: React.FC = () => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.ant-float-btn .ant-float-btn-body .ant-float-btn-content .ant-float-btn-icon {
|
.ant-float-btn .ant-float-btn-body .ant-float-btn-content .ant-float-btn-icon {
|
||||||
width: 40px;
|
width: 36px;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
@ -98,15 +67,15 @@ export const ChatButton: React.FC = () => {
|
|||||||
icon={
|
icon={
|
||||||
<Avatar
|
<Avatar
|
||||||
src={icon}
|
src={icon}
|
||||||
size={40}
|
size={36}
|
||||||
style={{
|
style={{
|
||||||
marginBottom: '4px',
|
marginBottom: '4px',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
// onClick={() => {
|
onClick={() => {
|
||||||
// setOpen(false);
|
setOpen(!open);
|
||||||
// }}
|
}}
|
||||||
shape="square"
|
shape="square"
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
@ -16,6 +16,7 @@ import { SenderHeader } from './SenderHeader';
|
|||||||
import { SenderFooter } from './SenderFooter';
|
import { SenderFooter } from './SenderFooter';
|
||||||
import { useChatConversations } from './ChatConversationsProvider';
|
import { useChatConversations } from './ChatConversationsProvider';
|
||||||
import { useChatMessages } from './ChatMessagesProvider';
|
import { useChatMessages } from './ChatMessagesProvider';
|
||||||
|
import { AIEmployeeHeader } from './AIEmployeeHeader';
|
||||||
|
|
||||||
export const Sender: React.FC = () => {
|
export const Sender: React.FC = () => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
@ -60,7 +61,7 @@ export const Sender: React.FC = () => {
|
|||||||
}
|
}
|
||||||
onCancel={cancelRequest}
|
onCancel={cancelRequest}
|
||||||
prefix={<SenderPrefix />}
|
prefix={<SenderPrefix />}
|
||||||
// header={!currentConversation ? <SenderHeader /> : null}
|
header={!currentEmployee ? <AIEmployeeHeader /> : null}
|
||||||
loading={responseLoading}
|
loading={responseLoading}
|
||||||
footer={({ components }) => <SenderFooter components={components} />}
|
footer={({ components }) => <SenderFooter components={components} />}
|
||||||
disabled={!currentEmployee}
|
disabled={!currentEmployee}
|
||||||
|
@ -14,5 +14,14 @@ import { Avatar } from 'antd';
|
|||||||
|
|
||||||
export const SenderPrefix: React.FC = () => {
|
export const SenderPrefix: React.FC = () => {
|
||||||
const currentEmployee = useChatBoxContext('currentEmployee');
|
const currentEmployee = useChatBoxContext('currentEmployee');
|
||||||
return currentEmployee ? <Avatar src={avatars(currentEmployee.avatar)} /> : null;
|
const switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||||
|
return currentEmployee ? (
|
||||||
|
<Avatar
|
||||||
|
src={avatars(currentEmployee.avatar)}
|
||||||
|
style={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => switchAIEmployee(null)}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
};
|
};
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Avatar, Popover, Button } from 'antd';
|
import { Avatar, Popover, Button, Spin } from 'antd';
|
||||||
import { avatars } from '../avatars';
|
import { avatars } from '../avatars';
|
||||||
import {
|
import {
|
||||||
SortableItem,
|
SortableItem,
|
||||||
@ -21,6 +21,7 @@ import { useFieldSchema } from '@formily/react';
|
|||||||
import { useChatBoxContext } from '../chatbox/ChatBoxContext';
|
import { useChatBoxContext } from '../chatbox/ChatBoxContext';
|
||||||
import { AIEmployee } from '../types';
|
import { AIEmployee } from '../types';
|
||||||
import { ProfileCard } from '../ProfileCard';
|
import { ProfileCard } from '../ProfileCard';
|
||||||
|
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||||
|
|
||||||
async function replaceVariables(template, variables, localVariables = {}) {
|
async function replaceVariables(template, variables, localVariables = {}) {
|
||||||
const regex = /\{\{\s*(.*?)\s*\}\}/g;
|
const regex = /\{\{\s*(.*?)\s*\}\}/g;
|
||||||
@ -58,20 +59,24 @@ async function replaceVariables(template, variables, localVariables = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AIEmployeeButton: React.FC<{
|
export const AIEmployeeButton: React.FC<{
|
||||||
aiEmployee: AIEmployee;
|
username: string;
|
||||||
taskDesc?: string;
|
taskDesc?: string;
|
||||||
autoSend?: boolean;
|
autoSend?: boolean;
|
||||||
message: {
|
message: {
|
||||||
type: string;
|
type: string;
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
infoForm: any;
|
}> = withDynamicSchemaProps(({ username, taskDesc, message, autoSend }) => {
|
||||||
}> = withDynamicSchemaProps(({ aiEmployee, taskDesc, message, infoForm: infoFormValues, autoSend }) => {
|
|
||||||
const triggerShortcut = useChatBoxContext('triggerShortcut');
|
const triggerShortcut = useChatBoxContext('triggerShortcut');
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { render } = useSchemaToolbarRender(fieldSchema);
|
const { render } = useSchemaToolbarRender(fieldSchema);
|
||||||
const variables = useVariables();
|
const variables = useVariables();
|
||||||
const localVariables = useLocalVariables();
|
const localVariables = useLocalVariables();
|
||||||
|
const {
|
||||||
|
aiEmployeesMap,
|
||||||
|
service: { loading },
|
||||||
|
} = useAIEmployeesContext();
|
||||||
|
const aiEmployee = aiEmployeesMap[username];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SortableItem
|
<SortableItem
|
||||||
@ -92,10 +97,10 @@ export const AIEmployeeButton: React.FC<{
|
|||||||
aiEmployee,
|
aiEmployee,
|
||||||
message: msg,
|
message: msg,
|
||||||
autoSend,
|
autoSend,
|
||||||
infoFormValues,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Spin spinning={loading}>
|
||||||
<Popover content={<ProfileCard taskDesc={taskDesc} aiEmployee={aiEmployee} />}>
|
<Popover content={<ProfileCard taskDesc={taskDesc} aiEmployee={aiEmployee} />}>
|
||||||
<Button
|
<Button
|
||||||
shape="circle"
|
shape="circle"
|
||||||
@ -104,10 +109,11 @@ export const AIEmployeeButton: React.FC<{
|
|||||||
height: '36px',
|
height: '36px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar src={avatars(aiEmployee.avatar)} size={36} />
|
{aiEmployee && <Avatar src={avatars(aiEmployee.avatar)} size={36} />}
|
||||||
</Button>
|
</Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
{render()}
|
{render()}
|
||||||
|
</Spin>
|
||||||
</SortableItem>
|
</SortableItem>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ const getAIEmployeesInitializer = () => ({
|
|||||||
'x-toolbar': 'ActionSchemaToolbar',
|
'x-toolbar': 'ActionSchemaToolbar',
|
||||||
'x-settings': 'aiEmployees:button',
|
'x-settings': 'aiEmployees:button',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
aiEmployee,
|
username: aiEmployee.username,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import cls from 'classnames';
|
import cls from 'classnames';
|
||||||
import { useToken, useUploadStyles } from '@nocobase/client';
|
import { useToken, useUploadStyles } from '@nocobase/client';
|
||||||
import useUploadStyle from 'antd/es/upload/style';
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useField } from '@formily/react';
|
import { useField } from '@formily/react';
|
||||||
import { Field } from '@formily/core';
|
import { Field } from '@formily/core';
|
||||||
import { avatars } from '../avatars';
|
import { avatars } from '../avatars';
|
||||||
|
import { List, Avatar as AntdAvatar } from 'antd';
|
||||||
|
|
||||||
export const Avatar: React.FC<{
|
export const Avatar: React.FC<{
|
||||||
srcs: [string, string][];
|
srcs: [string, string][];
|
||||||
@ -22,11 +22,9 @@ export const Avatar: React.FC<{
|
|||||||
selectable?: boolean;
|
selectable?: boolean;
|
||||||
highlightItem?: string;
|
highlightItem?: string;
|
||||||
onClick?: (name: string) => void;
|
onClick?: (name: string) => void;
|
||||||
}> = ({ srcs, size, selectable, highlightItem, onClick }) => {
|
}> = ({ srcs, size = 'large', selectable, highlightItem, onClick }) => {
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
const { wrapSSR, hashId, componentCls: prefixCls } = useUploadStyles();
|
const { wrapSSR, hashId, componentCls: prefixCls } = useUploadStyles();
|
||||||
const useUploadStyleVal = (useUploadStyle as any).default ? (useUploadStyle as any).default : useUploadStyle;
|
|
||||||
useUploadStyleVal(prefixCls);
|
|
||||||
|
|
||||||
const list =
|
const list =
|
||||||
srcs?.map(([src, name], index) => (
|
srcs?.map(([src, name], index) => (
|
||||||
@ -61,6 +59,44 @@ export const Avatar: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
)) || [];
|
)) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={srcs}
|
||||||
|
renderItem={([src, name]) => {
|
||||||
|
return (
|
||||||
|
<AntdAvatar
|
||||||
|
size={size === 'small' ? 32 : 80}
|
||||||
|
className={cls(
|
||||||
|
css`
|
||||||
|
margin: 2px;
|
||||||
|
border-radius: ${size === 'small' ? '4px' : '8px'};
|
||||||
|
border: 1px solid ${token.colorBorder};
|
||||||
|
padding: 1px;
|
||||||
|
`,
|
||||||
|
highlightItem === name
|
||||||
|
? css`
|
||||||
|
border-color: ${token.colorPrimary} !important;
|
||||||
|
`
|
||||||
|
: '',
|
||||||
|
selectable
|
||||||
|
? css`
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
border-color: ${token.colorPrimary} !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: '',
|
||||||
|
)}
|
||||||
|
src={src}
|
||||||
|
shape="square"
|
||||||
|
onClick={() => onClick && onClick(name)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
return wrapSSR(
|
return wrapSSR(
|
||||||
<div
|
<div
|
||||||
className={cls(
|
className={cls(
|
||||||
|
@ -8,13 +8,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { createContext, useContext, useMemo } from 'react';
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
import { Card, Row, Col, Avatar, Input, Space, Button, Tabs, App, Spin, Empty, Typography, Tag } from 'antd';
|
import {
|
||||||
|
Card,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Avatar as AntdAvatar,
|
||||||
|
Input,
|
||||||
|
Space,
|
||||||
|
Button,
|
||||||
|
Tabs,
|
||||||
|
App,
|
||||||
|
Spin,
|
||||||
|
Empty,
|
||||||
|
Typography,
|
||||||
|
Tag,
|
||||||
|
} from 'antd';
|
||||||
import {
|
import {
|
||||||
CollectionRecordProvider,
|
CollectionRecordProvider,
|
||||||
|
ExtendCollectionsProvider,
|
||||||
SchemaComponent,
|
SchemaComponent,
|
||||||
useAPIClient,
|
useAPIClient,
|
||||||
useActionContext,
|
useActionContext,
|
||||||
|
useCollection,
|
||||||
useCollectionRecordData,
|
useCollectionRecordData,
|
||||||
|
useDataBlockRequest,
|
||||||
|
useDataBlockResource,
|
||||||
useRequest,
|
useRequest,
|
||||||
useToken,
|
useToken,
|
||||||
} from '@nocobase/client';
|
} from '@nocobase/client';
|
||||||
@ -22,32 +40,37 @@ import { useT } from '../../locale';
|
|||||||
const { Meta } = Card;
|
const { Meta } = Card;
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { useForm } from '@formily/react';
|
import { useForm, useField } from '@formily/react';
|
||||||
import { createForm } from '@formily/core';
|
import { createForm, Field } from '@formily/core';
|
||||||
import { uid } from '@formily/shared';
|
import { uid } from '@formily/shared';
|
||||||
import { avatars } from '../avatars';
|
import { avatars } from '../avatars';
|
||||||
import { ModelSettings } from './ModelSettings';
|
import { ModelSettings } from './ModelSettings';
|
||||||
import { ProfileSettings } from './ProfileSettings';
|
import { ProfileSettings } from './ProfileSettings';
|
||||||
import { ChatSettings } from './ChatSettings';
|
import { ChatSettings } from './ChatSettings';
|
||||||
import { AIEmployee } from '../types';
|
import { AIEmployee } from '../types';
|
||||||
|
import aiEmployees from '../../../collections/ai-employees';
|
||||||
|
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||||
|
|
||||||
const EmployeeContext = createContext(null);
|
const EmployeeContext = createContext(null);
|
||||||
|
|
||||||
const AIEmployeeForm: React.FC = () => {
|
const AIEmployeeForm: React.FC<{
|
||||||
|
edit?: boolean;
|
||||||
|
}> = ({ edit }) => {
|
||||||
return (
|
return (
|
||||||
<Tabs
|
<Tabs
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: 'profile',
|
key: 'profile',
|
||||||
label: 'Profile',
|
label: 'Profile',
|
||||||
children: <ProfileSettings />,
|
children: <ProfileSettings edit={edit} />,
|
||||||
},
|
forceRender: true,
|
||||||
{
|
|
||||||
key: 'chat',
|
|
||||||
label: 'Chat settings',
|
|
||||||
children: <ChatSettings />,
|
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
|
// key: 'chat',
|
||||||
|
// label: 'Chat settings',
|
||||||
|
// children: <ChatSettings />,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
// key: 'skills',
|
// key: 'skills',
|
||||||
// label: 'Skills',
|
// label: 'Skills',
|
||||||
// // children: (
|
// // children: (
|
||||||
@ -130,6 +153,7 @@ const AIEmployeeForm: React.FC = () => {
|
|||||||
key: 'modelSettings',
|
key: 'modelSettings',
|
||||||
label: 'Model Settings',
|
label: 'Model Settings',
|
||||||
children: <ModelSettings />,
|
children: <ModelSettings />,
|
||||||
|
forceRender: true,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@ -182,7 +206,7 @@ const useCreateActionProps = () => {
|
|||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
const api = useAPIClient();
|
const api = useAPIClient();
|
||||||
const { refresh } = useContext(EmployeeContext);
|
const { refresh } = useDataBlockRequest();
|
||||||
const t = useT();
|
const t = useT();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -205,18 +229,20 @@ const useEditActionProps = () => {
|
|||||||
const { setVisible } = useActionContext();
|
const { setVisible } = useActionContext();
|
||||||
const { message } = App.useApp();
|
const { message } = App.useApp();
|
||||||
const form = useForm();
|
const form = useForm();
|
||||||
|
const resource = useDataBlockResource();
|
||||||
|
const { refresh } = useDataBlockRequest();
|
||||||
|
const collection = useCollection();
|
||||||
|
const filterTk = collection.getFilterTargetKey();
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const { refresh } = useContext(EmployeeContext);
|
|
||||||
const api = useAPIClient();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
async onClick() {
|
async onClick() {
|
||||||
await form.submit();
|
await form.submit();
|
||||||
const values = form.values;
|
const values = form.values;
|
||||||
await api.resource('aiEmployees').update({
|
await resource.update({
|
||||||
values,
|
values,
|
||||||
filterByTk: values.username,
|
filterByTk: values[filterTk],
|
||||||
});
|
});
|
||||||
refresh();
|
refresh();
|
||||||
message.success(t('Saved successfully'));
|
message.success(t('Saved successfully'));
|
||||||
@ -226,59 +252,269 @@ const useEditActionProps = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Employees: React.FC = () => {
|
// export const Employees: React.FC = () => {
|
||||||
const t = useT();
|
// const t = useT();
|
||||||
const { message, modal } = App.useApp();
|
// const { message, modal } = App.useApp();
|
||||||
const { token } = useToken();
|
// const { token } = useToken();
|
||||||
const api = useAPIClient();
|
// const api = useAPIClient();
|
||||||
const { data, loading, refresh } = useRequest<AIEmployee[]>(() =>
|
// const { data, loading, refresh } = useRequest<AIEmployee[]>(() =>
|
||||||
api
|
// api
|
||||||
.resource('aiEmployees')
|
// .resource('aiEmployees')
|
||||||
.list()
|
// .list()
|
||||||
.then((res) => res?.data?.data),
|
// .then((res) => res?.data?.data),
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
|
// const del = (username: string) => {
|
||||||
|
// modal.confirm({
|
||||||
|
// title: t('Delete AI employee'),
|
||||||
|
// content: t('Are you sure to delete this employee?'),
|
||||||
|
// onOk: async () => {
|
||||||
|
// await api.resource('aiEmployees').destroy({
|
||||||
|
// filterByTk: username,
|
||||||
|
// });
|
||||||
|
// message.success(t('Deleted successfully'));
|
||||||
|
// refresh();
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// return (
|
||||||
|
// <EmployeeContext.Provider value={{ refresh }}>
|
||||||
|
// <div
|
||||||
|
// style={{ marginBottom: token.marginLG }}
|
||||||
|
// className={css`
|
||||||
|
// justify-content: space-between;
|
||||||
|
// display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
// `}
|
||||||
|
// >
|
||||||
|
// <div>
|
||||||
|
// <Input allowClear placeholder={t('Search')} />
|
||||||
|
// </div>
|
||||||
|
// <div>
|
||||||
|
// <Space>
|
||||||
|
// <Button>{t('New from template')}</Button>
|
||||||
|
// <SchemaComponent
|
||||||
|
// scope={{ useCreateFormProps, useCancelActionProps, useCreateActionProps }}
|
||||||
|
// components={{ AIEmployeeForm }}
|
||||||
|
// schema={{
|
||||||
|
// type: 'void',
|
||||||
|
// name: uid(),
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// type: 'primary',
|
||||||
|
// },
|
||||||
|
// title: 'New AI employee',
|
||||||
|
// properties: {
|
||||||
|
// drawer: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'Action.Drawer',
|
||||||
|
// title: 'New AI employee',
|
||||||
|
// 'x-decorator': 'FormV2',
|
||||||
|
// 'x-use-decorator-props': 'useCreateFormProps',
|
||||||
|
// properties: {
|
||||||
|
// form: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'AIEmployeeForm',
|
||||||
|
// },
|
||||||
|
// footer: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'Action.Drawer.Footer',
|
||||||
|
// properties: {
|
||||||
|
// close: {
|
||||||
|
// title: 'Cancel',
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// type: 'default',
|
||||||
|
// },
|
||||||
|
// 'x-use-component-props': 'useCancelActionProps',
|
||||||
|
// },
|
||||||
|
// submit: {
|
||||||
|
// title: 'Submit',
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// type: 'primary',
|
||||||
|
// },
|
||||||
|
// 'x-use-component-props': 'useCreateActionProps',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </Space>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// {loading ? (
|
||||||
|
// <Spin />
|
||||||
|
// ) : data && data.length ? (
|
||||||
|
// <Row gutter={[16, 16]}>
|
||||||
|
// {data.map((employee) => (
|
||||||
|
// <CollectionRecordProvider key={employee.username} record={employee}>
|
||||||
|
// <Col span={6}>
|
||||||
|
// <Card
|
||||||
|
// variant="borderless"
|
||||||
|
// actions={[
|
||||||
|
// <SchemaComponent
|
||||||
|
// key="edit"
|
||||||
|
// scope={{ useCancelActionProps, useEditFormProps, useEditActionProps }}
|
||||||
|
// components={{ AIEmployeeForm }}
|
||||||
|
// schema={{
|
||||||
|
// type: 'void',
|
||||||
|
// name: uid(),
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// component: (props) => <EditOutlined {...props} />,
|
||||||
|
// },
|
||||||
|
// properties: {
|
||||||
|
// drawer: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'Action.Drawer',
|
||||||
|
// title: 'Edit AI employee',
|
||||||
|
// 'x-decorator': 'FormV2',
|
||||||
|
// 'x-use-decorator-props': 'useEditFormProps',
|
||||||
|
// properties: {
|
||||||
|
// form: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'AIEmployeeForm',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// edit: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// footer: {
|
||||||
|
// type: 'void',
|
||||||
|
// 'x-component': 'Action.Drawer.Footer',
|
||||||
|
// properties: {
|
||||||
|
// close: {
|
||||||
|
// title: 'Cancel',
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// type: 'default',
|
||||||
|
// },
|
||||||
|
// 'x-use-component-props': 'useCancelActionProps',
|
||||||
|
// },
|
||||||
|
// submit: {
|
||||||
|
// title: 'Submit',
|
||||||
|
// 'x-component': 'Action',
|
||||||
|
// 'x-component-props': {
|
||||||
|
// type: 'primary',
|
||||||
|
// },
|
||||||
|
// 'x-use-component-props': 'useEditActionProps',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// />,
|
||||||
|
// <DeleteOutlined key="delete" onClick={() => del(employee.username)} />,
|
||||||
|
// ]}
|
||||||
|
// >
|
||||||
|
// <Meta
|
||||||
|
// avatar={employee.avatar ? <Avatar size={40} src={avatars(employee.avatar)} /> : null}
|
||||||
|
// title={employee.nickname}
|
||||||
|
// description={
|
||||||
|
// <>
|
||||||
|
// {employee.position && (
|
||||||
|
// <Tag
|
||||||
|
// style={{
|
||||||
|
// marginBottom: token.marginXS,
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// {employee.position}
|
||||||
|
// </Tag>
|
||||||
|
// )}
|
||||||
|
// <Typography.Paragraph
|
||||||
|
// style={{ height: token.fontSize * token.lineHeight * 3 }}
|
||||||
|
// ellipsis={{ rows: 3 }}
|
||||||
|
// type="secondary"
|
||||||
|
// >
|
||||||
|
// {employee.bio}
|
||||||
|
// </Typography.Paragraph>
|
||||||
|
// </>
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
// </Card>
|
||||||
|
// </Col>
|
||||||
|
// </CollectionRecordProvider>
|
||||||
|
// ))}
|
||||||
|
// </Row>
|
||||||
|
// ) : (
|
||||||
|
// <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
// )}
|
||||||
|
// </EmployeeContext.Provider>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
const del = (username: string) => {
|
const Avatar: React.FC = (props) => {
|
||||||
modal.confirm({
|
const field = useField<Field>();
|
||||||
title: t('Delete AI employee'),
|
if (!field.value) {
|
||||||
content: t('Are you sure to delete this employee?'),
|
return null;
|
||||||
onOk: async () => {
|
}
|
||||||
await api.resource('aiEmployees').destroy({
|
return <AntdAvatar {...props} src={avatars(field.value)} />;
|
||||||
filterByTk: username,
|
|
||||||
});
|
|
||||||
message.success(t('Deleted successfully'));
|
|
||||||
refresh();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Employees = () => {
|
||||||
|
const t = useT();
|
||||||
return (
|
return (
|
||||||
<EmployeeContext.Provider value={{ refresh }}>
|
<ExtendCollectionsProvider collections={[aiEmployees]}>
|
||||||
<div
|
|
||||||
style={{ marginBottom: token.marginLG }}
|
|
||||||
className={css`
|
|
||||||
justify-content: space-between;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Input allowClear placeholder={t('Search')} />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Space>
|
|
||||||
<Button>{t('New from template')}</Button>
|
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
scope={{ useCreateFormProps, useCancelActionProps, useCreateActionProps }}
|
components={{ AIEmployeeForm, Avatar }}
|
||||||
components={{ AIEmployeeForm }}
|
scope={{
|
||||||
|
useCreateFormProps,
|
||||||
|
useEditFormProps,
|
||||||
|
useCancelActionProps,
|
||||||
|
useCreateActionProps,
|
||||||
|
useEditActionProps,
|
||||||
|
}}
|
||||||
schema={{
|
schema={{
|
||||||
type: 'void',
|
type: 'void',
|
||||||
name: uid(),
|
name: 'root',
|
||||||
|
properties: {
|
||||||
|
block: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-component-props': {
|
||||||
|
heightMode: 'fullHeight',
|
||||||
|
},
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'aiEmployees',
|
||||||
|
action: 'list',
|
||||||
|
rowKey: 'username',
|
||||||
|
dragSort: true,
|
||||||
|
dragSortBy: 'sort',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
refresh: {
|
||||||
|
title: "{{t('Refresh')}}",
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-use-component-props': 'useRefreshActionProps',
|
||||||
|
'x-component-props': {
|
||||||
|
icon: 'ReloadOutlined',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
add: {
|
||||||
|
type: 'void',
|
||||||
|
title: "{{t('New AI employee')}}",
|
||||||
|
'x-align': 'right',
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
icon: 'PlusOutlined',
|
||||||
},
|
},
|
||||||
title: 'New AI employee',
|
|
||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
@ -316,62 +552,130 @@ export const Employees: React.FC = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
/>
|
},
|
||||||
</Space>
|
},
|
||||||
</div>
|
table: {
|
||||||
</div>
|
type: 'array',
|
||||||
{loading ? (
|
'x-component': 'TableV2',
|
||||||
<Spin />
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
) : data && data.length ? (
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{data.map((employee) => (
|
|
||||||
<CollectionRecordProvider key={employee.username} record={employee}>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card
|
|
||||||
variant="borderless"
|
|
||||||
actions={[
|
|
||||||
<SchemaComponent
|
|
||||||
key="edit"
|
|
||||||
scope={{ useCancelActionProps, useEditFormProps, useEditActionProps }}
|
|
||||||
components={{ AIEmployeeForm }}
|
|
||||||
schema={{
|
|
||||||
type: 'void',
|
|
||||||
name: uid(),
|
|
||||||
'x-component': 'Action',
|
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
component: (props) => <EditOutlined {...props} />,
|
rowKey: 'username',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
column0: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Avatar'),
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
avatar: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Avatar',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column1: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Username'),
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column2: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Nickname'),
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
nickname: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column3: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Position'),
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
position: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input',
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column4: {
|
||||||
|
type: 'void',
|
||||||
|
title: t('Bio'),
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
bio: {
|
||||||
|
type: 'string',
|
||||||
|
'x-component': 'Input.TextArea',
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
'x-pattern': 'readPretty',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column5: {
|
||||||
|
type: 'void',
|
||||||
|
title: 'Actions',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
edit: {
|
||||||
|
type: 'void',
|
||||||
|
title: 'Edit',
|
||||||
|
'x-action': 'update',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-component-props': {
|
||||||
|
openMode: 'drawer',
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
|
title: t('Edit AI employee'),
|
||||||
'x-component': 'Action.Drawer',
|
'x-component': 'Action.Drawer',
|
||||||
title: 'Edit AI employee',
|
|
||||||
'x-decorator': 'FormV2',
|
'x-decorator': 'FormV2',
|
||||||
'x-use-decorator-props': 'useEditFormProps',
|
'x-use-decorator-props': 'useEditFormProps',
|
||||||
properties: {
|
properties: {
|
||||||
form: {
|
form: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'AIEmployeeForm',
|
'x-component': 'AIEmployeeForm',
|
||||||
|
'x-component-props': {
|
||||||
|
edit: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
type: 'void',
|
type: 'void',
|
||||||
'x-component': 'Action.Drawer.Footer',
|
'x-component': 'Action.Drawer.Footer',
|
||||||
properties: {
|
properties: {
|
||||||
close: {
|
close: {
|
||||||
title: 'Cancel',
|
title: t('Cancel'),
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
'x-component-props': {
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
'x-use-component-props': 'useCancelActionProps',
|
'x-use-component-props': 'useCancelActionProps',
|
||||||
},
|
},
|
||||||
submit: {
|
submit: {
|
||||||
title: 'Submit',
|
title: 'Submit',
|
||||||
'x-component': 'Action',
|
'x-component': 'Action',
|
||||||
'x-component-props': {
|
|
||||||
type: 'primary',
|
|
||||||
},
|
|
||||||
'x-use-component-props': 'useEditActionProps',
|
'x-use-component-props': 'useEditActionProps',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -379,43 +683,31 @@ export const Employees: React.FC = () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
destroy: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Delete") }}',
|
||||||
|
'x-action': 'destroy',
|
||||||
|
'x-component': 'Action.Link',
|
||||||
|
'x-use-component-props': 'useDestroyActionProps',
|
||||||
|
'x-component-props': {
|
||||||
|
confirm: {
|
||||||
|
title: "{{t('Delete AI employee')}}",
|
||||||
|
content: "{{t('Are you sure you want to delete this AI employee?')}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>,
|
|
||||||
<DeleteOutlined key="delete" onClick={() => del(employee.username)} />,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Meta
|
|
||||||
avatar={employee.avatar ? <Avatar size={40} src={avatars(employee.avatar)} /> : null}
|
|
||||||
title={employee.nickname}
|
|
||||||
description={
|
|
||||||
<>
|
|
||||||
{employee.position && (
|
|
||||||
<Tag
|
|
||||||
style={{
|
|
||||||
marginBottom: token.marginXS,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{employee.position}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
<Typography.Paragraph
|
|
||||||
style={{ height: token.fontSize * token.lineHeight * 3 }}
|
|
||||||
ellipsis={{ rows: 3 }}
|
|
||||||
type="secondary"
|
|
||||||
>
|
|
||||||
{employee.bio}
|
|
||||||
</Typography.Paragraph>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</ExtendCollectionsProvider>
|
||||||
</Col>
|
|
||||||
</CollectionRecordProvider>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
) : (
|
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
||||||
)}
|
|
||||||
</EmployeeContext.Provider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,9 @@ import React from 'react';
|
|||||||
import { AvatarSelect } from './AvatarSelect';
|
import { AvatarSelect } from './AvatarSelect';
|
||||||
import { useT } from '../../locale';
|
import { useT } from '../../locale';
|
||||||
|
|
||||||
export const ProfileSettings: React.FC = () => {
|
export const ProfileSettings: React.FC<{
|
||||||
|
edit?: boolean;
|
||||||
|
}> = ({ edit }) => {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
return (
|
return (
|
||||||
<SchemaComponent
|
<SchemaComponent
|
||||||
@ -26,6 +28,7 @@ export const ProfileSettings: React.FC = () => {
|
|||||||
'x-decorator': 'FormItem',
|
'x-decorator': 'FormItem',
|
||||||
'x-component': 'Input',
|
'x-component': 'Input',
|
||||||
required: true,
|
required: true,
|
||||||
|
'x-disabled': edit,
|
||||||
},
|
},
|
||||||
nickname: {
|
nickname: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -23,7 +23,9 @@ export const useAISelectionContext = () => {
|
|||||||
return useContext(AISelectionContext);
|
return useContext(AISelectionContext);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AISelectionProvider: React.FC = (props) => {
|
export const AISelectionProvider: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = (props) => {
|
||||||
const [selectable, setSelectable] = useState('');
|
const [selectable, setSelectable] = useState('');
|
||||||
const [selector, setSelector] = useState<Selector>(null);
|
const [selector, setSelector] = useState<Selector>(null);
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import { useAISelectionContext } from '../selector/AISelectorProvider';
|
|||||||
import { AIEmployee } from '../types';
|
import { AIEmployee } from '../types';
|
||||||
import { AIVariableRawTextArea } from './AIVariableRawTextArea';
|
import { AIVariableRawTextArea } from './AIVariableRawTextArea';
|
||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
|
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||||
|
|
||||||
const SettingsForm: React.FC<{
|
const SettingsForm: React.FC<{
|
||||||
form: any;
|
form: any;
|
||||||
@ -150,9 +151,11 @@ export const aiEmployeeButtonSettings = new SchemaSettings({
|
|||||||
const t = useT();
|
const t = useT();
|
||||||
const { dn } = useSchemaSettings();
|
const { dn } = useSchemaSettings();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const aiEmployee = dn.getSchemaAttribute('x-component-props.aiEmployee') || {};
|
const username = dn.getSchemaAttribute('x-component-props.username') || {};
|
||||||
const form = useMemo(() => createForm({}), []);
|
const form = useMemo(() => createForm({}), []);
|
||||||
const { selectable } = useAISelectionContext();
|
const { selectable } = useAISelectionContext();
|
||||||
|
const { aiEmployeesMap } = useAIEmployeesContext();
|
||||||
|
const aiEmployee = aiEmployeesMap[username];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
@ -176,7 +179,7 @@ export const aiEmployeeButtonSettings = new SchemaSettings({
|
|||||||
dn.deepMerge({
|
dn.deepMerge({
|
||||||
'x-uid': dn.getSchemaAttribute('x-uid'),
|
'x-uid': dn.getSchemaAttribute('x-uid'),
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
aiEmployee,
|
username,
|
||||||
message,
|
message,
|
||||||
taskDesc,
|
taskDesc,
|
||||||
autoSend,
|
autoSend,
|
||||||
|
@ -79,6 +79,5 @@ export type ResendOptions = {
|
|||||||
export type ShortcutOptions = {
|
export type ShortcutOptions = {
|
||||||
aiEmployee: AIEmployee;
|
aiEmployee: AIEmployee;
|
||||||
message: { type: MessageType; content: string };
|
message: { type: MessageType; content: string };
|
||||||
infoFormValues: any;
|
|
||||||
autoSend: boolean;
|
autoSend: boolean;
|
||||||
};
|
};
|
||||||
|
@ -187,7 +187,6 @@ export const llmsSchema = {
|
|||||||
'x-component': 'Action.Link',
|
'x-component': 'Action.Link',
|
||||||
'x-component-props': {
|
'x-component-props': {
|
||||||
openMode: 'drawer',
|
openMode: 'drawer',
|
||||||
icon: 'EditOutlined',
|
|
||||||
},
|
},
|
||||||
properties: {
|
properties: {
|
||||||
drawer: {
|
drawer: {
|
||||||
|
@ -13,5 +13,17 @@ import aiEmployees from '../../collections/ai-employees';
|
|||||||
export default defineCollection({
|
export default defineCollection({
|
||||||
migrationRules: ['overwrite', 'schema-only'],
|
migrationRules: ['overwrite', 'schema-only'],
|
||||||
autoGenId: false,
|
autoGenId: false,
|
||||||
|
sortable: true,
|
||||||
...aiEmployees,
|
...aiEmployees,
|
||||||
|
fields: [
|
||||||
|
...aiEmployees.fields,
|
||||||
|
{
|
||||||
|
name: 'userConfigs',
|
||||||
|
type: 'hasMany',
|
||||||
|
target: 'usersAiEmployees',
|
||||||
|
sourceKey: 'username',
|
||||||
|
foreignKey: 'aiEmployee',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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 { defineCollection } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default defineCollection({
|
||||||
|
name: 'usersAiEmployees',
|
||||||
|
migrationRules: ['schema-only'],
|
||||||
|
fields: [
|
||||||
|
{ type: 'sort', name: 'sort' },
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'prompt',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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 { extendCollection } from '@nocobase/database';
|
||||||
|
|
||||||
|
export default extendCollection({
|
||||||
|
name: 'users',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
type: 'belongsToMany',
|
||||||
|
name: 'aiEmployees',
|
||||||
|
target: 'aiEmployees',
|
||||||
|
foreignKey: 'userId',
|
||||||
|
otherKey: 'aiEmployee',
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
sourceKey: 'id',
|
||||||
|
targetKey: 'username',
|
||||||
|
through: 'usersAiEmployees',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -17,6 +17,7 @@ import { LLMInstruction } from './workflow/nodes/llm';
|
|||||||
import aiConversations from './resource/aiConversations';
|
import aiConversations from './resource/aiConversations';
|
||||||
import { AIEmployeesManager } from './ai-employees/ai-employees-manager';
|
import { AIEmployeesManager } from './ai-employees/ai-employees-manager';
|
||||||
import Snowflake from './snowflake';
|
import Snowflake from './snowflake';
|
||||||
|
import { listByUser } from './resource/aiEmployees';
|
||||||
|
|
||||||
export class PluginAIServer extends Plugin {
|
export class PluginAIServer extends Plugin {
|
||||||
aiManager = new AIManager();
|
aiManager = new AIManager();
|
||||||
@ -40,6 +41,7 @@ export class PluginAIServer extends Plugin {
|
|||||||
|
|
||||||
this.app.resourceManager.define(aiResource);
|
this.app.resourceManager.define(aiResource);
|
||||||
this.app.resourceManager.define(aiConversations);
|
this.app.resourceManager.define(aiConversations);
|
||||||
|
this.app.resourceManager.registerActionHandler('aiEmployees:listByUser', listByUser);
|
||||||
this.app.acl.registerSnippet({
|
this.app.acl.registerSnippet({
|
||||||
name: `pm.${this.name}.llm-services`,
|
name: `pm.${this.name}.llm-services`,
|
||||||
actions: ['ai:*', 'llmServices:*'],
|
actions: ['ai:*', 'llmServices:*'],
|
||||||
@ -48,6 +50,7 @@ export class PluginAIServer extends Plugin {
|
|||||||
name: `pm.${this.name}.ai-employees`,
|
name: `pm.${this.name}.ai-employees`,
|
||||||
actions: ['aiEmployees:*'],
|
actions: ['aiEmployees:*'],
|
||||||
});
|
});
|
||||||
|
this.app.acl.allow('aiEmployees', 'listByUser', 'loggedIn');
|
||||||
this.app.acl.allow('aiConversations', '*', 'loggedIn');
|
this.app.acl.allow('aiConversations', '*', 'loggedIn');
|
||||||
const workflowSnippet = this.app.acl.snippetManager.snippets.get('pm.workflow.workflows');
|
const workflowSnippet = this.app.acl.snippetManager.snippets.get('pm.workflow.workflows');
|
||||||
if (workflowSnippet) {
|
if (workflowSnippet) {
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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 { Context, Next } from '@nocobase/actions';
|
||||||
|
|
||||||
|
export const listByUser = async (ctx: Context, next: Next) => {
|
||||||
|
const user = ctx.auth.user;
|
||||||
|
const model = ctx.db.getModel('aiEmployees');
|
||||||
|
const sequelize = ctx.db.sequelize;
|
||||||
|
const rows = await model.findAll({
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: ctx.db.getModel('usersAiEmployees'),
|
||||||
|
as: 'userConfigs',
|
||||||
|
required: false,
|
||||||
|
where: { userId: user.id },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [
|
||||||
|
[sequelize.literal('CASE WHEN userConfigs.sort IS NOT NULL THEN 0 ELSE 1 END'), 'ASC'],
|
||||||
|
[sequelize.fn('COALESCE', sequelize.col('userConfigs.sort'), sequelize.col('aiEmployees.sort')), 'ASC'],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
ctx.body = rows.map((row) => ({
|
||||||
|
username: row.username,
|
||||||
|
nickname: row.nickname,
|
||||||
|
position: row.position,
|
||||||
|
avatar: row.avatar,
|
||||||
|
bio: row.bio,
|
||||||
|
greeting: row.greeting,
|
||||||
|
}));
|
||||||
|
await next();
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user