mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-17 08:37:59 +00:00
add manual create http toolset (#5743)
* add manual create http toolset * optimize code * optimize * fix * fix
This commit is contained in:
@@ -13,9 +13,9 @@ import { i18nT } from '../../../../web/i18n/utils';
|
||||
export const getHTTPToolSetRuntimeNode = ({
|
||||
name,
|
||||
avatar,
|
||||
baseUrl = '',
|
||||
customHeaders = '',
|
||||
apiSchemaStr = '',
|
||||
baseUrl,
|
||||
customHeaders,
|
||||
apiSchemaStr,
|
||||
toolList = [],
|
||||
headerSecret
|
||||
}: {
|
||||
@@ -34,12 +34,11 @@ export const getHTTPToolSetRuntimeNode = ({
|
||||
intro: 'HTTP Tools',
|
||||
toolConfig: {
|
||||
httpToolSet: {
|
||||
baseUrl,
|
||||
toolList,
|
||||
headerSecret,
|
||||
customHeaders,
|
||||
apiSchemaStr,
|
||||
toolId: ''
|
||||
...(baseUrl !== undefined && { baseUrl }),
|
||||
...(apiSchemaStr !== undefined && { apiSchemaStr }),
|
||||
...(customHeaders !== undefined && { customHeaders }),
|
||||
...(headerSecret !== undefined && { headerSecret })
|
||||
}
|
||||
},
|
||||
inputs: [],
|
||||
|
11
packages/global/core/app/type.d.ts
vendored
11
packages/global/core/app/type.d.ts
vendored
@@ -2,6 +2,7 @@ import type { FlowNodeTemplateType, StoreNodeItemType } from '../workflow/type/n
|
||||
import type { AppTypeEnum } from './constants';
|
||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||
import type {
|
||||
ContentTypes,
|
||||
NodeInputKeyEnum,
|
||||
VariableInputEnum,
|
||||
WorkflowIOValueTypeEnum
|
||||
@@ -127,6 +128,16 @@ export type HttpToolConfigType = {
|
||||
outputSchema: JSONSchemaOutputType;
|
||||
path: string;
|
||||
method: string;
|
||||
|
||||
// manual
|
||||
staticParams?: Array<{ key: string; value: string }>;
|
||||
staticHeaders?: Array<{ key: string; value: string }>;
|
||||
staticBody?: {
|
||||
type: ContentTypes;
|
||||
content?: string;
|
||||
formData?: Array<{ key: string; value: string }>;
|
||||
};
|
||||
headerSecret?: StoreSecretValueType;
|
||||
};
|
||||
|
||||
/* app chat config type */
|
||||
|
@@ -477,6 +477,19 @@ export enum ContentTypes {
|
||||
raw = 'raw-text'
|
||||
}
|
||||
|
||||
export const contentTypeMap = {
|
||||
[ContentTypes.none]: '',
|
||||
[ContentTypes.formData]: '',
|
||||
[ContentTypes.xWwwFormUrlencoded]: 'application/x-www-form-urlencoded',
|
||||
[ContentTypes.json]: 'application/json',
|
||||
[ContentTypes.xml]: 'application/xml',
|
||||
[ContentTypes.raw]: 'text/plain'
|
||||
};
|
||||
|
||||
// http request methods
|
||||
export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'] as const;
|
||||
export type HttpMethod = (typeof HTTP_METHODS)[number];
|
||||
|
||||
export const ArrayTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum> = {
|
||||
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
||||
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
|
||||
|
7
packages/global/core/workflow/type/node.d.ts
vendored
7
packages/global/core/workflow/type/node.d.ts
vendored
@@ -52,11 +52,10 @@ export type NodeToolConfigType = {
|
||||
}[];
|
||||
};
|
||||
httpToolSet?: {
|
||||
toolId: string;
|
||||
baseUrl: string;
|
||||
toolList: HttpToolConfigType[];
|
||||
apiSchemaStr: string;
|
||||
customHeaders: string;
|
||||
baseUrl?: string;
|
||||
apiSchemaStr?: string;
|
||||
customHeaders?: string;
|
||||
headerSecret?: StoreSecretValueType;
|
||||
};
|
||||
httpTool?: {
|
||||
|
@@ -3,6 +3,8 @@ import { getSecretValue } from '../../common/secret/utils';
|
||||
import axios from 'axios';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { HttpToolConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { contentTypeMap, ContentTypes } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export type RunHTTPToolParams = {
|
||||
baseUrl: string;
|
||||
@@ -11,6 +13,9 @@ export type RunHTTPToolParams = {
|
||||
params: Record<string, any>;
|
||||
headerSecret?: StoreSecretValueType;
|
||||
customHeaders?: Record<string, string>;
|
||||
staticParams?: HttpToolConfigType['staticParams'];
|
||||
staticHeaders?: HttpToolConfigType['staticHeaders'];
|
||||
staticBody?: HttpToolConfigType['staticBody'];
|
||||
};
|
||||
|
||||
export type RunHTTPToolResult = RequireOnlyOne<{
|
||||
@@ -18,41 +23,130 @@ export type RunHTTPToolResult = RequireOnlyOne<{
|
||||
errorMsg?: string;
|
||||
}>;
|
||||
|
||||
export async function runHTTPTool({
|
||||
const buildHttpRequest = ({
|
||||
method,
|
||||
params,
|
||||
headerSecret,
|
||||
customHeaders,
|
||||
staticParams,
|
||||
staticHeaders,
|
||||
staticBody
|
||||
}: Omit<RunHTTPToolParams, 'baseUrl' | 'toolPath'>) => {
|
||||
const body = (() => {
|
||||
if (!staticBody || staticBody.type === ContentTypes.none) {
|
||||
return ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) ? params : undefined;
|
||||
}
|
||||
|
||||
if (staticBody.type === ContentTypes.json) {
|
||||
const staticContent = staticBody.content ? JSON.parse(staticBody.content) : {};
|
||||
return { ...staticContent, ...params };
|
||||
}
|
||||
|
||||
if (staticBody.type === ContentTypes.formData) {
|
||||
const formData = new (require('form-data'))();
|
||||
staticBody.formData?.forEach(({ key, value }) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
formData.append(key, value);
|
||||
});
|
||||
return formData;
|
||||
}
|
||||
|
||||
if (staticBody.type === ContentTypes.xWwwFormUrlencoded) {
|
||||
const urlencoded = new URLSearchParams();
|
||||
staticBody.formData?.forEach(({ key, value }) => {
|
||||
urlencoded.append(key, value);
|
||||
});
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
urlencoded.append(key, String(value));
|
||||
});
|
||||
return urlencoded.toString();
|
||||
}
|
||||
|
||||
if (staticBody.type === ContentTypes.xml || staticBody.type === ContentTypes.raw) {
|
||||
return staticBody.content || '';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
const contentType = contentTypeMap[staticBody?.type || ContentTypes.none];
|
||||
const headers = {
|
||||
...(contentType && { 'Content-Type': contentType }),
|
||||
...(customHeaders || {}),
|
||||
...(headerSecret ? getSecretValue({ storeSecret: headerSecret }) : {}),
|
||||
...(staticHeaders?.reduce(
|
||||
(acc, { key, value }) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>
|
||||
) || {})
|
||||
};
|
||||
|
||||
const queryParams = (() => {
|
||||
const staticParamsObj =
|
||||
staticParams?.reduce(
|
||||
(acc, { key, value }) => {
|
||||
acc[key] = value;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) || {};
|
||||
|
||||
const mergedParams =
|
||||
method.toUpperCase() === 'GET' || staticParams
|
||||
? { ...staticParamsObj, ...params }
|
||||
: staticParamsObj;
|
||||
|
||||
return Object.keys(mergedParams).length > 0 ? mergedParams : undefined;
|
||||
})();
|
||||
|
||||
return {
|
||||
headers,
|
||||
body,
|
||||
queryParams
|
||||
};
|
||||
};
|
||||
|
||||
export const runHTTPTool = async ({
|
||||
baseUrl,
|
||||
toolPath,
|
||||
method = 'POST',
|
||||
params,
|
||||
headerSecret,
|
||||
customHeaders
|
||||
}: RunHTTPToolParams): Promise<RunHTTPToolResult> {
|
||||
customHeaders,
|
||||
staticParams,
|
||||
staticHeaders,
|
||||
staticBody
|
||||
}: RunHTTPToolParams): Promise<RunHTTPToolResult> => {
|
||||
try {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...(customHeaders || {}),
|
||||
...(headerSecret ? getSecretValue({ storeSecret: headerSecret }) : {})
|
||||
};
|
||||
const { headers, body, queryParams } = buildHttpRequest({
|
||||
method,
|
||||
params,
|
||||
headerSecret,
|
||||
customHeaders,
|
||||
staticParams,
|
||||
staticHeaders,
|
||||
staticBody
|
||||
});
|
||||
|
||||
const { data } = await axios({
|
||||
method: method.toUpperCase(),
|
||||
baseURL: baseUrl.startsWith('https://') ? baseUrl : `https://${baseUrl}`,
|
||||
url: toolPath,
|
||||
headers,
|
||||
data: params,
|
||||
params,
|
||||
data: body,
|
||||
params: queryParams,
|
||||
timeout: 300000,
|
||||
httpsAgent: new (require('https').Agent)({
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
data
|
||||
};
|
||||
return { data };
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return {
|
||||
errorMsg: getErrText(error)
|
||||
};
|
||||
return { errorMsg: getErrText(error) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -236,16 +236,19 @@ export const dispatchRunTool = async (props: RunToolProps): Promise<RunToolRespo
|
||||
}
|
||||
|
||||
const { data, errorMsg } = await runHTTPTool({
|
||||
baseUrl: baseUrl,
|
||||
baseUrl: baseUrl || '',
|
||||
toolPath: httpTool.path,
|
||||
method: httpTool.method,
|
||||
params,
|
||||
headerSecret,
|
||||
headerSecret: httpTool.headerSecret || headerSecret,
|
||||
customHeaders: customHeaders
|
||||
? typeof customHeaders === 'string'
|
||||
? JSON.parse(customHeaders)
|
||||
: customHeaders
|
||||
: undefined
|
||||
: undefined,
|
||||
staticParams: httpTool.staticParams,
|
||||
staticHeaders: httpTool.staticHeaders,
|
||||
staticBody: httpTool.staticBody
|
||||
});
|
||||
|
||||
if (errorMsg) {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import {
|
||||
contentTypeMap,
|
||||
ContentTypes,
|
||||
NodeInputKeyEnum,
|
||||
NodeOutputKeyEnum,
|
||||
@@ -59,15 +60,6 @@ type HttpResponse = DispatchNodeResultType<
|
||||
|
||||
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
|
||||
|
||||
const contentTypeMap = {
|
||||
[ContentTypes.none]: '',
|
||||
[ContentTypes.formData]: '',
|
||||
[ContentTypes.xWwwFormUrlencoded]: 'application/x-www-form-urlencoded',
|
||||
[ContentTypes.json]: 'application/json',
|
||||
[ContentTypes.xml]: 'application/xml',
|
||||
[ContentTypes.raw]: 'text/plain'
|
||||
};
|
||||
|
||||
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
|
||||
let {
|
||||
runningAppInfo: { id: appId, teamId, tmbId },
|
||||
|
@@ -25,6 +25,7 @@ const LeftRadio = <T = any,>({
|
||||
align = 'center',
|
||||
px = 3.5,
|
||||
py = 4,
|
||||
gridGap = [3, 5],
|
||||
defaultBg = 'myGray.50',
|
||||
activeBg = 'primary.50',
|
||||
onChange,
|
||||
@@ -75,7 +76,7 @@ const LeftRadio = <T = any,>({
|
||||
);
|
||||
|
||||
return (
|
||||
<Grid gridGap={[3, 5]} fontSize={['sm', 'md']} {...props}>
|
||||
<Grid gridGap={gridGap} fontSize={['sm', 'md']} {...props}>
|
||||
{list.map((item) => {
|
||||
const isActive = value === item.value;
|
||||
return (
|
||||
@@ -131,7 +132,7 @@ const LeftRadio = <T = any,>({
|
||||
lineHeight={1}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
<Box>{t(item.title as any)}</Box>
|
||||
<Box mb={1}>{t(item.title as any)}</Box>
|
||||
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
||||
</HStack>
|
||||
) : (
|
||||
|
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"Add_tool": "Add tool",
|
||||
"AutoOptimize": "Automatic optimization",
|
||||
"Click_to_delete_this_field": "Click to delete this field",
|
||||
"Custom_params": "Custom parameters",
|
||||
"Edit_tool": "Edit tool",
|
||||
"Filed_is_deprecated": "This field is deprecated",
|
||||
"HTTPTools_Create_Type": "Create Type",
|
||||
"HTTPTools_Create_Type_Tip": "Modification is not supported after selection",
|
||||
"HTTP_tools_list_with_number": "Tool list: {{total}}",
|
||||
"Index": "Index",
|
||||
"MCP_tools_debug": "debug",
|
||||
@@ -30,6 +35,7 @@
|
||||
"Selected": "Selected",
|
||||
"Start_config": "Start configuration",
|
||||
"Team_Tags": "Team tags",
|
||||
"Tool_name": "Tool name",
|
||||
"ai_point_price": "Billing",
|
||||
"ai_settings": "AI Configuration",
|
||||
"all_apps": "All Applications",
|
||||
@@ -283,6 +289,7 @@
|
||||
"tool_detail": "Tool details",
|
||||
"tool_input_param_tip": "This plugin requires configuration of related information to run properly.",
|
||||
"tool_not_active": "This tool has not been activated yet",
|
||||
"tool_params_description_tips": "The description of parameter functions, if used as tool invocation parameters, affects the model tool invocation effect.",
|
||||
"tool_run_free": "This tool runs without points consumption",
|
||||
"tool_tip": "When executed as a tool, is this field used as a tool response result?",
|
||||
"tool_type_tools": "tool",
|
||||
|
@@ -1,7 +1,12 @@
|
||||
{
|
||||
"Add_tool": "添加工具",
|
||||
"AutoOptimize": "自动优化",
|
||||
"Click_to_delete_this_field": "点击删除该字段",
|
||||
"Custom_params": "自定义参数",
|
||||
"Edit_tool": "编辑工具",
|
||||
"Filed_is_deprecated": "该字段已弃用",
|
||||
"HTTPTools_Create_Type": "创建方式",
|
||||
"HTTPTools_Create_Type_Tip": "选择后不支持修改",
|
||||
"HTTP_tools_detail": "查看详情",
|
||||
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
||||
"Index": "索引",
|
||||
@@ -31,6 +36,8 @@
|
||||
"Selected": "已选择",
|
||||
"Start_config": "开始配置",
|
||||
"Team_Tags": "团队标签",
|
||||
"Tool_description": "工具描述",
|
||||
"Tool_name": "工具名称",
|
||||
"ai_point_price": "AI积分计费",
|
||||
"ai_settings": "AI 配置",
|
||||
"all_apps": "全部应用",
|
||||
@@ -90,6 +97,7 @@
|
||||
"document_upload": "文档上传",
|
||||
"edit_app": "应用详情",
|
||||
"edit_info": "编辑信息",
|
||||
"edit_param": "编辑参数",
|
||||
"execute_time": "执行时间",
|
||||
"export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||
"export_configs": "导出配置",
|
||||
@@ -297,6 +305,7 @@
|
||||
"tool_detail": "工具详情",
|
||||
"tool_input_param_tip": "该插件正常运行需要配置相关信息",
|
||||
"tool_not_active": "该工具尚未激活",
|
||||
"tool_params_description_tips": "参数功能的描述,若作为工具调用参数,影响模型工具调用效果",
|
||||
"tool_run_free": "该工具运行无积分消耗",
|
||||
"tool_tip": "作为工具执行时,该字段是否作为工具响应结果",
|
||||
"tool_type_tools": "工具",
|
||||
|
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"Add_tool": "添加工具",
|
||||
"AutoOptimize": "自動優化",
|
||||
"Click_to_delete_this_field": "點擊刪除該字段",
|
||||
"Custom_params": "自定義參數",
|
||||
"Filed_is_deprecated": "該字段已棄用",
|
||||
"HTTPTools_Create_Type": "創建方式",
|
||||
"HTTPTools_Create_Type_Tip": "選擇後不支持修改",
|
||||
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
||||
"Index": "索引",
|
||||
"MCP_tools_debug": "偵錯",
|
||||
@@ -30,6 +34,8 @@
|
||||
"Selected": "已選擇",
|
||||
"Start_config": "開始配置",
|
||||
"Team_Tags": "團隊標籤",
|
||||
"Tool_description": "工具描述",
|
||||
"Tool_name": "工具名稱",
|
||||
"ai_point_price": "AI 積分計費",
|
||||
"ai_settings": "AI 設定",
|
||||
"all_apps": "所有應用程式",
|
||||
@@ -283,6 +289,7 @@
|
||||
"tool_detail": "工具詳情",
|
||||
"tool_input_param_tip": "這個外掛正常執行需要設定相關資訊",
|
||||
"tool_not_active": "該工具尚未激活",
|
||||
"tool_params_description_tips": "參數功能的描述,若作為工具調用參數,影響模型工具調用效果",
|
||||
"tool_run_free": "該工具運行無積分消耗",
|
||||
"tool_tip": "作為工具執行時,該字段是否作為工具響應結果",
|
||||
"tool_type_tools": "工具",
|
||||
|
@@ -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