diff --git a/frontend/packages/common/src/assets/feishu.png b/frontend/packages/common/src/assets/feishu.png new file mode 100644 index 00000000..1476ee5b Binary files /dev/null and b/frontend/packages/common/src/assets/feishu.png differ diff --git a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx index 5bf074d5..4ee40750 100644 --- a/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx +++ b/frontend/packages/common/src/components/aoplatform/BasicLayout.tsx @@ -137,7 +137,7 @@ function BasicLayout({ project = 'core' }: { project: string }) { const items: MenuProps['items'] = useMemo( () => [ - userInfo?.type !== 'guest' && { + !['guest', 'third-user'].includes(userInfo?.type as string) && { key: '2', label: ( + + {allowFeishuLogin && ( + <> + + + + + + + )} + {allowGuest && ( + <> + + + + + + + )} + - {/*
*/} -
-
- - - -
- -
-
-
-
- - - - - - - - - - - - { - allowGuest && <> - - - - - - - } - -
-
-
- -
-

- {$t('Version (0)-(1)',[state?.version,state?.updateDate])}, {$t(state?.powered || '-')} -

- -
-
- - ); + + + +
+

+ {$t('Version (0)-(1)', [state?.version, state?.updateDate])}, {$t(state?.powered || '-')} +

+ +
+ + + ) } -export default Login; \ No newline at end of file +export default Login diff --git a/frontend/packages/core/src/pages/auth/Auth.tsx b/frontend/packages/core/src/pages/auth/Auth.tsx new file mode 100644 index 00000000..cd337ea4 --- /dev/null +++ b/frontend/packages/core/src/pages/auth/Auth.tsx @@ -0,0 +1,167 @@ +import InsidePage from '@common/components/aoplatform/InsidePage' +import WithPermission from '@common/components/aoplatform/WithPermission' +import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Button, Form, Input, Row, Select, Switch } from 'antd' +import { useEffect, useState } from 'react' + +type AuthSetting = { + config: { + clientId: string + clientSecret: string + } + enabled: boolean +} + +type AuthFieldType = { + authType: string + clientId: string + clientSecret: string + enabled: boolean +} + +const Auth = () => { + const { message } = App.useApp() + const [form] = Form.useForm() + const { fetchData } = useFetch() + const [, forceUpdate] = useState(null) + const { state } = useGlobalContext() + const [thirdPartyDrivers, setThirdPartyDrivers] = useState<{ label: string; value: string }[]>([]) + useEffect(() => { + forceUpdate({}) + }, [state.language]) + const onFinish = () => { + form.validateFields().then((value) => { + return fetchData>(`/account/third/${value.authType}`, { + method: 'POST', + eoBody: { + enable: value.enabled, + config: { + client_id: value.clientId, + client_secret: value.clientSecret + } + } + }) + .then((response) => { + const { code, msg } = response + if (code === STATUS_CODE.SUCCESS) { + message.success(msg || $t(RESPONSE_TIPS.success)) + return Promise.resolve(true) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + return Promise.reject(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => { + return Promise.reject(errorInfo) + }) + }) + } + + /** + * 获取第三方授权列表 + */ + const getThirdPartyAuthList = () => { + fetchData< + BasicResponse<{ + drivers: { + name: string + value: string + }[] + }> + >('/account/third', { + method: 'GET', + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + setThirdPartyDrivers(data.drivers.map((item: any) => ({ label: item.name, value: item.value }))) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + /** + * 获取第三方授权配置 + */ + const getThirdPartyAuthSetting = () => { + fetchData>(`/account/third/${form.getFieldValue('authType')}`, { + method: 'GET', + eoTransformKeys: ['client_id', 'client_secret'] + }).then((response) => { + const { code, data, msg } = response + if (code === STATUS_CODE.SUCCESS) { + form.setFieldsValue({ + clientId: data.info?.config?.clientId || '', + clientSecret: data.info?.config?.clientSecret || '', + enabled: data.info?.enable || false + }) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + } + + useEffect(() => { + getThirdPartyAuthList() + }, []) + + return ( + + +
+ + label={$t('授权类型')} + name="authType" + rules={[{ required: true, message: $t('请选择授权类型') }]} + > + + + + + label={$t('APP Secret')} + name="clientSecret" + rules={[{ required: true, whitespace: true, message: $t('请输入APP Secret') }]} + extra={$t('APP Secret 参数位于飞书开发人员控制台中的应用程序凭证和基础信息页面上')} + > + + + + label={$t('启用授权')} name="enabled" valuePropName="checked"> + + + + + + + + + +
+
+ ) +} + +export default Auth diff --git a/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx b/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx index e566305f..48486726 100644 --- a/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx +++ b/frontend/packages/core/src/pages/mcpService/IntegrationAIContainer.tsx @@ -50,17 +50,23 @@ type ServiceApiKeyList = { expired: number }> } + +type ConsumerParamsType = { + consumerId: string + teamId: string +} export interface IntegrationAIContainerRef { getServiceKeysList: () => void; } export interface IntegrationAIContainerProps { - type: 'global' | 'service' + type: 'global' | 'service' | 'consumer' handleToolsChange: (value: Tool[]) => void customClassName?: string service?: ServiceDetailType serviceId?: string currentTab?: string openModal?: (type: 'apply') => void + consumerParams?: ConsumerParamsType } export const IntegrationAIContainer = forwardRef( ({ @@ -69,8 +75,9 @@ export const IntegrationAIContainer = forwardRef { /** 当前激活的标签 */ const [activeTab, setActiveTab] = useState(type === 'service' ? 'openApi' : 'mcp') @@ -184,6 +191,34 @@ export const IntegrationAIContainer = forwardRef { + fetchData>('app/mcp/config', { + method: 'GET', + eoParams: { app: consumerParams?.consumerId, team: consumerParams?.teamId } + }) + .then((response) => { + const { code, msg, data } = response + if (code === STATUS_CODE.SUCCESS) { + setTabContent((prevTabContent) => ({ + ...prevTabContent, + mcp: { + ...prevTabContent.mcp, + configContent: data.config || '' + } + })) + } else { + message.error(msg || $t(RESPONSE_TIPS.error)) + } + }) + .catch((errorInfo) => { + message.error(errorInfo || $t(RESPONSE_TIPS.error)) + }) + } + /** * 全局 MCP 跳转 */ @@ -229,12 +264,12 @@ export const IntegrationAIContainer = forwardRef { + const getServiceKeysList = (consumerId?: string) => { fetchData>(`my/app/apikeys`, { method: 'GET', - eoParams: { service: serviceId } + eoParams: consumerId ? { app: consumerId } : { service: serviceId } }) .then((response) => { const { code, msg, data } = response @@ -345,6 +380,10 @@ export const IntegrationAIContainer = forwardRef { initTabsData() type === 'global' && getGlobalMcpConfig() + type === 'consumer' && getConsumerMcpConfig() }, [state.language]) /** * 切换标签 @@ -408,7 +448,7 @@ export const IntegrationAIContainer = forwardRefAPI Key {apiKeyList.length ? ( <> - {type === 'global' ? ( + {type === 'global' || type === 'consumer' ? ( <> + label={$t("邮箱")} name="email" rules={[{required: true,whitespace:true },{type:"email",message: $t(VALIDATE_MESSAGE.email)}]} > - + label={$t("密码")} name="password" rules={[{required: type === 'addMember',whitespace:true }]} > - + label={$t("部门")} name="departmentIds" > { width: 600, okText: $t('确认'), okButtonProps: { - disabled: isActionAllowed(type) + disabled: isActionAllowed(type) || entity?.form !== 'self-build' }, cancelText: $t('取消'), closable: true, diff --git a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx index 96d16e22..708e63dd 100644 --- a/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx +++ b/frontend/packages/market/src/pages/serviceHub/management/ManagementInsidePage.tsx @@ -32,6 +32,7 @@ export default function ManagementInsidePage() { const TENANT_MANAGEMENT_APP_MENU: MenuProps['items'] = useMemo( () => [ getItem($t('订阅的服务'), 'service', undefined, undefined, undefined, 'team.application.subscription.view'), + getItem($t('MCP 服务'), 'mcp', undefined, undefined, undefined, 'team.consumer.mcp.view'), getItem($t('访问授权'), 'authorization', undefined, undefined, undefined, 'team.consumer.authorization.view'), getItem($t('消费者管理'), 'setting', undefined, undefined, undefined, 'team.application.application.view') ], diff --git a/frontend/packages/market/src/pages/serviceHub/management/mcpContent.tsx b/frontend/packages/market/src/pages/serviceHub/management/mcpContent.tsx new file mode 100644 index 00000000..a0f414ea --- /dev/null +++ b/frontend/packages/market/src/pages/serviceHub/management/mcpContent.tsx @@ -0,0 +1,40 @@ +import { $t } from '@common/locales' +import { IntegrationAIContainer } from '@core/pages/mcpService/IntegrationAIContainer' +import { Tool } from '@modelcontextprotocol/sdk/types.js' +import { useEffect, useState } from 'react' +import { useGlobalContext } from '@common/contexts/GlobalStateContext' +import McpToolsContainer from '@core/pages/mcpService/McpToolsContainer' +import { useParams } from 'react-router-dom' +import { RouterParams } from '@common/const/type' + +const mcpContent = () => { + const [tools, setTools] = useState([]) + const [, forceUpdate] = useState(null) + const { teamId, appId } = useParams() + const { state } = useGlobalContext() + const handleToolsChange = (value: Tool[]) => { + setTools(value) + } + useEffect(() => { + forceUpdate({}) + }, [state.language]) + return ( +
+
+ {$t('MCP 服务')} +
+
+
+ + +
+
+
+ ) +} + +export default mcpContent