feat: http body type & http input support editor variable (#2603)

* feat: http body type & http input support editor variable

* fix type

* chore: code

* code
This commit is contained in:
heheer
2024-09-03 23:43:21 +08:00
committed by GitHub
parent a7569037fe
commit 85a11d08b2
11 changed files with 407 additions and 258 deletions

View File

@@ -108,6 +108,8 @@ export enum NodeInputKeyEnum {
httpMethod = 'system_httpMethod', httpMethod = 'system_httpMethod',
httpParams = 'system_httpParams', httpParams = 'system_httpParams',
httpJsonBody = 'system_httpJsonBody', httpJsonBody = 'system_httpJsonBody',
httpFormBody = 'system_httpFormBody',
httpContentType = 'system_httpContentType',
httpTimeout = 'system_httpTimeout', httpTimeout = 'system_httpTimeout',
abandon_httpUrl = 'url', abandon_httpUrl = 'url',
@@ -217,3 +219,13 @@ export enum RuntimeEdgeStatusEnum {
export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID'; export const VARIABLE_NODE_ID = 'VARIABLE_NODE_ID';
export const DYNAMIC_INPUT_REFERENCE_KEY = 'DYNAMIC_INPUT_REFERENCE_KEY'; export const DYNAMIC_INPUT_REFERENCE_KEY = 'DYNAMIC_INPUT_REFERENCE_KEY';
// http node body content type
export enum ContentTypes {
none = 'none',
formData = 'form-data',
xWwwFormUrlencoded = 'x-www-form-urlencoded',
json = 'json',
xml = 'xml',
raw = 'raw-text'
}

View File

@@ -8,7 +8,8 @@ import {
WorkflowIOValueTypeEnum, WorkflowIOValueTypeEnum,
NodeInputKeyEnum, NodeInputKeyEnum,
NodeOutputKeyEnum, NodeOutputKeyEnum,
FlowNodeTemplateTypeEnum FlowNodeTemplateTypeEnum,
ContentTypes
} from '../../constants'; } from '../../constants';
import { Input_Template_DynamicInput } from '../input'; import { Input_Template_DynamicInput } from '../input';
import { Output_Template_AddOutput } from '../output'; import { Output_Template_AddOutput } from '../output';
@@ -82,6 +83,7 @@ export const HttpNode468: FlowNodeTemplateType = {
label: '', label: '',
required: false required: false
}, },
// json body data
{ {
key: NodeInputKeyEnum.httpJsonBody, key: NodeInputKeyEnum.httpJsonBody,
renderTypeList: [FlowNodeInputTypeEnum.hidden], renderTypeList: [FlowNodeInputTypeEnum.hidden],
@@ -89,6 +91,24 @@ export const HttpNode468: FlowNodeTemplateType = {
value: '', value: '',
label: '', label: '',
required: false required: false
},
// form body data
{
key: NodeInputKeyEnum.httpFormBody,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.any,
value: [],
label: '',
required: false
},
// body data type
{
key: NodeInputKeyEnum.httpContentType,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.string,
value: ContentTypes.json,
label: '',
required: false
} }
], ],
outputs: [ outputs: [

View File

@@ -16,6 +16,8 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { getSystemPluginCb } from '../../../../../plugins/register'; import { getSystemPluginCb } from '../../../../../plugins/register';
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
import { replaceEditorVariable } from '@fastgpt/global/core/workflow/utils';
type PropsArrType = { type PropsArrType = {
key: string; key: string;
@@ -29,6 +31,8 @@ type HttpRequestProps = ModuleDispatchProps<{
[NodeInputKeyEnum.httpHeaders]: PropsArrType[]; [NodeInputKeyEnum.httpHeaders]: PropsArrType[];
[NodeInputKeyEnum.httpParams]: PropsArrType[]; [NodeInputKeyEnum.httpParams]: PropsArrType[];
[NodeInputKeyEnum.httpJsonBody]: string; [NodeInputKeyEnum.httpJsonBody]: string;
[NodeInputKeyEnum.httpFormBody]: PropsArrType[];
[NodeInputKeyEnum.httpContentType]: ContentTypes;
[NodeInputKeyEnum.addInputParam]: Record<string, any>; [NodeInputKeyEnum.addInputParam]: Record<string, any>;
[NodeInputKeyEnum.httpTimeout]?: number; [NodeInputKeyEnum.httpTimeout]?: number;
[key: string]: any; [key: string]: any;
@@ -40,13 +44,23 @@ 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 }, runningAppInfo: { id: appId },
chatId, chatId,
responseChatItemId, responseChatItemId,
variables, variables,
node: { outputs }, node,
runtimeNodes,
histories, histories,
workflowStreamResponse, workflowStreamResponse,
params: { params: {
@@ -55,6 +69,8 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
system_httpHeader: httpHeader, system_httpHeader: httpHeader,
system_httpParams: httpParams = [], system_httpParams: httpParams = [],
system_httpJsonBody: httpJsonBody, system_httpJsonBody: httpJsonBody,
system_httpFormBody: httpFormBody,
system_httpContentType: httpContentType = ContentTypes.json,
system_httpTimeout: httpTimeout = 60, system_httpTimeout: httpTimeout = 60,
[NodeInputKeyEnum.addInputParam]: dynamicInput, [NodeInputKeyEnum.addInputParam]: dynamicInput,
...body ...body
@@ -77,21 +93,41 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
// ...dynamicInput, // ...dynamicInput,
...systemVariables ...systemVariables
}; };
const allVariables = { const allVariables = {
[NodeInputKeyEnum.addInputParam]: concatVariables, [NodeInputKeyEnum.addInputParam]: concatVariables,
...concatVariables ...concatVariables
}; };
httpReqUrl = replaceVariable(httpReqUrl, allVariables); httpReqUrl = replaceVariable(httpReqUrl, allVariables);
// parse header // parse header
const headers = await (() => { const headers = await (() => {
try { try {
const contentType = contentTypeMap[httpContentType];
if (contentType) {
httpHeader = [{ key: 'Content-Type', value: contentType, type: 'string' }, ...httpHeader];
}
if (!httpHeader || httpHeader.length === 0) return {}; if (!httpHeader || httpHeader.length === 0) return {};
// array // array
return httpHeader.reduce((acc: Record<string, string>, item) => { return httpHeader.reduce((acc: Record<string, string>, item) => {
const key = replaceVariable(item.key, allVariables); const key = replaceVariable(
const value = replaceVariable(item.value, allVariables); replaceEditorVariable({
text: item.key,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
);
const value = replaceVariable(
replaceEditorVariable({
text: item.value,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
);
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string); acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
return acc; return acc;
}, {}); }, {});
@@ -99,28 +135,109 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
return Promise.reject('Header 为非法 JSON 格式'); return Promise.reject('Header 为非法 JSON 格式');
} }
})(); })();
const params = httpParams.reduce((acc: Record<string, string>, item) => { const params = httpParams.reduce((acc: Record<string, string>, item) => {
const key = replaceVariable(item.key, allVariables); const key = replaceVariable(
const value = replaceVariable(item.value, allVariables); replaceEditorVariable({
text: item.key,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
);
const value = replaceVariable(
replaceEditorVariable({
text: item.value,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
);
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string); acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
return acc; return acc;
}, {}); }, {});
const requestBody = await (() => { const requestBody = await (() => {
if (!httpJsonBody) return {}; if (httpContentType === ContentTypes.none) return {};
try { try {
// Replace all variables in the string body if (httpContentType === ContentTypes.formData) {
httpJsonBody = replaceVariable(httpJsonBody, allVariables); if (!Array.isArray(httpFormBody)) return {};
httpFormBody = httpFormBody.map((item) => ({
// Text body, return directly key: replaceVariable(
if (headers['Content-Type']?.includes('text/plain')) { replaceEditorVariable({
return httpJsonBody?.replaceAll(UNDEFINED_SIGN, 'null'); text: item.key,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
),
type: item.type,
value: replaceVariable(
replaceEditorVariable({
text: item.value,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
)
}));
const formData = new FormData();
for (const { key, value } of httpFormBody) {
formData.append(key, value);
} }
return formData;
}
if (httpContentType === ContentTypes.xWwwFormUrlencoded) {
if (!Array.isArray(httpFormBody)) return {};
httpFormBody = httpFormBody.map((item) => ({
key: replaceVariable(
replaceEditorVariable({
text: item.key,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
),
type: item.type,
value: replaceVariable(
replaceEditorVariable({
text: item.value,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
)
}));
const urlSearchParams = new URLSearchParams();
for (const { key, value } of httpFormBody) {
urlSearchParams.append(key, value);
}
return urlSearchParams;
}
if (!httpJsonBody) return {};
if (httpContentType === ContentTypes.json) {
httpJsonBody = replaceVariable(httpJsonBody, allVariables);
// Json body, parse and return // Json body, parse and return
const jsonParse = JSON.parse(httpJsonBody); const jsonParse = JSON.parse(httpJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse); const removeSignJson = removeUndefinedSign(jsonParse);
return removeSignJson; return removeSignJson;
}
httpJsonBody = replaceVariable(
replaceEditorVariable({
text: httpJsonBody,
nodes: runtimeNodes,
variables,
runningNode: node
}),
allVariables
);
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`); return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
@@ -150,7 +267,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
// format output value type // format output value type
const results: Record<string, any> = {}; const results: Record<string, any> = {};
for (const key in formatResponse) { for (const key in formatResponse) {
const output = outputs.find((item) => item.key === key); const output = node.outputs.find((item) => item.key === key);
if (!output) continue; if (!output) continue;
results[key] = valueTypeFormat(formatResponse[key], output.valueType); results[key] = valueTypeFormat(formatResponse[key], output.valueType);
} }
@@ -213,7 +330,6 @@ async function fetchData({
baseURL: `http://${SERVICE_LOCAL_HOST}`, baseURL: `http://${SERVICE_LOCAL_HOST}`,
url, url,
headers: { headers: {
'Content-Type': 'application/json',
...headers ...headers
}, },
timeout: timeout * 1000, timeout: timeout * 1000,

View File

@@ -17,39 +17,40 @@ import { Box, Flex } from '@chakra-ui/react';
import styles from './index.module.scss'; import styles from './index.module.scss';
import { EditorState, LexicalEditor } from 'lexical'; import { EditorState, LexicalEditor } from 'lexical';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { EditorVariablePickerType } from '../../Textarea/PromptEditor/type'; import {
EditorVariableLabelPickerType,
EditorVariablePickerType
} from '../../Textarea/PromptEditor/type';
import { VariableNode } from '../../Textarea/PromptEditor/plugins/VariablePlugin/node'; import { VariableNode } from '../../Textarea/PromptEditor/plugins/VariablePlugin/node';
import { textToEditorState } from '../../Textarea/PromptEditor/utils'; import { textToEditorState } from '../../Textarea/PromptEditor/utils';
import DropDownMenu from '../../Textarea/PromptEditor/modules/DropDownMenu';
import { SingleLinePlugin } from '../../Textarea/PromptEditor/plugins/SingleLinePlugin'; import { SingleLinePlugin } from '../../Textarea/PromptEditor/plugins/SingleLinePlugin';
import OnBlurPlugin from '../../Textarea/PromptEditor/plugins/OnBlurPlugin'; import OnBlurPlugin from '../../Textarea/PromptEditor/plugins/OnBlurPlugin';
import VariablePlugin from '../../Textarea/PromptEditor/plugins/VariablePlugin'; import VariablePlugin from '../../Textarea/PromptEditor/plugins/VariablePlugin';
import VariablePickerPlugin from '../../Textarea/PromptEditor/plugins/VariablePickerPlugin'; import VariablePickerPlugin from '../../Textarea/PromptEditor/plugins/VariablePickerPlugin';
import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin'; import FocusPlugin from '../../Textarea/PromptEditor/plugins/FocusPlugin';
import VariableLabelPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin';
import { VariableLabelNode } from '../../Textarea/PromptEditor/plugins/VariableLabelPlugin/node';
import VariableLabelPickerPlugin from '../../Textarea/PromptEditor/plugins/VariableLabelPickerPlugin';
export default function Editor({ export default function Editor({
h = 40, h = 40,
hasVariablePlugin = true,
hasDropDownPlugin = false,
variables, variables,
variableLabels,
onChange, onChange,
onBlur, onBlur,
value, value,
currentValue, currentValue,
placeholder = '', placeholder = '',
setDropdownValue,
updateTrigger updateTrigger
}: { }: {
h?: number; h?: number;
hasVariablePlugin?: boolean;
hasDropDownPlugin?: boolean;
variables: EditorVariablePickerType[]; variables: EditorVariablePickerType[];
variableLabels: EditorVariableLabelPickerType[];
onChange?: (editorState: EditorState, editor: LexicalEditor) => void; onChange?: (editorState: EditorState, editor: LexicalEditor) => void;
onBlur?: (editor: LexicalEditor) => void; onBlur?: (editor: LexicalEditor) => void;
value?: string; value?: string;
currentValue?: string; currentValue?: string;
placeholder?: string; placeholder?: string;
setDropdownValue?: (value: string) => void;
updateTrigger?: boolean; updateTrigger?: boolean;
}) { }) {
const [key, setKey] = useState(getNanoid(6)); const [key, setKey] = useState(getNanoid(6));
@@ -58,7 +59,7 @@ export default function Editor({
const initialConfig = { const initialConfig = {
namespace: 'HttpInput', namespace: 'HttpInput',
nodes: [VariableNode], nodes: [VariableNode, VariableLabelNode],
editorState: textToEditorState(value), editorState: textToEditorState(value),
onError: (error: Error) => { onError: (error: Error) => {
throw error; throw error;
@@ -75,16 +76,6 @@ export default function Editor({
setFocus(false); setFocus(false);
}, [updateTrigger]); }, [updateTrigger]);
const dropdownVariables = useMemo(
() =>
variables.filter((item) => {
const key = item.key.toLowerCase();
const current = currentValue?.toLowerCase();
return key.includes(current || '') && item.key !== currentValue;
}),
[currentValue, variables]
);
return ( return (
<Flex <Flex
position={'relative'} position={'relative'}
@@ -133,14 +124,12 @@ export default function Editor({
}); });
}} }}
/> />
{hasVariablePlugin ? <VariablePickerPlugin variables={variables} /> : ''}
<VariablePlugin variables={variables} /> <VariablePlugin variables={variables} />
<VariableLabelPlugin variables={variableLabels} />
<VariableLabelPickerPlugin variables={variableLabels} isFocus={focus} />
<OnBlurPlugin onBlur={onBlur} /> <OnBlurPlugin onBlur={onBlur} />
<SingleLinePlugin /> <SingleLinePlugin />
</LexicalComposer> </LexicalComposer>
{focus && hasDropDownPlugin && (
<DropDownMenu variables={dropdownVariables} setDropdownValue={setDropdownValue} />
)}
</Flex> </Flex>
); );
} }

View File

@@ -1,58 +1,61 @@
import React, { useEffect } from 'react'; import React from 'react';
import { $getRoot, EditorState, type LexicalEditor } from 'lexical'; import { EditorState, type LexicalEditor } from 'lexical';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { editorStateToText } from '../../Textarea/PromptEditor/utils'; import { editorStateToText } from '../../Textarea/PromptEditor/utils';
import { EditorVariablePickerType } from '../../Textarea/PromptEditor/type'; import {
EditorVariableLabelPickerType,
EditorVariablePickerType
} from '../../Textarea/PromptEditor/type';
import Editor from './Editor'; import Editor from './Editor';
const HttpInput = ({ const HttpInput = ({
hasVariablePlugin = true,
hasDropDownPlugin = false,
variables = [], variables = [],
variableLabels = [],
value, value,
onChange, onChange,
onBlur, onBlur,
h, h,
placeholder, placeholder,
setDropdownValue,
updateTrigger updateTrigger
}: { }: {
hasVariablePlugin?: boolean;
hasDropDownPlugin?: boolean;
variables?: EditorVariablePickerType[]; variables?: EditorVariablePickerType[];
variableLabels?: EditorVariableLabelPickerType[];
value?: string; value?: string;
onChange?: (text: string) => void; onChange?: (text: string) => void;
onBlur?: (text: string) => void; onBlur?: (text: string) => void;
h?: number; h?: number;
placeholder?: string; placeholder?: string;
setDropdownValue?: (value: string) => void;
updateTrigger?: boolean; updateTrigger?: boolean;
}) => { }) => {
const [currentValue, setCurrentValue] = React.useState(value); const [currentValue, setCurrentValue] = React.useState(value);
const onChangeInput = useCallback((editorState: EditorState, editor: LexicalEditor) => { const onChangeInput = useCallback(
(editorState: EditorState, editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{'); const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
setCurrentValue(text); setCurrentValue(text);
onChange?.(text); onChange?.(text);
}, []); },
const onBlurInput = useCallback((editor: LexicalEditor) => { [onChange]
);
const onBlurInput = useCallback(
(editor: LexicalEditor) => {
const text = editorStateToText(editor).replaceAll('}}{{', '}} {{'); const text = editorStateToText(editor).replaceAll('}}{{', '}} {{');
onBlur?.(text); onBlur?.(text);
}, []); },
[onBlur]
);
return ( return (
<> <>
<Editor <Editor
hasVariablePlugin={hasVariablePlugin}
hasDropDownPlugin={hasDropDownPlugin}
variables={variables} variables={variables}
variableLabels={variableLabels}
h={h} h={h}
value={value} value={value}
currentValue={currentValue} currentValue={currentValue}
onChange={onChangeInput} onChange={onChangeInput}
onBlur={onBlurInput} onBlur={onBlurInput}
placeholder={placeholder} placeholder={placeholder}
setDropdownValue={setDropdownValue}
updateTrigger={updateTrigger} updateTrigger={updateTrigger}
/> />
</> </>

View File

@@ -866,7 +866,7 @@
"Key already exists": "Key already exists", "Key already exists": "Key already exists",
"Key cannot be empty": "Parameter name cannot be empty", "Key cannot be empty": "Parameter name cannot be empty",
"Props name": "Parameter name", "Props name": "Parameter name",
"Props tip": "Can set HTTP request related parameters\nCan use {{key}} to call global variables or external parameter input, currently available variables:\n{{variable}}", "Props tip": "Can set HTTP request related parameters\nCan use / to call variables, currently available variables:\n{{variable}}",
"Props value": "Parameter value", "Props value": "Parameter value",
"ResponseChatItemId": "AI response ID", "ResponseChatItemId": "AI response ID",
"Url and params have been split": "Path parameters have been automatically added to Params", "Url and params have been split": "Path parameters have been automatically added to Params",

View File

@@ -58,6 +58,9 @@
"greater_than": "greater than", "greater_than": "greater than",
"greater_than_or_equal_to": "Greater than or equal to", "greater_than_or_equal_to": "Greater than or equal to",
"greeting": "greet", "greeting": "greet",
"http": {
"body_none": "This request has no body parameters."
},
"http_raw_response_description": "The raw response of the HTTP request. \nOnly string or JSON type response data can be accepted.", "http_raw_response_description": "The raw response of the HTTP request. \nOnly string or JSON type response data can be accepted.",
"http_request": "HTTP request", "http_request": "HTTP request",
"http_request_error_info": "HTTP request error information, returns empty when successful", "http_request_error_info": "HTTP request error information, returns empty when successful",

View File

@@ -112,7 +112,6 @@
"common": { "common": {
"Action": "操作", "Action": "操作",
"Add": "添加", "Add": "添加",
"copy_to_clipboard": "复制到剪贴板",
"Add New": "新增", "Add New": "新增",
"Add Success": "添加成功", "Add Success": "添加成功",
"All": "全部", "All": "全部",
@@ -123,7 +122,6 @@
"Confirm": "确认", "Confirm": "确认",
"Confirm Create": "确认创建", "Confirm Create": "确认创建",
"Confirm Import": "确认导入", "Confirm Import": "确认导入",
"export_to_json": "导出为 JSON",
"Confirm Move": "移动到这", "Confirm Move": "移动到这",
"Confirm Update": "确认更新", "Confirm Update": "确认更新",
"Confirm to leave the page": "确认离开该页面?", "Confirm to leave the page": "确认离开该页面?",
@@ -195,7 +193,6 @@
"Save Success": "保存成功", "Save Success": "保存成功",
"Save_and_exit": "保存并退出", "Save_and_exit": "保存并退出",
"Search": "搜索", "Search": "搜索",
"json_config": "JSON 配置",
"Select File Failed": "选择文件异常", "Select File Failed": "选择文件异常",
"Select template": "选择模板", "Select template": "选择模板",
"Set Avatar": "点击设置头像", "Set Avatar": "点击设置头像",
@@ -230,6 +227,7 @@
"confirm": { "confirm": {
"Common Tip": "操作确认" "Common Tip": "操作确认"
}, },
"copy_to_clipboard": "复制到剪贴板",
"course": { "course": {
"Read Course": "查看教程" "Read Course": "查看教程"
}, },
@@ -241,6 +239,7 @@
"too_many_request": "请求太频繁了,请稍后重试。", "too_many_request": "请求太频繁了,请稍后重试。",
"unKnow": "出现了点意外~" "unKnow": "出现了点意外~"
}, },
"export_to_json": "导出为 JSON",
"failed": "失败", "failed": "失败",
"folder": { "folder": {
"Drag Tip": "点我可拖动", "Drag Tip": "点我可拖动",
@@ -260,6 +259,7 @@
"jsonEditor": { "jsonEditor": {
"Parse error": "JSON 可能有误,请仔细检查" "Parse error": "JSON 可能有误,请仔细检查"
}, },
"json_config": "JSON 配置",
"link": { "link": {
"UnValid": "无效的链接" "UnValid": "无效的链接"
}, },
@@ -647,7 +647,8 @@
"success": "开始同步" "success": "开始同步"
} }
}, },
"training": {} "training": {
}
}, },
"data": { "data": {
"Auxiliary Data": "辅助数据", "Auxiliary Data": "辅助数据",
@@ -865,7 +866,7 @@
"Key already exists": "Key 已经存在", "Key already exists": "Key 已经存在",
"Key cannot be empty": "参数名不能为空", "Key cannot be empty": "参数名不能为空",
"Props name": "参数名", "Props name": "参数名",
"Props tip": "可以设置 HTTP 请求的相关参数\n可通过 {{key}} 来调用全局变量或外部参数输入,当前可使用变量:\n{{variable}}", "Props tip": "可以设置 HTTP 请求的相关参数\n可通过输入 / 来调用变量,当前可使用变量:\n{{variable}}",
"Props value": "参数值", "Props value": "参数值",
"ResponseChatItemId": "AI 回复的 ID", "ResponseChatItemId": "AI 回复的 ID",
"Url and params have been split": "路径参数已被自动加入 Params 中", "Url and params have been split": "路径参数已被自动加入 Params 中",

View File

@@ -58,6 +58,9 @@
"greater_than": "大于", "greater_than": "大于",
"greater_than_or_equal_to": "大于等于", "greater_than_or_equal_to": "大于等于",
"greeting": "打招呼", "greeting": "打招呼",
"http": {
"body_none": "该请求没有 Body 体"
},
"http_raw_response_description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", "http_raw_response_description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"http_request": "HTTP 请求", "http_request": "HTTP 请求",
"http_request_error_info": "HTTP请求错误信息成功时返回空", "http_request_error_info": "HTTP请求错误信息成功时返回空",

View File

@@ -24,15 +24,18 @@ import {
NumberDecrementStepper, NumberDecrementStepper,
NumberInput NumberInput
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import {
ContentTypes,
NodeInputKeyEnum,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor'; import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils'; import { EditorVariableLabelPickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput'; import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MySelect from '@fastgpt/web/components/common/MySelect'; import MySelect from '@fastgpt/web/components/common/MySelect';
@@ -40,52 +43,22 @@ import RenderToolInput from '../render/RenderToolInput';
import IOTitle from '../../components/IOTitle'; import IOTitle from '../../components/IOTitle';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context'; import { WorkflowContext } from '../../../context';
import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils'; import { useCreation, useMemoizedFn } from 'ahooks';
import { useMemoizedFn } from 'ahooks';
import { AppContext } from '@/pages/app/detail/components/context'; import { AppContext } from '@/pages/app/detail/components/context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getEditorVariables } from '../../../utils';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
const CurlImportModal = dynamic(() => import('./CurlImportModal')); const CurlImportModal = dynamic(() => import('./CurlImportModal'));
export const HttpHeaders = [ const defaultFormBody = {
{ key: 'A-IM', label: 'A-IM' }, key: NodeInputKeyEnum.httpFormBody,
{ key: 'Accept', label: 'Accept' }, renderTypeList: [FlowNodeInputTypeEnum.hidden],
{ key: 'Accept-Charset', label: 'Accept-Charset' }, valueType: WorkflowIOValueTypeEnum.any,
{ key: 'Accept-Encoding', label: 'Accept-Encoding' }, value: [],
{ key: 'Accept-Language', label: 'Accept-Language' }, label: '',
{ key: 'Accept-Datetime', label: 'Accept-Datetime' }, required: false
{ key: 'Access-Control-Request-Method', label: 'Access-Control-Request-Method' }, };
{ key: 'Access-Control-Request-Headers', label: 'Access-Control-Request-Headers' },
{ key: 'Authorization', label: 'Authorization' },
{ key: 'Cache-Control', label: 'Cache-Control' },
{ key: 'Connection', label: 'Connection' },
{ key: 'Content-Length', label: 'Content-Length' },
{ key: 'Content-Type', label: 'Content-Type' },
{ key: 'Cookie', label: 'Cookie' },
{ key: 'Date', label: 'Date' },
{ key: 'Expect', label: 'Expect' },
{ key: 'Forwarded', label: 'Forwarded' },
{ key: 'From', label: 'From' },
{ key: 'Host', label: 'Host' },
{ key: 'If-Match', label: 'If-Match' },
{ key: 'If-Modified-Since', label: 'If-Modified-Since' },
{ key: 'If-None-Match', label: 'If-None-Match' },
{ key: 'If-Range', label: 'If-Range' },
{ key: 'If-Unmodified-Since', label: 'If-Unmodified-Since' },
{ key: 'Max-Forwards', label: 'Max-Forwards' },
{ key: 'Origin', label: 'Origin' },
{ key: 'Pragma', label: 'Pragma' },
{ key: 'Proxy-Authorization', label: 'Proxy-Authorization' },
{ key: 'Range', label: 'Range' },
{ key: 'Referer', label: 'Referer' },
{ key: 'TE', label: 'TE' },
{ key: 'User-Agent', label: 'User-Agent' },
{ key: 'Upgrade', label: 'Upgrade' },
{ key: 'Via', label: 'Via' },
{ key: 'Warning', label: 'Warning' },
{ key: 'Dnt', label: 'Dnt' },
{ key: 'X-Requested-With', label: 'X-Requested-With' },
{ key: 'X-CSRF-Token', label: 'X-CSRF-Token' }
];
enum TabEnum { enum TabEnum {
params = 'params', params = 'params',
@@ -260,7 +233,7 @@ export function RenderHttpProps({
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedTab, setSelectedTab] = useState(TabEnum.params); const [selectedTab, setSelectedTab] = useState(TabEnum.params);
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const getNodeDynamicInputs = useContextSelector(WorkflowContext, (v) => v.getNodeDynamicInputs); const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const { appDetail } = useContextSelector(AppContext, (v) => v); const { appDetail } = useContextSelector(AppContext, (v) => v);
@@ -268,21 +241,23 @@ export function RenderHttpProps({
const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams); const params = inputs.find((item) => item.key === NodeInputKeyEnum.httpParams);
const headers = inputs.find((item) => item.key === NodeInputKeyEnum.httpHeaders); const headers = inputs.find((item) => item.key === NodeInputKeyEnum.httpHeaders);
const jsonBody = inputs.find((item) => item.key === NodeInputKeyEnum.httpJsonBody); const jsonBody = inputs.find((item) => item.key === NodeInputKeyEnum.httpJsonBody);
const formBody =
inputs.find((item) => item.key === NodeInputKeyEnum.httpFormBody) || defaultFormBody;
const contentType = inputs.find((item) => item.key === NodeInputKeyEnum.httpContentType);
const paramsLength = params?.value?.length || 0; const paramsLength = params?.value?.length || 0;
const headersLength = headers?.value?.length || 0; const headersLength = headers?.value?.length || 0;
// get variable // get variable
const variables = useMemo(() => { const variables = useCreation(() => {
const globalVariables = getWorkflowGlobalVariables({ return getEditorVariables({
nodes: nodeList, nodeId,
chatConfig: appDetail.chatConfig nodeList,
edges,
appDetail,
t
}); });
}, [nodeList, edges, inputs, t]);
const nodeVariables = formatEditorVariablePickerIcon(getNodeDynamicInputs(nodeId));
return [...nodeVariables, ...globalVariables];
}, [appDetail.chatConfig, getNodeDynamicInputs, nodeId, nodeList]);
const variableText = useMemo(() => { const variableText = useMemo(() => {
return variables return variables
@@ -310,10 +285,11 @@ export function RenderHttpProps({
<QuestionTip <QuestionTip
ml={1} ml={1}
label={t('common:core.module.http.Props tip', { variable: variableText })} label={t('common:core.module.http.Props tip', { variable: variableText })}
></QuestionTip> />
</Flex> </Flex>
<LightRowTabs<TabEnum> <LightRowTabs<TabEnum>
width={'100%'} width={'100%'}
mb={selectedTab === TabEnum.body ? 1 : 2}
list={[ list={[
{ label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params }, { label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params },
...(!['GET', 'DELETE'].includes(requestMethods) ...(!['GET', 'DELETE'].includes(requestMethods)
@@ -337,33 +313,31 @@ export function RenderHttpProps({
value={selectedTab} value={selectedTab}
onChange={setSelectedTab} onChange={setSelectedTab}
/> />
<Box bg={'white'} borderRadius={'md'}> <Box bg={'white'} borderRadius={'md'} minW={'560px'}>
{params && {params &&
headers && headers &&
jsonBody && jsonBody &&
{ {
[TabEnum.params]: ( [TabEnum.params]: <RenderForm nodeId={nodeId} input={params} variables={variables} />,
<RenderForm [TabEnum.body]: (
<RenderBody
nodeId={nodeId} nodeId={nodeId}
input={params}
variables={variables} variables={variables}
tabType={TabEnum.params} jsonBody={jsonBody}
formBody={formBody}
typeInput={contentType}
/> />
), ),
[TabEnum.body]: <RenderJson nodeId={nodeId} variables={variables} input={jsonBody} />,
[TabEnum.headers]: ( [TabEnum.headers]: (
<RenderForm <RenderForm nodeId={nodeId} input={headers} variables={variables} />
nodeId={nodeId}
input={headers}
variables={variables}
tabType={TabEnum.headers}
/>
) )
}[selectedTab]} }[selectedTab]}
</Box> </Box>
</Box> </Box>
); );
}, [ }, [
contentType,
formBody,
headersLength, headersLength,
nodeId, nodeId,
paramsLength, paramsLength,
@@ -433,13 +407,11 @@ const RenderHttpTimeout = ({
const RenderForm = ({ const RenderForm = ({
nodeId, nodeId,
input, input,
variables, variables
tabType
}: { }: {
nodeId: string; nodeId: string;
input: FlowNodeInputItemType; input: FlowNodeInputItemType;
variables: EditorVariablePickerType[]; variables: EditorVariableLabelPickerType[];
tabType?: TabEnum;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const { toast } = useToast();
@@ -449,13 +421,6 @@ const RenderForm = ({
const [updateTrigger, setUpdateTrigger] = useState(false); const [updateTrigger, setUpdateTrigger] = useState(false);
const [shouldUpdateNode, setShouldUpdateNode] = useState(false); const [shouldUpdateNode, setShouldUpdateNode] = useState(false);
const leftVariables = useMemo(() => {
return (tabType === TabEnum.headers ? HttpHeaders : variables).filter((variable) => {
const existVariables = list.map((item) => item.key);
return !existVariables.includes(variable.key);
});
}, [list, tabType, variables]);
useEffect(() => { useEffect(() => {
setList(input.value || []); setList(input.value || []);
}, [input.value]); }, [input.value]);
@@ -529,7 +494,7 @@ const RenderForm = ({
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<Box mt={2} borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom={'none'}> <Box borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom={'none'}>
<TableContainer overflowY={'visible'} overflowX={'unset'}> <TableContainer overflowY={'visible'} overflowX={'unset'}>
<Table> <Table>
<Thead> <Thead>
@@ -545,29 +510,25 @@ const RenderForm = ({
<Tbody> <Tbody>
{list.map((item, index) => ( {list.map((item, index) => (
<Tr key={`${input.key}${index}`}> <Tr key={`${input.key}${index}`}>
<Td p={0} w={'150px'}> <Td p={0} w={'50%'}>
<HttpInput <HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(value) => {
handleKeyChange(index, value);
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('common:core.module.http.Props name')} placeholder={t('common:core.module.http.Props name')}
value={item.key} value={item.key}
variables={leftVariables} variableLabels={variables}
variables={variables}
onBlur={(val) => { onBlur={(val) => {
handleKeyChange(index, val); handleKeyChange(index, val);
}} }}
updateTrigger={updateTrigger} updateTrigger={updateTrigger}
/> />
</Td> </Td>
<Td p={0}> <Td p={0} w={'50%'}>
<Box display={'flex'} alignItems={'center'}> <Box display={'flex'} alignItems={'center'}>
<HttpInput <HttpInput
placeholder={t('common:core.module.http.Props value')} placeholder={t('common:core.module.http.Props value')}
value={item.value} value={item.value}
variables={variables} variables={variables}
variableLabels={variables}
onBlur={(val) => { onBlur={(val) => {
setList((prevList) => setList((prevList) =>
prevList.map((item, i) => prevList.map((item, i) =>
@@ -592,17 +553,12 @@ const RenderForm = ({
</Tr> </Tr>
))} ))}
<Tr> <Tr>
<Td p={0} w={'150px'}> <Td p={0} w={'50%'}>
<HttpInput <HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={tabType === TabEnum.headers}
setDropdownValue={(val) => {
handleAddNewProps(val);
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('common:core.module.http.Add props')} placeholder={t('common:core.module.http.Add props')}
value={''} value={''}
variables={leftVariables} variableLabels={variables}
variables={variables}
updateTrigger={updateTrigger} updateTrigger={updateTrigger}
onBlur={(val) => { onBlur={(val) => {
handleAddNewProps(val); handleAddNewProps(val);
@@ -610,7 +566,7 @@ const RenderForm = ({
}} }}
/> />
</Td> </Td>
<Td p={0}> <Td p={0} w={'50%'}>
<Box display={'flex'} alignItems={'center'}> <Box display={'flex'} alignItems={'center'}>
<HttpInput /> <HttpInput />
</Box> </Box>
@@ -621,50 +577,123 @@ const RenderForm = ({
</TableContainer> </TableContainer>
</Box> </Box>
); );
}, [ }, [handleAddNewProps, handleKeyChange, input.key, list, t, updateTrigger, variables]);
handleAddNewProps,
handleKeyChange,
input.key,
leftVariables,
list,
t,
tabType,
updateTrigger,
variables
]);
return Render; return Render;
}; };
const RenderJson = ({ const RenderBody = ({
nodeId, nodeId,
input, jsonBody,
formBody,
typeInput,
variables variables
}: { }: {
nodeId: string; nodeId: string;
input: FlowNodeInputItemType; jsonBody: FlowNodeInputItemType;
variables: EditorVariablePickerType[]; formBody: FlowNodeInputItemType;
typeInput: FlowNodeInputItemType | undefined;
variables: EditorVariableLabelPickerType[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const [_, startSts] = useTransition(); const [_, startSts] = useTransition();
useEffect(() => {
if (typeInput === undefined) {
onChangeNode({
nodeId,
type: 'addInput',
value: {
key: NodeInputKeyEnum.httpContentType,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.string,
value: ContentTypes.json,
label: '',
required: false
}
});
}
}, [nodeId, onChangeNode, typeInput]);
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<Box mt={1}> <Box>
<Flex bg={'myGray.50'}>
{Object.values(ContentTypes).map((item) => (
<Box
key={item}
as={'span'}
px={3}
py={1.5}
mb={2}
borderRadius={'6px'}
border={'1px solid'}
{...(typeInput?.value === item
? {
bg: 'white',
borderColor: 'myGray.200',
color: 'primary.700'
}
: {
bg: 'myGray.50',
borderColor: 'transparent',
color: 'myGray.500'
})}
_hover={{ bg: 'white', borderColor: 'myGray.200', color: 'primary.700' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpContentType,
value: {
key: NodeInputKeyEnum.httpContentType,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
valueType: WorkflowIOValueTypeEnum.string,
value: item,
label: '',
required: false
}
});
}}
cursor={'pointer'}
whiteSpace={'nowrap'}
>
{item}
</Box>
))}
</Flex>
{typeInput?.value === ContentTypes.none && (
<Box
px={4}
py={12}
bg={'white'}
color={'myGray.400'}
borderRadius={'6px'}
border={'1px solid'}
borderColor={'myGray.200'}
>
{t('workflow:http.body_none')}
</Box>
)}
{(typeInput?.value === ContentTypes.formData ||
typeInput?.value === ContentTypes.xWwwFormUrlencoded) && (
<RenderForm nodeId={nodeId} input={formBody} variables={variables} />
)}
{typeInput?.value === ContentTypes.json && (
<JSONEditor <JSONEditor
bg={'white'} bg={'white'}
defaultHeight={200} defaultHeight={200}
resize resize
value={input.value} value={jsonBody.value}
placeholder={t('common:core.module.template.http body placeholder')} placeholder={t('common:core.module.template.http body placeholder')}
onChange={(e) => { onChange={(e) => {
startSts(() => { startSts(() => {
onChangeNode({ onChangeNode({
nodeId, nodeId,
type: 'updateInput', type: 'updateInput',
key: input.key, key: jsonBody.key,
value: { value: {
...input, ...jsonBody,
value: e value: e
} }
}); });
@@ -672,12 +701,34 @@ const RenderJson = ({
}} }}
variables={variables} variables={variables}
/> />
)}
{(typeInput?.value === ContentTypes.xml || typeInput?.value === ContentTypes.raw) && (
<PromptEditor
value={jsonBody.value}
onChange={(e) => {
startSts(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: jsonBody.key,
value: {
...jsonBody,
value: e
}
});
});
}}
showOpenModal={false}
variableLabels={variables}
h={200}
/>
)}
</Box> </Box>
); );
}, [input, nodeId, onChangeNode, t, variables]); }, [typeInput?.value, t, nodeId, formBody, variables, jsonBody, onChangeNode]);
return Render; return Render;
}; };
const RenderPropsItem = ({ text, num }: { text: string; num: number }) => { const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
return ( return (
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
@@ -709,7 +760,7 @@ const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
[NodeInputKeyEnum.httpHeaders]: Headers, [NodeInputKeyEnum.httpHeaders]: Headers,
[NodeInputKeyEnum.httpTimeout]: HttpTimeout [NodeInputKeyEnum.httpTimeout]: HttpTimeout
}; };
}, [Headers, HttpMethodAndUrl]); }, [Headers, HttpMethodAndUrl, HttpTimeout]);
return ( return (
<NodeCard minW={'350px'} selected={selected} {...data}> <NodeCard minW={'350px'} selected={selected} {...data}>

View File

@@ -34,7 +34,6 @@ import { str2OpenApiSchema } from '@fastgpt/global/core/app/httpPlugin/utils';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput'; import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
import { HttpHeaders } from '../../detail/components/WorkflowComponents/Flow/nodes/NodeHttp';
import { OpenApiJsonSchema } from '@fastgpt/global/core/app/httpPlugin/type'; import { OpenApiJsonSchema } from '@fastgpt/global/core/app/httpPlugin/type';
import { AppSchema } from '@fastgpt/global/core/app/type'; import { AppSchema } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
@@ -168,15 +167,6 @@ const HttpPluginEditModal = ({
errorToast: t('common:plugin.Invalid Schema') errorToast: t('common:plugin.Invalid Schema')
}); });
const leftVariables = useMemo(
() =>
HttpHeaders.filter((variable) => {
const existVariables = customHeaders.map((item) => item.key);
return !existVariables.includes(variable.key);
}),
[customHeaders]
);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!apiSchemaStr) { if (!apiSchemaStr) {
@@ -315,28 +305,8 @@ const HttpPluginEditModal = ({
<Tr key={`${index}`}> <Tr key={`${index}`}>
<Td p={0} w={'150px'}> <Td p={0} w={'150px'}>
<HttpInput <HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
setDropdownValue={(val) => {
setCustomHeaders((prev) => {
const newHeaders = prev.map((item, i) =>
i === index ? { ...item, key: val } : item
);
setValue(
'pluginData.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('common:core.module.http.Props name')} placeholder={t('common:core.module.http.Props name')}
value={item.key} value={item.key}
variables={leftVariables}
onBlur={(val) => { onBlur={(val) => {
setCustomHeaders((prev) => { setCustomHeaders((prev) => {
const newHeaders = prev.map((item, i) => const newHeaders = prev.map((item, i) =>
@@ -360,7 +330,6 @@ const HttpPluginEditModal = ({
<Box display={'flex'} alignItems={'center'}> <Box display={'flex'} alignItems={'center'}>
<HttpInput <HttpInput
placeholder={t('common:core.module.http.Props value')} placeholder={t('common:core.module.http.Props value')}
hasVariablePlugin={false}
value={item.value} value={item.value}
onBlur={(val) => onBlur={(val) =>
setCustomHeaders((prev) => { setCustomHeaders((prev) => {
@@ -406,26 +375,8 @@ const HttpPluginEditModal = ({
<Tr> <Tr>
<Td p={0} w={'150px'}> <Td p={0} w={'150px'}>
<HttpInput <HttpInput
hasVariablePlugin={false}
hasDropDownPlugin={true}
setDropdownValue={(val) => {
setCustomHeaders((prev) => {
const newHeaders = [...prev, { key: val, value: '' }];
setValue(
'pluginData.customHeaders',
'{\n' +
newHeaders
.map((item) => `"${item.key}":"${item.value}"`)
.join(',\n') +
'\n}'
);
return newHeaders;
});
setUpdateTrigger((prev) => !prev);
}}
placeholder={t('common:core.module.http.Add props')} placeholder={t('common:core.module.http.Add props')}
value={''} value={''}
variables={leftVariables}
updateTrigger={updateTrigger} updateTrigger={updateTrigger}
onBlur={(val) => { onBlur={(val) => {
if (!val) return; if (!val) return;