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,253 @@
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
import { getMCPTools, postCreateMCPTools } from '@/web/core/app/api/plugin';
import {
Box,
Button,
Center,
Flex,
Input,
ModalBody,
ModalFooter,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr
} from '@chakra-ui/react';
import Avatar from '@fastgpt/web/components/common/Avatar';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { AppListContext } from './context';
import { useContextSelector } from 'use-context-selector';
import { ToolType } from '@fastgpt/global/core/app/type';
import { getMCPToolsBody } from '@/pages/api/core/app/mcpTools/getMCPTools';
export type MCPToolSetData = {
url: string;
toolList: ToolType[];
};
export type EditMCPToolsProps = {
avatar: string;
name: string;
mcpData: MCPToolSetData;
};
const MCPToolsEditModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { toast } = useToast();
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
const { register, setValue, handleSubmit, watch } = useForm<EditMCPToolsProps>({
defaultValues: {
avatar: 'core/app/type/mcpToolsFill',
name: '',
mcpData: {
url: '',
toolList: []
}
}
});
const avatar = watch('avatar');
const mcpData = watch('mcpData');
const { runAsync: onCreate, loading: isCreating } = useRequest2(
async (data: EditMCPToolsProps) => {
return postCreateMCPTools({
name: data.name,
avatar: data.avatar,
toolList: data.mcpData.toolList,
url: data.mcpData.url,
parentId
});
},
{
onSuccess() {
onClose();
loadMyApps();
},
successToast: t('common:common.Create Success'),
errorToast: t('common:common.Create Failed')
}
);
const { runAsync: runGetMCPTools, loading: isGettingTools } = useRequest2(
(data: getMCPToolsBody) => getMCPTools(data),
{
onSuccess: (res) => {
setValue('mcpData.toolList', res);
},
errorToast: t('app:MCP_tools_parse_failed')
}
);
const {
File,
onOpen: onOpenSelectFile,
onSelectImage
} = useSelectFile({
fileType: 'image/*',
multiple: false
});
return (
<>
<MyModal
isOpen={true}
onClose={onClose}
iconSrc="core/app/type/mcpToolsFill"
title={t('app:type.MCP tools')}
w={['90vw', '530px']}
position={'relative'}
>
<ModalBody>
<Box color={'myGray.900'} fontSize={'14px'} fontWeight={'medium'}>
{t('common:common.Set Name')}
</Box>
<Flex mt={2} alignItems={'center'}>
<MyTooltip label={t('common:common.Set Avatar')}>
<Avatar
flexShrink={0}
src={avatar}
w={['28px', '32px']}
h={['28px', '32px']}
cursor={'pointer'}
borderRadius={'md'}
onClick={onOpenSelectFile}
/>
</MyTooltip>
<Input
flex={1}
ml={4}
bg={'myWhite.600'}
{...register('name', {
required: t('common:common.name_is_empty')
})}
/>
</Flex>
<Box color={'myGray.900'} fontSize={'14px'} fontWeight={'medium'} mt={6}>
{t('app:MCP_tools_url')}
</Box>
<Flex alignItems={'center'} gap={2} mt={2}>
<Input
h={8}
placeholder={t('app:MCP_tools_url_placeholder')}
{...register('mcpData.url', {
required: t('app:MCP_tools_url_is_empty')
})}
/>
<Button
size={'sm'}
variant={'whitePrimary'}
h={8}
isLoading={isGettingTools}
onClick={() => {
runGetMCPTools({ url: mcpData.url });
}}
>
{t('common:common.Parse')}
</Button>
</Flex>
<Box color={'myGray.900'} fontSize={'14px'} fontWeight={'medium'} mt={6}>
{t('app:MCP_tools_list')}
</Box>
<Box
mt={2}
borderRadius={'md'}
overflow={'hidden'}
borderWidth={'1px'}
position={'relative'}
>
<TableContainer maxH={360} minH={200} overflowY={'auto'}>
<Table bg={'white'}>
<Thead bg={'myGray.50'}>
<Th fontSize={'mini'} py={0} h={'34px'}>
{t('common:Name')}
</Th>
<Th fontSize={'mini'} py={0} h={'34px'}>
{t('common:plugin.Description')}
</Th>
</Thead>
<Tbody>
{mcpData.toolList.map((item) => (
<Tr key={item.name} height={'28px'}>
<Td
fontSize={'mini'}
color={'myGray.900'}
fontWeight={'medium'}
py={2}
maxW={1 / 2}
overflow={'hidden'}
textOverflow={'ellipsis'}
whiteSpace={'nowrap'}
>
{item.name}
</Td>
<Td
fontSize={'mini'}
color={'myGray.900'}
fontWeight={'medium'}
py={2}
maxW={1 / 2}
overflow={'hidden'}
textOverflow={'ellipsis'}
whiteSpace={'nowrap'}
>
{item.description}
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{mcpData.toolList.length === 0 && (
<Center
position={'absolute'}
top={0}
left={0}
right={0}
bottom={0}
fontSize={'mini'}
color={'myGray.500'}
>
{t('app:no_mcp_tools_list')}
</Center>
)}
</Box>
</ModalBody>
<ModalFooter gap={2}>
<Button variant={'whitePrimary'} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button
isDisabled={mcpData.toolList.length === 0}
isLoading={isCreating}
onClick={handleSubmit(onCreate)}
>
{t('common:common.Confirm Create')}
</Button>
</ModalFooter>
</MyModal>
<File
onSelect={(e) =>
onSelectImage(e, {
maxH: 300,
maxW: 300,
callback: (e) => setValue('avatar', e)
})
}
/>
</>
);
};
export default MCPToolsEditModal;