diff --git a/client/public/imgs/module/http.png b/client/public/imgs/module/http.png
new file mode 100644
index 000000000..dca870cc0
Binary files /dev/null and b/client/public/imgs/module/http.png differ
diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json
index e296efe6f..b5b8a774f 100644
--- a/client/public/locales/en/common.json
+++ b/client/public/locales/en/common.json
@@ -9,7 +9,9 @@
"Confirm Save App Tip": "After saving, the advanced orchestration configuration will be overwritten. Make sure that the application does not use advanced orchestration.",
"Connection is invalid": "Connecting is invalid",
"Connection type is different": "Connection type is different",
- "My Apps": "My Apps"
+ "Input Field Settings": "Input Field Settings",
+ "My Apps": "My Apps",
+ "Output Field Settings": "Output Field Settings"
},
"chat": {
"Confirm to clear history": "Confirm to clear history?",
@@ -39,28 +41,28 @@
"Tools": "Tools"
},
"user": {
+ "Account": "Account",
+ "Application Name": "Application Name",
+ "Avatar": "Avatar",
+ "Balance": "Balance",
"Bill Detail": "Bill Detail",
+ "Change": "Change",
+ "Notice": "Notice",
"Old password is error": "Old password is error",
"OpenAI Account Setting": "OpenAI Account Setting",
+ "Password": "Password",
"Pay": "Pay",
+ "Personal Information": "Personal",
+ "Recharge Record": "Recharge",
+ "Replace": "Replace",
"Set OpenAI Account Failed": "Set OpenAI account failed",
+ "Sign Out": "Sign Out",
+ "Source": "Source",
+ "Time": "Time",
+ "Total Amount": "Total Amount",
"Update Password": "Update Password",
"Update password failed": "Update password failed",
"Update password succseful": "Update password succseful",
- "Personal Information": "Personal",
- "Usage Record": "Usage",
- "Recharge Record": "Recharge",
- "Notice": "Notice",
- "Sign Out": "Sign Out",
- "Avatar": "Avatar",
- "Account": "Account",
- "Balance": "Balance",
- "Time": "Time",
- "Source": "Source",
- "Application Name": "Application Name",
- "Total Amount": "Total Amount",
- "Change": "Change",
- "Password": "Password",
- "Replace": "Replace"
+ "Usage Record": "Usage"
}
-}
\ No newline at end of file
+}
diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json
index d776bc4db..0850d3cb1 100644
--- a/client/public/locales/zh/common.json
+++ b/client/public/locales/zh/common.json
@@ -9,7 +9,9 @@
"Confirm Save App Tip": "保存后将会覆盖高级编排配置,请确保该应用未使用高级编排功能。",
"Connection is invalid": "连接无效",
"Connection type is different": "连接的类型不一致",
- "My Apps": "我的应用"
+ "Input Field Settings": "输入字段编辑",
+ "My Apps": "我的应用",
+ "Output Field Settings": "输出字段编辑"
},
"chat": {
"Confirm to clear history": "确认清空该应用的聊天记录?",
@@ -39,28 +41,28 @@
"Tools": "工具"
},
"user": {
+ "Account": "账号",
+ "Application Name": "应用名",
+ "Avatar": "头像",
+ "Balance": "余额",
"Bill Detail": "账单详情",
+ "Change": "变更",
+ "Notice": "通知",
"Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置",
+ "Password": "密码",
"Pay": "充值",
+ "Personal Information": "个人信息",
+ "Recharge Record": "充值记录",
+ "Replace": "更换",
"Set OpenAI Account Failed": "设置 OpenAI 账号异常",
+ "Sign Out": "登出",
+ "Source": "来源",
+ "Time": "时间",
+ "Total Amount": "总金额",
"Update Password": "修改密码",
"Update password failed": "修改密码异常",
"Update password succseful": "修改密码成功",
- "Personal Information": "个人信息",
- "Usage Record": "使用记录",
- "Recharge Record": "充值记录",
- "Notice": "通知",
- "Sign Out": "登出",
- "Avatar": "头像",
- "Account": "账号",
- "Balance": "余额",
- "Time": "时间",
- "Source": "来源",
- "Application Name": "应用名",
- "Total Amount": "总金额",
- "Change": "变更",
- "Password": "密码",
- "Replace": "更换"
+ "Usage Record": "使用记录"
}
-}
\ No newline at end of file
+}
diff --git a/client/src/components/Icon/icons/circle/add.svg b/client/src/components/Icon/icons/circle/add.svg
new file mode 100644
index 000000000..7c4fd5cd1
--- /dev/null
+++ b/client/src/components/Icon/icons/circle/add.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/components/Icon/icons/withdraw.svg b/client/src/components/Icon/icons/withdraw.svg
deleted file mode 100644
index c7cc52da5..000000000
--- a/client/src/components/Icon/icons/withdraw.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/src/components/Icon/index.tsx b/client/src/components/Icon/index.tsx
index ed6a745e0..1c12299dd 100644
--- a/client/src/components/Icon/index.tsx
+++ b/client/src/components/Icon/index.tsx
@@ -8,7 +8,6 @@ const map = {
copy: require('./icons/copy.svg').default,
chatSend: require('./icons/chatSend.svg').default,
delete: require('./icons/delete.svg').default,
- withdraw: require('./icons/withdraw.svg').default,
stop: require('./icons/stop.svg').default,
collectionLight: require('./icons/collectionLight.svg').default,
collectionSolid: require('./icons/collectionSolid.svg').default,
@@ -72,7 +71,8 @@ const map = {
language_en: require('./icons/language/en.svg').default,
language_zh: require('./icons/language/zh.svg').default,
outlink_share: require('./icons/outlink/share.svg').default,
- outlink_iframe: require('./icons/outlink/iframe.svg').default
+ outlink_iframe: require('./icons/outlink/iframe.svg').default,
+ addCircle: require('./icons/circle/add.svg').default
};
export type IconName = keyof typeof map;
diff --git a/client/src/constants/flow/ModuleTemplate.ts b/client/src/constants/flow/ModuleTemplate.ts
index f2a3c868d..3bbe94a4b 100644
--- a/client/src/constants/flow/ModuleTemplate.ts
+++ b/client/src/constants/flow/ModuleTemplate.ts
@@ -15,7 +15,7 @@ import {
Input_Template_TFSwitch,
Input_Template_UserChatInput
} from './inputTemplate';
-import { ContextExtractEnum } from './flowField';
+import { ContextExtractEnum, HttpPropsEnum } from './flowField';
export const ChatModelSystemTip =
'模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}';
@@ -448,6 +448,34 @@ export const ContextExtractModule: FlowModuleTemplateType = {
}
]
};
+export const HttpModule: FlowModuleTemplateType = {
+ logo: '/imgs/module/http.png',
+ name: 'HTTP模块',
+ intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
+ description: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
+ flowType: FlowModuleTypeEnum.httpRequest,
+ inputs: [
+ {
+ key: HttpPropsEnum.url,
+ value: '',
+ type: FlowInputItemTypeEnum.input,
+ label: '请求地址',
+ description: '请求目标地址',
+ placeholder: 'https://api.fastgpt.run/getInventory',
+ required: true
+ },
+ Input_Template_TFSwitch
+ ],
+ outputs: [
+ {
+ key: HttpPropsEnum.finish,
+ label: '请求结束',
+ valueType: FlowValueTypeEnum.boolean,
+ type: FlowOutputItemTypeEnum.source,
+ targets: []
+ }
+ ]
+};
export const EmptyModule: FlowModuleTemplateType = {
logo: '/imgs/module/cq.png',
name: '该模块已被移除',
@@ -477,7 +505,7 @@ export const ModuleTemplates = [
},
{
label: 'Agent',
- list: [ClassifyQuestionModule, ContextExtractModule]
+ list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
}
];
export const ModuleTemplatesFlat = ModuleTemplates.map((templates) => templates.list)?.flat();
diff --git a/client/src/constants/flow/flowField.ts b/client/src/constants/flow/flowField.ts
index 0f7be6037..1cd0885d5 100644
--- a/client/src/constants/flow/flowField.ts
+++ b/client/src/constants/flow/flowField.ts
@@ -6,3 +6,10 @@ export enum ContextExtractEnum {
failed = 'failed',
fields = 'fields'
}
+
+export enum HttpPropsEnum {
+ url = 'url',
+ finish = 'finish',
+ body = 'body',
+ response = 'response'
+}
diff --git a/client/src/constants/flow/index.ts b/client/src/constants/flow/index.ts
index dc7196b19..e9945ee90 100644
--- a/client/src/constants/flow/index.ts
+++ b/client/src/constants/flow/index.ts
@@ -31,7 +31,8 @@ export enum FlowModuleTypeEnum {
tfSwitchNode = 'tfSwitchNode',
answerNode = 'answerNode',
classifyQuestion = 'classifyQuestion',
- contentExtract = 'contentExtract'
+ contentExtract = 'contentExtract',
+ httpRequest = 'httpRequest'
}
export enum SpecialInputKeyEnum {
diff --git a/client/src/pages/api/admin/initv4.ts b/client/src/pages/api/admin/initv4.ts
index 213a00d6c..e180ae581 100644
--- a/client/src/pages/api/admin/initv4.ts
+++ b/client/src/pages/api/admin/initv4.ts
@@ -3,9 +3,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authUser } from '@/service/utils/auth';
import { connectToDatabase, App } from '@/service/mongo';
-import { AppModuleInputItemType } from '@/types/app';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { TaskResponseKeyEnum } from '@/constants/chat';
+import { FlowInputItemType } from '@/types/flow';
const chatModelInput = ({
model,
@@ -21,7 +21,7 @@ const chatModelInput = ({
systemPrompt: string;
limitPrompt: string;
kbList: { kbId: string }[];
-}): AppModuleInputItemType[] => [
+}): FlowInputItemType[] => [
{
key: 'model',
value: model,
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx
index 1720f95e2..425aabd17 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeCQNode.tsx
@@ -27,7 +27,8 @@ const NodeCQNode = ({
CustomComponent={{
[SpecialInputKeyEnum.agents]: ({
key: agentKey,
- value: agents = []
+ value: agents = [],
+ ...props
}: {
key: string;
value?: ClassifyQuestionAgentItemType[];
@@ -50,7 +51,11 @@ const NodeCQNode = ({
moduleId,
type: 'inputs',
key: agentKey,
- value: newInputValue
+ value: {
+ ...props,
+ key: agentKey,
+ value: newInputValue
+ }
});
onChangeNode({
moduleId,
@@ -77,8 +82,13 @@ const NodeCQNode = ({
);
onChangeNode({
moduleId,
+ type: 'inputs',
key: agentKey,
- value: newVal
+ value: {
+ ...props,
+ key: agentKey,
+ value: newVal
+ }
});
}}
/>
@@ -97,11 +107,16 @@ const NodeCQNode = ({
type: FlowOutputItemTypeEnum.hidden,
targets: []
});
+
onChangeNode({
moduleId,
type: 'inputs',
key: agentKey,
- value: newInputValue
+ value: {
+ ...props,
+ key: agentKey,
+ value: newInputValue
+ }
});
onChangeNode({
moduleId,
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeChat.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeChat.tsx
index 5c7c1c32f..e8e6386bd 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeChat.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeChat.tsx
@@ -48,33 +48,32 @@ const NodeChat = ({
onchange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: inputItem.key,
- value: e
+ value: {
+ ...inputItem,
+ value: e
+ }
});
+
// update max tokens
- const model = chatModelList.find((item) => item.model === e);
+ const model =
+ chatModelList.find((item) => item.model === e) || chatModelList[0];
if (!model) return;
onChangeNode({
moduleId,
+ type: 'inputs',
key: 'maxToken',
- valueKey: 'markList',
- value: [
- { label: '100', value: 100 },
- { label: `${model.contextMaxToken}`, value: model.contextMaxToken }
- ]
- });
- onChangeNode({
- moduleId,
- key: 'maxToken',
- valueKey: 'max',
- value: model.contextMaxToken
- });
- onChangeNode({
- moduleId,
- key: 'maxToken',
- valueKey: 'value',
- value: model.contextMaxToken / 2
+ value: {
+ ...inputs.find((input) => input.key === 'maxToken'),
+ markList: [
+ { label: '100', value: 100 },
+ { label: `${model.contextMaxToken}`, value: model.contextMaxToken }
+ ],
+ max: model.contextMaxToken,
+ value: model.contextMaxToken / 2
+ }
});
}}
/>
@@ -100,8 +99,12 @@ const NodeChat = ({
onChange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: inputItem.key,
- value: e
+ value: {
+ ...inputItem,
+ value: e
+ }
});
}}
/>
@@ -115,7 +118,11 @@ const NodeChat = ({
<>
-
+
>
)}
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeExtract.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeExtract.tsx
index 4eaf07273..b06cc512b 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeExtract.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeExtract.tsx
@@ -31,10 +31,9 @@ const NodeExtract = ({
flowInputList={inputs}
CustomComponent={{
[ContextExtractEnum.extractKeys]: ({
- key,
- value: extractKeys = []
+ value: extractKeys = [],
+ ...props
}: {
- key: string;
value?: ContextExtractAgentItemType[];
}) => (
@@ -97,7 +96,10 @@ const NodeExtract = ({
moduleId,
type: 'inputs',
key: ContextExtractEnum.extractKeys,
- value: newInputValue
+ value: {
+ ...props,
+ value: newInputValue
+ }
});
onChangeNode({
moduleId,
@@ -121,7 +123,7 @@ const NodeExtract = ({
-
+
{!!editExtractFiled && (
@@ -160,7 +162,10 @@ const NodeExtract = ({
moduleId,
type: 'inputs',
key: ContextExtractEnum.extractKeys,
- value: newInputs
+ value: {
+ ...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
+ value: newInputs
+ }
});
onChangeNode({
moduleId,
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHistory.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHistory.tsx
index 0836a55c3..cf50c0825 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHistory.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHistory.tsx
@@ -18,7 +18,11 @@ const NodeHistory = ({
-
+
);
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHttp.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHttp.tsx
new file mode 100644
index 000000000..656f4144a
--- /dev/null
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeHttp.tsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import { NodeProps } from 'reactflow';
+import NodeCard from '../modules/NodeCard';
+import { FlowModuleItemType } from '@/types/flow';
+import Divider from '../modules/Divider';
+import Container from '../modules/Container';
+import RenderInput from '../render/RenderInput';
+import { Box, Button } from '@chakra-ui/react';
+import { SmallAddIcon } from '@chakra-ui/icons';
+import RenderOutput from '../render/RenderOutput';
+
+import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
+import { customAlphabet } from 'nanoid';
+const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
+
+const NodeHttp = ({
+ data: { moduleId, inputs, outputs, onChangeNode, ...props }
+}: NodeProps) => {
+ return (
+
+
+
+ }
+ onClick={() => {
+ const key = nanoid();
+ onChangeNode({
+ moduleId,
+ type: 'addInput',
+ key,
+ value: {
+ key,
+ value: '',
+ valueType: FlowValueTypeEnum.string,
+ type: FlowInputItemTypeEnum.target,
+ label: 'New Param',
+ edit: true
+ }
+ });
+ }}
+ >
+ 添加入参
+
+
+
+
+
+
+ }
+ onClick={() => {
+ const key = nanoid();
+ onChangeNode({
+ moduleId,
+ type: 'outputs',
+ key,
+ value: outputs.concat([
+ {
+ key,
+ label: '出参1',
+ valueType: FlowValueTypeEnum.string,
+ type: FlowOutputItemTypeEnum.source,
+ edit: true,
+ targets: []
+ }
+ ])
+ });
+ }}
+ >
+ 添加出参
+
+
+
+
+ );
+};
+export default React.memo(NodeHttp);
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeKbSearch.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeKbSearch.tsx
index bb621d55a..80eed4df5 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeKbSearch.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeKbSearch.tsx
@@ -81,14 +81,19 @@ const NodeKbSearch = ({
onChangeNode={onChangeNode}
flowInputList={inputs}
CustomComponent={{
- kbList: ({ key, value }) => (
+ kbList: ({ key, value, ...props }) => (
{
onChangeNode({
moduleId,
key,
- value: e
+ type: 'inputs',
+ value: {
+ ...props,
+ key,
+ value: e
+ }
});
}}
/>
@@ -98,7 +103,7 @@ const NodeKbSearch = ({
-
+
);
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx
index bed59d523..f428e382a 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeQuestionInput.tsx
@@ -9,7 +9,7 @@ import { FlowValueTypeEnum } from '@/constants/flow';
import SourceHandle from '../render/SourceHandle';
const QuestionInputNode = ({
- data: { inputs, outputs, onChangeNode, ...props }
+ data: { inputs, outputs, ...props }
}: NodeProps) => {
return (
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx
index 81d408d61..2b0313805 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeUserGuide.tsx
@@ -14,7 +14,7 @@ const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps) => {
const welcomeText = useMemo(
- () => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
+ () => inputs.find((item) => item.key === SystemInputEnum.welcomeText),
[inputs]
);
@@ -30,22 +30,27 @@ const NodeUserGuide = ({
-
diff --git a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeVariable.tsx b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeVariable.tsx
index b1f50e9eb..defe610a8 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeVariable.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/Nodes/NodeVariable.tsx
@@ -40,10 +40,13 @@ const NodeUserGuide = ({
moduleId: props.moduleId,
key: SystemInputEnum.variables,
type: 'inputs',
- value
+ value: {
+ ...inputs.find((item) => item.key === SystemInputEnum.variables),
+ value
+ }
});
},
- [onChangeNode, props.moduleId]
+ [inputs, onChangeNode, props.moduleId]
);
const onclickSubmit = useCallback(
diff --git a/client/src/pages/app/detail/components/AdEdit/components/modules/SetInputFieldModal.tsx b/client/src/pages/app/detail/components/AdEdit/components/modules/SetInputFieldModal.tsx
new file mode 100644
index 000000000..dd46ea5e4
--- /dev/null
+++ b/client/src/pages/app/detail/components/AdEdit/components/modules/SetInputFieldModal.tsx
@@ -0,0 +1,116 @@
+import React, { useMemo, useState } from 'react';
+import {
+ Box,
+ Button,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ Flex,
+ Switch,
+ Input,
+ FormControl
+} from '@chakra-ui/react';
+import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
+import { useForm } from 'react-hook-form';
+import { customAlphabet } from 'nanoid';
+const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
+import MyModal from '@/components/MyModal';
+import Avatar from '@/components/Avatar';
+import MyTooltip from '@/components/MyTooltip';
+import { FlowInputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
+import { useTranslation } from 'react-i18next';
+import MySelect from '@/components/Select';
+import { FlowInputItemType } from '@/types/flow';
+
+const typeSelectList = [
+ {
+ label: '字符串',
+ value: FlowValueTypeEnum.string
+ },
+ {
+ label: '数字',
+ value: FlowValueTypeEnum.number
+ },
+ {
+ label: '布尔',
+ value: FlowValueTypeEnum.boolean
+ },
+ {
+ label: '任意',
+ value: FlowValueTypeEnum.any
+ }
+];
+
+const SetInputFieldModal = ({
+ defaultField = {
+ label: '',
+ key: '',
+ type: FlowInputItemTypeEnum.target,
+ valueType: FlowValueTypeEnum.string,
+ description: '',
+ required: false
+ },
+ onClose,
+ onSubmit
+}: {
+ defaultField?: FlowInputItemType;
+ onClose: () => void;
+ onSubmit: (data: FlowInputItemType) => void;
+}) => {
+ const { t } = useTranslation();
+ const { register, getValues, setValue, handleSubmit } = useForm({
+ defaultValues: defaultField
+ });
+ const [refresh, setRefresh] = useState(false);
+
+ return (
+
+
+
+ {t('app.Input Field Settings')}
+
+
+
+ 必填
+
+
+
+ 字段类型
+ {
+ setValue('valueType', e);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+ 字段名
+
+
+
+
+ 字段 key
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(SetInputFieldModal);
diff --git a/client/src/pages/app/detail/components/AdEdit/components/modules/SetOutputFieldModal.tsx b/client/src/pages/app/detail/components/AdEdit/components/modules/SetOutputFieldModal.tsx
new file mode 100644
index 000000000..1ad4148d8
--- /dev/null
+++ b/client/src/pages/app/detail/components/AdEdit/components/modules/SetOutputFieldModal.tsx
@@ -0,0 +1,105 @@
+import React, { useMemo, useState } from 'react';
+import {
+ Box,
+ Button,
+ ModalHeader,
+ ModalFooter,
+ ModalBody,
+ Flex,
+ Switch,
+ Input,
+ FormControl
+} from '@chakra-ui/react';
+import type { ContextExtractAgentItemType, HttpFieldItemType } from '@/types/app';
+import { useForm } from 'react-hook-form';
+import { customAlphabet } from 'nanoid';
+const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
+import MyModal from '@/components/MyModal';
+import Avatar from '@/components/Avatar';
+import MyTooltip from '@/components/MyTooltip';
+import { FlowOutputItemTypeEnum, FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
+import { useTranslation } from 'react-i18next';
+import MySelect from '@/components/Select';
+import { FlowOutputItemType } from '@/types/flow';
+
+const typeSelectList = [
+ {
+ label: '字符串',
+ value: FlowValueTypeEnum.string
+ },
+ {
+ label: '数字',
+ value: FlowValueTypeEnum.number
+ },
+ {
+ label: '布尔',
+ value: FlowValueTypeEnum.boolean
+ },
+ {
+ label: '任意',
+ value: FlowValueTypeEnum.any
+ }
+];
+
+const SetInputFieldModal = ({
+ defaultField,
+ onClose,
+ onSubmit
+}: {
+ defaultField: FlowOutputItemType;
+ onClose: () => void;
+ onSubmit: (data: FlowOutputItemType) => void;
+}) => {
+ const { t } = useTranslation();
+ const { register, getValues, setValue, handleSubmit } = useForm({
+ defaultValues: defaultField
+ });
+ const [refresh, setRefresh] = useState(false);
+
+ return (
+
+
+
+ {t('app.Output Field Settings')}
+
+
+
+ 字段类型
+ {
+ setValue('valueType', e);
+ setRefresh(!refresh);
+ }}
+ />
+
+
+ 字段名
+
+
+
+
+ 字段 key
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default React.memo(SetInputFieldModal);
diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx
index b76743ee6..04b72a8f4 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/render/RenderInput.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import type { FlowInputItemType, FlowModuleItemType } from '@/types/flow';
import {
Box,
@@ -8,38 +8,129 @@ import {
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
- NumberDecrementStepper
+ NumberDecrementStepper,
+ Flex
} from '@chakra-ui/react';
import { FlowInputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
+import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
import TargetHandle from './TargetHandle';
+import MyIcon from '@/components/Icon';
+const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
export const Label = ({
- required = false,
- children,
- description
-}: {
- required?: boolean;
- children: React.ReactNode | string;
- description?: string;
-}) => (
-
- {children}
- {required && (
-
- *
+ moduleId,
+ inputKey,
+ onChangeNode,
+ ...item
+}: FlowInputItemType & {
+ moduleId: string;
+ inputKey: string;
+ onChangeNode: FlowModuleItemType['onChangeNode'];
+}) => {
+ const { required = false, description, edit, label, type, valueType } = item;
+ const [editField, setEditField] = useState();
+
+ return (
+
+
+ {label}
+ {description && (
+
+
+
+ )}
+ {required && (
+
+ *
+
+ )}
- )}
- {description && (
-
-
-
- )}
-
-);
+
+ {(type === FlowInputItemTypeEnum.target || valueType) && (
+
+ )}
+
+ {edit && (
+ <>
+
+ setEditField({
+ ...item,
+ key: inputKey
+ })
+ }
+ />
+ {
+ {
+ console.log(moduleId, inputKey, valueType);
+ }
+ onChangeNode({
+ moduleId,
+ type: 'delInput',
+ key: inputKey,
+ value: ''
+ });
+ }}
+ />
+ >
+ )}
+ {!!editField && (
+ setEditField(undefined)}
+ onSubmit={(data) => {
+ // same key
+ if (editField.key === data.key) {
+ onChangeNode({
+ moduleId,
+ type: 'inputs',
+ key: inputKey,
+ value: data
+ });
+ } else {
+ // diff key. del and add
+ onChangeNode({
+ moduleId,
+ type: 'delInput',
+ key: editField.key,
+ value: ''
+ });
+ onChangeNode({
+ moduleId,
+ type: 'addInput',
+ key: editField.key,
+ value: data
+ });
+ }
+ setEditField(undefined);
+ }}
+ />
+ )}
+
+ );
+};
const RenderBody = ({
flowInputList,
@@ -59,13 +150,12 @@ const RenderBody = ({
item.type !== FlowInputItemTypeEnum.hidden && (
{!!item.label && (
-
+
)}
{item.type === FlowInputItemTypeEnum.numberInput && (
@@ -76,8 +166,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: item.key,
- value: Number(e)
+ value: {
+ ...item,
+ value: Number(e)
+ }
});
}}
>
@@ -95,8 +189,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: item.key,
- value: e.target.value
+ value: {
+ ...item,
+ value: e.target.value
+ }
});
}}
/>
@@ -110,8 +208,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: item.key,
- value: e.target.value
+ value: {
+ ...item,
+ value: e.target.value
+ }
});
}}
/>
@@ -124,8 +226,12 @@ const RenderBody = ({
onchange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: item.key,
- value: e
+ value: {
+ ...item,
+ value: e
+ }
});
}}
/>
@@ -142,8 +248,12 @@ const RenderBody = ({
onChange={(e) => {
onChangeNode({
moduleId,
+ type: 'inputs',
key: item.key,
- value: e
+ value: {
+ ...item,
+ value: e
+ }
});
}}
/>
diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx
index f4178cd56..0769bff81 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/render/RenderOutput.tsx
@@ -1,37 +1,147 @@
-import React from 'react';
-import type { FlowOutputItemType } from '@/types/flow';
+import React, { useCallback, useState } from 'react';
+import type { FlowModuleItemType, FlowOutputItemType } from '@/types/flow';
import { Box, Flex } from '@chakra-ui/react';
import { FlowOutputItemTypeEnum } from '@/constants/flow';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { Handle, Position } from 'reactflow';
import MyTooltip from '@/components/MyTooltip';
import SourceHandle from './SourceHandle';
+import MyIcon from '@/components/Icon';
+import dynamic from 'next/dynamic';
+const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
const Label = ({
- children,
- description
-}: {
- children: React.ReactNode | string;
- description?: string;
-}) => (
-
- {description && (
-
-
-
- )}
- {children}
-
-);
+ moduleId,
+ outputKey,
+ delOutputByKey,
+ updateOutput,
+ onChangeNode,
+ addUpdateOutput,
+ ...item
+}: FlowOutputItemType & {
+ outputKey: string;
+ moduleId: string;
+ delOutputByKey: (key: string) => void;
+ updateOutput: (key: string, val: FlowOutputItemType) => void;
+ addUpdateOutput: (val: FlowOutputItemType) => void;
+ onChangeNode: FlowModuleItemType['onChangeNode'];
+}) => {
+ const { label, description, edit } = item;
+ const [editField, setEditField] = useState();
+
+ return (
+
+ {edit && (
+ <>
+
+ setEditField({
+ ...item,
+ key: outputKey
+ })
+ }
+ />
+ delOutputByKey(outputKey)}
+ />
+ >
+ )}
+ {description && (
+
+
+
+ )}
+ {label}
+
+ {!!editField && (
+ setEditField(undefined)}
+ onSubmit={(data) => {
+ console.log(data); // same key
+ if (editField.key === data.key) {
+ updateOutput(data.key, data);
+ } else {
+ delOutputByKey(editField.key);
+ addUpdateOutput(data);
+ }
+ setEditField(undefined);
+ }}
+ />
+ )}
+
+ );
+};
+
+const RenderOutput = ({
+ moduleId,
+ flowOutputList,
+ onChangeNode
+}: {
+ moduleId: string;
+ flowOutputList: FlowOutputItemType[];
+ onChangeNode: FlowModuleItemType['onChangeNode'];
+}) => {
+ const delOutputByKey = useCallback(
+ (key: string) => {
+ onChangeNode({
+ moduleId,
+ type: 'outputs',
+ key: '',
+ value: flowOutputList.filter((output) => output.key !== key)
+ });
+ },
+ [moduleId, flowOutputList, onChangeNode]
+ );
+ const updateOutput = useCallback(
+ (key: string, val: FlowOutputItemType) => {
+ onChangeNode({
+ moduleId,
+ type: 'outputs',
+ key: '',
+ value: flowOutputList.map((output) => (output.key === key ? val : output))
+ });
+ },
+ [flowOutputList, moduleId, onChangeNode]
+ );
+ const addUpdateOutput = useCallback(
+ (val: FlowOutputItemType) => {
+ onChangeNode({
+ moduleId,
+ type: 'outputs',
+ key: '',
+ value: flowOutputList.concat(val)
+ });
+ },
+ [flowOutputList, moduleId, onChangeNode]
+ );
-const RenderOutput = ({ flowOutputList }: { flowOutputList: FlowOutputItemType[] }) => {
return (
<>
{flowOutputList.map(
(item) =>
item.type !== FlowOutputItemTypeEnum.hidden && (
-
+
{item.type === FlowOutputItemTypeEnum.source && (
diff --git a/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx b/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx
index 6fe8a72b7..8eb0198f2 100644
--- a/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/components/render/TargetHandle.tsx
@@ -16,7 +16,7 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
valueType
? FlowValueTypeStyle[valueType]
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
- []
+ [valueType]
);
return (
diff --git a/client/src/pages/app/detail/components/AdEdit/index.tsx b/client/src/pages/app/detail/components/AdEdit/index.tsx
index 6761996bf..7fb8bdba6 100644
--- a/client/src/pages/app/detail/components/AdEdit/index.tsx
+++ b/client/src/pages/app/detail/components/AdEdit/index.tsx
@@ -71,6 +71,9 @@ const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'),
const NodeExtract = dynamic(() => import('./components/Nodes/NodeExtract'), {
ssr: false
});
+const NodeHttp = dynamic(() => import('./components/Nodes/NodeHttp'), {
+ ssr: false
+});
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
@@ -87,7 +90,8 @@ const nodeTypes = {
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeCQNode,
- [FlowModuleTypeEnum.contentExtract]: NodeExtract
+ [FlowModuleTypeEnum.contentExtract]: NodeExtract,
+ [FlowModuleTypeEnum.httpRequest]: NodeHttp
// [FlowModuleTypeEnum.empty]: EmptyModule
};
const edgeTypes = {
@@ -124,11 +128,10 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId,
- position: item.position,
flowType: item.data.flowType,
+ position: item.position,
inputs: item.data.inputs.map((item) => ({
- key: item.key,
- value: item.value,
+ ...item,
connected: item.type !== FlowInputItemTypeEnum.target
})),
outputs: item.data.outputs.map((item) => ({
@@ -163,7 +166,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
return modules;
}, [edges, nodes]);
const onChangeNode = useCallback(
- ({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
+ ({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== moduleId) return node;
@@ -172,18 +175,43 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
...node,
data: {
...node.data,
- inputs: node.data.inputs.map((item) => {
- if (item.key === key) {
- return {
- ...item,
- [valueKey]: value
- };
- }
- return item;
- })
+ inputs: node.data.inputs.map((item) => (item.key === key ? value : item))
}
};
}
+ if (type === 'addInput') {
+ const input = node.data.inputs.find((input) => input.key === value.key);
+ if (input) {
+ toast({
+ status: 'warning',
+ title: 'key 重复'
+ });
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ inputs: node.data.inputs
+ }
+ };
+ }
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ inputs: node.data.inputs.concat(value)
+ }
+ };
+ }
+ if (type === 'delInput') {
+ return {
+ ...node,
+ data: {
+ ...node.data,
+ inputs: node.data.inputs.filter((item) => item.key !== key)
+ }
+ };
+ }
+ console.log(value);
return {
...node,
diff --git a/client/src/pages/kb/detail/components/Import/FileSelect.tsx b/client/src/pages/kb/detail/components/Import/FileSelect.tsx
index c97375fa4..20cdf8c14 100644
--- a/client/src/pages/kb/detail/components/Import/FileSelect.tsx
+++ b/client/src/pages/kb/detail/components/Import/FileSelect.tsx
@@ -59,7 +59,7 @@ const FileSelect = ({
)}
{isCsv && (
): AnswerResponse => {
+ const { res, text = '', stream } = props as AnswerProps;
+
+ if (stream) {
+ sseResponse({
+ res,
+ event: sseResponseEventEnum.answer,
+ data: textAdaptGptResponse({
+ text: text.replace(/\\n/g, '\n')
+ })
+ });
+ }
+
+ return {
+ [TaskResponseKeyEnum.answerText]: text
+ };
+};
diff --git a/client/src/types/app.d.ts b/client/src/types/app.d.ts
index fdabd1d57..f544f00b7 100644
--- a/client/src/types/app.d.ts
+++ b/client/src/types/app.d.ts
@@ -51,6 +51,11 @@ export type ContextExtractAgentItemType = {
key: string;
required: boolean;
};
+export type HttpFieldItemType = {
+ label: string;
+ key: string;
+ type: `${FlowValueTypeEnum}`;
+};
export type VariableItemType = {
id: string;
@@ -63,12 +68,11 @@ export type VariableItemType = {
};
/* app module */
-export type AppModuleInputItemType = { key: string; value?: any; connected?: boolean };
export type AppModuleItemType = {
moduleId: string;
position?: XYPosition;
flowType: `${FlowModuleTypeEnum}`;
- inputs: AppModuleInputItemType[];
+ inputs: FlowInputItemType[];
outputs: FlowOutputItemType[];
};
diff --git a/client/src/types/flow.d.ts b/client/src/types/flow.d.ts
index 085888e16..c9ea19930 100644
--- a/client/src/types/flow.d.ts
+++ b/client/src/types/flow.d.ts
@@ -10,10 +10,9 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
export type FlowModuleItemChangeProps = {
moduleId: string;
- type?: 'inputs' | 'outputs';
+ type: 'inputs' | 'outputs' | 'addInput' | 'delInput';
key: string;
value: any;
- valueKey?: keyof FlowInputItemType & keyof FlowBodyItemType;
};
export type FlowInputItemType = {
@@ -22,6 +21,7 @@ export type FlowInputItemType = {
valueType?: `${FlowValueTypeEnum}`;
type: `${FlowInputItemTypeEnum}`;
label: string;
+ edit?: boolean;
connected?: boolean;
description?: string;
placeholder?: string;
@@ -40,6 +40,7 @@ export type FlowOutputTargetItemType = {
export type FlowOutputItemType = {
key: string; // 字段名
label?: string;
+ edit?: boolean;
description?: string;
valueType?: `${FlowValueTypeEnum}`;
type?: `${FlowOutputItemTypeEnum}`;
diff --git a/client/src/utils/adapt.ts b/client/src/utils/adapt.ts
index eb5460b51..0285966b9 100644
--- a/client/src/utils/adapt.ts
+++ b/client/src/utils/adapt.ts
@@ -75,11 +75,17 @@ export const appModule2FlowNode = ({
const template =
ModuleTemplatesFlat.find((template) => template.flowType === item.flowType) || EmptyModule;
+ const concatInputs = template.inputs.concat(
+ item.inputs.filter(
+ (input) => input.label && !template.inputs.find((item) => item.key === input.key)
+ )
+ );
+
// replace item data
const moduleItem: FlowModuleItemType = {
...item,
...template,
- inputs: template.inputs.map((templateInput) => {
+ inputs: concatInputs.map((templateInput) => {
// use latest inputs
const itemInput = item.inputs.find((item) => item.key === templateInput.key) || templateInput;
return {
@@ -87,7 +93,6 @@ export const appModule2FlowNode = ({
value: itemInput.value
};
}),
- // 合并 template 和数据库,文案以 template 为准
outputs: item.outputs.map((output) => {
// unChange outputs
const templateOutput = template.outputs.find((item) => item.key === output.key);
diff --git a/client/src/utils/app.ts b/client/src/utils/app.ts
index 9556612e0..4d21e339a 100644
--- a/client/src/utils/app.ts
+++ b/client/src/utils/app.ts
@@ -1,9 +1,10 @@
-import type { AppModuleInputItemType, AppModuleItemType, VariableItemType } from '@/types/app';
+import type { AppModuleItemType, VariableItemType } from '@/types/app';
import { chatModelList, vectorModelList } from '@/store/static';
import { FlowModuleTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { TaskResponseKeyEnum } from '@/constants/chat';
import type { SelectedKbType } from '@/types/plugin';
+import { FlowInputItemType } from '@/types/flow';
export type EditFormType = {
chatModel: {
@@ -64,7 +65,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
key
}: {
formKey: string;
- inputs: AppModuleInputItemType[];
+ inputs: FlowInputItemType[];
key: string;
}) => {
const propertyPath = formKey.split('.');
@@ -148,7 +149,7 @@ export const appModules2Form = (modules: AppModuleItemType[]) => {
return defaultAppForm;
};
-const chatModelInput = (formData: EditFormType): AppModuleInputItemType[] => [
+const chatModelInput = (formData: EditFormType): FlowInputItemType[] => [
{
key: 'model',
value: formData.chatModel.model,