mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-17 16:45:02 +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 = ({
|
export const getHTTPToolSetRuntimeNode = ({
|
||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
baseUrl = '',
|
baseUrl,
|
||||||
customHeaders = '',
|
customHeaders,
|
||||||
apiSchemaStr = '',
|
apiSchemaStr,
|
||||||
toolList = [],
|
toolList = [],
|
||||||
headerSecret
|
headerSecret
|
||||||
}: {
|
}: {
|
||||||
@@ -34,12 +34,11 @@ export const getHTTPToolSetRuntimeNode = ({
|
|||||||
intro: 'HTTP Tools',
|
intro: 'HTTP Tools',
|
||||||
toolConfig: {
|
toolConfig: {
|
||||||
httpToolSet: {
|
httpToolSet: {
|
||||||
baseUrl,
|
|
||||||
toolList,
|
toolList,
|
||||||
headerSecret,
|
...(baseUrl !== undefined && { baseUrl }),
|
||||||
customHeaders,
|
...(apiSchemaStr !== undefined && { apiSchemaStr }),
|
||||||
apiSchemaStr,
|
...(customHeaders !== undefined && { customHeaders }),
|
||||||
toolId: ''
|
...(headerSecret !== undefined && { headerSecret })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inputs: [],
|
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 type { AppTypeEnum } from './constants';
|
||||||
import { PermissionTypeEnum } from '../../support/permission/constant';
|
import { PermissionTypeEnum } from '../../support/permission/constant';
|
||||||
import type {
|
import type {
|
||||||
|
ContentTypes,
|
||||||
NodeInputKeyEnum,
|
NodeInputKeyEnum,
|
||||||
VariableInputEnum,
|
VariableInputEnum,
|
||||||
WorkflowIOValueTypeEnum
|
WorkflowIOValueTypeEnum
|
||||||
@@ -127,6 +128,16 @@ export type HttpToolConfigType = {
|
|||||||
outputSchema: JSONSchemaOutputType;
|
outputSchema: JSONSchemaOutputType;
|
||||||
path: string;
|
path: string;
|
||||||
method: 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 */
|
/* app chat config type */
|
||||||
|
@@ -477,6 +477,19 @@ export enum ContentTypes {
|
|||||||
raw = 'raw-text'
|
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> = {
|
export const ArrayTypeMap: Record<WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum> = {
|
||||||
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
[WorkflowIOValueTypeEnum.string]: WorkflowIOValueTypeEnum.arrayString,
|
||||||
[WorkflowIOValueTypeEnum.number]: WorkflowIOValueTypeEnum.arrayNumber,
|
[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?: {
|
httpToolSet?: {
|
||||||
toolId: string;
|
|
||||||
baseUrl: string;
|
|
||||||
toolList: HttpToolConfigType[];
|
toolList: HttpToolConfigType[];
|
||||||
apiSchemaStr: string;
|
baseUrl?: string;
|
||||||
customHeaders: string;
|
apiSchemaStr?: string;
|
||||||
|
customHeaders?: string;
|
||||||
headerSecret?: StoreSecretValueType;
|
headerSecret?: StoreSecretValueType;
|
||||||
};
|
};
|
||||||
httpTool?: {
|
httpTool?: {
|
||||||
|
@@ -3,6 +3,8 @@ import { getSecretValue } from '../../common/secret/utils';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/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 = {
|
export type RunHTTPToolParams = {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
@@ -11,6 +13,9 @@ export type RunHTTPToolParams = {
|
|||||||
params: Record<string, any>;
|
params: Record<string, any>;
|
||||||
headerSecret?: StoreSecretValueType;
|
headerSecret?: StoreSecretValueType;
|
||||||
customHeaders?: Record<string, string>;
|
customHeaders?: Record<string, string>;
|
||||||
|
staticParams?: HttpToolConfigType['staticParams'];
|
||||||
|
staticHeaders?: HttpToolConfigType['staticHeaders'];
|
||||||
|
staticBody?: HttpToolConfigType['staticBody'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RunHTTPToolResult = RequireOnlyOne<{
|
export type RunHTTPToolResult = RequireOnlyOne<{
|
||||||
@@ -18,41 +23,130 @@ export type RunHTTPToolResult = RequireOnlyOne<{
|
|||||||
errorMsg?: string;
|
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,
|
baseUrl,
|
||||||
toolPath,
|
toolPath,
|
||||||
method = 'POST',
|
method = 'POST',
|
||||||
params,
|
params,
|
||||||
headerSecret,
|
headerSecret,
|
||||||
customHeaders
|
customHeaders,
|
||||||
}: RunHTTPToolParams): Promise<RunHTTPToolResult> {
|
staticParams,
|
||||||
|
staticHeaders,
|
||||||
|
staticBody
|
||||||
|
}: RunHTTPToolParams): Promise<RunHTTPToolResult> => {
|
||||||
try {
|
try {
|
||||||
const headers = {
|
const { headers, body, queryParams } = buildHttpRequest({
|
||||||
'Content-Type': 'application/json',
|
method,
|
||||||
...(customHeaders || {}),
|
params,
|
||||||
...(headerSecret ? getSecretValue({ storeSecret: headerSecret }) : {})
|
headerSecret,
|
||||||
};
|
customHeaders,
|
||||||
|
staticParams,
|
||||||
|
staticHeaders,
|
||||||
|
staticBody
|
||||||
|
});
|
||||||
|
|
||||||
const { data } = await axios({
|
const { data } = await axios({
|
||||||
method: method.toUpperCase(),
|
method: method.toUpperCase(),
|
||||||
baseURL: baseUrl.startsWith('https://') ? baseUrl : `https://${baseUrl}`,
|
baseURL: baseUrl.startsWith('https://') ? baseUrl : `https://${baseUrl}`,
|
||||||
url: toolPath,
|
url: toolPath,
|
||||||
headers,
|
headers,
|
||||||
data: params,
|
data: body,
|
||||||
params,
|
params: queryParams,
|
||||||
timeout: 300000,
|
timeout: 300000,
|
||||||
httpsAgent: new (require('https').Agent)({
|
httpsAgent: new (require('https').Agent)({
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { data };
|
||||||
data
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
} 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({
|
const { data, errorMsg } = await runHTTPTool({
|
||||||
baseUrl: baseUrl,
|
baseUrl: baseUrl || '',
|
||||||
toolPath: httpTool.path,
|
toolPath: httpTool.path,
|
||||||
method: httpTool.method,
|
method: httpTool.method,
|
||||||
params,
|
params,
|
||||||
headerSecret,
|
headerSecret: httpTool.headerSecret || headerSecret,
|
||||||
customHeaders: customHeaders
|
customHeaders: customHeaders
|
||||||
? typeof customHeaders === 'string'
|
? typeof customHeaders === 'string'
|
||||||
? JSON.parse(customHeaders)
|
? JSON.parse(customHeaders)
|
||||||
: customHeaders
|
: customHeaders
|
||||||
: undefined
|
: undefined,
|
||||||
|
staticParams: httpTool.staticParams,
|
||||||
|
staticHeaders: httpTool.staticHeaders,
|
||||||
|
staticBody: httpTool.staticBody
|
||||||
});
|
});
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import {
|
import {
|
||||||
|
contentTypeMap,
|
||||||
ContentTypes,
|
ContentTypes,
|
||||||
NodeInputKeyEnum,
|
NodeInputKeyEnum,
|
||||||
NodeOutputKeyEnum,
|
NodeOutputKeyEnum,
|
||||||
@@ -59,15 +60,6 @@ type HttpResponse = DispatchNodeResultType<
|
|||||||
|
|
||||||
const UNDEFINED_SIGN = 'UNDEFINED_SIGN';
|
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> => {
|
export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<HttpResponse> => {
|
||||||
let {
|
let {
|
||||||
runningAppInfo: { id: appId, teamId, tmbId },
|
runningAppInfo: { id: appId, teamId, tmbId },
|
||||||
|
@@ -25,6 +25,7 @@ const LeftRadio = <T = any,>({
|
|||||||
align = 'center',
|
align = 'center',
|
||||||
px = 3.5,
|
px = 3.5,
|
||||||
py = 4,
|
py = 4,
|
||||||
|
gridGap = [3, 5],
|
||||||
defaultBg = 'myGray.50',
|
defaultBg = 'myGray.50',
|
||||||
activeBg = 'primary.50',
|
activeBg = 'primary.50',
|
||||||
onChange,
|
onChange,
|
||||||
@@ -75,7 +76,7 @@ const LeftRadio = <T = any,>({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid gridGap={[3, 5]} fontSize={['sm', 'md']} {...props}>
|
<Grid gridGap={gridGap} fontSize={['sm', 'md']} {...props}>
|
||||||
{list.map((item) => {
|
{list.map((item) => {
|
||||||
const isActive = value === item.value;
|
const isActive = value === item.value;
|
||||||
return (
|
return (
|
||||||
@@ -131,7 +132,7 @@ const LeftRadio = <T = any,>({
|
|||||||
lineHeight={1}
|
lineHeight={1}
|
||||||
color={'myGray.900'}
|
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'} />}
|
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
||||||
</HStack>
|
</HStack>
|
||||||
) : (
|
) : (
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"Add_tool": "Add tool",
|
||||||
"AutoOptimize": "Automatic optimization",
|
"AutoOptimize": "Automatic optimization",
|
||||||
"Click_to_delete_this_field": "Click to delete this field",
|
"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",
|
"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}}",
|
"HTTP_tools_list_with_number": "Tool list: {{total}}",
|
||||||
"Index": "Index",
|
"Index": "Index",
|
||||||
"MCP_tools_debug": "debug",
|
"MCP_tools_debug": "debug",
|
||||||
@@ -30,6 +35,7 @@
|
|||||||
"Selected": "Selected",
|
"Selected": "Selected",
|
||||||
"Start_config": "Start configuration",
|
"Start_config": "Start configuration",
|
||||||
"Team_Tags": "Team tags",
|
"Team_Tags": "Team tags",
|
||||||
|
"Tool_name": "Tool name",
|
||||||
"ai_point_price": "Billing",
|
"ai_point_price": "Billing",
|
||||||
"ai_settings": "AI Configuration",
|
"ai_settings": "AI Configuration",
|
||||||
"all_apps": "All Applications",
|
"all_apps": "All Applications",
|
||||||
@@ -283,6 +289,7 @@
|
|||||||
"tool_detail": "Tool details",
|
"tool_detail": "Tool details",
|
||||||
"tool_input_param_tip": "This plugin requires configuration of related information to run properly.",
|
"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_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_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_tip": "When executed as a tool, is this field used as a tool response result?",
|
||||||
"tool_type_tools": "tool",
|
"tool_type_tools": "tool",
|
||||||
|
@@ -1,7 +1,12 @@
|
|||||||
{
|
{
|
||||||
|
"Add_tool": "添加工具",
|
||||||
"AutoOptimize": "自动优化",
|
"AutoOptimize": "自动优化",
|
||||||
"Click_to_delete_this_field": "点击删除该字段",
|
"Click_to_delete_this_field": "点击删除该字段",
|
||||||
|
"Custom_params": "自定义参数",
|
||||||
|
"Edit_tool": "编辑工具",
|
||||||
"Filed_is_deprecated": "该字段已弃用",
|
"Filed_is_deprecated": "该字段已弃用",
|
||||||
|
"HTTPTools_Create_Type": "创建方式",
|
||||||
|
"HTTPTools_Create_Type_Tip": "选择后不支持修改",
|
||||||
"HTTP_tools_detail": "查看详情",
|
"HTTP_tools_detail": "查看详情",
|
||||||
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
||||||
"Index": "索引",
|
"Index": "索引",
|
||||||
@@ -31,6 +36,8 @@
|
|||||||
"Selected": "已选择",
|
"Selected": "已选择",
|
||||||
"Start_config": "开始配置",
|
"Start_config": "开始配置",
|
||||||
"Team_Tags": "团队标签",
|
"Team_Tags": "团队标签",
|
||||||
|
"Tool_description": "工具描述",
|
||||||
|
"Tool_name": "工具名称",
|
||||||
"ai_point_price": "AI积分计费",
|
"ai_point_price": "AI积分计费",
|
||||||
"ai_settings": "AI 配置",
|
"ai_settings": "AI 配置",
|
||||||
"all_apps": "全部应用",
|
"all_apps": "全部应用",
|
||||||
@@ -90,6 +97,7 @@
|
|||||||
"document_upload": "文档上传",
|
"document_upload": "文档上传",
|
||||||
"edit_app": "应用详情",
|
"edit_app": "应用详情",
|
||||||
"edit_info": "编辑信息",
|
"edit_info": "编辑信息",
|
||||||
|
"edit_param": "编辑参数",
|
||||||
"execute_time": "执行时间",
|
"execute_time": "执行时间",
|
||||||
"export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
"export_config_successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||||
"export_configs": "导出配置",
|
"export_configs": "导出配置",
|
||||||
@@ -297,6 +305,7 @@
|
|||||||
"tool_detail": "工具详情",
|
"tool_detail": "工具详情",
|
||||||
"tool_input_param_tip": "该插件正常运行需要配置相关信息",
|
"tool_input_param_tip": "该插件正常运行需要配置相关信息",
|
||||||
"tool_not_active": "该工具尚未激活",
|
"tool_not_active": "该工具尚未激活",
|
||||||
|
"tool_params_description_tips": "参数功能的描述,若作为工具调用参数,影响模型工具调用效果",
|
||||||
"tool_run_free": "该工具运行无积分消耗",
|
"tool_run_free": "该工具运行无积分消耗",
|
||||||
"tool_tip": "作为工具执行时,该字段是否作为工具响应结果",
|
"tool_tip": "作为工具执行时,该字段是否作为工具响应结果",
|
||||||
"tool_type_tools": "工具",
|
"tool_type_tools": "工具",
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"Add_tool": "添加工具",
|
||||||
"AutoOptimize": "自動優化",
|
"AutoOptimize": "自動優化",
|
||||||
"Click_to_delete_this_field": "點擊刪除該字段",
|
"Click_to_delete_this_field": "點擊刪除該字段",
|
||||||
|
"Custom_params": "自定義參數",
|
||||||
"Filed_is_deprecated": "該字段已棄用",
|
"Filed_is_deprecated": "該字段已棄用",
|
||||||
|
"HTTPTools_Create_Type": "創建方式",
|
||||||
|
"HTTPTools_Create_Type_Tip": "選擇後不支持修改",
|
||||||
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
"HTTP_tools_list_with_number": "工具列表: {{total}}",
|
||||||
"Index": "索引",
|
"Index": "索引",
|
||||||
"MCP_tools_debug": "偵錯",
|
"MCP_tools_debug": "偵錯",
|
||||||
@@ -30,6 +34,8 @@
|
|||||||
"Selected": "已選擇",
|
"Selected": "已選擇",
|
||||||
"Start_config": "開始配置",
|
"Start_config": "開始配置",
|
||||||
"Team_Tags": "團隊標籤",
|
"Team_Tags": "團隊標籤",
|
||||||
|
"Tool_description": "工具描述",
|
||||||
|
"Tool_name": "工具名稱",
|
||||||
"ai_point_price": "AI 積分計費",
|
"ai_point_price": "AI 積分計費",
|
||||||
"ai_settings": "AI 設定",
|
"ai_settings": "AI 設定",
|
||||||
"all_apps": "所有應用程式",
|
"all_apps": "所有應用程式",
|
||||||
@@ -283,6 +289,7 @@
|
|||||||
"tool_detail": "工具詳情",
|
"tool_detail": "工具詳情",
|
||||||
"tool_input_param_tip": "這個外掛正常執行需要設定相關資訊",
|
"tool_input_param_tip": "這個外掛正常執行需要設定相關資訊",
|
||||||
"tool_not_active": "該工具尚未激活",
|
"tool_not_active": "該工具尚未激活",
|
||||||
|
"tool_params_description_tips": "參數功能的描述,若作為工具調用參數,影響模型工具調用效果",
|
||||||
"tool_run_free": "該工具運行無積分消耗",
|
"tool_run_free": "該工具運行無積分消耗",
|
||||||
"tool_tip": "作為工具執行時,該字段是否作為工具響應結果",
|
"tool_tip": "作為工具執行時,該字段是否作為工具響應結果",
|
||||||
"tool_type_tools": "工具",
|
"tool_type_tools": "工具",
|
||||||
|
@@ -56,10 +56,13 @@ const ChatTest = ({
|
|||||||
return await postRunHTTPTool({
|
return await postRunHTTPTool({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
params: data,
|
params: data,
|
||||||
headerSecret,
|
headerSecret: currentTool.headerSecret || headerSecret,
|
||||||
toolPath: currentTool.path,
|
toolPath: currentTool.path,
|
||||||
method: currentTool.method,
|
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 baseUrl = toolSetData?.baseUrl ?? '';
|
||||||
const toolList = toolSetData?.toolList ?? [];
|
const toolList = toolSetData?.toolList ?? [];
|
||||||
const apiSchemaStr = toolSetData?.apiSchemaStr ?? '';
|
const apiSchemaStr = toolSetData?.apiSchemaStr;
|
||||||
const headerSecret = toolSetData?.headerSecret ?? {};
|
const headerSecret = toolSetData?.headerSecret ?? {};
|
||||||
const customHeaders = useMemo(() => {
|
const customHeaders = useMemo(() => {
|
||||||
try {
|
try {
|
||||||
|
@@ -14,6 +14,7 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
|||||||
import { putUpdateHttpPlugin } from '@/web/core/app/api/plugin';
|
import { putUpdateHttpPlugin } from '@/web/core/app/api/plugin';
|
||||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||||
import ConfigModal from './ConfigModal';
|
import ConfigModal from './ConfigModal';
|
||||||
|
import ManualToolModal from './ManualToolModal';
|
||||||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||||
import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpTools/update';
|
import type { UpdateHttpPluginBody } from '@/pages/api/core/app/httpTools/update';
|
||||||
|
|
||||||
@@ -36,7 +37,13 @@ const EditForm = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
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 [toolDetail, setToolDetail] = useState<HttpToolConfigType | null>(null);
|
||||||
|
const [editingManualTool, setEditingManualTool] = useState<HttpToolConfigType | null>(null);
|
||||||
|
|
||||||
|
const isBatchMode = apiSchemaStr !== undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
onOpen: onOpenConfigModal,
|
onOpen: onOpenConfigModal,
|
||||||
@@ -44,6 +51,28 @@ const EditForm = ({
|
|||||||
onClose: onCloseConfigModal
|
onClose: onCloseConfigModal
|
||||||
} = useDisclosure();
|
} = 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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box p={6}>
|
<Box p={6}>
|
||||||
@@ -54,21 +83,31 @@ const EditForm = ({
|
|||||||
total: toolList?.length || 0
|
total: toolList?.length || 0
|
||||||
})}
|
})}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Button
|
{isBatchMode ? (
|
||||||
px={'2'}
|
<Button
|
||||||
leftIcon={
|
px={'2'}
|
||||||
<MyIcon
|
leftIcon={
|
||||||
name={toolList?.length && toolList.length > 0 ? 'change' : 'common/setting'}
|
<MyIcon
|
||||||
w={'18px'}
|
name={toolList?.length && toolList.length > 0 ? 'change' : 'common/setting'}
|
||||||
/>
|
w={'18px'}
|
||||||
}
|
/>
|
||||||
onClick={onOpenConfigModal}
|
}
|
||||||
>
|
onClick={onOpenConfigModal}
|
||||||
{toolList?.length && toolList.length > 0 ? t('common:Config') : t('app:Start_config')}
|
>
|
||||||
</Button>
|
{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>
|
</Flex>
|
||||||
|
|
||||||
<Box mt={3}>
|
<MyBox mt={3} isLoading={isDeletingTool}>
|
||||||
{toolList?.map((tool, index) => {
|
{toolList?.map((tool, index) => {
|
||||||
return (
|
return (
|
||||||
<MyBox
|
<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%)"
|
bg="linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,1) 15%, rgba(255,255,255,1) 100%)"
|
||||||
paddingLeft="20px"
|
paddingLeft="20px"
|
||||||
>
|
>
|
||||||
<MyIconButton
|
{isBatchMode ? (
|
||||||
size={'16px'}
|
<MyIconButton
|
||||||
icon={'common/detail'}
|
size={'16px'}
|
||||||
p={2}
|
icon={'common/detail'}
|
||||||
border={'1px solid'}
|
p={2}
|
||||||
borderColor={'myGray.250'}
|
border={'1px solid'}
|
||||||
hoverBg={'rgba(51, 112, 255, 0.10)'}
|
borderColor={'myGray.250'}
|
||||||
hoverBorderColor={'primary.300'}
|
hoverBg={'rgba(51, 112, 255, 0.10)'}
|
||||||
tip={t('app:HTTP_tools_detail')}
|
hoverBorderColor={'primary.300'}
|
||||||
onClick={(e) => {
|
tip={t('app:HTTP_tools_detail')}
|
||||||
e.stopPropagation();
|
onClick={(e) => {
|
||||||
setToolDetail(tool);
|
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>
|
</Flex>
|
||||||
</MyBox>
|
</MyBox>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Box>
|
</MyBox>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{isOpenConfigModal && <ConfigModal onClose={onCloseConfigModal} />}
|
{isOpenConfigModal && <ConfigModal onClose={onCloseConfigModal} />}
|
||||||
|
{isOpenAddToolModal && <ManualToolModal onClose={onCloseAddToolModal} />}
|
||||||
{toolDetail && (
|
{toolDetail && (
|
||||||
<ToolDetailModal
|
<ToolDetailModal
|
||||||
tool={toolDetail}
|
tool={toolDetail}
|
||||||
@@ -181,6 +259,12 @@ const EditForm = ({
|
|||||||
customHeaders={customHeaders || '{}'}
|
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';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
ContentTypes,
|
ContentTypes,
|
||||||
|
HTTP_METHODS,
|
||||||
NodeInputKeyEnum,
|
NodeInputKeyEnum,
|
||||||
WorkflowIOValueTypeEnum
|
WorkflowIOValueTypeEnum
|
||||||
} from '@fastgpt/global/core/workflow/constants';
|
} from '@fastgpt/global/core/workflow/constants';
|
||||||
@@ -198,28 +199,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
|||||||
bg={'white'}
|
bg={'white'}
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
value={requestMethods?.value}
|
value={requestMethods?.value}
|
||||||
list={[
|
list={HTTP_METHODS.map((method) => ({ label: method, value: method }))}
|
||||||
{
|
|
||||||
label: 'GET',
|
|
||||||
value: 'GET'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'POST',
|
|
||||||
value: 'POST'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'PUT',
|
|
||||||
value: 'PUT'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'DELETE',
|
|
||||||
value: 'DELETE'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'PATCH',
|
|
||||||
value: 'PATCH'
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChangeNode({
|
onChangeNode({
|
||||||
nodeId,
|
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 { Box, Flex, Button, ModalBody, Input, Textarea, ModalFooter } from '@chakra-ui/react';
|
||||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@@ -13,6 +13,8 @@ import { useContextSelector } from 'use-context-selector';
|
|||||||
import { AppListContext } from './context';
|
import { AppListContext } from './context';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
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 = {
|
export type HttpToolsType = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -35,6 +37,8 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const [createType, setCreateType] = useState<'batch' | 'manual'>('batch');
|
||||||
|
|
||||||
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
const { parentId, loadMyApps } = useContextSelector(AppListContext, (v) => v);
|
||||||
|
|
||||||
const { register, setValue, handleSubmit, watch } = useForm<HttpToolsType>({
|
const { register, setValue, handleSubmit, watch } = useForm<HttpToolsType>({
|
||||||
@@ -46,6 +50,7 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
const { runAsync: onCreate, loading: isCreating } = useRequest2(
|
const { runAsync: onCreate, loading: isCreating } = useRequest2(
|
||||||
async (data: HttpToolsType) => {
|
async (data: HttpToolsType) => {
|
||||||
return postCreateHttpTools({
|
return postCreateHttpTools({
|
||||||
|
createType,
|
||||||
parentId,
|
parentId,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
intro: data.intro,
|
intro: data.intro,
|
||||||
@@ -83,94 +88,91 @@ const HttpPluginCreateModal = ({ onClose }: { onClose: () => void }) => {
|
|||||||
position={'relative'}
|
position={'relative'}
|
||||||
>
|
>
|
||||||
<ModalBody flex={'0 1 auto'} overflow={'auto'} pb={0} px={9}>
|
<ModalBody flex={'0 1 auto'} overflow={'auto'} pb={0} px={9}>
|
||||||
<>
|
<Box color={'myGray.900'} fontWeight={'medium'} fontSize={'14px'}>
|
||||||
<Box color={'myGray.800'} fontWeight={'bold'}>
|
{t('common:input_name')}
|
||||||
{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>
|
</Box>
|
||||||
<Flex mt={3} alignItems={'center'}>
|
<Box
|
||||||
<MyTooltip label={t('common:set_avatar')}>
|
display={'flex'}
|
||||||
<Avatar
|
justifyContent={'center'}
|
||||||
flexShrink={0}
|
alignItems={'center'}
|
||||||
src={avatar}
|
ml={'auto'}
|
||||||
w={['28px', '32px']}
|
gap={'4px'}
|
||||||
h={['28px', '32px']}
|
>
|
||||||
cursor={'pointer'}
|
<MyIcon name={'common/info'} w={'16px'} h={'16px'} />
|
||||||
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
|
<Box
|
||||||
display={'flex'}
|
fontSize={'12px'}
|
||||||
justifyContent={'center'}
|
fontStyle={'normal'}
|
||||||
alignItems={'center'}
|
fontWeight={'500'}
|
||||||
ml={'auto'}
|
lineHeight={'16px'}
|
||||||
gap={'4px'}
|
letterSpacing={'0.5px'}
|
||||||
>
|
>
|
||||||
<MyIcon name={'common/info'} w={'16px'} h={'16px'} />
|
{t('app:HTTPTools_Create_Type_Tip')}
|
||||||
<Box
|
|
||||||
fontSize={'12px'}
|
|
||||||
fontStyle={'normal'}
|
|
||||||
fontWeight={'500'}
|
|
||||||
lineHeight={'16px'}
|
|
||||||
letterSpacing={'0.5px'}
|
|
||||||
>
|
|
||||||
{t('common:plugin.Create Type Tip')}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box mt={2}>
|
</Box>
|
||||||
<LeftRadio
|
<Box my={2}>
|
||||||
list={[
|
<LeftRadio
|
||||||
{
|
list={[
|
||||||
title: t('app:type.Http batch'),
|
{
|
||||||
value: 'batch',
|
title: t('app:type.Http batch'),
|
||||||
desc: t('app:type.Http batch tip')
|
value: 'batch',
|
||||||
},
|
desc: t('app:type.Http batch tip')
|
||||||
{
|
},
|
||||||
title: t('app:type.Http manual'),
|
{
|
||||||
value: 'manual',
|
title: t('app:type.Http manual'),
|
||||||
desc: t('app:type.Http manual tip')
|
value: 'manual',
|
||||||
}
|
desc: t('app:type.Http manual tip')
|
||||||
]}
|
}
|
||||||
value={createType}
|
]}
|
||||||
fontSize={'xs'}
|
value={createType}
|
||||||
onChange={(e) => setCreateType(e as 'batch' | 'manual')}
|
fontSize={'xs'}
|
||||||
defaultBg={'white'}
|
onChange={(e) => setCreateType(e as 'batch' | 'manual')}
|
||||||
activeBg={'white'}
|
defaultBg={'white'}
|
||||||
/>
|
activeBg={'white'}
|
||||||
</Box>
|
py={2}
|
||||||
</> */}
|
px={3}
|
||||||
|
gridGap={4}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter my={6} py={0} px={9}>
|
<ModalFooter mt={4} mb={6} py={0} px={9}>
|
||||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||||
{t('common:Close')}
|
{t('common:Close')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -4,7 +4,6 @@ import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
|||||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { onCreateApp, type CreateAppBody } from '../create';
|
import { onCreateApp, type CreateAppBody } from '../create';
|
||||||
import { type AppSchema } from '@fastgpt/global/core/app/type';
|
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils';
|
||||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
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 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(
|
async function handler(
|
||||||
req: ApiRequestProps<createHttpToolsBody, createHttpToolsQuery>,
|
req: ApiRequestProps<createHttpToolsBody, createHttpToolsQuery>,
|
||||||
res: ApiResponseType<string>
|
res: ApiResponseType<string>
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const { name, avatar, intro, parentId } = req.body;
|
const { name, avatar, intro, parentId, createType } = req.body;
|
||||||
|
|
||||||
const { teamId, tmbId, userId } = parentId
|
const { teamId, tmbId, userId } = parentId
|
||||||
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
? await authApp({ req, appId: parentId, per: TeamAppCreatePermissionVal, authToken: true })
|
||||||
@@ -40,7 +41,14 @@ async function handler(
|
|||||||
modules: [
|
modules: [
|
||||||
getHTTPToolSetRuntimeNode({
|
getHTTPToolSetRuntimeNode({
|
||||||
name,
|
name,
|
||||||
avatar
|
avatar,
|
||||||
|
toolList: [],
|
||||||
|
...(createType === 'batch' && {
|
||||||
|
baseUrl: '',
|
||||||
|
apiSchemaStr: '',
|
||||||
|
customHeaders: '{}',
|
||||||
|
headerSecret: {}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
session
|
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 StoreSecretValueType } from '@fastgpt/global/common/secret/type';
|
||||||
import type { RunHTTPToolResult } from '@fastgpt/service/core/app/http';
|
import type { RunHTTPToolResult } from '@fastgpt/service/core/app/http';
|
||||||
import { runHTTPTool } 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 = {};
|
export type RunHTTPToolQuery = {};
|
||||||
|
|
||||||
@@ -13,6 +14,9 @@ export type RunHTTPToolBody = {
|
|||||||
method: string;
|
method: string;
|
||||||
customHeaders?: Record<string, string>;
|
customHeaders?: Record<string, string>;
|
||||||
headerSecret?: StoreSecretValueType;
|
headerSecret?: StoreSecretValueType;
|
||||||
|
staticParams?: HttpToolConfigType['staticParams'];
|
||||||
|
staticHeaders?: HttpToolConfigType['staticHeaders'];
|
||||||
|
staticBody?: HttpToolConfigType['staticBody'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RunHTTPToolResponse = RunHTTPToolResult;
|
export type RunHTTPToolResponse = RunHTTPToolResult;
|
||||||
@@ -21,7 +25,17 @@ async function handler(
|
|||||||
req: ApiRequestProps<RunHTTPToolBody, RunHTTPToolQuery>,
|
req: ApiRequestProps<RunHTTPToolBody, RunHTTPToolQuery>,
|
||||||
res: ApiResponseType<RunHTTPToolResponse>
|
res: ApiResponseType<RunHTTPToolResponse>
|
||||||
): Promise<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({
|
return runHTTPTool({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
@@ -29,7 +43,10 @@ async function handler(
|
|||||||
method,
|
method,
|
||||||
params,
|
params,
|
||||||
headerSecret,
|
headerSecret,
|
||||||
customHeaders
|
customHeaders,
|
||||||
|
staticParams,
|
||||||
|
staticHeaders,
|
||||||
|
staticBody
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,10 +13,10 @@ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
|||||||
|
|
||||||
export type UpdateHttpPluginBody = {
|
export type UpdateHttpPluginBody = {
|
||||||
appId: string;
|
appId: string;
|
||||||
baseUrl: string;
|
|
||||||
apiSchemaStr: string;
|
|
||||||
toolList: HttpToolConfigType[];
|
toolList: HttpToolConfigType[];
|
||||||
headerSecret: StoreSecretValueType;
|
baseUrl?: string;
|
||||||
|
apiSchemaStr?: string;
|
||||||
|
headerSecret?: StoreSecretValueType;
|
||||||
customHeaders?: string;
|
customHeaders?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,12 +27,17 @@ async function handler(req: ApiRequestProps<UpdateHttpPluginBody>, res: NextApiR
|
|||||||
|
|
||||||
const formatedHeaderAuth = storeSecretValue(headerSecret);
|
const formatedHeaderAuth = storeSecretValue(headerSecret);
|
||||||
|
|
||||||
|
const formattedToolList = toolList.map((tool) => ({
|
||||||
|
...tool,
|
||||||
|
headerSecret: tool.headerSecret ? storeSecretValue(tool.headerSecret) : undefined
|
||||||
|
}));
|
||||||
|
|
||||||
const toolSetRuntimeNode = getHTTPToolSetRuntimeNode({
|
const toolSetRuntimeNode = getHTTPToolSetRuntimeNode({
|
||||||
name: app.name,
|
name: app.name,
|
||||||
avatar: app.avatar,
|
avatar: app.avatar,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
apiSchemaStr,
|
apiSchemaStr,
|
||||||
toolList,
|
toolList: formattedToolList,
|
||||||
headerSecret: formatedHeaderAuth,
|
headerSecret: formatedHeaderAuth,
|
||||||
customHeaders
|
customHeaders
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user