mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +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.
|
||||
*/
|
||||
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { createContext } from 'react';
|
||||
import { ChatBoxProvider } from './chatbox/ChatBoxProvider';
|
||||
import { useAPIClient, useRequest } from '@nocobase/client';
|
||||
@ -46,12 +46,18 @@ export const useAIEmployeesContext = () => {
|
||||
() =>
|
||||
api
|
||||
.resource('aiEmployees')
|
||||
.list()
|
||||
.listByUser()
|
||||
.then((res) => res?.data?.data),
|
||||
{
|
||||
ready: !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.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import { List, Popover, Button, Avatar, Divider } from 'antd';
|
||||
import { useToken } from '@nocobase/client';
|
||||
import React from 'react';
|
||||
import { List, Popover, Button, Avatar } from 'antd';
|
||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||
import { useT } from '../../locale';
|
||||
import { useChatBoxContext } from './ChatBoxContext';
|
||||
@ -17,10 +16,6 @@ import { avatars } from '../avatars';
|
||||
import { css } from '@emotion/css';
|
||||
import { Sender } from '@ant-design/x';
|
||||
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 = () => {
|
||||
const {
|
||||
@ -28,36 +23,7 @@ export const AIEmployeeHeader: React.FC = () => {
|
||||
aiEmployees,
|
||||
} = useAIEmployeesContext();
|
||||
const t = useT();
|
||||
const { setMessages, addMessage } = useChatMessages();
|
||||
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],
|
||||
);
|
||||
const switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||
|
||||
return (
|
||||
<Sender.Header closable={false}>
|
||||
|
@ -42,6 +42,7 @@ type ChatBoxContextValues = {
|
||||
setSenderPlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
||||
startNewConversation: () => void;
|
||||
triggerShortcut: (options: ShortcutOptions) => void;
|
||||
switchAIEmployee: (aiEmployee: AIEmployee) => void;
|
||||
send(opts: SendOptions): void;
|
||||
};
|
||||
|
||||
@ -113,7 +114,7 @@ export const useSetChatBoxContext = () => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [showConversations, setShowConversations] = useState(false);
|
||||
const [currentEmployee, setCurrentEmployee] = useState<AIEmployee>(null);
|
||||
const { setMessages, sendMessages } = useChatMessages();
|
||||
const { setMessages, sendMessages, addMessage } = useChatMessages();
|
||||
const { currentConversation, setCurrentConversation, conversationsService } = useChatConversations();
|
||||
const [senderValue, setSenderValue] = useState<string>('');
|
||||
const [senderPlaceholder, setSenderPlaceholder] = useState<string>('');
|
||||
@ -156,6 +157,33 @@ export const useSetChatBoxContext = () => {
|
||||
senderRef.current?.focus();
|
||||
}, [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(
|
||||
(options: ShortcutOptions) => {
|
||||
const { aiEmployee, message, autoSend } = options;
|
||||
@ -238,6 +266,7 @@ export const useSetChatBoxContext = () => {
|
||||
setSenderPlaceholder,
|
||||
startNewConversation,
|
||||
triggerShortcut,
|
||||
switchAIEmployee,
|
||||
send,
|
||||
};
|
||||
};
|
||||
|
@ -7,51 +7,20 @@
|
||||
* 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 icon from '../icon.svg';
|
||||
import { css } from '@emotion/css';
|
||||
import { useChatBoxContext } from './ChatBoxContext';
|
||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||
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 = () => {
|
||||
const t = useT();
|
||||
const { aiEmployees } = useAIEmployeesContext();
|
||||
const open = useChatBoxContext('open');
|
||||
const setOpen = useChatBoxContext('setOpen');
|
||||
const setCurrentEmployee = useChatBoxContext('setCurrentEmployee');
|
||||
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 switchAIEmployee = useChatBoxContext('switchAIEmployee');
|
||||
|
||||
const items = useMemo(() => {
|
||||
return aiEmployees?.map((employee) => ({
|
||||
key: employee.username,
|
||||
@ -89,7 +58,7 @@ export const ChatButton: React.FC = () => {
|
||||
padding: 0;
|
||||
}
|
||||
.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={
|
||||
<Avatar
|
||||
src={icon}
|
||||
size={40}
|
||||
size={36}
|
||||
style={{
|
||||
marginBottom: '4px',
|
||||
}}
|
||||
/>
|
||||
}
|
||||
// onClick={() => {
|
||||
// setOpen(false);
|
||||
// }}
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
shape="square"
|
||||
/>
|
||||
</Dropdown>
|
||||
|
@ -16,6 +16,7 @@ import { SenderHeader } from './SenderHeader';
|
||||
import { SenderFooter } from './SenderFooter';
|
||||
import { useChatConversations } from './ChatConversationsProvider';
|
||||
import { useChatMessages } from './ChatMessagesProvider';
|
||||
import { AIEmployeeHeader } from './AIEmployeeHeader';
|
||||
|
||||
export const Sender: React.FC = () => {
|
||||
const t = useT();
|
||||
@ -60,7 +61,7 @@ export const Sender: React.FC = () => {
|
||||
}
|
||||
onCancel={cancelRequest}
|
||||
prefix={<SenderPrefix />}
|
||||
// header={!currentConversation ? <SenderHeader /> : null}
|
||||
header={!currentEmployee ? <AIEmployeeHeader /> : null}
|
||||
loading={responseLoading}
|
||||
footer={({ components }) => <SenderFooter components={components} />}
|
||||
disabled={!currentEmployee}
|
||||
|
@ -14,5 +14,14 @@ import { Avatar } from 'antd';
|
||||
|
||||
export const SenderPrefix: React.FC = () => {
|
||||
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 { Avatar, Popover, Button } from 'antd';
|
||||
import { Avatar, Popover, Button, Spin } from 'antd';
|
||||
import { avatars } from '../avatars';
|
||||
import {
|
||||
SortableItem,
|
||||
@ -21,6 +21,7 @@ import { useFieldSchema } from '@formily/react';
|
||||
import { useChatBoxContext } from '../chatbox/ChatBoxContext';
|
||||
import { AIEmployee } from '../types';
|
||||
import { ProfileCard } from '../ProfileCard';
|
||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||
|
||||
async function replaceVariables(template, variables, localVariables = {}) {
|
||||
const regex = /\{\{\s*(.*?)\s*\}\}/g;
|
||||
@ -58,20 +59,24 @@ async function replaceVariables(template, variables, localVariables = {}) {
|
||||
}
|
||||
|
||||
export const AIEmployeeButton: React.FC<{
|
||||
aiEmployee: AIEmployee;
|
||||
username: string;
|
||||
taskDesc?: string;
|
||||
autoSend?: boolean;
|
||||
message: {
|
||||
type: string;
|
||||
content: string;
|
||||
};
|
||||
infoForm: any;
|
||||
}> = withDynamicSchemaProps(({ aiEmployee, taskDesc, message, infoForm: infoFormValues, autoSend }) => {
|
||||
}> = withDynamicSchemaProps(({ username, taskDesc, message, autoSend }) => {
|
||||
const triggerShortcut = useChatBoxContext('triggerShortcut');
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { render } = useSchemaToolbarRender(fieldSchema);
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
const {
|
||||
aiEmployeesMap,
|
||||
service: { loading },
|
||||
} = useAIEmployeesContext();
|
||||
const aiEmployee = aiEmployeesMap[username];
|
||||
|
||||
return (
|
||||
<SortableItem
|
||||
@ -92,10 +97,10 @@ export const AIEmployeeButton: React.FC<{
|
||||
aiEmployee,
|
||||
message: msg,
|
||||
autoSend,
|
||||
infoFormValues,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Popover content={<ProfileCard taskDesc={taskDesc} aiEmployee={aiEmployee} />}>
|
||||
<Button
|
||||
shape="circle"
|
||||
@ -104,10 +109,11 @@ export const AIEmployeeButton: React.FC<{
|
||||
height: '36px',
|
||||
}}
|
||||
>
|
||||
<Avatar src={avatars(aiEmployee.avatar)} size={36} />
|
||||
{aiEmployee && <Avatar src={avatars(aiEmployee.avatar)} size={36} />}
|
||||
</Button>
|
||||
</Popover>
|
||||
{render()}
|
||||
</Spin>
|
||||
</SortableItem>
|
||||
);
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ const getAIEmployeesInitializer = () => ({
|
||||
'x-toolbar': 'ActionSchemaToolbar',
|
||||
'x-settings': 'aiEmployees:button',
|
||||
'x-component-props': {
|
||||
aiEmployee,
|
||||
username: aiEmployee.username,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -10,11 +10,11 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import cls from 'classnames';
|
||||
import { useToken, useUploadStyles } from '@nocobase/client';
|
||||
import useUploadStyle from 'antd/es/upload/style';
|
||||
import { css } from '@emotion/css';
|
||||
import { useField } from '@formily/react';
|
||||
import { Field } from '@formily/core';
|
||||
import { avatars } from '../avatars';
|
||||
import { List, Avatar as AntdAvatar } from 'antd';
|
||||
|
||||
export const Avatar: React.FC<{
|
||||
srcs: [string, string][];
|
||||
@ -22,11 +22,9 @@ export const Avatar: React.FC<{
|
||||
selectable?: boolean;
|
||||
highlightItem?: string;
|
||||
onClick?: (name: string) => void;
|
||||
}> = ({ srcs, size, selectable, highlightItem, onClick }) => {
|
||||
}> = ({ srcs, size = 'large', selectable, highlightItem, onClick }) => {
|
||||
const { token } = useToken();
|
||||
const { wrapSSR, hashId, componentCls: prefixCls } = useUploadStyles();
|
||||
const useUploadStyleVal = (useUploadStyle as any).default ? (useUploadStyle as any).default : useUploadStyle;
|
||||
useUploadStyleVal(prefixCls);
|
||||
|
||||
const list =
|
||||
srcs?.map(([src, name], index) => (
|
||||
@ -61,6 +59,44 @@ export const Avatar: React.FC<{
|
||||
</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(
|
||||
<div
|
||||
className={cls(
|
||||
|
@ -8,13 +8,31 @@
|
||||
*/
|
||||
|
||||
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 {
|
||||
CollectionRecordProvider,
|
||||
ExtendCollectionsProvider,
|
||||
SchemaComponent,
|
||||
useAPIClient,
|
||||
useActionContext,
|
||||
useCollection,
|
||||
useCollectionRecordData,
|
||||
useDataBlockRequest,
|
||||
useDataBlockResource,
|
||||
useRequest,
|
||||
useToken,
|
||||
} from '@nocobase/client';
|
||||
@ -22,32 +40,37 @@ import { useT } from '../../locale';
|
||||
const { Meta } = Card;
|
||||
import { css } from '@emotion/css';
|
||||
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||
import { useForm } from '@formily/react';
|
||||
import { createForm } from '@formily/core';
|
||||
import { useForm, useField } from '@formily/react';
|
||||
import { createForm, Field } from '@formily/core';
|
||||
import { uid } from '@formily/shared';
|
||||
import { avatars } from '../avatars';
|
||||
import { ModelSettings } from './ModelSettings';
|
||||
import { ProfileSettings } from './ProfileSettings';
|
||||
import { ChatSettings } from './ChatSettings';
|
||||
import { AIEmployee } from '../types';
|
||||
import aiEmployees from '../../../collections/ai-employees';
|
||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||
|
||||
const EmployeeContext = createContext(null);
|
||||
|
||||
const AIEmployeeForm: React.FC = () => {
|
||||
const AIEmployeeForm: React.FC<{
|
||||
edit?: boolean;
|
||||
}> = ({ edit }) => {
|
||||
return (
|
||||
<Tabs
|
||||
items={[
|
||||
{
|
||||
key: 'profile',
|
||||
label: 'Profile',
|
||||
children: <ProfileSettings />,
|
||||
},
|
||||
{
|
||||
key: 'chat',
|
||||
label: 'Chat settings',
|
||||
children: <ChatSettings />,
|
||||
children: <ProfileSettings edit={edit} />,
|
||||
forceRender: true,
|
||||
},
|
||||
// {
|
||||
// key: 'chat',
|
||||
// label: 'Chat settings',
|
||||
// children: <ChatSettings />,
|
||||
// },
|
||||
// {
|
||||
// key: 'skills',
|
||||
// label: 'Skills',
|
||||
// // children: (
|
||||
@ -130,6 +153,7 @@ const AIEmployeeForm: React.FC = () => {
|
||||
key: 'modelSettings',
|
||||
label: 'Model Settings',
|
||||
children: <ModelSettings />,
|
||||
forceRender: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@ -182,7 +206,7 @@ const useCreateActionProps = () => {
|
||||
const { message } = App.useApp();
|
||||
const form = useForm();
|
||||
const api = useAPIClient();
|
||||
const { refresh } = useContext(EmployeeContext);
|
||||
const { refresh } = useDataBlockRequest();
|
||||
const t = useT();
|
||||
|
||||
return {
|
||||
@ -205,18 +229,20 @@ const useEditActionProps = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
const { message } = App.useApp();
|
||||
const form = useForm();
|
||||
const resource = useDataBlockResource();
|
||||
const { refresh } = useDataBlockRequest();
|
||||
const collection = useCollection();
|
||||
const filterTk = collection.getFilterTargetKey();
|
||||
const t = useT();
|
||||
const { refresh } = useContext(EmployeeContext);
|
||||
const api = useAPIClient();
|
||||
|
||||
return {
|
||||
type: 'primary',
|
||||
async onClick() {
|
||||
await form.submit();
|
||||
const values = form.values;
|
||||
await api.resource('aiEmployees').update({
|
||||
await resource.update({
|
||||
values,
|
||||
filterByTk: values.username,
|
||||
filterByTk: values[filterTk],
|
||||
});
|
||||
refresh();
|
||||
message.success(t('Saved successfully'));
|
||||
@ -226,59 +252,269 @@ const useEditActionProps = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const Employees: React.FC = () => {
|
||||
const t = useT();
|
||||
const { message, modal } = App.useApp();
|
||||
const { token } = useToken();
|
||||
const api = useAPIClient();
|
||||
const { data, loading, refresh } = useRequest<AIEmployee[]>(() =>
|
||||
api
|
||||
.resource('aiEmployees')
|
||||
.list()
|
||||
.then((res) => res?.data?.data),
|
||||
);
|
||||
// export const Employees: React.FC = () => {
|
||||
// const t = useT();
|
||||
// const { message, modal } = App.useApp();
|
||||
// const { token } = useToken();
|
||||
// const api = useAPIClient();
|
||||
// const { data, loading, refresh } = useRequest<AIEmployee[]>(() =>
|
||||
// api
|
||||
// .resource('aiEmployees')
|
||||
// .list()
|
||||
// .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) => {
|
||||
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();
|
||||
},
|
||||
});
|
||||
const Avatar: React.FC = (props) => {
|
||||
const field = useField<Field>();
|
||||
if (!field.value) {
|
||||
return null;
|
||||
}
|
||||
return <AntdAvatar {...props} src={avatars(field.value)} />;
|
||||
};
|
||||
|
||||
export const Employees = () => {
|
||||
const t = useT();
|
||||
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>
|
||||
<ExtendCollectionsProvider collections={[aiEmployees]}>
|
||||
<SchemaComponent
|
||||
scope={{ useCreateFormProps, useCancelActionProps, useCreateActionProps }}
|
||||
components={{ AIEmployeeForm }}
|
||||
components={{ AIEmployeeForm, Avatar }}
|
||||
scope={{
|
||||
useCreateFormProps,
|
||||
useEditFormProps,
|
||||
useCancelActionProps,
|
||||
useCreateActionProps,
|
||||
useEditActionProps,
|
||||
}}
|
||||
schema={{
|
||||
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-props': {
|
||||
type: 'primary',
|
||||
icon: 'PlusOutlined',
|
||||
},
|
||||
title: 'New AI employee',
|
||||
properties: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
@ -316,62 +552,130 @@ export const Employees: React.FC = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</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',
|
||||
},
|
||||
},
|
||||
},
|
||||
table: {
|
||||
type: 'array',
|
||||
'x-component': 'TableV2',
|
||||
'x-use-component-props': 'useTableBlockProps',
|
||||
'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: {
|
||||
drawer: {
|
||||
type: 'void',
|
||||
title: t('Edit AI employee'),
|
||||
'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',
|
||||
title: t('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',
|
||||
},
|
||||
},
|
||||
@ -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>
|
||||
</Col>
|
||||
</CollectionRecordProvider>
|
||||
))}
|
||||
</Row>
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</EmployeeContext.Provider>
|
||||
</ExtendCollectionsProvider>
|
||||
);
|
||||
};
|
||||
|
@ -12,7 +12,9 @@ import React from 'react';
|
||||
import { AvatarSelect } from './AvatarSelect';
|
||||
import { useT } from '../../locale';
|
||||
|
||||
export const ProfileSettings: React.FC = () => {
|
||||
export const ProfileSettings: React.FC<{
|
||||
edit?: boolean;
|
||||
}> = ({ edit }) => {
|
||||
const t = useT();
|
||||
return (
|
||||
<SchemaComponent
|
||||
@ -26,6 +28,7 @@ export const ProfileSettings: React.FC = () => {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
required: true,
|
||||
'x-disabled': edit,
|
||||
},
|
||||
nickname: {
|
||||
type: 'string',
|
||||
|
@ -23,7 +23,9 @@ export const useAISelectionContext = () => {
|
||||
return useContext(AISelectionContext);
|
||||
};
|
||||
|
||||
export const AISelectionProvider: React.FC = (props) => {
|
||||
export const AISelectionProvider: React.FC<{
|
||||
children: React.ReactNode;
|
||||
}> = (props) => {
|
||||
const [selectable, setSelectable] = useState('');
|
||||
const [selector, setSelector] = useState<Selector>(null);
|
||||
|
||||
|
@ -19,6 +19,7 @@ import { useAISelectionContext } from '../selector/AISelectorProvider';
|
||||
import { AIEmployee } from '../types';
|
||||
import { AIVariableRawTextArea } from './AIVariableRawTextArea';
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useAIEmployeesContext } from '../AIEmployeesProvider';
|
||||
|
||||
const SettingsForm: React.FC<{
|
||||
form: any;
|
||||
@ -150,9 +151,11 @@ export const aiEmployeeButtonSettings = new SchemaSettings({
|
||||
const t = useT();
|
||||
const { dn } = useSchemaSettings();
|
||||
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 { selectable } = useAISelectionContext();
|
||||
const { aiEmployeesMap } = useAIEmployeesContext();
|
||||
const aiEmployee = aiEmployeesMap[username];
|
||||
|
||||
return (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
@ -176,7 +179,7 @@ export const aiEmployeeButtonSettings = new SchemaSettings({
|
||||
dn.deepMerge({
|
||||
'x-uid': dn.getSchemaAttribute('x-uid'),
|
||||
'x-component-props': {
|
||||
aiEmployee,
|
||||
username,
|
||||
message,
|
||||
taskDesc,
|
||||
autoSend,
|
||||
|
@ -79,6 +79,5 @@ export type ResendOptions = {
|
||||
export type ShortcutOptions = {
|
||||
aiEmployee: AIEmployee;
|
||||
message: { type: MessageType; content: string };
|
||||
infoFormValues: any;
|
||||
autoSend: boolean;
|
||||
};
|
||||
|
@ -187,7 +187,6 @@ export const llmsSchema = {
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
icon: 'EditOutlined',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
|
@ -13,5 +13,17 @@ import aiEmployees from '../../collections/ai-employees';
|
||||
export default defineCollection({
|
||||
migrationRules: ['overwrite', 'schema-only'],
|
||||
autoGenId: false,
|
||||
sortable: true,
|
||||
...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 { AIEmployeesManager } from './ai-employees/ai-employees-manager';
|
||||
import Snowflake from './snowflake';
|
||||
import { listByUser } from './resource/aiEmployees';
|
||||
|
||||
export class PluginAIServer extends Plugin {
|
||||
aiManager = new AIManager();
|
||||
@ -40,6 +41,7 @@ export class PluginAIServer extends Plugin {
|
||||
|
||||
this.app.resourceManager.define(aiResource);
|
||||
this.app.resourceManager.define(aiConversations);
|
||||
this.app.resourceManager.registerActionHandler('aiEmployees:listByUser', listByUser);
|
||||
this.app.acl.registerSnippet({
|
||||
name: `pm.${this.name}.llm-services`,
|
||||
actions: ['ai:*', 'llmServices:*'],
|
||||
@ -48,6 +50,7 @@ export class PluginAIServer extends Plugin {
|
||||
name: `pm.${this.name}.ai-employees`,
|
||||
actions: ['aiEmployees:*'],
|
||||
});
|
||||
this.app.acl.allow('aiEmployees', 'listByUser', 'loggedIn');
|
||||
this.app.acl.allow('aiConversations', '*', 'loggedIn');
|
||||
const workflowSnippet = this.app.acl.snippetManager.snippets.get('pm.workflow.workflows');
|
||||
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