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:
@@ -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;
|
||||
Reference in New Issue
Block a user