V4.9.6 feature (#4565)

* Dashboard submenu (#4545)

* add app submenu (#4452)

* add app submenu

* fix

* width & i18n

* optimize submenu code (#4515)

* optimize submenu code

* fix

* fix

* fix

* fix ts

* perf: dashboard sub menu

* doc

---------

Co-authored-by: heheer <heheer@sealos.io>

* feat: value format test

* doc

* Mcp export (#4555)

* feat: mcp server

* feat: mcp server

* feat: mcp server build

* update doc

* perf: path selector (#4556)

* perf: path selector

* fix: docker file path

* perf: add image endpoint to dataset search (#4557)

* perf: add image endpoint to dataset search

* fix: mcp_server url

* human in loop (#4558)

* Support interactive nodes for loops, and enhance the function of merging nested and loop node history messages. (#4552)

* feat: add LoopInteractive definition

* feat: Support LoopInteractive type and update related logic

* fix: Refactor loop handling logic and improve output value initialization

* feat: Add mergeSignId to dispatchLoop and dispatchRunAppNode responses

* feat: Enhance mergeChatResponseData to recursively merge plugin details and improve response handling

* refactor: Remove redundant comments in mergeChatResponseData for clarity

* perf: loop interactive

* perf: human in loop

---------

Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>

* mcp server ui

* integrate mcp (#4549)

* integrate mcp

* delete unused code

* fix ts

* bug fix

* fix

* support whole mcp tools

* add try catch

* fix

* fix

* fix ts

* fix test

* fix ts

* fix: interactive in v1 completions

* doc

* fix: router path

* fix mcp integrate (#4563)

* fix mcp integrate

* fix ui

* fix: mcp ux

* feat: mcp call title

* remove repeat loading

* fix mcp tools avatar (#4564)

* fix

* fix avatar

* fix update version

* update doc

* fix: value format

* close server and remove cache

* perf: avatar

---------

Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Theresa <63280168+sd0ric4@users.noreply.github.com>
This commit is contained in:
Archer
2025-04-16 22:18:51 +08:00
committed by GitHub
parent ab799e13cd
commit 952412f648
166 changed files with 6318 additions and 1263 deletions

View File

@@ -0,0 +1,366 @@
import React, { useMemo, useState } from 'react';
import { Box, Flex, Button, useDisclosure, Input, InputGroup } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/i18n/utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { postCreateAppFolder } from '@/web/core/app/api/app';
import type { EditFolderFormType } from '@fastgpt/web/components/common/MyModal/EditFolderModal';
import { useContextSelector } from 'use-context-selector';
import AppListContextProvider, { AppListContext } from '@/pageComponents/dashboard/apps/context';
import FolderPath from '@/components/common/folder/Path';
import { useRouter } from 'next/router';
import FolderSlideCard from '@/components/common/folder/SlideCard';
import { delAppById, resumeInheritPer } from '@/web/core/app/api';
import { AppPermissionList } from '@fastgpt/global/support/permission/app/constant';
import {
deleteAppCollaborators,
getCollaboratorList,
postUpdateAppCollaborators
} from '@/web/core/app/api/collaborator';
import type { CreateAppType } from '@/pageComponents/dashboard/apps/CreateModal';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import MyBox from '@fastgpt/web/components/common/MyBox';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import MyIcon from '@fastgpt/web/components/common/Icon';
import JsonImportModal from '@/pageComponents/dashboard/apps/JsonImportModal';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import DashboardContainer from '@/pageComponents/dashboard/Container';
import List from '@/pageComponents/dashboard/apps/List';
import MCPToolsEditModal from '@/pageComponents/dashboard/apps/MCPToolsEditModal';
const CreateModal = dynamic(() => import('@/pageComponents/dashboard/apps/CreateModal'));
const EditFolderModal = dynamic(
() => import('@fastgpt/web/components/common/MyModal/EditFolderModal')
);
const HttpEditModal = dynamic(() => import('@/pageComponents/dashboard/apps/HttpPluginEditModal'));
const MyApps = ({ MenuIcon }: { MenuIcon: JSX.Element }) => {
const { t } = useTranslation();
const router = useRouter();
const { isPc } = useSystem();
const {
paths,
parentId,
myApps,
appType,
loadMyApps,
onUpdateApp,
setMoveAppId,
isFetchingApps,
folderDetail,
refetchFolderDetail,
searchKey,
setSearchKey
} = useContextSelector(AppListContext, (v) => v);
const { userInfo } = useUserStore();
const [createAppType, setCreateAppType] = useState<CreateAppType>();
const {
isOpen: isOpenCreateHttpPlugin,
onOpen: onOpenCreateHttpPlugin,
onClose: onCloseCreateHttpPlugin
} = useDisclosure();
const {
isOpen: isOpenCreateMCPTools,
onOpen: onOpenCreateMCPTools,
onClose: onCloseCreateMCPTools
} = useDisclosure();
const {
isOpen: isOpenJsonImportModal,
onOpen: onOpenJsonImportModal,
onClose: onCloseJsonImportModal
} = useDisclosure();
const [editFolder, setEditFolder] = useState<EditFolderFormType>();
const { runAsync: onCreateFolder } = useRequest2(postCreateAppFolder, {
onSuccess() {
loadMyApps();
},
errorToast: 'Error'
});
const { runAsync: onDeleFolder } = useRequest2(delAppById, {
onSuccess() {
router.replace({
query: {
parentId: folderDetail?.parentId
}
});
},
errorToast: 'Error'
});
const appTypeName = useMemo(() => {
const map: Record<AppTypeEnum | 'all', string> = {
all: t('common:core.module.template.Team app'),
[AppTypeEnum.simple]: t('app:type.Simple bot'),
[AppTypeEnum.workflow]: t('app:type.Workflow bot'),
[AppTypeEnum.plugin]: t('app:type.Plugin'),
[AppTypeEnum.httpPlugin]: t('app:type.Http plugin'),
[AppTypeEnum.folder]: t('common:Folder'),
[AppTypeEnum.toolSet]: t('app:type.MCP tools'),
[AppTypeEnum.tool]: t('app:type.MCP tools')
};
return map[appType] || map['all'];
}, [appType, t]);
const RenderSearchInput = useMemo(
() => (
<InputGroup maxW={['auto', '250px']} position={'relative'}>
<MyIcon
position={'absolute'}
zIndex={10}
name={'common/searchLight'}
w={'1rem'}
color={'myGray.600'}
left={2.5}
top={'50%'}
transform={'translateY(-50%)'}
/>
<Input
value={searchKey}
onChange={(e) => setSearchKey(e.target.value)}
placeholder={t('app:search_app')}
maxLength={30}
pl={8}
bg={'white'}
/>
</InputGroup>
),
[searchKey, setSearchKey, t]
);
return (
<Flex flexDirection={'column'} h={'100%'}>
{paths.length > 0 && (
<Box pt={[4, 6]} pl={5}>
<FolderPath
paths={paths}
hoverStyle={{ bg: 'myGray.200' }}
forbidLastClick
onClick={(parentId) => {
router.push({
query: {
...router.query,
parentId
}
});
}}
/>
</Box>
)}
<Flex gap={5} flex={'1 0 0'} h={0}>
<Flex
flex={'1 0 0'}
flexDirection={'column'}
h={'100%'}
pr={folderDetail ? [3, 2] : [3, 6]}
pl={6}
overflowY={'auto'}
overflowX={'hidden'}
>
<Flex pt={paths.length > 0 ? 3 : [4, 6]} alignItems={'center'} gap={3}>
{isPc ? (
<Box fontSize={'lg'} color={'myGray.900'} fontWeight={500}>
{appTypeName}
</Box>
) : (
MenuIcon
)}
<Box flex={1} />
{isPc && RenderSearchInput}
{(folderDetail
? folderDetail.permission.hasWritePer && folderDetail?.type !== AppTypeEnum.httpPlugin
: userInfo?.team.permission.hasAppCreatePer) && (
<MyMenu
size="md"
Button={
<Button variant={'primary'} leftIcon={<AddIcon />}>
<Box>{t('common:new_create')}</Box>
</Button>
}
menuList={[
{
children: [
{
icon: 'core/app/simpleBot',
label: t('app:type.Simple bot'),
description: t('app:type.Create simple bot tip'),
onClick: () => setCreateAppType(AppTypeEnum.simple)
},
{
icon: 'core/app/type/workflowFill',
label: t('app:type.Workflow bot'),
description: t('app:type.Create workflow tip'),
onClick: () => setCreateAppType(AppTypeEnum.workflow)
},
{
icon: 'core/app/type/pluginFill',
label: t('app:type.Plugin'),
description: t('app:type.Create one plugin tip'),
onClick: () => setCreateAppType(AppTypeEnum.plugin)
},
{
icon: 'core/app/type/httpPluginFill',
label: t('app:type.Http plugin'),
description: t('app:type.Create http plugin tip'),
onClick: onOpenCreateHttpPlugin
},
{
icon: 'core/app/type/mcpToolsFill',
label: t('app:type.MCP tools'),
description: t('app:type.Create mcp tools tip'),
onClick: onOpenCreateMCPTools
}
]
},
{
children: [
{
icon: 'core/app/type/jsonImport',
label: t('app:type.Import from json'),
description: t('app:type.Import from json tip'),
onClick: onOpenJsonImportModal
}
]
},
{
children: [
{
icon: FolderIcon,
label: t('common:Folder'),
onClick: () => setEditFolder({})
}
]
}
]}
/>
)}
</Flex>
{!isPc && <Box mt={2}>{RenderSearchInput}</Box>}
<MyBox flex={'1 0 0'} isLoading={myApps.length === 0 && isFetchingApps}>
<List />
</MyBox>
</Flex>
{/* Folder slider */}
{!!folderDetail && isPc && (
<Box pt={[4, 6]} pr={[4, 6]}>
<FolderSlideCard
refetchResource={() => Promise.all([refetchFolderDetail(), loadMyApps()])}
resumeInheritPermission={() => resumeInheritPer(folderDetail._id)}
isInheritPermission={folderDetail.inheritPermission}
hasParent={!!folderDetail.parentId}
refreshDeps={[folderDetail._id, folderDetail.inheritPermission]}
name={folderDetail.name}
intro={folderDetail.intro}
onEdit={() => {
setEditFolder({
id: folderDetail._id,
name: folderDetail.name,
intro: folderDetail.intro
});
}}
onMove={() => setMoveAppId(folderDetail._id)}
deleteTip={t('app:confirm_delete_folder_tip')}
onDelete={() => onDeleFolder(folderDetail._id)}
managePer={{
permission: folderDetail.permission,
onGetCollaboratorList: () => getCollaboratorList(folderDetail._id),
permissionList: AppPermissionList,
onUpdateCollaborators: ({
members,
groups,
permission
}: {
members?: string[];
groups?: string[];
permission: PermissionValueType;
}) => {
return postUpdateAppCollaborators({
members,
groups,
permission,
appId: folderDetail._id
});
},
refreshDeps: [folderDetail._id, folderDetail.inheritPermission],
onDelOneCollaborator: async ({
tmbId,
groupId,
orgId
}: {
tmbId?: string;
groupId?: string;
orgId?: string;
}) => {
if (tmbId) {
return deleteAppCollaborators({
appId: folderDetail._id,
tmbId
});
} else if (groupId) {
return deleteAppCollaborators({
appId: folderDetail._id,
groupId
});
} else if (orgId) {
return deleteAppCollaborators({
appId: folderDetail._id,
orgId
});
}
}
}}
/>
</Box>
)}
</Flex>
{!!editFolder && (
<EditFolderModal
{...editFolder}
onClose={() => setEditFolder(undefined)}
onCreate={(data) => onCreateFolder({ ...data, parentId })}
onEdit={({ id, ...data }) => onUpdateApp(id, data)}
/>
)}
{!!createAppType && (
<CreateModal type={createAppType} onClose={() => setCreateAppType(undefined)} />
)}
{isOpenCreateHttpPlugin && <HttpEditModal onClose={onCloseCreateHttpPlugin} />}
{isOpenCreateMCPTools && <MCPToolsEditModal onClose={onCloseCreateMCPTools} />}
{isOpenJsonImportModal && <JsonImportModal onClose={onCloseJsonImportModal} />}
</Flex>
);
};
function ContextRender() {
return (
<DashboardContainer>
{({ MenuIcon }) => (
<AppListContextProvider>
<MyApps MenuIcon={MenuIcon} />
</AppListContextProvider>
)}
</DashboardContainer>
);
}
export default ContextRender;
export async function getServerSideProps(content: any) {
return {
props: {
...(await serviceSideProps(content, ['app', 'user']))
}
};
}