mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-27 00:17:31 +00:00
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:
@@ -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'
|
||||||
|
}
|
||||||
|
@@ -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: [
|
||||||
|
@@ -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,
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@@ -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",
|
||||||
|
@@ -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",
|
||||||
|
@@ -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 中",
|
||||||
|
@@ -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请求错误信息,成功时返回空",
|
||||||
|
@@ -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}>
|
||||||
|
@@ -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;
|
||||||
|
Reference in New Issue
Block a user