mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-18 09:24:03 +00:00
add manual create http toolset (#5743)
* add manual create http toolset * optimize code * optimize * fix * fix
This commit is contained in:
@@ -56,10 +56,13 @@ const ChatTest = ({
|
||||
return await postRunHTTPTool({
|
||||
baseUrl,
|
||||
params: data,
|
||||
headerSecret,
|
||||
headerSecret: currentTool.headerSecret || headerSecret,
|
||||
toolPath: currentTool.path,
|
||||
method: currentTool.method,
|
||||
customHeaders: customHeaders
|
||||
customHeaders: customHeaders,
|
||||
staticParams: currentTool.staticParams,
|
||||
staticHeaders: currentTool.staticHeaders,
|
||||
staticBody: currentTool.staticBody
|
||||
});
|
||||
},
|
||||
{
|
||||
|
@@ -0,0 +1,107 @@
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Button, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { parseCurl } from '@fastgpt/global/common/string/http';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { type HttpMethod, ContentTypes } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { ParamItemType } from './ManualToolModal';
|
||||
|
||||
export type CurlImportResult = {
|
||||
method: HttpMethod;
|
||||
path: string;
|
||||
params?: ParamItemType[];
|
||||
headers?: ParamItemType[];
|
||||
bodyType: string;
|
||||
bodyContent?: string;
|
||||
bodyFormData?: ParamItemType[];
|
||||
};
|
||||
|
||||
type CurlImportModalProps = {
|
||||
onClose: () => void;
|
||||
onImport: (result: CurlImportResult) => void;
|
||||
};
|
||||
|
||||
const CurlImportModal = ({ onClose, onImport }: CurlImportModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { register, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
curlContent: ''
|
||||
}
|
||||
});
|
||||
|
||||
const handleCurlImport = (data: { curlContent: string }) => {
|
||||
try {
|
||||
const parsed = parseCurl(data.curlContent);
|
||||
|
||||
const convertToParamItemType = (
|
||||
items: Array<{ key: string; value?: string; type?: string }>
|
||||
): ParamItemType[] => {
|
||||
return items.map((item) => ({
|
||||
key: item.key,
|
||||
value: item.value || ''
|
||||
}));
|
||||
};
|
||||
|
||||
const bodyType = (() => {
|
||||
if (!parsed.body || parsed.body === '{}') {
|
||||
return ContentTypes.none;
|
||||
}
|
||||
return ContentTypes.json;
|
||||
})();
|
||||
|
||||
const result: CurlImportResult = {
|
||||
method: parsed.method as HttpMethod,
|
||||
path: parsed.url,
|
||||
params: parsed.params.length > 0 ? convertToParamItemType(parsed.params) : undefined,
|
||||
headers: parsed.headers.length > 0 ? convertToParamItemType(parsed.headers) : undefined,
|
||||
bodyType,
|
||||
bodyContent: bodyType === ContentTypes.json ? parsed.body : undefined
|
||||
};
|
||||
|
||||
onImport(result);
|
||||
toast({
|
||||
title: t('common:import_success'),
|
||||
status: 'success'
|
||||
});
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
title: t('common:import_failed'),
|
||||
description: error.message,
|
||||
status: 'error'
|
||||
});
|
||||
console.error('Curl import error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc="modal/edit"
|
||||
title={t('common:core.module.http.curl import')}
|
||||
w={600}
|
||||
>
|
||||
<ModalBody>
|
||||
<Textarea
|
||||
rows={20}
|
||||
mt={2}
|
||||
autoFocus
|
||||
{...register('curlContent')}
|
||||
placeholder={t('common:core.module.http.curl import placeholder')}
|
||||
/>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(handleCurlImport)}>{t('common:Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CurlImportModal);
|
@@ -27,7 +27,7 @@ const Edit = () => {
|
||||
);
|
||||
const baseUrl = toolSetData?.baseUrl ?? '';
|
||||
const toolList = toolSetData?.toolList ?? [];
|
||||
const apiSchemaStr = toolSetData?.apiSchemaStr ?? '';
|
||||
const apiSchemaStr = toolSetData?.apiSchemaStr;
|
||||
const headerSecret = toolSetData?.headerSecret ?? {};
|
||||
const customHeaders = useMemo(() => {
|
||||
try {
|
||||
|
@@ -14,6 +14,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { putUpdateHttpPlugin } from '@/web/core/app/api/plugin';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import ConfigModal from './ConfigModal';
|
||||
import ManualToolModal from './ManualToolModal';
|
||||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||
import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpTools/update';
|
||||
|
||||
@@ -36,7 +37,13 @@ const EditForm = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const reloadApp = useContextSelector(AppContext, (v) => v.reloadApp);
|
||||
|
||||
const [toolDetail, setToolDetail] = useState<HttpToolConfigType | null>(null);
|
||||
const [editingManualTool, setEditingManualTool] = useState<HttpToolConfigType | null>(null);
|
||||
|
||||
const isBatchMode = apiSchemaStr !== undefined;
|
||||
|
||||
const {
|
||||
onOpen: onOpenConfigModal,
|
||||
@@ -44,6 +51,28 @@ const EditForm = ({
|
||||
onClose: onCloseConfigModal
|
||||
} = useDisclosure();
|
||||
|
||||
const {
|
||||
onOpen: onOpenAddToolModal,
|
||||
isOpen: isOpenAddToolModal,
|
||||
onClose: onCloseAddToolModal
|
||||
} = useDisclosure();
|
||||
|
||||
const { runAsync: runDeleteHttpTool, loading: isDeletingTool } = useRequest2(
|
||||
async (updatedToolList: HttpToolConfigType[]) =>
|
||||
await putUpdateHttpPlugin({
|
||||
appId: appDetail._id,
|
||||
toolList: updatedToolList
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: () => {
|
||||
reloadApp();
|
||||
},
|
||||
successToast: t('common:delete_success'),
|
||||
errorToast: t('common:delete_failed')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box p={6}>
|
||||
@@ -54,21 +83,31 @@ const EditForm = ({
|
||||
total: toolList?.length || 0
|
||||
})}
|
||||
</FormLabel>
|
||||
<Button
|
||||
px={'2'}
|
||||
leftIcon={
|
||||
<MyIcon
|
||||
name={toolList?.length && toolList.length > 0 ? 'change' : 'common/setting'}
|
||||
w={'18px'}
|
||||
/>
|
||||
}
|
||||
onClick={onOpenConfigModal}
|
||||
>
|
||||
{toolList?.length && toolList.length > 0 ? t('common:Config') : t('app:Start_config')}
|
||||
</Button>
|
||||
{isBatchMode ? (
|
||||
<Button
|
||||
px={'2'}
|
||||
leftIcon={
|
||||
<MyIcon
|
||||
name={toolList?.length && toolList.length > 0 ? 'change' : 'common/setting'}
|
||||
w={'18px'}
|
||||
/>
|
||||
}
|
||||
onClick={onOpenConfigModal}
|
||||
>
|
||||
{toolList?.length && toolList.length > 0 ? t('common:Config') : t('app:Start_config')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
px={'2'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'18px'} />}
|
||||
onClick={onOpenAddToolModal}
|
||||
>
|
||||
{t('common:Add')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Box mt={3}>
|
||||
<MyBox mt={3} isLoading={isDeletingTool}>
|
||||
{toolList?.map((tool, index) => {
|
||||
return (
|
||||
<MyBox
|
||||
@@ -148,28 +187,67 @@ const EditForm = ({
|
||||
bg="linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 15%, rgba(255,255,255,1) 100%)"
|
||||
paddingLeft="20px"
|
||||
>
|
||||
<MyIconButton
|
||||
size={'16px'}
|
||||
icon={'common/detail'}
|
||||
p={2}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
hoverBg={'rgba(51, 112, 255, 0.10)'}
|
||||
hoverBorderColor={'primary.300'}
|
||||
tip={t('app:HTTP_tools_detail')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setToolDetail(tool);
|
||||
}}
|
||||
/>
|
||||
{isBatchMode ? (
|
||||
<MyIconButton
|
||||
size={'16px'}
|
||||
icon={'common/detail'}
|
||||
p={2}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
hoverBg={'rgba(51, 112, 255, 0.10)'}
|
||||
hoverBorderColor={'primary.300'}
|
||||
tip={t('app:HTTP_tools_detail')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setToolDetail(tool);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<MyIconButton
|
||||
size={'16px'}
|
||||
icon={'edit'}
|
||||
p={2}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
hoverBg={'rgba(51, 112, 255, 0.10)'}
|
||||
hoverBorderColor={'primary.300'}
|
||||
tip={t('common:Edit')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setEditingManualTool(tool);
|
||||
}}
|
||||
/>
|
||||
<MyIconButton
|
||||
size={'16px'}
|
||||
icon={'delete'}
|
||||
p={2}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
_hover={{
|
||||
color: 'red.500',
|
||||
bg: 'rgba(255, 0, 0, 0.10)',
|
||||
borderColor: 'red.300'
|
||||
}}
|
||||
tip={t('common:Delete')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
const updatedToolList =
|
||||
toolList?.filter((t) => t.name !== tool.name) || [];
|
||||
runDeleteHttpTool(updatedToolList);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</MyBox>
|
||||
</Box>
|
||||
|
||||
{isOpenConfigModal && <ConfigModal onClose={onCloseConfigModal} />}
|
||||
{isOpenAddToolModal && <ManualToolModal onClose={onCloseAddToolModal} />}
|
||||
{toolDetail && (
|
||||
<ToolDetailModal
|
||||
tool={toolDetail}
|
||||
@@ -181,6 +259,12 @@ const EditForm = ({
|
||||
customHeaders={customHeaders || '{}'}
|
||||
/>
|
||||
)}
|
||||
{editingManualTool && (
|
||||
<ManualToolModal
|
||||
onClose={() => setEditingManualTool(null)}
|
||||
editingTool={editingManualTool}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -0,0 +1,685 @@
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Textarea,
|
||||
useDisclosure,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../context';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import {
|
||||
HTTP_METHODS,
|
||||
type HttpMethod,
|
||||
toolValueTypeList,
|
||||
ContentTypes
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
headerValue2StoreHeader,
|
||||
storeHeader2HeaderValue
|
||||
} from '@/components/common/secret/HeaderAuthConfig';
|
||||
import HeaderAuthForm from '@/components/common/secret/HeaderAuthForm';
|
||||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
|
||||
import { putUpdateHttpPlugin } from '@/web/core/app/api/plugin';
|
||||
import type { HttpToolConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import CurlImportModal from './CurlImportModal';
|
||||
|
||||
type ManualToolFormType = {
|
||||
name: string;
|
||||
description: string;
|
||||
method: HttpMethod;
|
||||
path: string;
|
||||
headerSecret: StoreSecretValueType;
|
||||
customParams: CustomParamItemType[];
|
||||
params: ParamItemType[];
|
||||
bodyType: ContentTypes;
|
||||
bodyContent: string;
|
||||
bodyFormData: ParamItemType[];
|
||||
headers: ParamItemType[];
|
||||
};
|
||||
|
||||
type CustomParamItemType = {
|
||||
key: string;
|
||||
description: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
isTool: boolean;
|
||||
};
|
||||
|
||||
export type ParamItemType = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const ManualToolModal = ({
|
||||
onClose,
|
||||
editingTool
|
||||
}: {
|
||||
onClose: () => void;
|
||||
editingTool?: HttpToolConfigType;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const reloadApp = useContextSelector(AppContext, (v) => v.reloadApp);
|
||||
|
||||
const isEditMode = editingTool !== undefined;
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm<ManualToolFormType>({
|
||||
defaultValues: {
|
||||
name: editingTool?.name || '',
|
||||
description: editingTool?.description || '',
|
||||
method: (editingTool?.method.toUpperCase() as any) || 'POST',
|
||||
path: editingTool?.path || '',
|
||||
headerSecret: editingTool?.headerSecret || {},
|
||||
customParams: editingTool
|
||||
? Object.entries(editingTool.inputSchema.properties || {}).map(
|
||||
([key, value]: [string, any]) => ({
|
||||
key,
|
||||
description: value.description || '',
|
||||
type: value.type || 'string',
|
||||
required: editingTool.inputSchema.required?.includes(key) || false,
|
||||
isTool: !!value['x-tool-description']
|
||||
})
|
||||
)
|
||||
: [],
|
||||
params: editingTool?.staticParams || [],
|
||||
bodyType: editingTool?.staticBody?.type || ContentTypes.json,
|
||||
bodyContent: editingTool?.staticBody?.content || '',
|
||||
bodyFormData: editingTool?.staticBody?.formData || [],
|
||||
headers: editingTool?.staticHeaders || []
|
||||
}
|
||||
});
|
||||
|
||||
const method = watch('method');
|
||||
const headerSecret = watch('headerSecret');
|
||||
const customParams = watch('customParams');
|
||||
const params = watch('params');
|
||||
const bodyType = watch('bodyType');
|
||||
const bodyContent = watch('bodyContent');
|
||||
const bodyFormData = watch('bodyFormData');
|
||||
const headers = watch('headers');
|
||||
|
||||
const hasBody = method !== 'GET' && method !== 'DELETE';
|
||||
const isFormBody =
|
||||
bodyType === ContentTypes.formData || bodyType === ContentTypes.xWwwFormUrlencoded;
|
||||
const isContentBody =
|
||||
bodyType === ContentTypes.json ||
|
||||
bodyType === ContentTypes.xml ||
|
||||
bodyType === ContentTypes.raw;
|
||||
|
||||
const [editingParam, setEditingParam] = useState<CustomParamItemType | null>(null);
|
||||
|
||||
const {
|
||||
onOpen: onOpenCurlImport,
|
||||
isOpen: isOpenCurlImport,
|
||||
onClose: onCloseCurlImport
|
||||
} = useDisclosure();
|
||||
|
||||
const { runAsync: onSubmit, loading: isSubmitting } = useRequest2(
|
||||
async (data: ManualToolFormType) => {
|
||||
if (bodyType === ContentTypes.json && bodyContent) {
|
||||
try {
|
||||
JSON.parse(bodyContent);
|
||||
} catch (error) {
|
||||
return Promise.reject(t('common:json_parse_error'));
|
||||
}
|
||||
}
|
||||
|
||||
const inputProperties: Record<string, any> = {};
|
||||
const inputRequired: string[] = [];
|
||||
customParams.forEach((param) => {
|
||||
inputProperties[param.key] = {
|
||||
type: param.type,
|
||||
description: param.description || '',
|
||||
'x-tool-description': param.isTool ? param.description : ''
|
||||
};
|
||||
if (param.required) {
|
||||
inputRequired.push(param.key);
|
||||
}
|
||||
});
|
||||
|
||||
const newTool: HttpToolConfigType = {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
path: data.path,
|
||||
method: data.method.toLowerCase(),
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: inputProperties,
|
||||
required: inputRequired
|
||||
},
|
||||
outputSchema: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: []
|
||||
},
|
||||
...(params.length > 0 && { staticParams: params }),
|
||||
...(headers.length > 0 && { staticHeaders: headers }),
|
||||
...(hasBody &&
|
||||
bodyType !== ContentTypes.none && {
|
||||
staticBody: {
|
||||
type: bodyType,
|
||||
...(isContentBody ? { content: bodyContent } : {}),
|
||||
...(isFormBody ? { formData: bodyFormData } : {})
|
||||
}
|
||||
}),
|
||||
headerSecret: data.headerSecret
|
||||
};
|
||||
|
||||
const toolSetNode = appDetail.modules.find(
|
||||
(item) => item.flowNodeType === FlowNodeTypeEnum.toolSet
|
||||
);
|
||||
const existingToolList = toolSetNode?.toolConfig?.httpToolSet?.toolList || [];
|
||||
|
||||
const updatedToolList = (() => {
|
||||
if (isEditMode) {
|
||||
return existingToolList.map((tool) => (tool.name === editingTool?.name ? newTool : tool));
|
||||
}
|
||||
return [...existingToolList, newTool];
|
||||
})();
|
||||
|
||||
return putUpdateHttpPlugin({
|
||||
appId: appDetail._id,
|
||||
toolList: updatedToolList
|
||||
});
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
reloadApp();
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc={isEditMode ? 'modal/edit' : 'common/addLight'}
|
||||
iconColor={'primary.600'}
|
||||
title={isEditMode ? t('app:Edit_tool') : t('app:Add_tool')}
|
||||
maxW={'1167px'}
|
||||
>
|
||||
<ModalBody display={'flex'}>
|
||||
<Flex w={'1167px'}>
|
||||
<Flex
|
||||
w={'500px'}
|
||||
px={9}
|
||||
py={3}
|
||||
flexDirection={'column'}
|
||||
gap={6}
|
||||
borderRight={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
>
|
||||
<Flex gap={8} alignItems={'center'}>
|
||||
<FormLabel>{t('app:Tool_name')}</FormLabel>
|
||||
<Input
|
||||
h={8}
|
||||
{...register('name', { required: true })}
|
||||
placeholder={t('app:Tool_name')}
|
||||
/>
|
||||
</Flex>
|
||||
<Box>
|
||||
<FormLabel mb={2}>{t('app:Tool_description')}</FormLabel>
|
||||
<Textarea
|
||||
{...register('description')}
|
||||
rows={8}
|
||||
minH={'150px'}
|
||||
maxH={'400px'}
|
||||
placeholder={t('app:Tool_description')}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Flex mb={2} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<FormLabel>{t('common:core.module.Http request settings')}</FormLabel>
|
||||
<Button size={'sm'} onClick={onOpenCurlImport}>
|
||||
{t('common:core.module.http.curl import')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<MySelect
|
||||
h={9}
|
||||
w={'100px'}
|
||||
value={method}
|
||||
list={HTTP_METHODS.map((method) => ({ label: method, value: method }))}
|
||||
onChange={(e) => setValue('method', e)}
|
||||
/>
|
||||
<Input
|
||||
{...register('path', { required: true })}
|
||||
placeholder={t('common:core.module.input.label.Http Request Url')}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box alignItems={'center'}>
|
||||
<FormLabel mb={0}>{t('common:auth_config')}</FormLabel>
|
||||
<Box>
|
||||
<HeaderAuthForm
|
||||
headerSecretValue={storeHeader2HeaderValue(headerSecret)}
|
||||
onChange={(data) => {
|
||||
const storeData = headerValue2StoreHeader(data);
|
||||
setValue('headerSecret', storeData);
|
||||
}}
|
||||
fontWeight="normal"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<Flex flex={1} px={9} py={3} flexDirection={'column'} gap={6}>
|
||||
<Box>
|
||||
<Flex alignItems={'center'} mb={2}>
|
||||
<FormLabel flex={1}>{t('app:Custom_params')}</FormLabel>
|
||||
<Button
|
||||
size={'sm'}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'14px'} />}
|
||||
onClick={() => {
|
||||
setEditingParam({
|
||||
key: '',
|
||||
description: '',
|
||||
type: 'string',
|
||||
required: false,
|
||||
isTool: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common:add_new')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<CustomParamsTable
|
||||
list={customParams}
|
||||
onEdit={(param) => {
|
||||
setEditingParam(param);
|
||||
}}
|
||||
onDelete={(index) => {
|
||||
setValue(
|
||||
'customParams',
|
||||
customParams.filter((_, i) => i !== index)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<FormLabel mb={2}>Params</FormLabel>
|
||||
<ParamsTable list={params} setList={(newParams) => setValue('params', newParams)} />
|
||||
</Box>
|
||||
{hasBody && (
|
||||
<Box>
|
||||
<FormLabel mb={2}>Body</FormLabel>
|
||||
<Flex
|
||||
mb={2}
|
||||
p={1}
|
||||
flexWrap={'nowrap'}
|
||||
bg={'myGray.25'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'8px'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
{Object.values(ContentTypes).map((type) => (
|
||||
<Box
|
||||
key={type}
|
||||
cursor={'pointer'}
|
||||
px={3}
|
||||
py={1.5}
|
||||
fontSize={'12px'}
|
||||
fontWeight={'medium'}
|
||||
color={'myGray.500'}
|
||||
borderRadius={'6px'}
|
||||
bg={bodyType === type ? 'white' : 'none'}
|
||||
boxShadow={
|
||||
bodyType === type
|
||||
? '0 1px 2px 0 rgba(19, 51, 107, 0.10), 0 0 1px 0 rgba(19, 51, 107, 0.15)'
|
||||
: ''
|
||||
}
|
||||
onClick={() => setValue('bodyType', type)}
|
||||
>
|
||||
{type}
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
{isContentBody && (
|
||||
<Textarea
|
||||
value={bodyContent}
|
||||
onChange={(e) => setValue('bodyContent', e.target.value)}
|
||||
onBlur={(e) => {
|
||||
if (bodyType === ContentTypes.json && e.target.value) {
|
||||
try {
|
||||
JSON.parse(e.target.value);
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:json_parse_error')
|
||||
});
|
||||
}
|
||||
}
|
||||
}}
|
||||
minH={'100px'}
|
||||
maxH={'200px'}
|
||||
/>
|
||||
)}
|
||||
{isFormBody && (
|
||||
<ParamsTable
|
||||
list={bodyFormData}
|
||||
setList={(newFormData) => setValue('bodyFormData', newFormData)}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<FormLabel mb={2}>Headers</FormLabel>
|
||||
<ParamsTable
|
||||
list={headers}
|
||||
setList={(newHeaders) => setValue('headers', newHeaders)}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit((data) => onSubmit(data))} isLoading={isSubmitting}>
|
||||
{t('common:Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
{isOpenCurlImport && (
|
||||
<CurlImportModal
|
||||
onClose={onCloseCurlImport}
|
||||
onImport={(result) => {
|
||||
setValue('method', result.method);
|
||||
setValue('path', result.path);
|
||||
if (result.params) {
|
||||
setValue('params', result.params);
|
||||
}
|
||||
if (result.headers) {
|
||||
setValue('headers', result.headers);
|
||||
}
|
||||
setValue('bodyType', result.bodyType as ContentTypes);
|
||||
if (result.bodyContent) {
|
||||
setValue('bodyContent', result.bodyContent);
|
||||
}
|
||||
if (result.bodyFormData) {
|
||||
setValue('bodyFormData', result.bodyFormData);
|
||||
}
|
||||
onCloseCurlImport();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{editingParam && (
|
||||
<CustomParamEditModal
|
||||
param={editingParam}
|
||||
onClose={() => setEditingParam(null)}
|
||||
onConfirm={(newParam) => {
|
||||
if (editingParam.key) {
|
||||
setValue(
|
||||
'customParams',
|
||||
customParams.map((param) => (param.key === editingParam.key ? newParam : param))
|
||||
);
|
||||
} else {
|
||||
setValue('customParams', [...customParams, newParam]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomParamEditModal = ({
|
||||
param,
|
||||
onClose,
|
||||
onConfirm
|
||||
}: {
|
||||
param: CustomParamItemType;
|
||||
onClose: () => void;
|
||||
onConfirm: (param: CustomParamItemType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const isEdit = !!param.key;
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm<CustomParamItemType>({
|
||||
defaultValues: param
|
||||
});
|
||||
|
||||
const type = watch('type');
|
||||
const required = watch('required');
|
||||
const isTool = watch('isTool');
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
iconSrc={isEdit ? 'modal/edit' : 'common/addLight'}
|
||||
iconColor={'primary.600'}
|
||||
title={isEdit ? t('app:edit_param') : t('common:add_new_param')}
|
||||
w={500}
|
||||
>
|
||||
<ModalBody px={9}>
|
||||
<Flex mb={6} alignItems={'center'}>
|
||||
<FormLabel w={'120px'}>{t('common:core.module.http.Props name')}</FormLabel>
|
||||
<Input
|
||||
{...register('key', { required: true })}
|
||||
placeholder={t('common:core.module.http.Props name')}
|
||||
bg={'myGray.50'}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={6}>
|
||||
<FormLabel w={'120px'}>{t('common:plugin.Description')}</FormLabel>
|
||||
<Textarea
|
||||
{...register('description', { required: isTool })}
|
||||
rows={4}
|
||||
placeholder={t('app:tool_params_description_tips')}
|
||||
bg={'myGray.50'}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={6} alignItems={'center'}>
|
||||
<FormLabel w={'120px'}>{t('common:core.module.Data Type')}</FormLabel>
|
||||
<MySelect
|
||||
value={type}
|
||||
list={toolValueTypeList}
|
||||
onChange={(val) => setValue('type', val)}
|
||||
flex={1}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={6} alignItems={'center'}>
|
||||
<FormLabel w={'120px'}>{t('common:Required_input')}</FormLabel>
|
||||
<Switch isChecked={required} onChange={(e) => setValue('required', e.target.checked)} />
|
||||
</Flex>
|
||||
|
||||
<Flex mb={6} alignItems={'center'}>
|
||||
<FormLabel w={'120px'}>{t('workflow:field_used_as_tool_input')}</FormLabel>
|
||||
<Switch isChecked={isTool} onChange={(e) => setValue('isTool', e.target.checked)} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:Close')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit((data) => {
|
||||
onConfirm(data);
|
||||
onClose();
|
||||
})}
|
||||
>
|
||||
{t('common:Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomParamsTable = ({
|
||||
list,
|
||||
onEdit,
|
||||
onDelete
|
||||
}: {
|
||||
list: CustomParamItemType[];
|
||||
onEdit: (param: CustomParamItemType) => void;
|
||||
onDelete: (index: number) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom={'none'}
|
||||
bg={'white'}
|
||||
>
|
||||
<TableContainer overflowY={'visible'} overflowX={'unset'}>
|
||||
<Table size={'sm'}>
|
||||
<Thead>
|
||||
<Tr bg={'myGray.50'} h={8}>
|
||||
<Th px={2}>{t('common:core.module.http.Props name')}</Th>
|
||||
<Th px={2}>{t('common:plugin.Description')}</Th>
|
||||
<Th px={2}>{t('common:support.standard.type')}</Th>
|
||||
<Th px={2}>{t('app:type.Tool')}</Th>
|
||||
<Th px={2}>{t('common:Operation')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{list.map((item, index) => (
|
||||
<Tr key={index} h={8}>
|
||||
<Td px={2}>{item.key}</Td>
|
||||
<Td px={2}>{item.description}</Td>
|
||||
<Td px={2}>{item.type}</Td>
|
||||
<Td px={2}>{item.isTool ? t('common:yes') : t('common:no')}</Td>
|
||||
<Td px={2}>
|
||||
<Flex gap={2}>
|
||||
<MyIcon
|
||||
name={'edit'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
w={'14px'}
|
||||
onClick={() => onEdit(item)}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
w={'14px'}
|
||||
onClick={() => onDelete(index)}
|
||||
/>
|
||||
</Flex>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const ParamsTable = ({
|
||||
list,
|
||||
setList
|
||||
}: {
|
||||
list: ParamItemType[];
|
||||
setList: (list: ParamItemType[]) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [updateTrigger, setUpdateTrigger] = useState(false);
|
||||
|
||||
return (
|
||||
<Box borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom={'none'}>
|
||||
<TableContainer overflowY={'visible'} overflowX={'unset'}>
|
||||
<Table size={'sm'}>
|
||||
<Thead>
|
||||
<Tr bg={'myGray.50'} h={8}>
|
||||
<Th px={2}>{t('common:core.module.http.Props name')}</Th>
|
||||
<Th px={2}>{t('common:core.module.http.Props value')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{[...list, { key: '', value: '' }].map((item, index) => (
|
||||
<Tr key={index}>
|
||||
<Td w={1 / 2} p={0} borderRight={'1px solid'} borderColor={'myGray.150'}>
|
||||
<HttpInput
|
||||
placeholder={'key'}
|
||||
value={item.key}
|
||||
onBlur={(val) => {
|
||||
if (!val) return;
|
||||
|
||||
if (list.find((item, i) => i !== index && item.key === val)) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.module.http.Key already exists')
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (index === list.length) {
|
||||
setList([...list, { key: val, value: '' }]);
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
} else {
|
||||
setList(list.map((p, i) => (i === index ? { ...p, key: val } : p)));
|
||||
}
|
||||
}}
|
||||
updateTrigger={updateTrigger}
|
||||
/>
|
||||
</Td>
|
||||
<Td w={1 / 2} p={0} borderColor={'myGray.150'}>
|
||||
<Box display={'flex'} alignItems={'center'}>
|
||||
<HttpInput
|
||||
placeholder={'value'}
|
||||
value={item.value}
|
||||
onBlur={(val) =>
|
||||
setList(list.map((p, i) => (i === index ? { ...p, value: val } : p)))
|
||||
}
|
||||
/>
|
||||
{index !== list.length && (
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
w={'14px'}
|
||||
mx={'2'}
|
||||
display={'block'}
|
||||
onClick={() => setList(list.filter((_, i) => i !== index))}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ManualToolModal);
|
@@ -25,6 +25,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
ContentTypes,
|
||||
HTTP_METHODS,
|
||||
NodeInputKeyEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
@@ -198,28 +199,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
bg={'white'}
|
||||
width={'100%'}
|
||||
value={requestMethods?.value}
|
||||
list={[
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET'
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST'
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 'PUT'
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 'DELETE'
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 'PATCH'
|
||||
}
|
||||
]}
|
||||
list={HTTP_METHODS.map((method) => ({ label: method, value: method }))}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Flex, Button, ModalBody, Input, Textarea, ModalFooter } from '@chakra-ui/react';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -13,6 +13,8 @@ import { useContextSelector } from 'use-context-selector';
|
||||
import { AppListContext } from './context';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
export type HttpToolsType = {
|
||||
id?: string;
|
||||
@@ -35,6 +37,8 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
|
||||
const [createType, setCreateType] = useState<'batch' | 'manual'>('batch');
|
||||
|
||||
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||
|
||||
const { register, setValue, handleSubmit, watch } = useForm<HttpToolsType>({
|
||||
@@ -46,6 +50,7 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { runAsync: onCreate, loading: isCreating } = useRequest2(
|
||||
async (data: HttpToolsType) => {
|
||||
return postCreateHttpTools({
|
||||
createType,
|
||||
parentId,
|
||||
name: data.name,
|
||||
intro: data.intro,
|
||||
@@ -83,94 +88,91 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
||||
position={'relative'}
|
||||
>
|
||||
<ModalBody flex={'0 1 auto'} overflow={'auto'} pb={0} px={9}>
|
||||
<>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('common:input_name')}
|
||||
<Box color={'myGray.900'} fontWeight={'medium'} fontSize={'14px'}>
|
||||
{t('common:input_name')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<MyTooltip label={t('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:name_is_empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box color={'myGray.900'} fontWeight={'medium'} mt={6} fontSize={'14px'}>
|
||||
{t('common:core.app.App intro')}
|
||||
</Box>
|
||||
<Textarea
|
||||
{...register('intro')}
|
||||
bg={'myWhite.600'}
|
||||
h={'122px'}
|
||||
rows={3}
|
||||
mt={3}
|
||||
placeholder={t('common:core.app.Make a brief introduction of your app')}
|
||||
/>
|
||||
<Box display={'flex'} alignItems={'center'} py={1} gap={'281px'} mt={6}>
|
||||
<Box color={'myGray.900'} fontWeight={'medium'} fontSize={'14px'}>
|
||||
{t('app:HTTPTools_Create_Type')}
|
||||
</Box>
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<MyTooltip label={t('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:name_is_empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'} mt={6}>
|
||||
{t('common:core.app.App intro')}
|
||||
</Box>
|
||||
<Textarea
|
||||
{...register('intro')}
|
||||
bg={'myWhite.600'}
|
||||
h={'122px'}
|
||||
rows={3}
|
||||
mt={3}
|
||||
placeholder={t('common:core.app.Make a brief introduction of your app')}
|
||||
/>
|
||||
</>
|
||||
</>
|
||||
{/* <>
|
||||
<Box display={'flex'} alignItems={'center'} py={1} gap={'281px'} mt={6}>
|
||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
||||
{t('common:plugin.Create Type')}
|
||||
</Box>
|
||||
<Box
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
ml={'auto'}
|
||||
gap={'4px'}
|
||||
>
|
||||
<MyIcon name={'common/info'} w={'16px'} h={'16px'} />
|
||||
<Box
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
ml={'auto'}
|
||||
gap={'4px'}
|
||||
fontSize={'12px'}
|
||||
fontStyle={'normal'}
|
||||
fontWeight={'500'}
|
||||
lineHeight={'16px'}
|
||||
letterSpacing={'0.5px'}
|
||||
>
|
||||
<MyIcon name={'common/info'} w={'16px'} h={'16px'} />
|
||||
<Box
|
||||
fontSize={'12px'}
|
||||
fontStyle={'normal'}
|
||||
fontWeight={'500'}
|
||||
lineHeight={'16px'}
|
||||
letterSpacing={'0.5px'}
|
||||
>
|
||||
{t('common:plugin.Create Type Tip')}
|
||||
</Box>
|
||||
{t('app:HTTPTools_Create_Type_Tip')}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box mt={2}>
|
||||
<LeftRadio
|
||||
list={[
|
||||
{
|
||||
title: t('app:type.Http batch'),
|
||||
value: 'batch',
|
||||
desc: t('app:type.Http batch tip')
|
||||
},
|
||||
{
|
||||
title: t('app:type.Http manual'),
|
||||
value: 'manual',
|
||||
desc: t('app:type.Http manual tip')
|
||||
}
|
||||
]}
|
||||
value={createType}
|
||||
fontSize={'xs'}
|
||||
onChange={(e) => setCreateType(e as 'batch' | 'manual')}
|
||||
defaultBg={'white'}
|
||||
activeBg={'white'}
|
||||
/>
|
||||
</Box>
|
||||
</> */}
|
||||
</Box>
|
||||
<Box my={2}>
|
||||
<LeftRadio
|
||||
list={[
|
||||
{
|
||||
title: t('app:type.Http batch'),
|
||||
value: 'batch',
|
||||
desc: t('app:type.Http batch tip')
|
||||
},
|
||||
{
|
||||
title: t('app:type.Http manual'),
|
||||
value: 'manual',
|
||||
desc: t('app:type.Http manual tip')
|
||||
}
|
||||
]}
|
||||
value={createType}
|
||||
fontSize={'xs'}
|
||||
onChange={(e) => setCreateType(e as 'batch' | 'manual')}
|
||||
defaultBg={'white'}
|
||||
activeBg={'white'}
|
||||
py={2}
|
||||
px={3}
|
||||
gridGap={4}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter my={6} py={0} px={9}>
|
||||
<ModalFooter mt={4} mb={6} py={0} px={9}>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common:Close')}
|
||||
</Button>
|
||||
|
@@ -4,7 +4,6 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { onCreateApp, type CreateAppBody } from '../create';
|
||||
import { type AppSchema } from '@fastgpt/global/core/app/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
@@ -14,13 +13,15 @@ import { getHTTPToolSetRuntimeNode } from '@fastgpt/global/core/app/httpTools/ut
|
||||
|
||||
export type createHttpToolsQuery = {};
|
||||
|
||||
export type createHttpToolsBody = Omit<CreateAppBody, 'type' | 'modules' | 'edges' | 'chatConfig'>;
|
||||
export type createHttpToolsBody = {
|
||||
createType: 'batch' | 'manual';
|
||||
} & Omit<CreateAppBody, 'type' | 'modules' | 'edges' | 'chatConfig'>;
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<createHttpToolsBody, createHttpToolsQuery>,
|
||||
res: ApiResponseType<string>
|
||||
): Promise<string> {
|
||||
const { name, avatar, intro, parentId } = req.body;
|
||||
const { name, avatar, intro, parentId, createType } = req.body;
|
||||
|
||||
const { teamId, tmbId, userId } = parentId
|
||||
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||
@@ -40,7 +41,14 @@ async function handler(
|
||||
modules: [
|
||||
getHTTPToolSetRuntimeNode({
|
||||
name,
|
||||
avatar
|
||||
avatar,
|
||||
toolList: [],
|
||||
...(createType === 'batch' && {
|
||||
baseUrl: '',
|
||||
apiSchemaStr: '',
|
||||
customHeaders: '{}',
|
||||
headerSecret: {}
|
||||
})
|
||||
})
|
||||
],
|
||||
session
|
||||
|
@@ -3,6 +3,7 @@ import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/nex
|
||||
import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||
import type { RunHTTPToolResult } from '@fastgpt/service/core/app/http';
|
||||
import { runHTTPTool } from '@fastgpt/service/core/app/http';
|
||||
import type { HttpToolConfigType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
export type RunHTTPToolQuery = {};
|
||||
|
||||
@@ -13,6 +14,9 @@ export type RunHTTPToolBody = {
|
||||
method: string;
|
||||
customHeaders?: Record<string, string>;
|
||||
headerSecret?: StoreSecretValueType;
|
||||
staticParams?: HttpToolConfigType['staticParams'];
|
||||
staticHeaders?: HttpToolConfigType['staticHeaders'];
|
||||
staticBody?: HttpToolConfigType['staticBody'];
|
||||
};
|
||||
|
||||
export type RunHTTPToolResponse = RunHTTPToolResult;
|
||||
@@ -21,7 +25,17 @@ async function handler(
|
||||
req: ApiRequestProps<RunHTTPToolBody, RunHTTPToolQuery>,
|
||||
res: ApiResponseType<RunHTTPToolResponse>
|
||||
): Promise<RunHTTPToolResponse> {
|
||||
const { params, baseUrl, toolPath, method = 'POST', customHeaders, headerSecret } = req.body;
|
||||
const {
|
||||
params,
|
||||
baseUrl,
|
||||
toolPath,
|
||||
method = 'POST',
|
||||
customHeaders,
|
||||
headerSecret,
|
||||
staticParams,
|
||||
staticHeaders,
|
||||
staticBody
|
||||
} = req.body;
|
||||
|
||||
return runHTTPTool({
|
||||
baseUrl,
|
||||
@@ -29,7 +43,10 @@ async function handler(
|
||||
method,
|
||||
params,
|
||||
headerSecret,
|
||||
customHeaders
|
||||
customHeaders,
|
||||
staticParams,
|
||||
staticHeaders,
|
||||
staticBody
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -13,10 +13,10 @@ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
|
||||
export type UpdateHttpPluginBody = {
|
||||
appId: string;
|
||||
baseUrl: string;
|
||||
apiSchemaStr: string;
|
||||
toolList: HttpToolConfigType[];
|
||||
headerSecret: StoreSecretValueType;
|
||||
baseUrl?: string;
|
||||
apiSchemaStr?: string;
|
||||
headerSecret?: StoreSecretValueType;
|
||||
customHeaders?: string;
|
||||
};
|
||||
|
||||
@@ -27,12 +27,17 @@ async function handler(req: ApiRequestProps<UpdateHttpPluginBody>, res: NextApiR
|
||||
|
||||
const formatedHeaderAuth = storeSecretValue(headerSecret);
|
||||
|
||||
const formattedToolList = toolList.map((tool) => ({
|
||||
...tool,
|
||||
headerSecret: tool.headerSecret ? storeSecretValue(tool.headerSecret) : undefined
|
||||
}));
|
||||
|
||||
const toolSetRuntimeNode = getHTTPToolSetRuntimeNode({
|
||||
name: app.name,
|
||||
avatar: app.avatar,
|
||||
baseUrl,
|
||||
apiSchemaStr,
|
||||
toolList,
|
||||
toolList: formattedToolList,
|
||||
headerSecret: formatedHeaderAuth,
|
||||
customHeaders
|
||||
});
|
||||
|
Reference in New Issue
Block a user