4.8 preview (#1288)
* Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Newflow (#89) * docs: Add doc for Xinference (#1266) Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * perf: workflow ux * system config * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * Revert "lafAccount add pat & re request when token invalid (#76)" (#77) This reverts commit 83d85dfe37adcaef4833385ea52ee79fd84720be. * rename code * move code * update flow * input type selector * perf: workflow runtime * feat: node adapt newflow * feat: adapt plugin * feat: 360 connection * check workflow * perf: flow 性能 * change plugin input type (#81) * change plugin input type * plugin label mode * perf: nodecard * debug * perf: debug ui * connection ui * change workflow ui (#82) * feat: workflow debug * adapt openAPI for new workflow (#83) * adapt openAPI for new workflow * i18n * perf: plugin debug * plugin input ui * delete * perf: global variable select * fix rebase * perf: workflow performance * feat: input render type icon * input icon * adapt flow (#84) * adapt newflow * temp * temp * fix * feat: app schedule trigger * feat: app schedule trigger * perf: schedule ui * feat: ioslatevm run js code * perf: workflow varialbe table ui * feat: adapt simple mode * feat: adapt input params * output * feat: adapt tamplate * fix: ts * add if-else module (#86) * perf: worker * if else node * perf: tiktoken worker * fix: ts * perf: tiktoken * fix if-else node (#87) * fix if-else node * type * fix * perf: audio render * perf: Parallel worker * log * perf: if else node * adapt plugin * prompt * perf: reference ui * reference ui * handle ux * template ui and plugin tool * adapt v1 workflow * adapt v1 workflow completions * perf: time variables * feat: workflow keyboard shortcuts * adapt v1 workflow * update workflow example doc (#88) * fix: simple mode select tool --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com> * doc * perf: extract node * extra node field * update plugin version * doc * variable * change doc & fix prompt editor (#90) * fold workflow code * value type label --------- Signed-off-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: Carson Yang <yangchuansheng33@gmail.com> Co-authored-by: heheer <71265218+newfish-cmyk@users.noreply.github.com>
@@ -2,7 +2,7 @@
|
||||
"author": "FastGPT Team",
|
||||
"templateType": "other",
|
||||
"name": "自定义反馈",
|
||||
"avatar": "/imgs/module/customFeedback.svg",
|
||||
"avatar": "/imgs/workflow/customFeedback.svg",
|
||||
"intro": "该模块被触发时,会给当前的对话记录增加一条反馈。可用于自动记录对话效果等。",
|
||||
"showStatus": false,
|
||||
"isTool": false,
|
||||
@@ -177,7 +177,7 @@
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"type": "target",
|
||||
"valueType": "any",
|
||||
"label": "core.module.inputType.dynamicTargetInput",
|
||||
"label": "core.workflow.inputType.dynamicTargetInput",
|
||||
"description": "core.module.input.description.dynamic input",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
|
||||
@@ -2,194 +2,130 @@
|
||||
"author": "FastGPT Team",
|
||||
"templateType": "tools",
|
||||
"name": "获取当前时间",
|
||||
"avatar": "/imgs/module/getCurrentTime.svg",
|
||||
"avatar": "/imgs/workflow/getCurrentTime.svg",
|
||||
"intro": "获取用户当前时区的时间。",
|
||||
"showStatus": false,
|
||||
"isTool": true,
|
||||
"weight": 10,
|
||||
"modules": [
|
||||
"nodes": [
|
||||
{
|
||||
"moduleId": "m8dupj",
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/module/input.png",
|
||||
"flowType": "pluginInput",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 187.94161749205568,
|
||||
"y": 179.78772129776746
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "pluginStart",
|
||||
"type": "hidden",
|
||||
"valueType": "boolean",
|
||||
"label": "插件开始运行",
|
||||
"description": "插件开始运行时,会输出一个 True 的标识。有时候,插件不会有额外的的输入,为了顺利的进入下一个阶段,你可以将该值连接到下一个节点的触发器中。",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "pluginStart",
|
||||
"label": "插件开始运行",
|
||||
"type": "source",
|
||||
"valueType": "boolean",
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "cv13yt",
|
||||
"key": "switch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"moduleId": "bjsa7r",
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/module/output.png",
|
||||
"flowType": "pluginOutput",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1176.9471084832217,
|
||||
"y": 138.94098316727695
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "time",
|
||||
"valueType": "string",
|
||||
"label": "time",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": false,
|
||||
"dataType": true,
|
||||
"inputType": false
|
||||
"valueType": true
|
||||
},
|
||||
"connected": true
|
||||
"value": ["WNUvWwYBUfEr", "HdIl1GWi0tnc"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "time",
|
||||
"valueType": "string",
|
||||
"label": "time",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": []
|
||||
}
|
||||
]
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"moduleId": "cv13yt",
|
||||
"nodeId": "WNUvWwYBUfEr",
|
||||
"name": "文本加工",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
"avatar": "/imgs/module/textEditor.svg",
|
||||
"flowType": "pluginModule",
|
||||
"flowNodeType": "pluginModule",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 600.7190079155914,
|
||||
"y": 1.4754510232677944
|
||||
"x": 1030.0794269310472,
|
||||
"y": -350.61013802286885
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "pluginId",
|
||||
"type": "hidden",
|
||||
"label": "",
|
||||
"value": "community-textEditor",
|
||||
"valueType": "string",
|
||||
"connected": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false
|
||||
},
|
||||
{
|
||||
"key": "switch",
|
||||
"type": "triggerAndFinish",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Trigger",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "textarea",
|
||||
"valueType": "string",
|
||||
"label": "文本内容",
|
||||
"type": "textarea",
|
||||
"required": true,
|
||||
"description": "可以通过 {{key}} 的方式引用传入的变量。变量仅支持字符串或数字。",
|
||||
"edit": false,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": true
|
||||
},
|
||||
"connected": false,
|
||||
"placeholder": "可以通过 {{key}} 的方式引用传入的变量。变量仅支持字符串或数字。",
|
||||
"value": "{{cTime}}"
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"valueType": "any",
|
||||
"label": "需要加工的输入",
|
||||
"type": "addInputParam",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "可动态的添加字符串类型变量,在文本编辑中通过 {{key}} 使用变量。非字符串类型,会自动转成字符串类型。",
|
||||
"edit": false,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": false
|
||||
"key": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"inputType": "target",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "{{cTime}}",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"connected": false
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "core.module.output.label.text",
|
||||
"type": "source",
|
||||
"edit": false,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "bjsa7r",
|
||||
"key": "time"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "finish",
|
||||
"label": "",
|
||||
"description": "",
|
||||
"valueType": "boolean",
|
||||
"type": "hidden",
|
||||
"targets": []
|
||||
"label": "text",
|
||||
"type": "static",
|
||||
"canEdit": false
|
||||
}
|
||||
]
|
||||
],
|
||||
"pluginId": "community-textEditor"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "lmpb9v2lo2lk",
|
||||
"target": "WNUvWwYBUfEr",
|
||||
"sourceHandle": "lmpb9v2lo2lk-source-right",
|
||||
"targetHandle": "WNUvWwYBUfEr-target-left"
|
||||
},
|
||||
{
|
||||
"source": "WNUvWwYBUfEr",
|
||||
"target": "i7uow4wj2wdp",
|
||||
"sourceHandle": "WNUvWwYBUfEr-source-right",
|
||||
"targetHandle": "i7uow4wj2wdp-target-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
"author": "FastGPT Team",
|
||||
"templateType": "tools",
|
||||
"name": "文本加工",
|
||||
"avatar": "/imgs/module/textEditor.svg",
|
||||
"avatar": "/imgs/workflow/textEditor.svg",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
"showStatus": false,
|
||||
"isTool": false,
|
||||
"weight": 100,
|
||||
"modules": [
|
||||
"nodes": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "定义插件输入",
|
||||
"flowType": "pluginInput",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
@@ -19,313 +21,223 @@
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "textarea",
|
||||
"valueType": "string",
|
||||
"label": "文本内容",
|
||||
"type": "textarea",
|
||||
"required": true,
|
||||
"description": "可以通过 {{key}} 的方式引用传入的变量。变量仅支持字符串或数字。",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": true
|
||||
},
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"valueType": "any",
|
||||
"label": "需要加工的输入",
|
||||
"type": "addInputParam",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "可动态的添加字符串类型变量,在文本编辑中通过 {{key}} 使用变量。非字符串类型,会自动转成字符串类型。",
|
||||
"edit": true,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": false
|
||||
"key": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"inputType": "target",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"connected": true
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "textarea",
|
||||
"id": "ILc8GS7iU53M",
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本内容",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"key": "text"
|
||||
}
|
||||
]
|
||||
"label": "文本",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"valueType": "any",
|
||||
"label": "需要加工的输入",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"key": "DYNAMIC_INPUT_KEY"
|
||||
}
|
||||
]
|
||||
"id": "2LCxDnOSculb",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "定义插件输出",
|
||||
"flowType": "pluginOutput",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -145.93201540017395
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "core.module.output.label.text",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": false,
|
||||
"dataType": true,
|
||||
"inputType": false
|
||||
"valueType": true
|
||||
},
|
||||
"connected": true
|
||||
"value": ["CRT7oIEU8v2P", "vooswj3VxKW8"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "core.module.output.label.text",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": []
|
||||
"label": "text",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "49de3g",
|
||||
"name": "HTTP模块",
|
||||
"flowType": "httpRequest468",
|
||||
"nodeId": "CRT7oIEU8v2P",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "/imgs/workflow/http.png",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1086.8929621216014,
|
||||
"y": -451.7550009773506
|
||||
"x": 1070.8458389994719,
|
||||
"y": -415.09022555407836
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "switch",
|
||||
"type": "target",
|
||||
"label": "core.module.input.label.switch",
|
||||
"description": "core.module.input.description.Trigger",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"type": "custom",
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"list": [
|
||||
{
|
||||
"label": "GET",
|
||||
"value": "GET"
|
||||
},
|
||||
{
|
||||
"label": "POST",
|
||||
"value": "POST"
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"type": "hidden",
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"value": "/api/plugins/textEditor",
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"type": "custom",
|
||||
"valueType": "any",
|
||||
"value": "",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"type": "hidden",
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"type": "hidden",
|
||||
"valueType": "any",
|
||||
"value": "{\r\n \"text\": \"{{text}}\"\r\n}",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"type": "target",
|
||||
"valueType": "any",
|
||||
"label": "core.module.inputType.dynamicTargetInput",
|
||||
"description": "core.module.input.description.dynamic input",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": true,
|
||||
"hideInApp": true,
|
||||
"connected": true
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"]
|
||||
},
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
"valueType": true
|
||||
},
|
||||
"connected": true
|
||||
"value": ["lmpb9v2lo2lk", "ILc8GS7iU53M"]
|
||||
},
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"type": "addInputParam",
|
||||
"valueType": "any",
|
||||
"key": "system_httpMethod",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"inputType": "target",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
},
|
||||
"connected": false
|
||||
"value": "/api/plugins/textEditor"
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": "{\r\n \"text\":\"{{text}}\",\r\n \"system_addInputParam\": {{system_addInputParam}}\r\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "finish",
|
||||
"label": "core.module.output.label.running done",
|
||||
"description": "core.module.output.description.running done",
|
||||
"valueType": "boolean",
|
||||
"type": "hidden",
|
||||
"targets": []
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "addOutputParam",
|
||||
"valueType": "any",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"targets": [],
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"outputType": "source",
|
||||
"valueType": "string"
|
||||
"valueType": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"valueType": "string",
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "vooswj3VxKW8",
|
||||
"type": "dynamic",
|
||||
"key": "text",
|
||||
"label": "core.module.output.label.text",
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"key": "text"
|
||||
}
|
||||
]
|
||||
"valueType": "string",
|
||||
"label": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"source": "lmpb9v2lo2lk",
|
||||
"target": "CRT7oIEU8v2P",
|
||||
"sourceHandle": "lmpb9v2lo2lk-source-right",
|
||||
"targetHandle": "CRT7oIEU8v2P-target-left"
|
||||
},
|
||||
{
|
||||
"source": "CRT7oIEU8v2P",
|
||||
"target": "i7uow4wj2wdp",
|
||||
"sourceHandle": "CRT7oIEU8v2P-source-right",
|
||||
"targetHandle": "i7uow4wj2wdp-target-left"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
{
|
||||
"author": "FastGPT Team",
|
||||
"templateType": "tools",
|
||||
"name": "判断器",
|
||||
"avatar": "/imgs/module/tfSwitch.svg",
|
||||
"intro": "根据传入的内容进行 True False 输出。默认情况下,当传入的内容为 false, undefined, null, 0, none 时,会输出 false。你也可以增加一些自定义的字符串来补充输出 false 的内容。非字符、非数字、非布尔类型,直接输出 True。",
|
||||
"showStatus": false,
|
||||
"isTool": false,
|
||||
"weight": 10,
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"name": "定义插件输入",
|
||||
"flowType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "input",
|
||||
"valueType": "any",
|
||||
"type": "target",
|
||||
"label": "core.module.input.label.TFSwitch input tip",
|
||||
"required": true,
|
||||
"edit": true,
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "rule",
|
||||
"valueType": "string",
|
||||
"label": "core.module.input.label.TFSwitch textarea",
|
||||
"type": "textarea",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.TFSwitch textarea",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true,
|
||||
"inputType": true
|
||||
},
|
||||
"connected": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "input",
|
||||
"valueType": "any",
|
||||
"label": "core.module.input.label.TFSwitch input tip",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "8kld99",
|
||||
"key": "input"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "rule",
|
||||
"valueType": "string",
|
||||
"label": "core.module.input.label.TFSwitch textarea",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "8kld99",
|
||||
"key": "rule"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"name": "定义插件输出",
|
||||
"flowType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1985.3791673445353,
|
||||
"y": -144.90535546692078
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "true",
|
||||
"type": "target",
|
||||
"valueType": "boolean",
|
||||
"label": "True",
|
||||
"required": true,
|
||||
"edit": true,
|
||||
"connected": true,
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"key": "false",
|
||||
"valueType": "boolean",
|
||||
"label": "False",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": false,
|
||||
"dataType": true,
|
||||
"inputType": false
|
||||
},
|
||||
"connected": true
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "true",
|
||||
"valueType": "boolean",
|
||||
"label": "True",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": []
|
||||
},
|
||||
{
|
||||
"key": "false",
|
||||
"valueType": "boolean",
|
||||
"label": "False",
|
||||
"type": "source",
|
||||
"edit": true,
|
||||
"targets": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"moduleId": "8kld99",
|
||||
"name": "HTTP模块",
|
||||
"flowType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1210.560012858087,
|
||||
"y": -387.62433050951756
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "switch",
|
||||
"type": "target",
|
||||
"label": "core.module.input.label.switch",
|
||||
"description": "core.module.input.description.Trigger",
|
||||
"valueType": "any",
|
||||
"showTargetInApp": true,
|
||||
"showTargetInPlugin": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"type": "custom",
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"list": [
|
||||
{
|
||||
"label": "GET",
|
||||
"value": "GET"
|
||||
},
|
||||
{
|
||||
"label": "POST",
|
||||
"value": "POST"
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"type": "hidden",
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"value": "/api/plugins/TFSwitch",
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"type": "custom",
|
||||
"valueType": "any",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"type": "hidden",
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"type": "hidden",
|
||||
"valueType": "any",
|
||||
"value": "{\r\n \"input\": \"{{input}}\",\r\n \"rule\": \"{{rule}}\"\r\n}",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "DYNAMIC_INPUT_KEY",
|
||||
"type": "target",
|
||||
"valueType": "any",
|
||||
"label": "core.module.inputType.dynamicTargetInput",
|
||||
"description": "core.module.input.description.dynamic input",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": true,
|
||||
"hideInApp": true,
|
||||
"connected": false
|
||||
},
|
||||
{
|
||||
"key": "input",
|
||||
"valueType": "any",
|
||||
"label": "input",
|
||||
"type": "target",
|
||||
"required": true,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "rule",
|
||||
"valueType": "string",
|
||||
"label": "rule",
|
||||
"type": "target",
|
||||
"required": false,
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"connected": true
|
||||
},
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"type": "addInputParam",
|
||||
"valueType": "any",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"showTargetInApp": false,
|
||||
"showTargetInPlugin": false,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"required": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"inputType": "target",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
},
|
||||
"connected": false
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"key": "finish",
|
||||
"label": "core.module.output.label.running done",
|
||||
"description": "core.module.output.description.running done",
|
||||
"valueType": "boolean",
|
||||
"type": "hidden",
|
||||
"targets": []
|
||||
},
|
||||
{
|
||||
"key": "system_addOutputParam",
|
||||
"type": "addOutputParam",
|
||||
"valueType": "any",
|
||||
"label": "",
|
||||
"targets": [],
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"defaultEditField": {
|
||||
"label": "",
|
||||
"key": "",
|
||||
"description": "",
|
||||
"outputType": "source",
|
||||
"valueType": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"valueType": "boolean",
|
||||
"key": "true",
|
||||
"label": "true",
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"key": "true"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "source",
|
||||
"valueType": "boolean",
|
||||
"key": "false",
|
||||
"label": "false",
|
||||
"description": "",
|
||||
"edit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"name": true,
|
||||
"description": true,
|
||||
"dataType": true
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"key": "false"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8,15 +8,6 @@ const nextConfig = {
|
||||
reactStrictMode: process.env.NODE_ENV === 'development' ? false : true,
|
||||
compress: true,
|
||||
webpack(config, { isServer }) {
|
||||
if (!isServer) {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
fallback: {
|
||||
...config.resolve.fallback,
|
||||
fs: false
|
||||
}
|
||||
};
|
||||
}
|
||||
Object.assign(config.resolve.alias, {
|
||||
'@mongodb-js/zstd': false,
|
||||
'@aws-sdk/credential-providers': false,
|
||||
@@ -41,6 +32,45 @@ const nextConfig = {
|
||||
unknownContextCritical: false
|
||||
};
|
||||
|
||||
if (isServer) {
|
||||
config.externals.push('isolated-vm');
|
||||
config.externals.push('worker_threads');
|
||||
|
||||
if (config.name === 'server') {
|
||||
// config.output.globalObject = 'self';
|
||||
|
||||
const oldEntry = config.entry;
|
||||
config = {
|
||||
...config,
|
||||
async entry(...args) {
|
||||
const entries = await oldEntry(...args);
|
||||
return {
|
||||
...entries,
|
||||
'worker/htmlStr2Md': path.resolve(
|
||||
process.cwd(),
|
||||
'../../packages/service/worker/htmlStr2Md.ts'
|
||||
),
|
||||
'worker/countGptMessagesTokens': path.resolve(
|
||||
process.cwd(),
|
||||
'../../packages/service/worker/tiktoken/countGptMessagesTokens.ts'
|
||||
)
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
config.resolve = {
|
||||
...config.resolve,
|
||||
fallback: {
|
||||
...config.resolve.fallback,
|
||||
fs: false
|
||||
}
|
||||
};
|
||||
if (!config.externals) {
|
||||
config.externals = [];
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
transpilePackages: ['@fastgpt/*'],
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@fastgpt/service": "workspace:*",
|
||||
"@fastgpt/web": "workspace:*",
|
||||
"@fortaine/fetch-event-source": "^3.0.6",
|
||||
"@node-rs/jieba": "1.10.0",
|
||||
"@tanstack/react-query": "^4.24.10",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"axios": "^1.5.1",
|
||||
@@ -39,7 +40,6 @@
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"mermaid": "^10.2.3",
|
||||
"@node-rs/jieba": "1.10.0",
|
||||
"nanoid": "^4.0.1",
|
||||
"next": "13.5.2",
|
||||
"next-i18next": "15.2.0",
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 893 B After Width: | Height: | Size: 893 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 503 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
3
projects/app/public/imgs/workflow/ifElse.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2927 3.37501C14.4606 2.89006 14.0406 2.40519 13.5366 2.50225L10.702 3.04821C10.198 3.14526 9.98814 3.75145 10.3242 4.13935L10.6298 4.49219L7.33594 7.29243H2.67613C2.16053 7.29243 1.74255 7.7104 1.74255 8.226C1.74255 8.7416 2.16053 9.15957 2.67613 9.15957H7.46753C7.54295 9.15957 7.61629 9.15063 7.68653 9.13374C7.90786 9.1392 8.13194 9.0664 8.31389 8.91172L11.8524 5.90351L12.2143 6.32125C12.5503 6.70915 13.1803 6.58782 13.3482 6.10287L14.2927 3.37501ZM9.04488 9.24647C8.81019 9.44721 8.78266 9.80019 8.98339 10.0349L10.8753 12.2468L10.4929 12.5816C10.1067 12.9196 10.2312 13.5489 10.717 13.7143L13.4496 14.645C13.9354 14.8104 14.4181 14.3879 14.3185 13.8845L13.7582 11.0527C13.6586 10.5492 13.0513 10.3424 12.6652 10.6804L12.2804 11.0171L10.4023 8.82125C10.2016 8.58656 9.84861 8.55903 9.61392 8.75977L9.04488 9.24647Z" fill="#00A9A6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 995 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 776 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 471 B After Width: | Height: | Size: 471 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -108,6 +108,9 @@
|
||||
"Load Failed": "Load Failed",
|
||||
"Loading": "Loading",
|
||||
"More settings": "More settings",
|
||||
"MultipleRowSelect": {
|
||||
"No data": "No data"
|
||||
},
|
||||
"Name": "Name",
|
||||
"Name Can": "Name Can't Be Empty",
|
||||
"Name is empty": "Name is empty",
|
||||
@@ -276,8 +279,12 @@
|
||||
"App intro": "App intro",
|
||||
"App params config": "App Config",
|
||||
"Chat Variable": "",
|
||||
"Config schedule plan": "Config schedule config",
|
||||
"Config whisper": "Config whisper",
|
||||
"External using": "External use",
|
||||
"Interval timer config": "Interval timer config",
|
||||
"Interval timer run": "Timed execution",
|
||||
"Interval timer tip": "Applications can be executed periodically",
|
||||
"Make a brief introduction of your app": "Make a brief introduction of your app",
|
||||
"Max histories": "Dialog round",
|
||||
"Max tokens": "Max tokens",
|
||||
@@ -293,6 +300,7 @@
|
||||
"Select app from template": "Select from the template",
|
||||
"Select quote template": "Select quote template",
|
||||
"Set a name for your app": "App name",
|
||||
"Setting ai property": "Click Configure AI model properties",
|
||||
"Share link": "Share",
|
||||
"Share link desc": "Share links with other users and use them directly without logging in",
|
||||
"Share link desc detail": "You can share the model directly with other users to have a conversation, and the other user can have a conversation directly without logging in. Note that this function will consume the balance of your account, please keep the link!",
|
||||
@@ -306,6 +314,7 @@
|
||||
"Tool call tip": "The AI model automatically selects one or more tools to use. If this feature is enabled, the knowledge base call is also called as a tool. Please try to choose the AI model that supports \"function call\", the effect is better.",
|
||||
"ToolCall": {
|
||||
"No plugin": "No plugins",
|
||||
"Parameter setting": "",
|
||||
"Setting tool": "Setting tool",
|
||||
"System": "System",
|
||||
"Team": "Team",
|
||||
@@ -358,6 +367,15 @@
|
||||
"Show History": "Show History",
|
||||
"Web Link": "Web Link"
|
||||
},
|
||||
"schedule": {
|
||||
"Default prompt": "Default prompt",
|
||||
"Default prompt placeholder": "Default problem when executing the application",
|
||||
"Every day": "Every {{hour}} hour",
|
||||
"Every month": "{{day}} {{hour}}:00 Every month",
|
||||
"Every week": "Every week {{day}} {{hour}}:00",
|
||||
"Interval": "Per {{interval}} hour",
|
||||
"Open schedule": "Enable schedule"
|
||||
},
|
||||
"setting": "App Setting",
|
||||
"share": {
|
||||
"Amount limit tip": "A maximum of 10 groups can be created",
|
||||
@@ -415,6 +433,7 @@
|
||||
},
|
||||
"chat": {
|
||||
"Admin Mark Content": "Corrected response",
|
||||
"Audio Not Support": "The device does not support voice play",
|
||||
"Audio Speech Error": "Audio Speech Error",
|
||||
"Cancel Speak": "Cancel speak",
|
||||
"Canceled Speak": "Voice input has been cancelled",
|
||||
@@ -505,6 +524,7 @@
|
||||
"module http body": "Body",
|
||||
"module http result": "Response",
|
||||
"module http url": "Request Url",
|
||||
"module if else Result": "if-else Result",
|
||||
"module limit": "Count Limit",
|
||||
"module maxToken": "MaxTokens",
|
||||
"module model": "Model",
|
||||
@@ -831,6 +851,7 @@
|
||||
"Input description": "",
|
||||
"label": "Dataset quote"
|
||||
},
|
||||
"Default Value": "Default value",
|
||||
"Default value": "Default ",
|
||||
"Default value placeholder": "Null characters are returned by default",
|
||||
"Edit intro": "Edit",
|
||||
@@ -842,13 +863,22 @@
|
||||
"Http request settings": "Request settings",
|
||||
"Input Type": "Input Type",
|
||||
"Laf sync params": "Sync params",
|
||||
"Max Length": "Max Length",
|
||||
"Max Length placeholder": "The max length of input text",
|
||||
"Max Value": "Max Value",
|
||||
"Min Value": "",
|
||||
"Model List": "Model list",
|
||||
"No Config Tips": "no configuration",
|
||||
"Output Type": "Output Type",
|
||||
"Plugin output must connect": "Custom outputs must all be connected",
|
||||
"Plugin tool Description": "",
|
||||
"QueryExtension": {
|
||||
"placeholder": "Questions about python introduction and usage, etc. The current conversation is related to the game GTA5.",
|
||||
"tip": "Describes the scope of the current conversation, making it easier for the AI to complete first or vague questions, thereby enhancing the knowledge base's ability to continue conversations.If \n is empty, the question completion function is not used in the first conversation. "
|
||||
},
|
||||
"Quote prompt setting": "Quote prompt setting",
|
||||
"Qupte prompt setting": "",
|
||||
"Select Data List": "Data list",
|
||||
"Select app": "Select app",
|
||||
"Setting quote prompt": "Setting quote prompt",
|
||||
"Unlink tip": "[{{name}}] An unfilled or unconnected parameter exists",
|
||||
@@ -857,7 +887,8 @@
|
||||
"Variable import": "Variable import",
|
||||
"edit": {
|
||||
"Field Already Exist": "Key already exist",
|
||||
"Field Edit": "Field Edit"
|
||||
"Field Edit": "Field Edit",
|
||||
"Field Name Cannot Be Empty": "Field Name Cannot Be Empty"
|
||||
},
|
||||
"extract": {
|
||||
"Add field": "Add",
|
||||
@@ -890,8 +921,10 @@
|
||||
"input": {
|
||||
"Add Input": "Add Input",
|
||||
"Input Number": "Input: {{length}}",
|
||||
"add": "Add condition",
|
||||
"description": {
|
||||
"Background": "",
|
||||
"HTTP Dynamic Input": "Receive output values from preceding nodes as variables, which can be used as parameters in HTTP requests.",
|
||||
"Http Request Header": "User-defined request header, please strictly fill in the JSON string.\n1. Make sure the last attribute has no commas\n2. Make sure key contains double quotes\nFor example: {\"Authorization\":\"Bearer xxx\"}",
|
||||
"Http Request Url": "New HTTP request address. If two 'request addresses' appear, the module can be deleted and rejoined, and the latest module configuration will be pulled.",
|
||||
"Quote": "Object array format, structure:\n[{q:' question ',a:' answer '}]",
|
||||
@@ -924,17 +957,6 @@
|
||||
"Classify background": "For example:\n1.AIGC (Artificial Intelligence Generates content) refers to the automatic or semi-automatic generation of digital content, such as text, images, music, videos, and so on, using artificial intelligence technologies. AIGC technologies include, but are not limited to, natural language processing, computer vision, machine learning, and deep learning. These technologies can create new content or modify existing content to meet specific creative, educational, entertainment or information needs."
|
||||
}
|
||||
},
|
||||
"inputType": {
|
||||
"chat history": "History",
|
||||
"dynamicTargetInput": "dynamic Target Input",
|
||||
"input": "Input",
|
||||
"selectApp": "App Selector",
|
||||
"selectDataset": "Dataset Selector",
|
||||
"selectLLMModel": "Select Chat Model",
|
||||
"switch": "Switch",
|
||||
"target": "Target Data",
|
||||
"textarea": "Textarea"
|
||||
},
|
||||
"laf": {
|
||||
"Select laf function": ""
|
||||
},
|
||||
@@ -1051,6 +1073,63 @@
|
||||
"shareChat": {
|
||||
"Init Error": "Init Chat Error",
|
||||
"Init History Error": "Init History Error"
|
||||
},
|
||||
"workflow": {
|
||||
"Add variable": "Add",
|
||||
"Can not delete node": "Can not delete the node",
|
||||
"Change input type tip": "Changing the input type will empty the entered values, please confirm!",
|
||||
"Check Failed": "Workflow verification fails. Check whether the node or connection is normal",
|
||||
"Confirm stop debug": "Do you want to terminate debugging? Debugging information is not retained.",
|
||||
"Copy node": "Copy node",
|
||||
"Custom inputs": "Inputs",
|
||||
"Custom outputs": "Outputs",
|
||||
"Custom variable": "Custom variable",
|
||||
"Dataset quote": "Dataset quote",
|
||||
"Debug": "Debug",
|
||||
"Debug Node": "Workflow Debug",
|
||||
"Failed": "Running failed",
|
||||
"Not intro": "This node is not introduced",
|
||||
"Run from here": "Run from here",
|
||||
"Run result": "Run result",
|
||||
"Running": "Running",
|
||||
"Skipped": "Skipped",
|
||||
"Stop debug": "Stop",
|
||||
"Success": "Running success",
|
||||
"Value type": "Type",
|
||||
"chat": {
|
||||
"Quote prompt": "Quote prompt"
|
||||
},
|
||||
"debug": {
|
||||
"Done": "Done",
|
||||
"Hide result": "Hide result",
|
||||
"Not result": "Not result",
|
||||
"Run result": "",
|
||||
"Show result": "Show result"
|
||||
},
|
||||
"inputType": {
|
||||
"JSON Editor": "JSON Editor",
|
||||
"Manual input": "",
|
||||
"Manual select": "Select",
|
||||
"Reference": "Reference",
|
||||
"Required": "Required",
|
||||
"Select edit field": "Editable field",
|
||||
"Select input default value": "Default value",
|
||||
"Select input type": "Configurable input types",
|
||||
"Select input type placeholder": "Please select a configurable input type",
|
||||
"chat history": "History",
|
||||
"dynamicTargetInput": "dynamic Target Input",
|
||||
"input": "Input",
|
||||
"number input": "number input",
|
||||
"selectApp": "App Selector",
|
||||
"selectDataset": "Dataset Selector",
|
||||
"selectLLMModel": "Select Chat Model",
|
||||
"switch": "Switch",
|
||||
"target": "Target Data",
|
||||
"textarea": "Textarea"
|
||||
},
|
||||
"tool": {
|
||||
"Select Tool": "Select Tool"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"Connection type is different": "连接的类型不一致",
|
||||
"Copy Module Config": "复制配置",
|
||||
"Dataset Quote Template": "知识库问答模式",
|
||||
"Export Config Successful": "已复制配置,请注意检查是否有重要数据",
|
||||
"Export Config Successful": "已复制配置,自动过滤部分敏感信息,请注意检查是否仍有敏感数据",
|
||||
"Export Configs": "导出配置",
|
||||
"Feedback Count": "用户反馈",
|
||||
"Import Configs": "导入配置",
|
||||
@@ -108,6 +108,9 @@
|
||||
"Load Failed": "加载失败",
|
||||
"Loading": "加载中...",
|
||||
"More settings": "更多设置",
|
||||
"MultipleRowSelect": {
|
||||
"No data": "没有可选值"
|
||||
},
|
||||
"Name": "名称",
|
||||
"Name Can": "名称不能为空",
|
||||
"Name is empty": "名称不能为空",
|
||||
@@ -276,8 +279,12 @@
|
||||
"App intro": "应用介绍",
|
||||
"App params config": "应用配置",
|
||||
"Chat Variable": "对话框变量",
|
||||
"Config schedule plan": "配置定时执行",
|
||||
"Config whisper": "配置语音输入",
|
||||
"External using": "外部使用途径",
|
||||
"Interval timer config": "定时执行配置",
|
||||
"Interval timer run": "定时执行",
|
||||
"Interval timer tip": "可定时执行应用",
|
||||
"Make a brief introduction of your app": "给你的 AI 应用一个介绍",
|
||||
"Max histories": "聊天记录数量",
|
||||
"Max tokens": "回复上限",
|
||||
@@ -293,6 +300,7 @@
|
||||
"Select app from template": "从模板中选择",
|
||||
"Select quote template": "选择引用提示模板",
|
||||
"Set a name for your app": "给应用设置一个名称",
|
||||
"Setting ai property": "点击配置AI模型相关属性",
|
||||
"Share link": "免登录窗口",
|
||||
"Share link desc": "分享链接给其他用户,无需登录即可直接进行使用",
|
||||
"Share link desc detail": "可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!",
|
||||
@@ -306,6 +314,7 @@
|
||||
"Tool call tip": "通过AI模型自动选择一个或多个工具进行使用。如果启动该功能,知识库调用也会被当做一个工具调用。请尽量选择支持“函数调用”的AI模型,效果更好。",
|
||||
"ToolCall": {
|
||||
"No plugin": "没有可用的插件",
|
||||
"Parameter setting": "输入参数",
|
||||
"Setting tool": "配置工具",
|
||||
"System": "系统",
|
||||
"Team": "团队",
|
||||
@@ -358,6 +367,15 @@
|
||||
"Show History": "展示历史对话",
|
||||
"Web Link": "网络链接"
|
||||
},
|
||||
"schedule": {
|
||||
"Default prompt": "默认问题",
|
||||
"Default prompt placeholder": "执行应用时的默认问题",
|
||||
"Every day": "每天{{hour}}:00",
|
||||
"Every month": "每月{{day}}号{{hour}}:00",
|
||||
"Every week": "每周{{day}} {{hour}}:00",
|
||||
"Interval": "每{{interval}}小时",
|
||||
"Open schedule": "定时执行"
|
||||
},
|
||||
"setting": "应用信息设置",
|
||||
"share": {
|
||||
"Amount limit tip": "最多创建10组",
|
||||
@@ -415,6 +433,7 @@
|
||||
},
|
||||
"chat": {
|
||||
"Admin Mark Content": "纠正后的回复",
|
||||
"Audio Not Support": "设备不支持语音播放",
|
||||
"Audio Speech Error": "语音播报异常",
|
||||
"Cancel Speak": "取消语音输入",
|
||||
"Canceled Speak": "语音输入已取消",
|
||||
@@ -505,6 +524,7 @@
|
||||
"module http body": "请求体",
|
||||
"module http result": "响应体",
|
||||
"module http url": "请求地址",
|
||||
"module if else Result": "判断器结果",
|
||||
"module limit": "单次搜索上限",
|
||||
"module maxToken": "最大响应 Tokens",
|
||||
"module model": "模型",
|
||||
@@ -611,8 +631,7 @@
|
||||
"success": "开始同步"
|
||||
}
|
||||
},
|
||||
"training": {
|
||||
}
|
||||
"training": {}
|
||||
},
|
||||
"data": {
|
||||
"Auxiliary Data": "辅助数据",
|
||||
@@ -833,6 +852,7 @@
|
||||
"Input description": "可接收知识库搜索的结果。",
|
||||
"label": "知识库引用"
|
||||
},
|
||||
"Default Value": "默认值",
|
||||
"Default value": "默认值",
|
||||
"Default value placeholder": "不填则默认返回空字符",
|
||||
"Edit intro": "编辑描述",
|
||||
@@ -844,13 +864,22 @@
|
||||
"Http request settings": "请求配置",
|
||||
"Input Type": "输入类型",
|
||||
"Laf sync params": "同步参数",
|
||||
"Max Length": "最大长度",
|
||||
"Max Length placeholder": "输入文本的最大长度",
|
||||
"Max Value": "最大值",
|
||||
"Min Value": "最小值",
|
||||
"Model List": "模型列表",
|
||||
"No Config Tips": "无可配置项",
|
||||
"Output Type": "输出类型",
|
||||
"Plugin output must connect": "自定义输出必须全部连接",
|
||||
"Plugin tool Description": "工具参数需要描述",
|
||||
"QueryExtension": {
|
||||
"placeholder": "例如:\n关于 python 的介绍和使用等问题。\n当前对话与游戏《GTA5》有关。",
|
||||
"tip": "描述当前对话的范围,便于AI补全首次问题或模糊的问题,从而增强知识库连续对话的能力。建议开启该功能后,都简单的描述在对话的背景,否则容易造成补全对象不准确。"
|
||||
},
|
||||
"Quote prompt setting": "引用提示词配置",
|
||||
"Qupte prompt setting": "",
|
||||
"Select Data List": "数据列表",
|
||||
"Select app": "选择应用",
|
||||
"Setting quote prompt": "配置引用提示词",
|
||||
"Unlink tip": "【{{name}}】存在未填或未连接参数",
|
||||
@@ -859,7 +888,8 @@
|
||||
"Variable import": "外部参数输入",
|
||||
"edit": {
|
||||
"Field Already Exist": "key 重复",
|
||||
"Field Edit": "字段编辑"
|
||||
"Field Edit": "字段编辑",
|
||||
"Field Name Cannot Be Empty": "字段名不能为空"
|
||||
},
|
||||
"extract": {
|
||||
"Add field": "新增字段",
|
||||
@@ -892,8 +922,10 @@
|
||||
"input": {
|
||||
"Add Input": "添加入参",
|
||||
"Input Number": "入参: {{length}}",
|
||||
"add": "添加条件",
|
||||
"description": {
|
||||
"Background": "你可以添加一些特定内容的介绍,从而更好的识别用户的问题类型。这个内容通常是给模型介绍一个它不知道的内容。",
|
||||
"HTTP Dynamic Input": "接收前方节点的输出值作为变量,这些变量可以被HTTP请求参数使用。",
|
||||
"Http Request Header": "自定义请求头,请严格填入JSON字符串。\n1. 确保最后一个属性没有逗号\n2. 确保 key 包含双引号\n例如: {\"Authorization\":\"Bearer xxx\"}",
|
||||
"Http Request Url": "新的HTTP请求地址。如果出现两个“请求地址”,可以删除该模块重新加入,会拉取最新的模块配置。",
|
||||
"Quote": "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
@@ -926,17 +958,6 @@
|
||||
"Classify background": "例如: \n1. AIGC(人工智能生成内容)是指使用人工智能技术自动或半自动地生成数字内容,如文本、图像、音乐、视频等。\n2. AIGC技术包括但不限于自然语言处理、计算机视觉、机器学习和深度学习。这些技术可以创建新内容或修改现有内容,以满足特定的创意、教育、娱乐或信息需求。"
|
||||
}
|
||||
},
|
||||
"inputType": {
|
||||
"chat history": "历史记录",
|
||||
"dynamicTargetInput": "动态外部数据",
|
||||
"input": "输入框",
|
||||
"selectApp": "应用选择",
|
||||
"selectDataset": "知识库选择",
|
||||
"selectLLMModel": "对话模型选择",
|
||||
"switch": "开关",
|
||||
"target": "外部数据",
|
||||
"textarea": "段落输入"
|
||||
},
|
||||
"laf": {
|
||||
"Select laf function": "选择laf函数"
|
||||
},
|
||||
@@ -1053,6 +1074,63 @@
|
||||
"shareChat": {
|
||||
"Init Error": "初始化对话框失败",
|
||||
"Init History Error": "初始化聊天记录失败"
|
||||
},
|
||||
"workflow": {
|
||||
"Add variable": "新增变量",
|
||||
"Can not delete node": "该节点不允许删除",
|
||||
"Change input type tip": "修改输入类型会清空已填写的值,请确认!",
|
||||
"Check Failed": "工作流校验失败,请检查节点是否正确填值,以及连线是否正常",
|
||||
"Confirm stop debug": "确认终止调试?调试信息将会不保留。",
|
||||
"Copy node": "已复制节点",
|
||||
"Custom inputs": "自定义输入",
|
||||
"Custom outputs": "自定义输出",
|
||||
"Custom variable": "自定义变量",
|
||||
"Dataset quote": "知识库引用",
|
||||
"Debug": "调试",
|
||||
"Debug Node": "Debug模式",
|
||||
"Failed": "运行失败",
|
||||
"Not intro": "这个节点没有介绍~\\",
|
||||
"Run from here": "从这里开始运行",
|
||||
"Run result": "",
|
||||
"Running": "运行中",
|
||||
"Skipped": "跳过运行",
|
||||
"Stop debug": "停止调试",
|
||||
"Success": "运行成功",
|
||||
"Value type": "数据类型",
|
||||
"chat": {
|
||||
"Quote prompt": "引用提示词"
|
||||
},
|
||||
"debug": {
|
||||
"Done": "完成调试",
|
||||
"Hide result": "隐藏结果",
|
||||
"Not result": "无运行结果",
|
||||
"Run result": "运行结果",
|
||||
"Show result": "展示结果"
|
||||
},
|
||||
"inputType": {
|
||||
"JSON Editor": "JSON 输入框",
|
||||
"Manual input": "手动输入",
|
||||
"Manual select": "手动选择",
|
||||
"Reference": "变量引用",
|
||||
"Required": "必填",
|
||||
"Select edit field": "可编辑内容",
|
||||
"Select input default value": "默认值",
|
||||
"Select input type": "可配置的输入类型",
|
||||
"Select input type placeholder": "请选择可配置的输入类型",
|
||||
"chat history": "历史记录",
|
||||
"dynamicTargetInput": "动态外部数据",
|
||||
"input": "单行输入框",
|
||||
"number input": "数字输入框",
|
||||
"selectApp": "应用选择",
|
||||
"selectDataset": "知识库选择",
|
||||
"selectLLMModel": "对话模型选择",
|
||||
"switch": "开关",
|
||||
"target": "外部数据",
|
||||
"textarea": "多行输入框"
|
||||
},
|
||||
"tool": {
|
||||
"Select Tool": "选择工具"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataset": {
|
||||
|
||||
@@ -14,7 +14,7 @@ const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
w={w}
|
||||
h={w}
|
||||
p={'1px'}
|
||||
src={src}
|
||||
src={src || LOGO_ICON}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
|
||||
const ContextModal = ({
|
||||
context = [],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React, { useContext, createContext, useState, useMemo, useEffect, useCallback } from 'react';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import {
|
||||
AppTTSConfigType,
|
||||
AppWhisperConfigType,
|
||||
@@ -91,7 +91,7 @@ const StateContext = createContext<useChatStoreType>({
|
||||
});
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
userGuideModule?: ModuleItemType;
|
||||
userGuideModule?: StoreNodeItemType;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { type ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/module/runtime/type.d';
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { DispatchNodeResponseType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||
import { Flex, BoxProps, useDisclosure, useTheme, Box } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Tag from '../Tag';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
import { strIsLink } from '@fastgpt/global/common/string/tools';
|
||||
@@ -99,11 +98,6 @@ const ResponseTags = ({
|
||||
};
|
||||
}, [showDetail, flowResponses]);
|
||||
|
||||
const TagStyles: BoxProps = {
|
||||
mr: 2,
|
||||
bg: 'transparent'
|
||||
};
|
||||
|
||||
return flowResponses.length === 0 ? null : (
|
||||
<>
|
||||
{sourceList.length > 0 && (
|
||||
@@ -150,52 +144,52 @@ const ResponseTags = ({
|
||||
</>
|
||||
)}
|
||||
{showDetail && (
|
||||
<Flex alignItems={'center'} mt={3} flexWrap={'wrap'}>
|
||||
<Flex alignItems={'center'} mt={3} flexWrap={'wrap'} gap={2}>
|
||||
{quoteList.length > 0 && (
|
||||
<MyTooltip label="查看引用">
|
||||
<Tag
|
||||
<MyTag
|
||||
colorSchema="blue"
|
||||
type="solid"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setQuoteModalData({ rawSearch: quoteList })}
|
||||
>
|
||||
{quoteList.length}条引用
|
||||
</Tag>
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{llmModuleAccount === 1 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看上下文预览'}>
|
||||
<Tag
|
||||
<MyTag
|
||||
colorSchema="green"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
type="solid"
|
||||
onClick={() => setContextModalData(historyPreview)}
|
||||
>
|
||||
{historyPreview.length}条上下文
|
||||
</Tag>
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{llmModuleAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
<MyTag type="solid" colorSchema="blue">
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
</MyTag>
|
||||
)}
|
||||
|
||||
{isPc && runningTime > 0 && (
|
||||
<MyTooltip label={'模块运行时间和'}>
|
||||
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
|
||||
<MyTag colorSchema="purple" type="solid" cursor={'default'}>
|
||||
{runningTime}s
|
||||
</Tag>
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={t('core.chat.response.Read complete response tips')}>
|
||||
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
|
||||
<MyTag colorSchema="gray" type="solid" cursor={'pointer'} onClick={onOpenWholeModal}>
|
||||
{t('core.chat.response.Read complete response')}
|
||||
</Tag>
|
||||
</MyTag>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react';
|
||||
import { Box, useTheme, Flex, Image } from '@chakra-ui/react';
|
||||
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/module/template/constants';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
|
||||
import Tabs from '../Tabs';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -34,12 +34,13 @@ function Row({
|
||||
{t(label)}:
|
||||
</Box>
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
borderRadius={'sm'}
|
||||
fontSize={'sm'}
|
||||
bg={'myGray.50'}
|
||||
{...(isCodeBlock
|
||||
? { transform: 'translateY(-3px)' }
|
||||
: value
|
||||
? { px: 3, py: 1, border: theme.borders.base }
|
||||
? { px: 3, py: 2, border: theme.borders.base }
|
||||
: {})}
|
||||
>
|
||||
{value && <Markdown source={strValue} />}
|
||||
@@ -86,12 +87,14 @@ const WholeResponseModal = ({
|
||||
|
||||
export default WholeResponseModal;
|
||||
|
||||
const ResponseBox = React.memo(function ResponseBox({
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
showDetail
|
||||
showDetail,
|
||||
hideTabs = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
showDetail: boolean;
|
||||
hideTabs?: boolean;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -105,7 +108,7 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowType)
|
||||
moduleTemplatesFlat.find((template) => item.moduleType === template.flowNodeType)
|
||||
?.avatar
|
||||
}
|
||||
alt={''}
|
||||
@@ -125,9 +128,11 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box>
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
{!hideTabs && (
|
||||
<Box>
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
)}
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
<>
|
||||
<Row label={t('core.chat.response.module name')} value={t(activeModule.moduleName)} />
|
||||
@@ -222,6 +227,7 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
|
||||
{/* classify question */}
|
||||
<>
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
<Row
|
||||
label={t('core.chat.response.module cq')}
|
||||
value={(() => {
|
||||
@@ -229,7 +235,14 @@ const ResponseBox = React.memo(function ResponseBox({
|
||||
return activeModule.cqList.map((item) => `* ${item.value}`).join('\n');
|
||||
})()}
|
||||
/>
|
||||
<Row label={t('core.chat.response.module cq result')} value={activeModule?.cqResult} />
|
||||
</>
|
||||
|
||||
{/* if-else */}
|
||||
<>
|
||||
<Row
|
||||
label={t('core.chat.response.module if else Result')}
|
||||
value={activeModule?.ifElseResult}
|
||||
/>
|
||||
</>
|
||||
|
||||
{/* extract */}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { useAudioPlay } from '@/web/common/utils/voice';
|
||||
import { Flex, FlexProps, Image, css, useTheme } from '@chakra-ui/react';
|
||||
import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from '@fastgpt/global/core/chat/constants';
|
||||
import FilesBlock from './FilesBox';
|
||||
import { useChatProviderStore } from '../Provider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
|
||||
const colorMap = {
|
||||
[ChatStatusEnum.loading]: {
|
||||
@@ -157,7 +158,7 @@ ${JSON.stringify(questionGuides)}`;
|
||||
color: 'primary.600'
|
||||
}}
|
||||
>
|
||||
<Image src={tool.toolAvatar} alt={''} w={'14px'} mr={2} />
|
||||
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'14px'} mr={2} />
|
||||
<Box mr={1}>{tool.toolName}</Box>
|
||||
{isChatting && !tool.response && (
|
||||
<MyIcon name={'common/loading'} w={'14px'} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Input, Textarea } from '@chakra-ui/react';
|
||||
import ChatAvatar from './ChatAvatar';
|
||||
import { MessageCardStyle } from '../constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatBoxInputFormType } from '../type.d';
|
||||
|
||||
@@ -21,9 +21,9 @@ import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { Box, Flex, Checkbox } from '@chakra-ui/react';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -52,7 +52,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { formatChatValue2InputType } from './utils';
|
||||
import { textareaMinH } from './constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import ChatProvider, { useChatProviderStore } from './Provider';
|
||||
|
||||
import ChatItem from './components/ChatItem';
|
||||
@@ -79,7 +79,7 @@ type Props = OutLinkChatAuthProps & {
|
||||
showEmptyIntro?: boolean;
|
||||
appAvatar?: string;
|
||||
userAvatar?: string;
|
||||
userGuideModule?: ModuleItemType;
|
||||
userGuideModule?: StoreNodeItemType;
|
||||
showFileSelector?: boolean;
|
||||
active?: boolean; // can use
|
||||
appId: string;
|
||||
@@ -588,7 +588,7 @@ const ChatBox = (
|
||||
setLoading(false);
|
||||
};
|
||||
},
|
||||
[chatHistories, onDelMessage, sendPrompt, setLoading, toast]
|
||||
[chatHistories, onDelMessage, sendPrompt, setChatHistories, setLoading, toast]
|
||||
);
|
||||
// delete one message(One human and the ai response)
|
||||
const delOneMessage = useCallback(
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ChatSiteItemType,
|
||||
ToolModuleResponseItemType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/module/runtime/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
|
||||
export type generatingMessageProps = {
|
||||
event: `${SseResponseEventEnum}`;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Flex, Box, FlexProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
type Props = FlexProps & {
|
||||
text?: string | React.ReactNode;
|
||||
};
|
||||
|
||||
const EmptyTip = ({ text, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Flex mt={5} flexDirection={'column'} alignItems={'center'} pt={'10vh'} {...props}>
|
||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||
<Box mt={2} color={'myGray.500'}>
|
||||
{text || t('common.empty.Common Tip')}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyTip;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { Box, useColorMode, Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -10,8 +10,8 @@ import { getUnreadCount } from '@/web/support/user/inform/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import Auth from './auth';
|
||||
import Navbar from './navbar';
|
||||
import NavbarPhone from './navbarPhone';
|
||||
const Navbar = dynamic(() => import('./navbar'));
|
||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
||||
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'));
|
||||
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'));
|
||||
@@ -42,7 +42,6 @@ const phoneUnShowLayoutRoute: Record<string, boolean> = {
|
||||
|
||||
const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
const router = useRouter();
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
const { Loading } = useLoading();
|
||||
const { loading, setScreenWidth, isPc, feConfigs, isNotSufficientModal } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
@@ -52,12 +51,7 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
[router.pathname, router.query]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (colorMode === 'dark' && router.pathname !== '/chat') {
|
||||
setColorMode('light');
|
||||
}
|
||||
}, [colorMode, router.pathname, setColorMode]);
|
||||
|
||||
// listen screen width
|
||||
useEffect(() => {
|
||||
const resize = throttle(() => {
|
||||
setScreenWidth(document.documentElement.clientWidth);
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Box,
|
||||
useOutsideClick,
|
||||
MenuButton,
|
||||
MenuItemProps
|
||||
} from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
type MenuItemType = 'primary' | 'danger';
|
||||
|
||||
interface Props {
|
||||
width?: number | string;
|
||||
offset?: [number, number];
|
||||
Button: React.ReactNode;
|
||||
trigger?: 'hover' | 'click';
|
||||
menuList: {
|
||||
isActive?: boolean;
|
||||
label: string | React.ReactNode;
|
||||
icon?: string;
|
||||
type?: MenuItemType;
|
||||
onClick: () => any;
|
||||
}[];
|
||||
}
|
||||
|
||||
const MyMenu = ({
|
||||
width = 'auto',
|
||||
trigger = 'hover',
|
||||
offset = [0, 5],
|
||||
Button,
|
||||
menuList
|
||||
}: Props) => {
|
||||
const typeMapStyle: Record<MenuItemType, MenuItemProps> = {
|
||||
primary: {
|
||||
_hover: {
|
||||
backgroundColor: 'primary.50',
|
||||
color: 'primary.600'
|
||||
}
|
||||
},
|
||||
danger: {
|
||||
_hover: {
|
||||
color: 'red.600',
|
||||
background: 'red.1'
|
||||
}
|
||||
}
|
||||
};
|
||||
const menuItemStyles: MenuItemProps = {
|
||||
borderRadius: 'sm',
|
||||
py: 3,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
};
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const closeTimer = useRef<any>();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useOutsideClick({
|
||||
ref: ref,
|
||||
handler: () => {
|
||||
setIsOpen(false);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Menu offset={offset} isOpen={isOpen} autoSelect={false} direction={'ltr'} isLazy>
|
||||
<Box
|
||||
ref={ref}
|
||||
onMouseEnter={() => {
|
||||
if (trigger === 'hover') {
|
||||
setIsOpen(true);
|
||||
}
|
||||
clearTimeout(closeTimer.current);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (trigger === 'hover') {
|
||||
closeTimer.current = setTimeout(() => {
|
||||
setIsOpen(false);
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position={'relative'}
|
||||
onClickCapture={() => {
|
||||
if (trigger === 'click') {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuButton
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
/>
|
||||
<Box position={'relative'}>{Button}</Box>
|
||||
</Box>
|
||||
<MenuList
|
||||
minW={isOpen ? `${width}px !important` : 0}
|
||||
p={'6px'}
|
||||
border={'1px solid #fff'}
|
||||
boxShadow={
|
||||
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
|
||||
}
|
||||
>
|
||||
{menuList.map((item, i) => (
|
||||
<MenuItem
|
||||
key={i}
|
||||
{...menuItemStyles}
|
||||
{...typeMapStyle[item.type || 'primary']}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(false);
|
||||
item.onClick && item.onClick();
|
||||
}}
|
||||
color={item.isActive ? 'primary.700' : 'myGray.600'}
|
||||
whiteSpace={'pre-wrap'}
|
||||
>
|
||||
{!!item.icon && <MyIcon name={item.icon as any} w={'16px'} mr={2} />}
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Box>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyMenu;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -9,7 +9,7 @@ import { HUGGING_FACE_ICON, LOGO_ICON } from '@fastgpt/global/common/system/cons
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import Avatar from '../Avatar';
|
||||
|
||||
const AIModelSelector = ({ list, ...props }: SelectProps) => {
|
||||
const AIModelSelector = ({ list, onchange, ...props }: SelectProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs, llmModelList, vectorModelList } = useSystemStore();
|
||||
const router = useRouter();
|
||||
@@ -50,19 +50,20 @@ const AIModelSelector = ({ list, ...props }: SelectProps) => {
|
||||
: avatarList;
|
||||
}, [feConfigs.show_pay, avatarList, t]);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: string) => {
|
||||
if (e === 'price') {
|
||||
router.push(AI_POINT_USAGE_CARD_ROUTE);
|
||||
return;
|
||||
}
|
||||
onchange?.(e);
|
||||
},
|
||||
[onchange, router]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MySelect
|
||||
list={expandList}
|
||||
{...props}
|
||||
onchange={(e) => {
|
||||
if (e === 'price') {
|
||||
router.push(AI_POINT_USAGE_CARD_ROUTE);
|
||||
return;
|
||||
}
|
||||
props.onchange?.(e);
|
||||
}}
|
||||
/>
|
||||
<MySelect list={expandList} {...props} onchange={onSelect} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Flex, type FlexProps } from '@chakra-ui/react';
|
||||
|
||||
interface Props extends FlexProps {
|
||||
children: React.ReactNode | React.ReactNode[];
|
||||
colorSchema?: 'blue' | 'green' | 'gray' | 'purple';
|
||||
}
|
||||
|
||||
const Tag = ({ children, colorSchema = 'blue', ...props }: Props) => {
|
||||
const theme = useMemo(() => {
|
||||
const map = {
|
||||
blue: {
|
||||
borderColor: 'primary.500',
|
||||
bg: '#F2FBFF',
|
||||
color: 'primary.600'
|
||||
},
|
||||
green: {
|
||||
borderColor: '#67c13b',
|
||||
bg: '#f8fff8',
|
||||
color: '#67c13b'
|
||||
},
|
||||
purple: {
|
||||
borderColor: '#A558C9',
|
||||
bg: '#F6EEFA',
|
||||
color: '#A558C9'
|
||||
},
|
||||
gray: {
|
||||
borderColor: 'borderColor.base',
|
||||
bg: 'myGray.50',
|
||||
color: 'myGray.700'
|
||||
}
|
||||
};
|
||||
return map[colorSchema];
|
||||
}, [colorSchema]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
{...theme}
|
||||
borderWidth={'1px'}
|
||||
px={2}
|
||||
lineHeight={1}
|
||||
py={1}
|
||||
borderRadius={'sm'}
|
||||
fontSize={'xs'}
|
||||
alignItems={'center'}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default Tag;
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MySlider from '@/components/Slider';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import type { SettingAIDataType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
@@ -42,7 +42,7 @@ const AIChatSettingsModal = ({
|
||||
defaultValues: defaultData
|
||||
});
|
||||
const model = watch('model');
|
||||
const showResponseAnswerText = watch(ModuleInputKeyEnum.aiChatIsResponseText) !== undefined;
|
||||
const showResponseAnswerText = watch(NodeInputKeyEnum.aiChatIsResponseText) !== undefined;
|
||||
const showMaxHistoriesSlider = watch('maxHistories') !== undefined;
|
||||
const selectedModel = llmModelList.find((item) => item.model === model) || llmModelList[0];
|
||||
|
||||
@@ -72,7 +72,7 @@ const AIChatSettingsModal = ({
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/module/AI.png"
|
||||
iconSrc="/imgs/workflow/AI.png"
|
||||
onClose={onClose}
|
||||
title={
|
||||
<>
|
||||
@@ -136,7 +136,7 @@ const AIChatSettingsModal = ({
|
||||
<QuestionTip ml={1} label={t('core.module.template.AI support tool tip')} />
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
{selectedModel?.usedInToolCall ? '支持' : '不支持'}
|
||||
{selectedModel?.toolChoice || selectedModel?.functionCall ? '支持' : '不支持'}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex mt={8}>
|
||||
@@ -152,9 +152,9 @@ const AIChatSettingsModal = ({
|
||||
width={'95%'}
|
||||
min={0}
|
||||
max={10}
|
||||
value={getValues(ModuleInputKeyEnum.aiChatTemperature)}
|
||||
value={getValues(NodeInputKeyEnum.aiChatTemperature)}
|
||||
onChange={(e) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatTemperature, e);
|
||||
setValue(NodeInputKeyEnum.aiChatTemperature, e);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -174,9 +174,9 @@ const AIChatSettingsModal = ({
|
||||
min={100}
|
||||
max={tokenLimit}
|
||||
step={50}
|
||||
value={getValues(ModuleInputKeyEnum.aiChatMaxToken)}
|
||||
value={getValues(NodeInputKeyEnum.aiChatMaxToken)}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.aiChatMaxToken, val);
|
||||
setValue(NodeInputKeyEnum.aiChatMaxToken, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -215,11 +215,11 @@ const AIChatSettingsModal = ({
|
||||
</Box>
|
||||
<Box flex={1} ml={'10px'}>
|
||||
<Switch
|
||||
isChecked={getValues(ModuleInputKeyEnum.aiChatIsResponseText)}
|
||||
isChecked={getValues(NodeInputKeyEnum.aiChatIsResponseText)}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
setValue(ModuleInputKeyEnum.aiChatIsResponseText, value);
|
||||
setValue(NodeInputKeyEnum.aiChatIsResponseText, value);
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { LLMModelTypeEnum, llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
|
||||
import { Box, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box, Button, Flex, css, useDisclosure } from '@chakra-ui/react';
|
||||
import type { SettingAIDataType } from '@fastgpt/global/core/app/type.d';
|
||||
import AISettingModal from '@/components/core/ai/AISettingModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { HUGGING_FACE_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
type Props = {
|
||||
llmModelType?: `${LLMModelTypeEnum}`;
|
||||
@@ -14,6 +16,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const SettingLLMModel = ({ llmModelType = LLMModelTypeEnum.all, defaultData, onChange }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList } = useSystemStore();
|
||||
|
||||
const model = defaultData.model;
|
||||
@@ -41,30 +44,39 @@ const SettingLLMModel = ({ llmModelType = LLMModelTypeEnum.all, defaultData, onC
|
||||
model: modelList[0].model
|
||||
});
|
||||
}
|
||||
}, [defaultData, model, modelList, onChange]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box position={'relative'}>
|
||||
<Button
|
||||
w={'100%'}
|
||||
justifyContent={'flex-start'}
|
||||
variant={'whitePrimary'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
leftIcon={
|
||||
<Avatar
|
||||
borderRadius={'0'}
|
||||
src={selectedModel?.avatar || HUGGING_FACE_ICON}
|
||||
fallbackSrc={HUGGING_FACE_ICON}
|
||||
w={'18px'}
|
||||
/>
|
||||
<Box
|
||||
css={css({
|
||||
span: {
|
||||
display: 'block'
|
||||
}
|
||||
pl={4}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{selectedModel?.name}
|
||||
</Button>
|
||||
})}
|
||||
position={'relative'}
|
||||
>
|
||||
<MyTooltip label={t('core.app.Setting ai property')}>
|
||||
<Button
|
||||
w={'100%'}
|
||||
justifyContent={'flex-start'}
|
||||
variant={'whiteFlow'}
|
||||
_active={{
|
||||
transform: 'none'
|
||||
}}
|
||||
leftIcon={
|
||||
<Avatar
|
||||
borderRadius={'0'}
|
||||
src={selectedModel?.avatar || HUGGING_FACE_ICON}
|
||||
fallbackSrc={HUGGING_FACE_ICON}
|
||||
w={'18px'}
|
||||
/>
|
||||
}
|
||||
pl={4}
|
||||
onClick={onOpenAIChatSetting}
|
||||
>
|
||||
{selectedModel?.name}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
{isOpenAIChatSetting && (
|
||||
<AISettingModal
|
||||
onClose={onCloseAIChatSetting}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -133,7 +133,7 @@ const DatasetParamsModal = ({
|
||||
{
|
||||
label: t('core.module.template.Query extension'),
|
||||
id: SearchSettingTabEnum.queryExtension,
|
||||
icon: '/imgs/module/cfr.svg'
|
||||
icon: '/imgs/workflow/cfr.svg'
|
||||
}
|
||||
]}
|
||||
activeId={currentTabType}
|
||||
@@ -223,9 +223,9 @@ const DatasetParamsModal = ({
|
||||
min={100}
|
||||
max={maxTokens}
|
||||
step={50}
|
||||
value={getValues(ModuleInputKeyEnum.datasetMaxTokens) ?? 1000}
|
||||
value={getValues(NodeInputKeyEnum.datasetMaxTokens) ?? 1000}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetMaxTokens, val);
|
||||
setValue(NodeInputKeyEnum.datasetMaxTokens, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -249,9 +249,9 @@ const DatasetParamsModal = ({
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={getValues(ModuleInputKeyEnum.datasetSimilarity) ?? 0.5}
|
||||
value={getValues(NodeInputKeyEnum.datasetSimilarity) ?? 0.5}
|
||||
onChange={(val) => {
|
||||
setValue(ModuleInputKeyEnum.datasetSimilarity, val);
|
||||
setValue(NodeInputKeyEnum.datasetSimilarity, val);
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Divider
|
||||
} from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/module/api.d';
|
||||
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/api.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
@@ -20,7 +20,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
|
||||
export const DatasetSelectModal = ({
|
||||
isOpen,
|
||||
@@ -11,7 +11,7 @@ const QGSwitch = (props: SwitchProps) => {
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/chat/QGFill'} mr={2} w={'20px'} />
|
||||
<Box>{t('core.app.Question Guide')}</Box>
|
||||
<Box fontWeight={'medium'}>{t('core.app.Question Guide')}</Box>
|
||||
<MyTooltip label={t('core.app.Question Guide Tip')} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
|
||||
340
projects/app/src/components/core/app/ScheduledTriggerConfig.tsx
Normal file
@@ -0,0 +1,340 @@
|
||||
import { Box, Button, Flex, ModalBody, useDisclosure, Switch, Textarea } from '@chakra-ui/react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { AppScheduledTriggerConfigType } from '@fastgpt/global/core/app/type';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { MultipleSelectProps } from '@fastgpt/web/components/common/MySelect/type.d';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { cronParser2Fields } from '@fastgpt/global/common/string/time';
|
||||
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
|
||||
|
||||
const MultipleRowSelect = dynamic(
|
||||
() => import('@fastgpt/web/components/common/MySelect/MultipleRowSelect')
|
||||
);
|
||||
|
||||
// options type:
|
||||
enum CronJobTypeEnum {
|
||||
month = 'month',
|
||||
week = 'week',
|
||||
day = 'day',
|
||||
interval = 'interval'
|
||||
}
|
||||
type CronType = 'month' | 'week' | 'day' | 'interval';
|
||||
|
||||
const get24HoursOptions = () => {
|
||||
return Array.from({ length: 24 }, (_, i) => ({
|
||||
label: `${i < 10 ? '0' : ''}${i}:00`,
|
||||
value: i
|
||||
}));
|
||||
};
|
||||
const getWeekOptions = () => {
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
if (i === 0) {
|
||||
return {
|
||||
label: '星期日',
|
||||
value: i,
|
||||
children: get24HoursOptions()
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: `星期${i}`,
|
||||
value: i,
|
||||
children: get24HoursOptions()
|
||||
};
|
||||
});
|
||||
};
|
||||
const getMonthOptions = () => {
|
||||
return Array.from({ length: 28 }, (_, i) => ({
|
||||
label: `${i + 1}号`,
|
||||
value: i,
|
||||
children: get24HoursOptions()
|
||||
}));
|
||||
};
|
||||
const getInterValOptions = () => {
|
||||
// 每n小时
|
||||
return [
|
||||
{
|
||||
label: `每小时`,
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: `每2小时`,
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: `每3小时`,
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: `每4小时`,
|
||||
value: 4
|
||||
},
|
||||
{
|
||||
label: `每6小时`,
|
||||
value: 6
|
||||
},
|
||||
{
|
||||
label: `每12小时`,
|
||||
value: 12
|
||||
}
|
||||
];
|
||||
};
|
||||
const defaultValue = ['day', 0, 0];
|
||||
const defaultCronString = '0 0 * * *';
|
||||
|
||||
type CronFieldType = [CronType, number, number];
|
||||
|
||||
const ScheduledTriggerConfig = ({
|
||||
value,
|
||||
onChange
|
||||
}: {
|
||||
value: AppScheduledTriggerConfigType | null;
|
||||
onChange: (e: AppScheduledTriggerConfigType | null) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { register, setValue, watch } = useForm<AppScheduledTriggerConfigType>({
|
||||
defaultValues: {
|
||||
cronString: value?.cronString || '',
|
||||
timezone: value?.timezone,
|
||||
defaultPrompt: value?.defaultPrompt || ''
|
||||
}
|
||||
});
|
||||
const timezone = watch('timezone');
|
||||
const cronString = watch('cronString');
|
||||
|
||||
const cronSelectList = useRef<MultipleSelectProps['list']>([
|
||||
{
|
||||
label: '每天执行',
|
||||
value: CronJobTypeEnum.day,
|
||||
children: get24HoursOptions()
|
||||
},
|
||||
{
|
||||
label: '每周执行',
|
||||
value: CronJobTypeEnum.week,
|
||||
children: getWeekOptions()
|
||||
},
|
||||
{
|
||||
label: '每月执行',
|
||||
value: CronJobTypeEnum.month,
|
||||
children: getMonthOptions()
|
||||
},
|
||||
{
|
||||
label: '间隔执行',
|
||||
value: CronJobTypeEnum.interval,
|
||||
children: getInterValOptions()
|
||||
}
|
||||
]);
|
||||
|
||||
/* cron string to config field */
|
||||
const cronConfig = useMemo(() => {
|
||||
if (!cronString) {
|
||||
return null;
|
||||
}
|
||||
const cronField = cronParser2Fields(cronString);
|
||||
|
||||
if (!cronField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cronField.dayOfMonth.length !== 31) {
|
||||
return {
|
||||
isOpen: true,
|
||||
cronField: [CronJobTypeEnum.month, cronField.dayOfMonth[0], cronField.hour[0]]
|
||||
};
|
||||
}
|
||||
if (cronField.dayOfWeek.length !== 8) {
|
||||
return {
|
||||
isOpen: true,
|
||||
cronField: [CronJobTypeEnum.week, cronField.dayOfWeek[0], cronField.hour[0]]
|
||||
};
|
||||
}
|
||||
if (cronField.hour.length === 1) {
|
||||
return {
|
||||
isOpen: true,
|
||||
cronField: [CronJobTypeEnum.day, cronField.hour[0], 0]
|
||||
};
|
||||
}
|
||||
return {
|
||||
isOpen: true,
|
||||
cronField: [CronJobTypeEnum.interval, 24 / cronField.hour.length, 0]
|
||||
};
|
||||
}, [cronString]);
|
||||
const isOpenSchedule = cronConfig?.isOpen;
|
||||
const cronField = (cronConfig?.cronField || defaultValue) as CronFieldType;
|
||||
|
||||
const cronConfig2cronString = useCallback(
|
||||
(e: CronFieldType) => {
|
||||
if (e[0] === CronJobTypeEnum.month) {
|
||||
setValue('cronString', `0 ${e[2]} ${e[1]} * *`);
|
||||
} else if (e[0] === CronJobTypeEnum.week) {
|
||||
setValue('cronString', `0 ${e[2]} * * ${e[1]}`);
|
||||
} else if (e[0] === CronJobTypeEnum.day) {
|
||||
setValue('cronString', `0 ${e[1]} * * *`);
|
||||
} else if (e[0] === CronJobTypeEnum.interval) {
|
||||
setValue('cronString', `0 */${e[1]} * * *`);
|
||||
} else {
|
||||
setValue('cronString', '');
|
||||
}
|
||||
},
|
||||
[setValue]
|
||||
);
|
||||
|
||||
// cron config to show label
|
||||
const formatLabel = useMemo(() => {
|
||||
if (!isOpenSchedule) {
|
||||
return t('common.Not open');
|
||||
}
|
||||
|
||||
if (cronField[0] === 'month') {
|
||||
return t('core.app.schedule.Every month', {
|
||||
day: cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'week') {
|
||||
return t('core.app.schedule.Every week', {
|
||||
day: cronField[1] === 0 ? '日' : cronField[1],
|
||||
hour: cronField[2]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'day') {
|
||||
return t('core.app.schedule.Every day', {
|
||||
hour: cronField[1]
|
||||
});
|
||||
}
|
||||
if (cronField[0] === 'interval') {
|
||||
return t('core.app.schedule.Interval', {
|
||||
interval: cronField[1]
|
||||
});
|
||||
}
|
||||
|
||||
return t('common.Not open');
|
||||
}, [cronField, isOpenSchedule, t]);
|
||||
|
||||
// update value
|
||||
watch((data) => {
|
||||
if (!data.cronString) {
|
||||
onChange(null);
|
||||
return;
|
||||
}
|
||||
onChange({
|
||||
cronString: data.cronString,
|
||||
timezone: data.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
defaultPrompt: data.defaultPrompt || ''
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!value?.timezone) {
|
||||
setValue('timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/schedulePlan'} w={'20px'} />
|
||||
<Flex alignItems={'center'} ml={2} flex={1}>
|
||||
{t('core.app.Interval timer run')}
|
||||
<QuestionTip ml={1} label={t('core.app.Interval timer tip')} />
|
||||
</Flex>
|
||||
<MyTooltip label={t('core.app.Config schedule plan')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
>
|
||||
{formatLabel}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
iconSrc={'core/app/schedulePlan'}
|
||||
title={t('core.app.Interval timer config')}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Box flex={'0 0 80px'}> {t('core.app.schedule.Open schedule')}</Box>
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={isOpenSchedule}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setValue('cronString', defaultCronString);
|
||||
} else {
|
||||
setValue('cronString', '');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{isOpenSchedule && (
|
||||
<>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'}>执行时机</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MultipleRowSelect
|
||||
label={formatLabel}
|
||||
value={cronField}
|
||||
list={cronSelectList.current}
|
||||
onSelect={(e) => {
|
||||
cronConfig2cronString(e as CronFieldType);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 80px'}>时区</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<TimezoneSelect
|
||||
value={timezone}
|
||||
onChange={(e) => {
|
||||
setValue('timezone', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box mt={5}>
|
||||
<Box>{t('core.app.schedule.Default prompt')}</Box>
|
||||
<Textarea
|
||||
{...register('defaultPrompt')}
|
||||
rows={8}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('core.app.schedule.Default prompt placeholder')}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
cronConfig2cronString,
|
||||
cronField,
|
||||
formatLabel,
|
||||
isOpen,
|
||||
isOpenSchedule,
|
||||
onClose,
|
||||
onOpen,
|
||||
register,
|
||||
setValue,
|
||||
t,
|
||||
timezone
|
||||
]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
|
||||
export default React.memo(ScheduledTriggerConfig);
|
||||
@@ -80,7 +80,7 @@ const TTSSelect = ({
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/tts'} mr={2} w={'20px'} />
|
||||
<Box>{t('core.app.TTS')}</Box>
|
||||
<Box fontWeight={'medium'}>{t('core.app.TTS')}</Box>
|
||||
<MyTooltip label={t('core.app.TTS Tip')} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
@@ -90,7 +90,6 @@ const TTSSelect = ({
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'md'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
>
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { VariableInputEnum, variableMap } from '@fastgpt/global/core/module/constants';
|
||||
import { VariableInputEnum, variableMap } from '@fastgpt/global/core/workflow/constants';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -34,11 +34,11 @@ import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { variableTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import { variableTip } from '@fastgpt/global/core/workflow/template/tip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/module/utils';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
const VariableEdit = ({
|
||||
variables,
|
||||
@@ -98,7 +98,7 @@ const VariableEdit = ({
|
||||
<Box>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/variable'} w={'20px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
<Box ml={2} flex={1} fontWeight={'medium'}>
|
||||
{t('core.module.Variable')}
|
||||
<MyTooltip label={t(variableTip)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
@@ -110,7 +110,6 @@ const VariableEdit = ({
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
fontSize={'md'}
|
||||
onClick={() => {
|
||||
resetEdit({ variable: addVariable() });
|
||||
onOpenEdit();
|
||||
|
||||
@@ -32,14 +32,13 @@ const WhisperConfig = ({
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/whisper'} mr={2} w={'20px'} />
|
||||
<Box>{t('core.app.Whisper')}</Box>
|
||||
<Box fontWeight={'medium'}>{t('core.app.Whisper')}</Box>
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={t('core.app.Config whisper')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
fontSize={'md'}
|
||||
mr={'-5px'}
|
||||
onClick={onOpen}
|
||||
>
|
||||
|
||||
@@ -183,7 +183,9 @@ const QuoteItem = ({
|
||||
w={'100%'}
|
||||
size="sm"
|
||||
borderRadius={'20px'}
|
||||
colorScheme={scoreTheme[i]?.colorSchema}
|
||||
{...(scoreTheme[i] && {
|
||||
colorScheme: scoreTheme[i].colorSchema
|
||||
})}
|
||||
bg="#E8EBF0"
|
||||
/>
|
||||
)}
|
||||
@@ -199,7 +201,14 @@ const QuoteItem = ({
|
||||
</Box>
|
||||
|
||||
{canViewSource && (
|
||||
<Flex alignItems={'center'} mt={3} gap={4} color={'myGray.500'} fontSize={'xs'}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
flexWrap={'wrap'}
|
||||
mt={3}
|
||||
gap={4}
|
||||
color={'myGray.500'}
|
||||
fontSize={'xs'}
|
||||
>
|
||||
<MyTooltip label={t('core.dataset.Quote Length')}>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
|
||||
|
||||
@@ -33,7 +33,7 @@ const DatasetSelectContainer = ({
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc="/imgs/module/db.png"
|
||||
iconSrc="/imgs/workflow/db.png"
|
||||
title={
|
||||
<Box fontWeight={'normal'}>
|
||||
<ParentPaths
|
||||
|
||||
@@ -1,572 +0,0 @@
|
||||
import {
|
||||
type Node,
|
||||
type NodeChange,
|
||||
type Edge,
|
||||
type EdgeChange,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
Connection,
|
||||
addEdge
|
||||
} from 'reactflow';
|
||||
import type { FlowModuleItemType, FlowNodeTemplateType } from '@fastgpt/global/core/module/type.d';
|
||||
import type {
|
||||
FlowNodeChangeProps,
|
||||
FlowNodeInputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
useContext,
|
||||
useCallback,
|
||||
createContext,
|
||||
useRef,
|
||||
useEffect,
|
||||
useMemo
|
||||
} from 'react';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
type requestEventType =
|
||||
| 'onChangeNode'
|
||||
| 'onCopyNode'
|
||||
| 'onResetNode'
|
||||
| 'onDelNode'
|
||||
| 'onDelConnect'
|
||||
| 'setNodes';
|
||||
|
||||
export type useFlowProviderStoreType = {
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
mode: 'app' | 'plugin';
|
||||
filterAppIds: string[];
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
setNodes: Dispatch<SetStateAction<Node<FlowModuleItemType, string | undefined>[]>>;
|
||||
onNodesChange: OnChange<NodeChange>;
|
||||
edges: Edge<any>[];
|
||||
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
|
||||
onEdgesChange: OnChange<EdgeChange>;
|
||||
onFixView: () => void;
|
||||
onDelNode: (nodeId: string) => void;
|
||||
onChangeNode: (e: FlowNodeChangeProps) => void;
|
||||
onCopyNode: (nodeId: string) => void;
|
||||
onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void;
|
||||
onDelEdge: (e: {
|
||||
moduleId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}) => void;
|
||||
onDelConnect: (id: string) => void;
|
||||
onConnect: ({ connect }: { connect: Connection }) => any;
|
||||
initData: (modules: ModuleItemType[]) => void;
|
||||
splitToolInputs: (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
moduleId: string
|
||||
) => {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
};
|
||||
hasToolNode: boolean;
|
||||
};
|
||||
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
mode: 'app',
|
||||
filterAppIds: [],
|
||||
nodes: [],
|
||||
setNodes: function (
|
||||
value: React.SetStateAction<Node<FlowModuleItemType, string | undefined>[]>
|
||||
): void {
|
||||
return;
|
||||
},
|
||||
onNodesChange: function (changes: NodeChange[]): void {
|
||||
return;
|
||||
},
|
||||
edges: [],
|
||||
setEdges: function (value: React.SetStateAction<Edge<any>[]>): void {
|
||||
return;
|
||||
},
|
||||
onEdgesChange: function (changes: EdgeChange[]): void {
|
||||
return;
|
||||
},
|
||||
onFixView: function (): void {
|
||||
return;
|
||||
},
|
||||
onDelNode: function (nodeId: string): void {
|
||||
return;
|
||||
},
|
||||
onChangeNode: function (e: FlowNodeChangeProps): void {
|
||||
return;
|
||||
},
|
||||
onCopyNode: function (nodeId: string): void {
|
||||
return;
|
||||
},
|
||||
onDelEdge: function (e: {
|
||||
moduleId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}): void {
|
||||
return;
|
||||
},
|
||||
onDelConnect: function (id: string): void {
|
||||
return;
|
||||
},
|
||||
onConnect: function ({ connect }: { connect: Connection }) {
|
||||
return;
|
||||
},
|
||||
initData: function (modules: ModuleItemType[]): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onResetNode: function (e): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
splitToolInputs: function (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
moduleId: string
|
||||
): {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
} {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
hasToolNode: false
|
||||
});
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
export const FlowProvider = ({
|
||||
mode,
|
||||
filterAppIds = [],
|
||||
children
|
||||
}: {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
filterAppIds?: string[];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
|
||||
const hasToolNode = useMemo(() => {
|
||||
return !!nodes.find((node) => node.data.flowType === FlowNodeTypeEnum.tools);
|
||||
}, [nodes]);
|
||||
|
||||
const onFixView = useCallback(() => {
|
||||
const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement;
|
||||
|
||||
setTimeout(() => {
|
||||
btn && btn.click();
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
const onDelEdge = useCallback(
|
||||
({
|
||||
moduleId,
|
||||
sourceHandle,
|
||||
targetHandle
|
||||
}: {
|
||||
moduleId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}) => {
|
||||
if (!sourceHandle && !targetHandle) return;
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => {
|
||||
if (edge.source === moduleId && edge.sourceHandle === sourceHandle) return false;
|
||||
if (edge.target === moduleId && edge.targetHandle === targetHandle) return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onDelConnect = useCallback(
|
||||
(id: string) => {
|
||||
setEdges((state) => state.filter((item) => item.id !== id));
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
({ connect }: { connect: Connection }) => {
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
const type = source?.outputs.find(
|
||||
(output) => output.key === connect.sourceHandle
|
||||
)?.valueType;
|
||||
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion && !type) {
|
||||
return ModuleIOValueTypeEnum.boolean;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
}
|
||||
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
|
||||
})();
|
||||
|
||||
const targetType = nodes
|
||||
.find((node) => node.id === connect.target)
|
||||
?.data?.inputs.find((input) => input.key === connect.targetHandle)?.valueType;
|
||||
|
||||
if (
|
||||
connect.sourceHandle === ModuleOutputKeyEnum.selectedTools &&
|
||||
connect.targetHandle === ModuleOutputKeyEnum.selectedTools
|
||||
) {
|
||||
} else if (!sourceType || !targetType) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('app.Connection is invalid')
|
||||
});
|
||||
} else if (
|
||||
sourceType !== ModuleIOValueTypeEnum.any &&
|
||||
targetType !== ModuleIOValueTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('app.Connection type is different')
|
||||
});
|
||||
}
|
||||
|
||||
setEdges((state) =>
|
||||
addEdge(
|
||||
{
|
||||
...connect,
|
||||
type: EDGE_TYPE
|
||||
},
|
||||
state
|
||||
)
|
||||
);
|
||||
},
|
||||
[nodes, setEdges, t, toast]
|
||||
);
|
||||
|
||||
const onDelNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
setNodes((state) => state.filter((item) => item.id !== nodeId));
|
||||
setEdges((state) => state.filter((edge) => edge.source !== nodeId && edge.target !== nodeId));
|
||||
},
|
||||
[setEdges, setNodes]
|
||||
);
|
||||
|
||||
/* change */
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, type, key, value, index }: FlowNodeChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
if (node.id !== moduleId) return node;
|
||||
|
||||
const updateObj: Record<string, any> = {};
|
||||
|
||||
if (type === 'attr') {
|
||||
if (key) {
|
||||
updateObj[key] = value;
|
||||
}
|
||||
} else if (type === 'updateInput') {
|
||||
updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
const oldInputIndex = node.data.inputs.findIndex((item) => item.key === key);
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
index: oldInputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addInput') {
|
||||
const input = node.data.inputs.find((input) => input.key === value.key);
|
||||
if (input) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.inputs = node.data.inputs;
|
||||
} else {
|
||||
if (index !== undefined) {
|
||||
const inputs = [...node.data.inputs];
|
||||
inputs.splice(index, 0, value);
|
||||
updateObj.inputs = inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
} else if (type === 'updateOutput') {
|
||||
updateObj.outputs = node.data.outputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === key);
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
index: oldOutputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addOutput') {
|
||||
const output = node.data.outputs.find((output) => output.key === value.key);
|
||||
if (output) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.outputs = node.data.outputs;
|
||||
} else {
|
||||
if (index !== undefined) {
|
||||
const outputs = [...node.data.outputs];
|
||||
outputs.splice(index, 0, value);
|
||||
updateObj.outputs = outputs;
|
||||
} else {
|
||||
updateObj.outputs = node.data.outputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...updateObj
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes, toast]
|
||||
);
|
||||
|
||||
const onCopyNode = useCallback(
|
||||
(nodeId: string) => {
|
||||
setNodes((nodes) => {
|
||||
const node = nodes.find((node) => node.id === nodeId);
|
||||
if (!node) return nodes;
|
||||
const template = {
|
||||
avatar: node.data.avatar,
|
||||
name: node.data.name,
|
||||
intro: node.data.intro,
|
||||
flowType: node.data.flowType,
|
||||
inputs: node.data.inputs,
|
||||
outputs: node.data.outputs,
|
||||
showStatus: node.data.showStatus
|
||||
};
|
||||
return nodes.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...template,
|
||||
moduleId: nanoid(),
|
||||
position: { x: node.position.x + 200, y: node.position.y + 50 }
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
},
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
/* If the module is connected by a tool, the tool input and the normal input are separated */
|
||||
const splitToolInputs = useCallback(
|
||||
(inputs: FlowNodeInputItemType[], moduleId: string) => {
|
||||
const isTool = !!edges.find(
|
||||
(edge) =>
|
||||
edge.targetHandle === ModuleOutputKeyEnum.selectedTools && edge.target === moduleId
|
||||
);
|
||||
|
||||
return {
|
||||
isTool,
|
||||
toolInputs: inputs.filter((item) => isTool && item.toolDescription),
|
||||
commonInputs: inputs.filter((item) => {
|
||||
if (!isTool) return true;
|
||||
return !item.toolDescription;
|
||||
})
|
||||
};
|
||||
},
|
||||
[edges]
|
||||
);
|
||||
|
||||
// reset a node data. delete edge and replace it
|
||||
const onResetNode = useCallback(
|
||||
({ id, module }: { id: string; module: FlowNodeTemplateType }) => {
|
||||
setNodes((state) =>
|
||||
state.map((node) => {
|
||||
if (node.id === id) {
|
||||
// delete edge
|
||||
node.data.inputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, targetHandle: item.key });
|
||||
});
|
||||
node.data.outputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, sourceHandle: item.key });
|
||||
});
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...module
|
||||
}
|
||||
};
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes]
|
||||
);
|
||||
|
||||
const initData = useCallback(
|
||||
(modules: ModuleItemType[]) => {
|
||||
const edges = appModule2FlowEdge({
|
||||
modules
|
||||
});
|
||||
setEdges(edges);
|
||||
|
||||
setNodes(modules.map((item) => appModule2FlowNode({ item })));
|
||||
|
||||
onFixView();
|
||||
},
|
||||
[setEdges, setNodes, onFixView]
|
||||
);
|
||||
|
||||
// use eventbus to avoid refresh ReactComponents
|
||||
useEffect(() => {
|
||||
eventBus.on(
|
||||
EventNameEnum.requestFlowEvent,
|
||||
({ type, data }: { type: requestEventType; data: any }) => {
|
||||
switch (type) {
|
||||
case 'onChangeNode':
|
||||
onChangeNode(data);
|
||||
return;
|
||||
case 'onCopyNode':
|
||||
onCopyNode(data);
|
||||
return;
|
||||
case 'onResetNode':
|
||||
onResetNode(data);
|
||||
return;
|
||||
case 'onDelNode':
|
||||
onDelNode(data);
|
||||
return;
|
||||
case 'onDelConnect':
|
||||
onDelConnect(data);
|
||||
return;
|
||||
case 'setNodes':
|
||||
setNodes(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
eventBus.off(EventNameEnum.requestFlowEvent);
|
||||
};
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
eventBus.on(EventNameEnum.requestFlowStore, () => {
|
||||
eventBus.emit('receiveFlowStore', {
|
||||
nodes,
|
||||
edges,
|
||||
mode,
|
||||
filterAppIds,
|
||||
reactFlowWrapper
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
eventBus.off(EventNameEnum.requestFlowStore);
|
||||
};
|
||||
}, [edges, filterAppIds, mode, nodes]);
|
||||
|
||||
const value = {
|
||||
reactFlowWrapper,
|
||||
mode,
|
||||
filterAppIds,
|
||||
nodes,
|
||||
setNodes,
|
||||
onNodesChange,
|
||||
edges,
|
||||
setEdges,
|
||||
onEdgesChange,
|
||||
onFixView,
|
||||
onDelNode,
|
||||
onChangeNode,
|
||||
onResetNode,
|
||||
onCopyNode,
|
||||
onDelEdge,
|
||||
onDelConnect,
|
||||
onConnect,
|
||||
initData,
|
||||
splitToolInputs,
|
||||
hasToolNode
|
||||
};
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
};
|
||||
|
||||
export default React.memo(FlowProvider);
|
||||
|
||||
export const onChangeNode = (e: FlowNodeChangeProps) => {
|
||||
eventBus.emit(EventNameEnum.requestFlowEvent, {
|
||||
type: 'onChangeNode',
|
||||
data: e
|
||||
});
|
||||
};
|
||||
export const onCopyNode = (nodeId: string) => {
|
||||
eventBus.emit(EventNameEnum.requestFlowEvent, {
|
||||
type: 'onCopyNode',
|
||||
data: nodeId
|
||||
});
|
||||
};
|
||||
export const onResetNode = (e: Parameters<useFlowProviderStoreType['onResetNode']>[0]) => {
|
||||
eventBus.emit(EventNameEnum.requestFlowEvent, {
|
||||
type: 'onResetNode',
|
||||
data: e
|
||||
});
|
||||
};
|
||||
export const onDelConnect = (e: Parameters<useFlowProviderStoreType['onDelConnect']>[0]) => {
|
||||
eventBus.emit(EventNameEnum.requestFlowEvent, {
|
||||
type: 'onDelConnect',
|
||||
data: e
|
||||
});
|
||||
};
|
||||
export const onSetNodes = (e: useFlowProviderStoreType['nodes']) => {
|
||||
eventBus.emit(EventNameEnum.requestFlowEvent, {
|
||||
type: 'setNodes',
|
||||
data: e
|
||||
});
|
||||
};
|
||||
|
||||
export const getFlowStore = () =>
|
||||
new Promise<{
|
||||
nodes: useFlowProviderStoreType['nodes'];
|
||||
edges: useFlowProviderStoreType['edges'];
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
filterAppIds: useFlowProviderStoreType['filterAppIds'];
|
||||
reactFlowWrapper: useFlowProviderStoreType['reactFlowWrapper'];
|
||||
}>((resolve) => {
|
||||
eventBus.on('receiveFlowStore', (data: any) => {
|
||||
resolve(data);
|
||||
eventBus.off('receiveFlowStore');
|
||||
});
|
||||
eventBus.emit(EventNameEnum.requestFlowStore);
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useStoreApi, type ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
const CustomConnection = ({ fromX, fromY, toX, toY }: ConnectionLineComponentProps) => {
|
||||
const store = useStoreApi();
|
||||
|
||||
const { connectionHandleId } = store.getState();
|
||||
console.log(fromX, fromY, toX, toY, connectionHandleId);
|
||||
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle
|
||||
cx={toX}
|
||||
cy={toY}
|
||||
fill="#fff"
|
||||
r={3}
|
||||
stroke={connectionHandleId || ''}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomConnection;
|
||||
@@ -1,116 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { BezierEdge, getBezierPath, EdgeLabelRenderer, EdgeProps } from 'reactflow';
|
||||
import { onDelConnect, useFlowProviderStore } from '../../FlowProvider';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const ButtonEdge = (props: EdgeProps) => {
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const {
|
||||
id,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
selected,
|
||||
sourceHandleId,
|
||||
animated,
|
||||
style = {}
|
||||
} = props;
|
||||
|
||||
const active = (() => {
|
||||
const connectNode = nodes.find((node) => {
|
||||
return (node.id === props.source || node.id === props.target) && node.selected;
|
||||
});
|
||||
return !!(connectNode || selected);
|
||||
})();
|
||||
|
||||
const [, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition
|
||||
});
|
||||
|
||||
const isToolEdge = sourceHandleId === ModuleOutputKeyEnum.selectedTools;
|
||||
|
||||
const memoEdgeLabel = useMemo(() => {
|
||||
return (
|
||||
<EdgeLabelRenderer>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
position={'absolute'}
|
||||
transform={`translate(-50%, -50%) translate(${labelX}px,${labelY}px)`}
|
||||
pointerEvents={'all'}
|
||||
w={'20px'}
|
||||
h={'20px'}
|
||||
bg={'white'}
|
||||
borderRadius={'20px'}
|
||||
color={'black'}
|
||||
cursor={'pointer'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={'borderColor.low'}
|
||||
zIndex={active ? 1000 : 0}
|
||||
_hover={{
|
||||
boxShadow: '0 0 6px 2px rgba(0, 0, 0, 0.08)'
|
||||
}}
|
||||
onClick={() => onDelConnect(id)}
|
||||
>
|
||||
<MyIcon
|
||||
name="closeSolid"
|
||||
w={'100%'}
|
||||
color={active ? 'primary.700' : 'myGray.400'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
{!isToolEdge && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
position={'absolute'}
|
||||
transform={`translate(-78%, -50%) translate(${targetX}px,${targetY}px)`}
|
||||
pointerEvents={'all'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
bg={'white'}
|
||||
zIndex={active ? 1000 : 0}
|
||||
>
|
||||
<MyIcon
|
||||
name={'common/rightArrowLight'}
|
||||
w={'100%'}
|
||||
color={active ? 'primary.700' : 'myGray.400'}
|
||||
></MyIcon>
|
||||
</Flex>
|
||||
)}
|
||||
</EdgeLabelRenderer>
|
||||
);
|
||||
}, [labelX, labelY, active, isToolEdge, targetX, targetY, id]);
|
||||
|
||||
const memoBezierEdge = useMemo(() => {
|
||||
const edgeStyle: React.CSSProperties = {
|
||||
...style,
|
||||
...(active
|
||||
? {
|
||||
strokeWidth: 5,
|
||||
stroke: '#3370ff'
|
||||
}
|
||||
: { strokeWidth: 2, zIndex: 2, stroke: 'myGray.300' })
|
||||
};
|
||||
|
||||
return <BezierEdge {...props} style={edgeStyle} />;
|
||||
}, [style, active, props]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{memoBezierEdge}
|
||||
{memoEdgeLabel}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ButtonEdge);
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
import Divider from '../modules/Divider';
|
||||
import RenderToolInput from '../render/RenderToolInput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const NodeAnswer = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { splitToolInputs } = useFlowProviderStore();
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
{toolInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<RenderInput moduleId={moduleId} flowInputList={commonInputs} />
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeAnswer);
|
||||
@@ -1,149 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/module/template/system/datasetConcat';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MySlider from '@/components/Slider';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList } = useSystemStore();
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
const quotes = useMemo(
|
||||
() => inputs.filter((item) => item.valueType === ModuleIOValueTypeEnum.datasetQuote),
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
llmModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [llmModelList, nodes]);
|
||||
|
||||
const RenderQuoteList = useMemo(() => {
|
||||
return (
|
||||
<Box>
|
||||
<Box>
|
||||
{quotes.map((quote, i) => (
|
||||
<Flex key={quote.key} position={'relative'} mb={4} alignItems={'center'}>
|
||||
<TargetHandle handleKey={quote.key} valueType={quote.valueType} />
|
||||
<Box>
|
||||
{t('core.chat.Quote')}
|
||||
{i + 1}
|
||||
</Box>
|
||||
<MyIcon
|
||||
ml={2}
|
||||
w={'14px'}
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: quote.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
<Button
|
||||
leftIcon={<AddIcon />}
|
||||
variant={'whiteBase'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: getOneQuoteInputTemplate()
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('core.module.Dataset quote.Add quote')}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}, [moduleId, quotes, t]);
|
||||
|
||||
const CustomComponent = useMemo(() => {
|
||||
console.log(111);
|
||||
return {
|
||||
[ModuleInputKeyEnum.datasetMaxTokens]: (item: FlowNodeInputItemType) => (
|
||||
<Box px={2}>
|
||||
<MySlider
|
||||
markList={[
|
||||
{ label: '100', value: 100 },
|
||||
{ label: tokenLimit, value: tokenLimit }
|
||||
]}
|
||||
width={'100%'}
|
||||
min={100}
|
||||
max={tokenLimit}
|
||||
step={50}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
};
|
||||
}, [moduleId, tokenLimit]);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} position={'relative'}>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} CustomComponent={CustomComponent} />
|
||||
{/* render dataset select */}
|
||||
{RenderQuoteList}
|
||||
<Flex position={'absolute'} right={4} top={'60%'}>
|
||||
<Box>{t('core.module.Dataset quote.Concat result')}</Box>
|
||||
<SourceHandle
|
||||
handleKey={ModuleOutputKeyEnum.datasetQuoteQA}
|
||||
valueType={ModuleIOValueTypeEnum.datasetQuote}
|
||||
// transform={'translate(-14px, -50%)'}
|
||||
/>
|
||||
</Flex>
|
||||
{/* <RenderOutput moduleId={moduleId} flowOutputList={outputs} /> */}
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeDatasetConcat);
|
||||
@@ -1,10 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeEmpty = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
return <NodeCard selected={selected} {...data}></NodeCard>;
|
||||
};
|
||||
|
||||
export default React.memo(NodeEmpty);
|
||||
@@ -1,649 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState, useTransition } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../../modules/Divider';
|
||||
import Container from '../../modules/Container';
|
||||
import RenderInput from '../../render/RenderInput';
|
||||
import RenderOutput from '../../render/RenderOutput';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Input,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Tr,
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Button,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import {
|
||||
formatEditorVariablePickerIcon,
|
||||
getGuideModule,
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/module/utils';
|
||||
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
|
||||
import HttpInput from '@fastgpt/web/components/common/Input/HttpInput';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import RenderToolInput from '../../render/RenderToolInput';
|
||||
const CurlImportModal = dynamic(() => import('./CurlImportModal'));
|
||||
|
||||
export const HttpHeaders = [
|
||||
{ key: 'A-IM', label: 'A-IM' },
|
||||
{ key: 'Accept', label: 'Accept' },
|
||||
{ key: 'Accept-Charset', label: 'Accept-Charset' },
|
||||
{ key: 'Accept-Encoding', label: 'Accept-Encoding' },
|
||||
{ key: 'Accept-Language', label: 'Accept-Language' },
|
||||
{ key: 'Accept-Datetime', label: 'Accept-Datetime' },
|
||||
{ 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 {
|
||||
params = 'params',
|
||||
headers = 'headers',
|
||||
body = 'body'
|
||||
}
|
||||
export type PropsArrType = {
|
||||
key: string;
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
moduleId,
|
||||
inputs
|
||||
}: {
|
||||
moduleId: string;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [_, startSts] = useTransition();
|
||||
|
||||
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
|
||||
|
||||
const requestMethods = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod);
|
||||
const requestUrl = inputs.find((item) => item.key === ModuleInputKeyEnum.httpReqUrl);
|
||||
|
||||
const onChangeUrl = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpReqUrl,
|
||||
value: {
|
||||
...requestUrl,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
},
|
||||
[moduleId, requestUrl]
|
||||
);
|
||||
const onBlurUrl = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const val = e.target.value;
|
||||
// 拆分params和url
|
||||
const url = val.split('?')[0];
|
||||
const params = val.split('?')[1];
|
||||
if (params) {
|
||||
const paramsArr = params.split('&');
|
||||
const paramsObj = paramsArr.reduce((acc, cur) => {
|
||||
const [key, value] = cur.split('=');
|
||||
return {
|
||||
...acc,
|
||||
[key]: value
|
||||
};
|
||||
}, {});
|
||||
const inputParams = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
|
||||
|
||||
if (!inputParams || Object.keys(paramsObj).length === 0) return;
|
||||
|
||||
const concatParams: PropsArrType[] = inputParams?.value || [];
|
||||
Object.entries(paramsObj).forEach(([key, value]) => {
|
||||
if (!concatParams.find((item) => item.key === key)) {
|
||||
concatParams.push({ key, value: value as string, type: 'string' });
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpParams,
|
||||
value: {
|
||||
...inputParams,
|
||||
value: concatParams
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpReqUrl,
|
||||
value: {
|
||||
...requestUrl,
|
||||
value: url
|
||||
}
|
||||
});
|
||||
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('core.module.http.Url and params have been split')
|
||||
});
|
||||
}
|
||||
},
|
||||
[inputs, moduleId, requestUrl, t, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={2} display={'flex'} justifyContent={'space-between'}>
|
||||
<Box>{t('core.module.Http request settings')}</Box>
|
||||
<Button variant={'link'} onClick={onOpenCurl}>
|
||||
{t('core.module.http.curl import')}
|
||||
</Button>
|
||||
</Box>
|
||||
<Flex alignItems={'center'} className="nodrag">
|
||||
<MySelect
|
||||
h={'34px'}
|
||||
w={'88px'}
|
||||
bg={'myGray.50'}
|
||||
width={'100%'}
|
||||
value={requestMethods?.value}
|
||||
list={[
|
||||
{
|
||||
label: 'GET',
|
||||
value: 'GET'
|
||||
},
|
||||
{
|
||||
label: 'POST',
|
||||
value: 'POST'
|
||||
},
|
||||
{
|
||||
label: 'PUT',
|
||||
value: 'PUT'
|
||||
},
|
||||
{
|
||||
label: 'DELETE',
|
||||
value: 'DELETE'
|
||||
},
|
||||
{
|
||||
label: 'PATCH',
|
||||
value: 'PATCH'
|
||||
}
|
||||
]}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: ModuleInputKeyEnum.httpMethod,
|
||||
value: {
|
||||
...requestMethods,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Input
|
||||
flex={'1 0 0'}
|
||||
ml={2}
|
||||
h={'34px'}
|
||||
value={requestUrl?.value}
|
||||
placeholder={t('core.module.input.label.Http Request Url')}
|
||||
fontSize={'xs'}
|
||||
onChange={onChangeUrl}
|
||||
onBlur={onBlurUrl}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
{isOpenCurl && <CurlImportModal moduleId={moduleId} inputs={inputs} onClose={onCloseCurl} />}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
export function RenderHttpProps({
|
||||
moduleId,
|
||||
inputs
|
||||
}: {
|
||||
moduleId: string;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const requestMethods = inputs.find((item) => item.key === ModuleInputKeyEnum.httpMethod)?.value;
|
||||
const params = inputs.find((item) => item.key === ModuleInputKeyEnum.httpParams);
|
||||
const headers = inputs.find((item) => item.key === ModuleInputKeyEnum.httpHeaders);
|
||||
const jsonBody = inputs.find((item) => item.key === ModuleInputKeyEnum.httpJsonBody);
|
||||
|
||||
const paramsLength = params?.value?.length || 0;
|
||||
const headersLength = headers?.value?.length || 0;
|
||||
|
||||
// get variable
|
||||
const variables = useMemo(() => {
|
||||
const globalVariables = formatEditorVariablePickerIcon(
|
||||
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
|
||||
);
|
||||
const systemVariables = [
|
||||
{
|
||||
key: 'appId',
|
||||
label: t('core.module.http.AppId')
|
||||
},
|
||||
{
|
||||
key: 'chatId',
|
||||
label: t('core.module.http.ChatId')
|
||||
},
|
||||
{
|
||||
key: 'responseChatItemId',
|
||||
label: t('core.module.http.ResponseChatItemId')
|
||||
},
|
||||
{
|
||||
key: 'variables',
|
||||
label: t('core.module.http.Variables')
|
||||
},
|
||||
{
|
||||
key: 'histories',
|
||||
label: t('core.module.http.Histories')
|
||||
},
|
||||
{
|
||||
key: 'cTime',
|
||||
label: t('core.module.http.Current time')
|
||||
}
|
||||
];
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.edit || input.toolDescription)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
|
||||
return [...moduleVariables, ...globalVariables, ...systemVariables];
|
||||
}, [inputs, nodes, t]);
|
||||
|
||||
const variableText = useMemo(() => {
|
||||
return variables
|
||||
.map((item) => `${item.key}${item.key !== item.label ? `(${item.label})` : ''}`)
|
||||
.join('\n');
|
||||
}, [variables]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} mb={2}>
|
||||
{t('core.module.Http request props')}
|
||||
<MyTooltip label={t('core.module.http.Props tip', { variable: variableText })}>
|
||||
<QuestionOutlineIcon ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Tabs
|
||||
list={[
|
||||
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
|
||||
...(!['GET', 'DELETE'].includes(requestMethods)
|
||||
? [
|
||||
{
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
Body
|
||||
{jsonBody?.value && <Box ml={1}>✅</Box>}
|
||||
</Flex>
|
||||
),
|
||||
id: TabEnum.body
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{ label: <RenderPropsItem text="Headers" num={headersLength} />, id: TabEnum.headers }
|
||||
]}
|
||||
activeId={selectedTab}
|
||||
onChange={(e) => setSelectedTab(e as any)}
|
||||
/>
|
||||
{params &&
|
||||
headers &&
|
||||
jsonBody &&
|
||||
{
|
||||
[TabEnum.params]: (
|
||||
<RenderForm
|
||||
moduleId={moduleId}
|
||||
input={params}
|
||||
variables={variables}
|
||||
tabType={TabEnum.params}
|
||||
/>
|
||||
),
|
||||
[TabEnum.body]: <RenderJson moduleId={moduleId} variables={variables} input={jsonBody} />,
|
||||
[TabEnum.headers]: (
|
||||
<RenderForm
|
||||
moduleId={moduleId}
|
||||
input={headers}
|
||||
variables={variables}
|
||||
tabType={TabEnum.headers}
|
||||
/>
|
||||
)
|
||||
}[selectedTab]}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
const RenderForm = ({
|
||||
moduleId,
|
||||
input,
|
||||
variables,
|
||||
tabType
|
||||
}: {
|
||||
moduleId: string;
|
||||
input: FlowNodeInputItemType;
|
||||
variables: EditorVariablePickerType[];
|
||||
tabType?: TabEnum;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [list, setList] = useState<PropsArrType[]>(input.value || []);
|
||||
const [updateTrigger, setUpdateTrigger] = 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(() => {
|
||||
setList(input.value || []);
|
||||
}, [input.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldUpdateNode) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: list
|
||||
}
|
||||
});
|
||||
setShouldUpdateNode(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [list]);
|
||||
|
||||
const handleKeyChange = (index: number, newKey: string) => {
|
||||
setList((prevList) => {
|
||||
if (!newKey) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key cannot be empty')
|
||||
});
|
||||
return prevList;
|
||||
}
|
||||
const checkExist = prevList.find((item, i) => i !== index && item.key == newKey);
|
||||
if (checkExist) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
return prevList;
|
||||
}
|
||||
return prevList.map((item, i) => (i === index ? { ...item, key: newKey } : item));
|
||||
});
|
||||
setShouldUpdateNode(true);
|
||||
};
|
||||
|
||||
const handleAddNewProps = (key: string, value: string = '') => {
|
||||
setList((prevList) => {
|
||||
if (!key) {
|
||||
return prevList;
|
||||
}
|
||||
|
||||
const checkExist = prevList.find((item) => item.key === key);
|
||||
if (checkExist) {
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.http.Key already exists')
|
||||
});
|
||||
return prevList;
|
||||
}
|
||||
return [...prevList, { key, type: 'string', value }];
|
||||
});
|
||||
|
||||
setShouldUpdateNode(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableContainer overflowY={'visible'} overflowX={'unset'}>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th px={2}>{t('core.module.http.Props name')}</Th>
|
||||
<Th px={2}>{t('core.module.http.Props value')}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{list.map((item, index) => (
|
||||
<Tr key={`${input.key}${index}`}>
|
||||
<Td p={0} w={'150px'}>
|
||||
<HttpInput
|
||||
hasVariablePlugin={false}
|
||||
hasDropDownPlugin={tabType === TabEnum.headers}
|
||||
setDropdownValue={(value) => {
|
||||
handleKeyChange(index, value);
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
}}
|
||||
placeholder={t('core.module.http.Props name')}
|
||||
value={item.key}
|
||||
variables={leftVariables}
|
||||
onBlur={(val) => {
|
||||
handleKeyChange(index, val);
|
||||
}}
|
||||
updateTrigger={updateTrigger}
|
||||
/>
|
||||
</Td>
|
||||
<Td p={0}>
|
||||
<Box display={'flex'} alignItems={'center'}>
|
||||
<HttpInput
|
||||
placeholder={t('core.module.http.Props value')}
|
||||
value={item.value}
|
||||
variables={variables}
|
||||
onBlur={(val) => {
|
||||
setList((prevList) =>
|
||||
prevList.map((item, i) => (i === index ? { ...item, value: val } : item))
|
||||
);
|
||||
setShouldUpdateNode(true);
|
||||
}}
|
||||
/>
|
||||
<MyIcon
|
||||
name={'delete'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
w={'14px'}
|
||||
onClick={() => {
|
||||
setList((prevlist) => prevlist.filter((val) => val.key !== item.key));
|
||||
setShouldUpdateNode(true);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
<Tr>
|
||||
<Td p={0} w={'150px'}>
|
||||
<HttpInput
|
||||
hasVariablePlugin={false}
|
||||
hasDropDownPlugin={tabType === TabEnum.headers}
|
||||
setDropdownValue={(val) => {
|
||||
handleAddNewProps(val);
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
}}
|
||||
placeholder={t('core.module.http.Add props')}
|
||||
value={''}
|
||||
variables={leftVariables}
|
||||
updateTrigger={updateTrigger}
|
||||
onBlur={(val) => {
|
||||
handleAddNewProps(val);
|
||||
setUpdateTrigger((prev) => !prev);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
<Td p={0}>
|
||||
<Box display={'flex'} alignItems={'center'}>
|
||||
<HttpInput />
|
||||
</Box>
|
||||
</Td>
|
||||
</Tr>
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
const RenderJson = ({
|
||||
moduleId,
|
||||
input,
|
||||
variables
|
||||
}: {
|
||||
moduleId: string;
|
||||
input: FlowNodeInputItemType;
|
||||
variables: EditorVariablePickerType[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [_, startSts] = useTransition();
|
||||
|
||||
return (
|
||||
<Box mt={1}>
|
||||
<JSONEditor
|
||||
bg={'myGray.50'}
|
||||
defaultHeight={200}
|
||||
resize
|
||||
value={input.value}
|
||||
placeholder={t('core.module.template.http body placeholder')}
|
||||
onChange={(e) => {
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
});
|
||||
}}
|
||||
variables={variables}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const RenderPropsItem = ({ text, num }: { text: string; num: number }) => {
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<Box>{text}</Box>
|
||||
{num > 0 && (
|
||||
<Box ml={1} borderRadius={'50%'} bg={'myGray.200'} px={2} py={'1px'}>
|
||||
{num}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const NodeHttp = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { splitToolInputs, hasToolNode } = useFlowProviderStore();
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
const CustomComponents = useMemo(
|
||||
() => ({
|
||||
[ModuleInputKeyEnum.httpMethod]: () => (
|
||||
<RenderHttpMethodAndUrl moduleId={moduleId} inputs={inputs} />
|
||||
),
|
||||
[ModuleInputKeyEnum.httpHeaders]: () => (
|
||||
<>
|
||||
<RenderHttpProps moduleId={moduleId} inputs={inputs} />
|
||||
<Box mt={2} transform={'translateY(10px)'}>
|
||||
{t('core.module.Variable import')}
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}),
|
||||
[inputs, moduleId, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
{hasToolNode && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} canEdit />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
<>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput
|
||||
moduleId={moduleId}
|
||||
flowInputList={commonInputs}
|
||||
CustomComponent={CustomComponents}
|
||||
/>
|
||||
</Container>
|
||||
</>
|
||||
<>
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeHttp);
|
||||
@@ -1,266 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import type {
|
||||
EditInputFieldMap,
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type.d';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField: EditInputFieldMap = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: true,
|
||||
isToolInput: true
|
||||
};
|
||||
|
||||
const NodePluginInput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} selected={selected} forbidMenu {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'right'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={7}
|
||||
>
|
||||
{item.edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
required: item.required,
|
||||
isToolInput: !!item.toolDescription
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{item.description && (
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{t(item.label)}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
...(data.inputType === FlowNodeInputTypeEnum.addInputParam
|
||||
? {
|
||||
editField: {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: true,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
},
|
||||
defaultEditField: {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
: {})
|
||||
};
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
console.log(data);
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodePluginInput);
|
||||
@@ -1,238 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import {
|
||||
EditNodeFieldType,
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
required: false,
|
||||
dataType: true,
|
||||
inputType: false
|
||||
};
|
||||
|
||||
const NodePluginOutput = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} selected={selected} forbidMenu {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'left'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={7}
|
||||
>
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
<Box position={'relative'}>
|
||||
{t(item.label)}
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
{item.description && (
|
||||
<MyTooltip label={t(item.description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={2} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: item.type,
|
||||
valueType: item.valueType,
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key,
|
||||
value: ''
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'left'} mt={5}>
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setCreateField(defaultCreateField);
|
||||
}}
|
||||
>
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: createEditField
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={editField}
|
||||
keys={[editField.key]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
// check key valid
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...memInput,
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...memOutput,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodePluginOutput);
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const QuestionInputNode = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'240px'} selected={selected} {...data}>
|
||||
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'} textAlign={'end'}>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(QuestionInputNode);
|
||||
@@ -1,59 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import RenderToolInput from '../render/RenderToolInput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
const NodeSimple = ({
|
||||
data,
|
||||
selected,
|
||||
minW = '350px',
|
||||
maxW
|
||||
}: NodeProps<FlowModuleItemType> & { minW?: string | number; maxW?: string | number }) => {
|
||||
const { t } = useTranslation();
|
||||
const { splitToolInputs } = useFlowProviderStore();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, moduleId);
|
||||
|
||||
const filterHiddenInputs = useMemo(
|
||||
() => commonInputs.filter((item) => item.type !== 'hidden'),
|
||||
[commonInputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<NodeCard minW={minW} maxW={maxW} selected={selected} {...data}>
|
||||
{toolInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput moduleId={moduleId} inputs={toolInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
{filterHiddenInputs.length > 0 && (
|
||||
<>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={commonInputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
{outputs.filter((output) => output.type !== FlowNodeOutputTypeEnum.hidden).length > 0 && (
|
||||
<>
|
||||
<Divider text={t('common.Output')} />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeSimple);
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ToolSourceHandle } from '../render/ToolHandle';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
const NodeTools = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'350px'} selected={selected} {...data}>
|
||||
<Divider text={t('common.Input')} />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
|
||||
<Box position={'relative'}>
|
||||
<Box borderBottomLeftRadius={'md'} borderBottomRadius={'md'} overflow={'hidden'}>
|
||||
<Divider showBorderBottom={false} text={t('core.module.template.Tool module')} />
|
||||
</Box>
|
||||
<ToolSourceHandle moduleId={moduleId} />
|
||||
</Box>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeTools);
|
||||
@@ -1,194 +0,0 @@
|
||||
import React, { useCallback, useMemo, useTransition } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Flex, Textarea, useTheme } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { welcomeTextTip } from '@fastgpt/global/core/module/template/tip';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import VariableEdit from '../../../../app/VariableEdit';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import Container from '../modules/Container';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
|
||||
import QGSwitch from '@/components/core/app/QGSwitch';
|
||||
import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
import WhisperConfig from '@/components/core/app/WhisperConfig';
|
||||
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { TTSTypeEnum } from '@/constants/app';
|
||||
|
||||
const NodeUserGuide = ({ data, selected }: NodeProps<FlowModuleItemType>) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<>
|
||||
<NodeCard minW={'300px'} selected={selected} forbidMenu {...data}>
|
||||
<Container className="nodrag" borderTop={'2px solid'} borderTopColor={'myGray.200'}>
|
||||
<WelcomeText data={data} />
|
||||
<Box pt={4} pb={2}>
|
||||
<ChatStartVariable data={data} />
|
||||
</Box>
|
||||
<Box pt={3} borderTop={theme.borders.base}>
|
||||
<TTSGuide data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<WhisperGuide data={data} />
|
||||
</Box>
|
||||
<Box mt={3} pt={3} borderTop={theme.borders.base}>
|
||||
<QuestionGuide data={data} />
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeUserGuide);
|
||||
|
||||
function WelcomeText({ data }: { data: FlowModuleItemType }) {
|
||||
const { t } = useTranslation();
|
||||
const { inputs, moduleId } = data;
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
const welcomeText = inputs.find((item) => item.key === ModuleInputKeyEnum.welcomeText);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex mb={1} alignItems={'center'}>
|
||||
<MyIcon name={'core/modules/welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
|
||||
<Box>{t('core.app.Welcome Text')}</Box>
|
||||
<MyTooltip label={t(welcomeTextTip)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
{welcomeText && (
|
||||
<Textarea
|
||||
className="nodrag"
|
||||
rows={6}
|
||||
resize={'both'}
|
||||
defaultValue={welcomeText.value}
|
||||
bg={'myWhite.500'}
|
||||
placeholder={t(welcomeTextTip)}
|
||||
onChange={(e) => {
|
||||
startTst(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.welcomeText,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...welcomeText,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
|
||||
const variables = useMemo(
|
||||
() =>
|
||||
(inputs.find((item) => item.key === ModuleInputKeyEnum.variables)
|
||||
?.value as VariableItemType[]) || [],
|
||||
[inputs]
|
||||
);
|
||||
|
||||
const updateVariables = useCallback(
|
||||
(value: VariableItemType[]) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.variables,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
return <VariableEdit variables={variables} onChange={(e) => updateVariables(e)} />;
|
||||
}
|
||||
|
||||
function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
|
||||
const questionGuide = useMemo(
|
||||
() =>
|
||||
(inputs.find((item) => item.key === ModuleInputKeyEnum.questionGuide)?.value as boolean) ||
|
||||
false,
|
||||
[inputs]
|
||||
);
|
||||
|
||||
return (
|
||||
<QGSwitch
|
||||
isChecked={questionGuide}
|
||||
size={'lg'}
|
||||
onChange={(e) => {
|
||||
const value = e.target.checked;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.questionGuide,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.questionGuide),
|
||||
value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function TTSGuide({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
const { ttsConfig } = splitGuideModule({ inputs } as ModuleItemType);
|
||||
|
||||
return (
|
||||
<TTSSelect
|
||||
value={ttsConfig}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.tts,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.tts),
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function WhisperGuide({ data }: { data: FlowModuleItemType }) {
|
||||
const { inputs, moduleId } = data;
|
||||
const { ttsConfig, whisperConfig } = splitGuideModule({ inputs } as ModuleItemType);
|
||||
|
||||
return (
|
||||
<WhisperConfig
|
||||
isOpenAudio={ttsConfig.type !== TTSTypeEnum.none}
|
||||
value={whisperConfig}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: ModuleInputKeyEnum.whisper,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === ModuleInputKeyEnum.whisper),
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { DYNAMIC_INPUT_KEY, ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { EditInputFieldMap, EditNodeFieldType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
|
||||
const FieldEditModal = ({
|
||||
editField = {
|
||||
key: true,
|
||||
name: true,
|
||||
description: true,
|
||||
dataType: true
|
||||
},
|
||||
defaultField,
|
||||
keys = [],
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
editField?: EditInputFieldMap;
|
||||
defaultField: EditNodeFieldType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const isCreate = useMemo(() => !defaultField.key, [defaultField.key]);
|
||||
const showDynamicInputSelect =
|
||||
!keys.includes(DYNAMIC_INPUT_KEY) || defaultField.key === DYNAMIC_INPUT_KEY;
|
||||
|
||||
const inputTypeList = [
|
||||
{
|
||||
label: t('core.module.inputType.target'),
|
||||
value: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
valueType: ModuleIOValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.module.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
valueType: ModuleIOValueTypeEnum.selectDataset
|
||||
},
|
||||
...(showDynamicInputSelect
|
||||
? [
|
||||
{
|
||||
label: t('core.module.inputType.dynamicTargetInput'),
|
||||
value: FlowNodeInputTypeEnum.addInputParam,
|
||||
valueType: ModuleIOValueTypeEnum.any
|
||||
}
|
||||
]
|
||||
: [])
|
||||
];
|
||||
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -2)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const inputType = watch('inputType');
|
||||
const outputType = watch('outputType');
|
||||
const required = watch('required');
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const showDataTypeSelect = useMemo(() => {
|
||||
if (!editField.dataType) return false;
|
||||
if (inputType === undefined) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.target) return true;
|
||||
if (outputType === FlowNodeOutputTypeEnum.source) return true;
|
||||
|
||||
return false;
|
||||
}, [editField.dataType, inputType, outputType]);
|
||||
|
||||
const showRequired = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.required || editField.defaultValue;
|
||||
}, [editField.defaultValue, editField.required, inputType]);
|
||||
|
||||
const showNameInput = useMemo(() => {
|
||||
return editField.name;
|
||||
}, [editField.name]);
|
||||
|
||||
const showKeyInput = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.key;
|
||||
}, [editField.key, inputType]);
|
||||
|
||||
const showDescriptionInput = useMemo(() => {
|
||||
return editField.description;
|
||||
}, [editField.description]);
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: EditNodeFieldType) => {
|
||||
if (!data.key) return;
|
||||
if (isCreate && keys.includes(data.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Already Exist')
|
||||
});
|
||||
}
|
||||
onSubmit({
|
||||
data,
|
||||
changeKey: !keys.includes(data.key)
|
||||
});
|
||||
},
|
||||
[isCreate, keys, onSubmit, t, toast]
|
||||
);
|
||||
const onSubmitError = useCallback(
|
||||
(e: Object) => {
|
||||
for (const item of Object.values(e)) {
|
||||
if (item.message) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: item.message
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
[toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/module/extract.png"
|
||||
title={t('core.module.edit.Field Edit')}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody overflow={'visible'}>
|
||||
{/* input type select: target, input, textarea.... */}
|
||||
{editField.inputType && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Input Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={inputTypeList}
|
||||
value={getValues('inputType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeInputTypeEnum}`;
|
||||
const selectedItem = inputTypeList.find((item) => item.value === type);
|
||||
setValue('inputType', type);
|
||||
setValue('valueType', selectedItem?.valueType);
|
||||
|
||||
if (type === FlowNodeInputTypeEnum.selectDataset) {
|
||||
setValue('label', selectedItem?.label);
|
||||
} else if (type === FlowNodeInputTypeEnum.addInputParam) {
|
||||
setValue('label', t('core.module.valueType.dynamicTargetInput'));
|
||||
setValue('key', DYNAMIC_INPUT_KEY);
|
||||
setValue('required', false);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>{t('common.Require Input')}</Box>
|
||||
<Switch
|
||||
{...register('required', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
setValue('defaultValue', '');
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showRequired && required && editField.defaultValue && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={['0 0 70px']}>{t('core.module.Default value')}</Box>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('core.module.Default value placeholder')}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{editField.isToolInput && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>工具参数</Box>
|
||||
<Switch {...register('isToolInput')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showDataTypeSelect && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Data Type')}</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={dataTypeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${ModuleIOValueTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === ModuleIOValueTypeEnum.chatHistory ||
|
||||
type === ModuleIOValueTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = dataTypeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showNameInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field Name')}</Box>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showKeyInput && (
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>{t('core.module.Field key')}</Box>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true,
|
||||
onChange: (e) => {
|
||||
const value = e.target.value;
|
||||
// auto fill label
|
||||
if (!showNameInput) {
|
||||
setValue('label', value);
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showDescriptionInput && (
|
||||
<Box mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'} mb={'1px'}>
|
||||
{t('core.module.Field Description')}
|
||||
</Box>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('common.choosable')}
|
||||
rows={5}
|
||||
{...register('description')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={3} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmitSuccess, onSubmitError)}>
|
||||
{t('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
@@ -1,270 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { onChangeNode, onCopyNode, onResetNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getPreviewPluginModule } from '@/web/core/plugin/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import { ToolTargetHandle } from './ToolHandle';
|
||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||
import TriggerAndFinish from './RenderInput/templates/TriggerAndFinish';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
minW?: string | number;
|
||||
maxW?: string | number;
|
||||
forbidMenu?: boolean;
|
||||
selected?: boolean;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
children,
|
||||
avatar = LOGO_ICON,
|
||||
name = t('core.module.template.UnKnow Module'),
|
||||
intro,
|
||||
minW = '300px',
|
||||
maxW = '600px',
|
||||
moduleId,
|
||||
flowType,
|
||||
inputs,
|
||||
selected,
|
||||
forbidMenu,
|
||||
isTool = false
|
||||
} = props;
|
||||
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { nodes, splitToolInputs, onDelNode } = useFlowProviderStore();
|
||||
// edit intro
|
||||
const { onOpenModal: onOpenIntroModal, EditModal: EditIntroModal } = useEditTextarea({
|
||||
title: t('core.module.Edit intro'),
|
||||
tip: '调整该模块会对工具调用时机有影响。\n你可以通过精确的描述该模块功能,引导模型进行工具调用。',
|
||||
canEmpty: false
|
||||
});
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
title: t('common.Custom Title'),
|
||||
placeholder: t('app.module.Custom Title Tip') || ''
|
||||
});
|
||||
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
|
||||
content: t('module.Confirm Sync Plugin')
|
||||
});
|
||||
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
|
||||
content: t('core.module.Confirm Delete Node'),
|
||||
type: 'delete'
|
||||
});
|
||||
|
||||
const showToolHandle = useMemo(
|
||||
() => isTool && !!nodes.find((item) => item.data?.flowType === FlowNodeTypeEnum.tools),
|
||||
[isTool, nodes]
|
||||
);
|
||||
const moduleIsTool = useMemo(() => {
|
||||
const { isTool } = splitToolInputs([], moduleId);
|
||||
return isTool;
|
||||
}, [moduleId, splitToolInputs]);
|
||||
|
||||
const Header = useMemo(() => {
|
||||
const menuList = [
|
||||
...(flowType === FlowNodeTypeEnum.pluginModule
|
||||
? [
|
||||
{
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === ModuleInputKeyEnum.pluginId
|
||||
)?.value;
|
||||
if (!pluginId) return;
|
||||
onOpenConfirmSync(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPreviewPluginModule(pluginId);
|
||||
onResetNode({
|
||||
id: moduleId,
|
||||
module: pluginModule
|
||||
});
|
||||
} catch (e) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('common.Rename'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () =>
|
||||
onOpenModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app.modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
variant: 'whiteBase',
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
variant: 'whiteDanger',
|
||||
onClick: onOpenConfirmDeleteNode(() => onDelNode(moduleId))
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Box className="custom-drag-handle" px={4} py={3} position={'relative'}>
|
||||
{showToolHandle && <ToolTargetHandle moduleId={moduleId} />}
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={avatar} borderRadius={'0'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'}>
|
||||
{t(name)}
|
||||
</Box>
|
||||
</Flex>
|
||||
{!forbidMenu && (
|
||||
<Box
|
||||
className="nodrag controller-menu"
|
||||
display={'none'}
|
||||
flexDirection={'column'}
|
||||
gap={3}
|
||||
position={'absolute'}
|
||||
top={'-20px'}
|
||||
right={0}
|
||||
transform={'translateX(90%)'}
|
||||
pl={'20px'}
|
||||
pr={'10px'}
|
||||
pb={'20px'}
|
||||
pt={'20px'}
|
||||
>
|
||||
{menuList.map((item) => (
|
||||
<Box key={item.icon}>
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={item.variant}
|
||||
leftIcon={<MyIcon name={item.icon as any} w={'12px'} />}
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.label}
|
||||
</Button>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<Flex alignItems={'flex-end'} py={1}>
|
||||
<Box fontSize={'xs'} color={'myGray.600'} flex={'1 0 0'}>
|
||||
{t(intro)}
|
||||
</Box>
|
||||
{moduleIsTool && (
|
||||
<Button
|
||||
size={'xs'}
|
||||
variant={'whiteBase'}
|
||||
onClick={() => {
|
||||
onOpenIntroModal({
|
||||
defaultVal: intro,
|
||||
onSuccess(e) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'intro',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('core.module.Edit intro')}
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
{/* switch */}
|
||||
<TriggerAndFinish moduleId={moduleId} isTool={moduleIsTool} />
|
||||
</Box>
|
||||
);
|
||||
}, [
|
||||
flowType,
|
||||
t,
|
||||
onOpenConfirmDeleteNode,
|
||||
showToolHandle,
|
||||
moduleId,
|
||||
avatar,
|
||||
name,
|
||||
forbidMenu,
|
||||
intro,
|
||||
moduleIsTool,
|
||||
inputs,
|
||||
onOpenConfirmSync,
|
||||
setLoading,
|
||||
toast,
|
||||
onOpenModal,
|
||||
onDelNode,
|
||||
onOpenIntroModal
|
||||
]);
|
||||
|
||||
const RenderModal = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<EditTitleModal maxLength={20} />
|
||||
{moduleIsTool && <EditIntroModal maxLength={500} />}
|
||||
<ConfirmSyncModal />
|
||||
<ConfirmDeleteModal />
|
||||
</>
|
||||
);
|
||||
}, [ConfirmDeleteModal, ConfirmSyncModal, EditIntroModal, EditTitleModal, moduleIsTool]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={maxW}
|
||||
bg={'white'}
|
||||
borderWidth={'1px'}
|
||||
borderColor={selected ? 'primary.600' : 'borderColor.base'}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'1'}
|
||||
_hover={{
|
||||
boxShadow: '4',
|
||||
'& .controller-menu': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{Header}
|
||||
<Box className="nowheel">{children}</Box>
|
||||
{RenderModal}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
@@ -1,147 +0,0 @@
|
||||
import { EditNodeFieldType, FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { onChangeNode, useFlowProviderStoreType } from '../../../FlowProvider';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import TargetHandle from '../TargetHandle';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
type Props = FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
};
|
||||
|
||||
const InputLabel = ({ moduleId, inputKey, mode, ...item }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
required = false,
|
||||
description,
|
||||
edit,
|
||||
label,
|
||||
type,
|
||||
valueType,
|
||||
showTargetInApp,
|
||||
showTargetInPlugin
|
||||
} = item;
|
||||
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
const targetHandle = useMemo(() => {
|
||||
if (type === FlowNodeInputTypeEnum.target) return true;
|
||||
if (mode === 'app' && showTargetInApp) return true;
|
||||
if (mode === 'plugin' && showTargetInPlugin) return true;
|
||||
return false;
|
||||
}, [mode, showTargetInApp, showTargetInPlugin, type]);
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'}>
|
||||
{t(label)}
|
||||
{description && (
|
||||
<MyTooltip label={t(description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
{required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{targetHandle && <TargetHandle handleKey={inputKey} valueType={valueType} />}
|
||||
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
inputType: type,
|
||||
valueType: valueType,
|
||||
key: inputKey,
|
||||
required,
|
||||
label,
|
||||
description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: inputKey,
|
||||
value: ''
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
keys={[editField.key]}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...item,
|
||||
type: data.inputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(InputLabel);
|
||||
@@ -1,57 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddInputParam = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.input.Add Input')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: data.key,
|
||||
value: {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: data.inputType,
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
edit: true,
|
||||
editField: item.editField
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddInputParam);
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import {
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper
|
||||
} from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const NumberInputRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<NumberInput
|
||||
defaultValue={item.value}
|
||||
min={item.min}
|
||||
max={item.max}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: Number(e)
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<NumberInputField />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NumberInputRender);
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
|
||||
const SelectRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<MySelect
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectRender);
|
||||
@@ -1,175 +0,0 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { getFlowStore, onChangeNode, useFlowProviderStoreType } from '../../../../FlowProvider';
|
||||
import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@fastgpt/global/core/module/api';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const DatasetSelectModal = dynamic(() => import('@/components/core/module/DatasetSelectModal'));
|
||||
const DatasetParamsModal = dynamic(() => import('@/components/core/module/DatasetParamsModal'));
|
||||
|
||||
const SelectDatasetRender = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { llmModelList } = useSystemStore();
|
||||
const [nodes, setNodes] = useState<useFlowProviderStoreType['nodes']>([]);
|
||||
const [data, setData] = useState({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
similarity: 0.5,
|
||||
usingReRank: false
|
||||
});
|
||||
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenDatasetSelect,
|
||||
onOpen: onOpenDatasetSelect,
|
||||
onClose: onCloseDatasetSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
const value = item.value as SelectedDatasetType;
|
||||
return allDatasets.filter((dataset) => value?.find((item) => item.datasetId === dataset._id));
|
||||
}, [allDatasets, item.value]);
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
|
||||
nodes.forEach((item) => {
|
||||
if (item.type === FlowNodeTypeEnum.chatNode) {
|
||||
const model =
|
||||
item.data.inputs.find((item) => item.key === ModuleInputKeyEnum.aiModel)?.value || '';
|
||||
const quoteMaxToken =
|
||||
llmModelList.find((item) => item.model === model)?.quoteMaxToken || 3000;
|
||||
|
||||
maxTokens = Math.max(maxTokens, quoteMaxToken);
|
||||
}
|
||||
});
|
||||
|
||||
return maxTokens;
|
||||
}, [nodes]);
|
||||
|
||||
const {
|
||||
isOpen: isOpenDatasetPrams,
|
||||
onOpen: onOpenDatasetParams,
|
||||
onClose: onCloseDatasetParams
|
||||
} = useDisclosure();
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
if (data[input.key] !== undefined) {
|
||||
setData((state) => ({
|
||||
...state,
|
||||
[input.key]: input.value
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, [inputs]);
|
||||
|
||||
useEffect(() => {
|
||||
async () => {
|
||||
const { nodes } = await getFlowStore();
|
||||
setNodes(nodes);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button
|
||||
h={'36px'}
|
||||
leftIcon={<MyIcon name={'common/selectLight'} w={'14px'} />}
|
||||
onClick={onOpenDatasetSelect}
|
||||
>
|
||||
{t('common.Choose')}
|
||||
</Button>
|
||||
{/* <Button
|
||||
h={'36px'}
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<MyIcon name={'common/settingLight'} w={'14px'} />}
|
||||
onClick={onOpenDatasetParams}
|
||||
>
|
||||
{t('core.dataset.search.Params Setting')}
|
||||
</Button> */}
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
alignItems={'center'}
|
||||
h={'36px'}
|
||||
border={theme.borders.base}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'}></Avatar>
|
||||
<Box
|
||||
ml={3}
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
className="textEllipsis"
|
||||
fontWeight={'bold'}
|
||||
fontSize={['md', 'lg', 'xl']}
|
||||
>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Grid>
|
||||
{isOpenDatasetSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenDatasetSelect}
|
||||
defaultSelectedDatasets={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
onClose={onCloseDatasetSelect}
|
||||
/>
|
||||
)}
|
||||
{/* {isOpenDatasetPrams && (
|
||||
<DatasetParamsModal
|
||||
{...data}
|
||||
maxTokens={tokenLimit}
|
||||
onClose={onCloseDatasetParams}
|
||||
onSuccess={(e) => {
|
||||
for (let key in e) {
|
||||
const item = inputs.find((input) => input.key === key);
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
//@ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectDatasetRender);
|
||||
@@ -1,54 +0,0 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { llmModelTypeFilterMap } from '@fastgpt/global/core/ai/constants';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
|
||||
const SelectAiModelRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { llmModelList } = useSystemStore();
|
||||
|
||||
const modelList = llmModelList.filter((model) => {
|
||||
if (!item.llmModelType) return true;
|
||||
const filterField = llmModelTypeFilterMap[item.llmModelType];
|
||||
if (!filterField) return true;
|
||||
//@ts-ignore
|
||||
return !!model[filterField];
|
||||
});
|
||||
|
||||
const onChangeModel = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
},
|
||||
[item, moduleId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!item.value && modelList.length > 0) {
|
||||
onChangeModel(modelList[0].model);
|
||||
}
|
||||
}, [item.value, modelList, onChangeModel]);
|
||||
|
||||
return (
|
||||
<AIModelSelector
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={modelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectAiModelRender);
|
||||
@@ -1,49 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { SettingAIDataType } from '@fastgpt/global/core/module/node/type';
|
||||
import SettingLLMModel from '@/components/core/ai/SettingLLMModel';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const SelectAiModelRender = ({ item, inputs = [], moduleId }: RenderInputProps) => {
|
||||
const onChangeModel = useCallback(
|
||||
(e: SettingAIDataType) => {
|
||||
for (const key in e) {
|
||||
const input = inputs.find((input) => input.key === key);
|
||||
input &&
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...input,
|
||||
// @ts-ignore
|
||||
value: e[key]
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const llmModelData: SettingAIDataType = {
|
||||
model: inputs.find((input) => input.key === ModuleInputKeyEnum.aiModel)?.value ?? '',
|
||||
maxToken:
|
||||
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatMaxToken)?.value ?? 2048,
|
||||
temperature:
|
||||
inputs.find((input) => input.key === ModuleInputKeyEnum.aiChatTemperature)?.value ?? 1,
|
||||
isResponseAnswerText: inputs.find(
|
||||
(input) => input.key === ModuleInputKeyEnum.aiChatIsResponseText
|
||||
)?.value
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingLLMModel
|
||||
llmModelType={item.llmModelType}
|
||||
defaultData={llmModelData}
|
||||
onChange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SelectAiModelRender);
|
||||
@@ -1,243 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, BoxProps, Button, Flex, ModalFooter, useDisclosure } from '@chakra-ui/react';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../../../FlowProvider';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
formatEditorVariablePickerIcon,
|
||||
getGuideModule,
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/module/utils';
|
||||
import { ModalBody } from '@chakra-ui/react';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import {
|
||||
Prompt_QuotePromptList,
|
||||
Prompt_QuoteTemplateList
|
||||
} from '@fastgpt/global/core/ai/prompt/AIChat';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import PromptTemplate from '@/components/PromptTemplate';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const SettingQuotePrompt = ({ inputs = [], moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { nodes } = useFlowProviderStore();
|
||||
const { watch, setValue, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
quoteTemplate: inputs.find((input) => input.key === 'quoteTemplate')?.value || '',
|
||||
quotePrompt: inputs.find((input) => input.key === 'quotePrompt')?.value || ''
|
||||
}
|
||||
});
|
||||
const aiChatQuoteTemplate = watch('quoteTemplate');
|
||||
const aiChatQuotePrompt = watch('quotePrompt');
|
||||
|
||||
const variables = useMemo(() => {
|
||||
const globalVariables = formatEditorVariablePickerIcon(
|
||||
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
|
||||
);
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.edit)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
const systemVariables = [
|
||||
{
|
||||
key: 'cTime',
|
||||
label: t('core.module.http.Current time')
|
||||
}
|
||||
];
|
||||
|
||||
return [...globalVariables, ...moduleVariables, ...systemVariables];
|
||||
}, [inputs, t]);
|
||||
const [selectTemplateData, setSelectTemplateData] = useState<{
|
||||
title: string;
|
||||
templates: PromptTemplateItem[];
|
||||
}>();
|
||||
const quoteTemplateVariables = (() => [
|
||||
{
|
||||
key: 'q',
|
||||
label: 'q',
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
{
|
||||
key: 'a',
|
||||
label: 'a',
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
{
|
||||
key: 'source',
|
||||
label: t('core.dataset.search.Source name'),
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
{
|
||||
key: 'sourceId',
|
||||
label: t('core.dataset.search.Source id'),
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
{
|
||||
key: 'index',
|
||||
label: t('core.dataset.search.Quote index'),
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
...variables
|
||||
])();
|
||||
const quotePromptVariables = (() => [
|
||||
{
|
||||
key: 'quote',
|
||||
label: t('core.app.Quote templates'),
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
{
|
||||
key: 'question',
|
||||
label: t('core.module.input.label.user question'),
|
||||
icon: 'core/app/simpleMode/variable'
|
||||
},
|
||||
...variables
|
||||
])();
|
||||
|
||||
const LabelStyles: BoxProps = {
|
||||
fontSize: ['sm', 'md']
|
||||
};
|
||||
const selectTemplateBtn: BoxProps = {
|
||||
color: 'primary.500',
|
||||
cursor: 'pointer'
|
||||
};
|
||||
|
||||
const onSubmit = (data: { quoteTemplate: string; quotePrompt: string }) => {
|
||||
const quoteTemplateInput = inputs.find(
|
||||
(input) => input.key === ModuleInputKeyEnum.aiChatQuoteTemplate
|
||||
);
|
||||
const quotePromptInput = inputs.find(
|
||||
(input) => input.key === ModuleInputKeyEnum.aiChatQuotePrompt
|
||||
);
|
||||
if (quoteTemplateInput) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: quoteTemplateInput.key,
|
||||
value: {
|
||||
...quoteTemplateInput,
|
||||
value: data.quoteTemplate
|
||||
}
|
||||
});
|
||||
}
|
||||
if (quotePromptInput) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: quotePromptInput.key,
|
||||
value: {
|
||||
...quotePromptInput,
|
||||
value: data.quotePrompt
|
||||
}
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant={'whitePrimary'} size={'sm'} onClick={onOpen}>
|
||||
{t('core.module.Setting quote prompt')}
|
||||
</Button>
|
||||
<MyModal
|
||||
isOpen={isOpen}
|
||||
iconSrc={'modal/edit'}
|
||||
title={t('core.module.Quote prompt setting')}
|
||||
w={'600px'}
|
||||
>
|
||||
<ModalBody>
|
||||
<Box>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
{t('core.app.Quote templates')}
|
||||
<MyTooltip
|
||||
label={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Box
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: t('core.app.Select quote template'),
|
||||
templates: Prompt_QuoteTemplateList
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('common.Select template')}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<PromptEditor
|
||||
variables={quoteTemplateVariables}
|
||||
h={160}
|
||||
title={t('core.app.Quote templates')}
|
||||
placeholder={t('template.Quote Content Tip', {
|
||||
default: Prompt_QuoteTemplateList[0].value
|
||||
})}
|
||||
value={aiChatQuoteTemplate}
|
||||
onChange={(e) => {
|
||||
setValue('quoteTemplate', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<Flex {...LabelStyles} mb={1}>
|
||||
{t('core.app.Quote prompt')}
|
||||
<MyTooltip
|
||||
label={t('template.Quote Prompt Tip', { default: Prompt_QuotePromptList[0].value })}
|
||||
forceShow
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<PromptEditor
|
||||
variables={quotePromptVariables}
|
||||
title={t('core.app.Quote prompt')}
|
||||
h={280}
|
||||
placeholder={t('template.Quote Prompt Tip', {
|
||||
default: Prompt_QuotePromptList[0].value
|
||||
})}
|
||||
value={aiChatQuotePrompt}
|
||||
onChange={(e) => {
|
||||
setValue('quotePrompt', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
|
||||
{t('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>{t('common.Confirm')}</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
{!!selectTemplateData && (
|
||||
<PromptTemplate
|
||||
title={selectTemplateData.title}
|
||||
templates={selectTemplateData.templates}
|
||||
onClose={() => setSelectTemplateData(undefined)}
|
||||
onSuccess={(e) => {
|
||||
const quoteVal = e.value;
|
||||
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
|
||||
setValue('quoteTemplate', quoteVal);
|
||||
setValue('quotePrompt', promptVal);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SettingQuotePrompt);
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import MySlider from '@/components/Slider';
|
||||
|
||||
const SliderRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Box px={2}>
|
||||
<MySlider
|
||||
markList={item.markList}
|
||||
width={'100%'}
|
||||
min={item.min || 0}
|
||||
max={item.max}
|
||||
step={item.step || 1}
|
||||
value={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SliderRender);
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const SwitchRender = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Switch
|
||||
size={'lg'}
|
||||
isChecked={item.value}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.checked
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SwitchRender);
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
|
||||
const TextInput = ({ item, moduleId }: RenderInputProps) => {
|
||||
return (
|
||||
<Input
|
||||
placeholder={item.placeholder}
|
||||
defaultValue={item.value}
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e.target.value
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextInput);
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { useCallback, useMemo, useTransition } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../../../FlowProvider';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import {
|
||||
formatEditorVariablePickerIcon,
|
||||
getGuideModule,
|
||||
splitGuideModule
|
||||
} from '@fastgpt/global/core/module/utils';
|
||||
|
||||
const TextareaRender = ({ inputs = [], item, moduleId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
// get variable
|
||||
const variables = useMemo(() => {
|
||||
const globalVariables = formatEditorVariablePickerIcon(
|
||||
splitGuideModule(getGuideModule(nodes.map((node) => node.data)))?.variableModules || []
|
||||
);
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.edit)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
const systemVariables = [
|
||||
{
|
||||
key: 'cTime',
|
||||
label: t('core.module.http.Current time')
|
||||
}
|
||||
];
|
||||
|
||||
return [...globalVariables, ...moduleVariables, ...systemVariables];
|
||||
}, [inputs, nodes, t]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: string) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
},
|
||||
[item, moduleId]
|
||||
);
|
||||
|
||||
return (
|
||||
<PromptEditor
|
||||
variables={variables}
|
||||
title={t(item.label)}
|
||||
h={150}
|
||||
placeholder={t(item.placeholder || '')}
|
||||
value={item.value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(TextareaRender);
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import TargetHandle from '../../TargetHandle';
|
||||
import SourceHandle from '../../SourceHandle';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { useFlowProviderStore } from '../../../../FlowProvider';
|
||||
|
||||
const TriggerAndFinish = ({ moduleId, isTool }: { moduleId: string; isTool: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodes } = useFlowProviderStore();
|
||||
|
||||
const inputs = useMemo(
|
||||
() => nodes.find((node) => node.data.moduleId === moduleId)?.data?.inputs || [],
|
||||
[moduleId, nodes]
|
||||
);
|
||||
const hasSwitch = useMemo(
|
||||
() => inputs.some((input) => input.key === ModuleInputKeyEnum.switch),
|
||||
[inputs]
|
||||
);
|
||||
const outputs = useMemo(
|
||||
() => nodes.find((node) => node.data.moduleId === moduleId)?.data?.outputs || [],
|
||||
[moduleId, nodes]
|
||||
);
|
||||
const hasFinishOutput = useMemo(
|
||||
() => outputs.some((output) => output.key === ModuleOutputKeyEnum.finish),
|
||||
[outputs]
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<Flex
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box position={'relative'}>
|
||||
{!isTool && (
|
||||
<Box mt={2}>
|
||||
<TargetHandle handleKey={ModuleInputKeyEnum.switch} valueType={'any'} />
|
||||
{t('core.module.input.label.switch')}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{hasFinishOutput && (
|
||||
<Box position={'relative'} mt={2}>
|
||||
{t('core.module.output.label.running done')}
|
||||
<SourceHandle handleKey={ModuleOutputKeyEnum.finish} valueType={'boolean'} />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}, [hasFinishOutput, isTool, t]);
|
||||
|
||||
return hasSwitch ? Render : null;
|
||||
};
|
||||
|
||||
export default React.memo(TriggerAndFinish);
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import TargetHandle from '../../TargetHandle';
|
||||
import SourceHandle from '../../SourceHandle';
|
||||
import { ModuleInputKeyEnum, ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
const UserChatInput = ({ item }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
position={'relative'}
|
||||
>
|
||||
<Box position={'relative'}>
|
||||
<TargetHandle handleKey={ModuleInputKeyEnum.userChatInput} valueType={item.valueType} />
|
||||
{t('core.module.input.label.user question')}
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
right={'-8px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
</Box>
|
||||
<Box position={'relative'}>
|
||||
{t('core.module.input.label.user question')}
|
||||
<SourceHandle handleKey={ModuleOutputKeyEnum.userChatInput} valueType={item.valueType} />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserChatInput);
|
||||
@@ -1,142 +0,0 @@
|
||||
import { EditNodeFieldType, FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { onChangeNode } from '../../../FlowProvider';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import SourceHandle from '../SourceHandle';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
|
||||
const OutputLabel = ({
|
||||
moduleId,
|
||||
outputKey,
|
||||
outputs,
|
||||
...item
|
||||
}: FlowNodeOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { label = '', description, edit } = item;
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'right'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
>
|
||||
{edit && (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: outputKey,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
valueType: item.valueType,
|
||||
outputType: item.type,
|
||||
required: item.required,
|
||||
defaultValue: item.defaultValue
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: outputKey
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{description && (
|
||||
<MyTooltip label={t(description)} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'-2px'}
|
||||
left={'-5px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{t(label)}
|
||||
</Box>
|
||||
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={outputKey} valueType={item.valueType} />
|
||||
)}
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={[outputKey]}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.outputType || !data.key) return;
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...item,
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
defaultValue: data.defaultValue
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(OutputLabel);
|
||||
@@ -1,80 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import OutputLabel from './Label';
|
||||
import { RenderOutputProps } from './type';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const RenderList: {
|
||||
types: `${FlowNodeOutputTypeEnum}`[];
|
||||
Component: React.ComponentType<RenderOutputProps>;
|
||||
}[] = [
|
||||
{
|
||||
types: [FlowNodeOutputTypeEnum.addOutputParam],
|
||||
Component: dynamic(() => import('./templates/AddOutputParam'))
|
||||
}
|
||||
];
|
||||
|
||||
const RenderToolOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
}) => {
|
||||
const sortOutputs = useMemo(
|
||||
() =>
|
||||
[...flowOutputList].sort((a, b) => {
|
||||
if (a.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return 1;
|
||||
}
|
||||
if (b.type === FlowNodeOutputTypeEnum.addOutputParam) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (a.key === ModuleOutputKeyEnum.finish) return -1;
|
||||
if (b.key === ModuleOutputKeyEnum.finish) return 1;
|
||||
return 0;
|
||||
}),
|
||||
[flowOutputList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{sortOutputs.map((output) => {
|
||||
const RenderComponent = (() => {
|
||||
const Component = RenderList.find(
|
||||
(item) => output.type && item.types.includes(output.type)
|
||||
)?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component outputs={sortOutputs} item={output} moduleId={moduleId} />;
|
||||
})();
|
||||
|
||||
return (
|
||||
output.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={output.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{output.label && (
|
||||
<OutputLabel
|
||||
moduleId={moduleId}
|
||||
outputKey={output.key}
|
||||
outputs={sortOutputs}
|
||||
{...output}
|
||||
/>
|
||||
)}
|
||||
{!!RenderComponent && (
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{RenderComponent}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(RenderToolOutput);
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { RenderOutputProps } from '../type';
|
||||
import { onChangeNode } from '../../../../FlowProvider';
|
||||
import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/module/node/type';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddOutputParam = ({ outputs = [], item, moduleId }: RenderOutputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<Box textAlign={'right'}>
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
setEditField(item.defaultEditField || {});
|
||||
}}
|
||||
>
|
||||
{t('core.module.output.Add Output')}
|
||||
</Button>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={outputs.map((output) => output.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
key: data.key,
|
||||
value: {
|
||||
type: data.outputType,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
required: data.required,
|
||||
defaultValue: data.defaultValue,
|
||||
edit: true,
|
||||
editField: item.editField,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AddOutputParam);
|
||||
@@ -1,25 +0,0 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
export const defaultEditFormData: FlowNodeInputItemType = {
|
||||
valueType: 'string',
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
key: '',
|
||||
label: '',
|
||||
toolDescription: '',
|
||||
required: true,
|
||||
edit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true,
|
||||
dataType: true
|
||||
},
|
||||
defaultEditField: {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.target,
|
||||
valueType: ModuleIOValueTypeEnum.string
|
||||
}
|
||||
};
|
||||
@@ -1,58 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModuleIOValueTypeEnum } from '@fastgpt/global/core/module/constants';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${ModuleIOValueTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? ModuleIOValueTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType && FlowValueTypeMap[valueType]
|
||||
? FlowValueTypeMap[valueType]?.handlerStyle
|
||||
: FlowValueTypeMap[ModuleIOValueTypeEnum.any]?.handlerStyle,
|
||||
[valueType]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
top={'50%'}
|
||||
right={'-18px'}
|
||||
transform={'translate(0,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeMap[valType]?.label),
|
||||
description: FlowValueTypeMap[valType]?.description
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '14px',
|
||||
height: '14px',
|
||||
borderWidth: '3.5px',
|
||||
backgroundColor: 'white',
|
||||
...valueStyle
|
||||
}}
|
||||
type="source"
|
||||
id={handleKey}
|
||||
position={Position.Right}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SourceHandle);
|
||||
@@ -1,107 +0,0 @@
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import {
|
||||
ModuleIOValueTypeEnum,
|
||||
ModuleInputKeyEnum,
|
||||
ModuleOutputKeyEnum
|
||||
} from '@fastgpt/global/core/module/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Connection, Handle, Position } from 'reactflow';
|
||||
import { useFlowProviderStore } from '../../FlowProvider';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
type ToolHandleProps = BoxProps & {
|
||||
moduleId: string;
|
||||
};
|
||||
export const ToolTargetHandle = ({ moduleId }: ToolHandleProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valueTypeMap = FlowValueTypeMap[ModuleIOValueTypeEnum.tools];
|
||||
|
||||
return (
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(valueTypeMap?.label),
|
||||
description: valueTypeMap?.description
|
||||
})}
|
||||
shouldWrapChildren={false}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
type="target"
|
||||
id={ModuleOutputKeyEnum.selectedTools}
|
||||
position={Position.Top}
|
||||
>
|
||||
<Box
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
border={'4px solid #5E8FFF'}
|
||||
transform={'translate(-40%,-30%) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
</Handle>
|
||||
</MyTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export const ToolSourceHandle = ({ moduleId }: ToolHandleProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setEdges, nodes } = useFlowProviderStore();
|
||||
|
||||
const valueTypeMap = FlowValueTypeMap[ModuleIOValueTypeEnum.tools];
|
||||
|
||||
/* onConnect edge, delete tool input and switch */
|
||||
const onConnect = useCallback(
|
||||
(e: Connection) => {
|
||||
const node = nodes.find((node) => node.id === e.target);
|
||||
if (!node) return;
|
||||
const inputs = node.data.inputs;
|
||||
setEdges((edges) =>
|
||||
edges.filter((edge) => {
|
||||
const input = inputs.find((input) => input.key === edge.targetHandle);
|
||||
if (
|
||||
edge.target === node.id &&
|
||||
(!!input?.toolDescription || input?.key === ModuleInputKeyEnum.switch)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
},
|
||||
[nodes, setEdges]
|
||||
);
|
||||
|
||||
return (
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(valueTypeMap?.label),
|
||||
description: valueTypeMap?.description
|
||||
})}
|
||||
shouldWrapChildren={false}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
borderRadius: '0',
|
||||
backgroundColor: 'transparent'
|
||||
}}
|
||||
type="source"
|
||||
id={ModuleOutputKeyEnum.selectedTools}
|
||||
position={Position.Bottom}
|
||||
onConnect={onConnect}
|
||||
>
|
||||
<Box
|
||||
w={'14px'}
|
||||
h={'14px'}
|
||||
border={'4px solid #5E8FFF'}
|
||||
transform={'translate(-40%,-30%) rotate(45deg)'}
|
||||
pointerEvents={'none'}
|
||||
/>
|
||||
</Handle>
|
||||
</MyTooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,203 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Connection,
|
||||
Controls,
|
||||
ControlButton,
|
||||
MiniMap,
|
||||
NodeProps,
|
||||
ReactFlowProvider,
|
||||
useReactFlow
|
||||
} from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import ButtonEdge from './components/modules/ButtonEdge';
|
||||
import ModuleTemplateList from './ModuleTemplateList';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
|
||||
const nodeTypes: Record<`${FlowNodeTypeEnum}`, any> = {
|
||||
[FlowNodeTypeEnum.userGuide]: dynamic(() => import('./components/nodes/NodeUserGuide')),
|
||||
[FlowNodeTypeEnum.questionInput]: dynamic(() => import('./components/nodes/NodeQuestionInput')),
|
||||
[FlowNodeTypeEnum.historyNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.chatNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.datasetConcatNode]: dynamic(
|
||||
() => import('./components/nodes/NodeDatasetConcat')
|
||||
),
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.httpRequest]: NodeSimple,
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple,
|
||||
[FlowNodeTypeEnum.tools]: dynamic(() => import('./components/nodes/NodeTools')),
|
||||
[FlowNodeTypeEnum.stopTool]: (data: NodeProps<FlowModuleItemType>) => (
|
||||
<NodeSimple {...data} minW={'100px'} maxW={'300px'} />
|
||||
),
|
||||
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./components/nodes/NodeLaf'))
|
||||
};
|
||||
const edgeTypes = {
|
||||
[EDGE_TYPE]: ButtonEdge
|
||||
};
|
||||
|
||||
const Container = React.memo(function Container() {
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect } =
|
||||
useFlowProviderStore();
|
||||
|
||||
const customOnConnect = useCallback(
|
||||
(connect: Connection) => {
|
||||
if (!connect.sourceHandle || !connect.targetHandle) {
|
||||
return;
|
||||
}
|
||||
if (connect.source === connect.target) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.Can not connect self')
|
||||
});
|
||||
}
|
||||
onConnect({
|
||||
connect
|
||||
});
|
||||
},
|
||||
[onConnect, t, toast]
|
||||
);
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={{
|
||||
animated: true,
|
||||
zIndex: 0
|
||||
}}
|
||||
elevateEdgesOnSelect
|
||||
connectionLineStyle={{ strokeWidth: 2, stroke: '#5A646Es' }}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={customOnConnect}
|
||||
>
|
||||
<FlowController />
|
||||
</ReactFlow>
|
||||
);
|
||||
});
|
||||
|
||||
const Flow = ({ Header, ...data }: { Header: React.ReactNode }) => {
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
onOpen: onOpenTemplate,
|
||||
onClose: onCloseTemplate
|
||||
} = useDisclosure();
|
||||
|
||||
const memoRenderContainer = useMemo(() => {
|
||||
return (
|
||||
<Box
|
||||
minH={'400px'}
|
||||
flex={'1 0 0'}
|
||||
w={'100%'}
|
||||
h={0}
|
||||
position={'relative'}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{/* open module template */}
|
||||
<IconButton
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
left={5}
|
||||
size={'mdSquare'}
|
||||
borderRadius={'50%'}
|
||||
icon={<SmallCloseIcon fontSize={'26px'} />}
|
||||
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
|
||||
transition={'0.2s ease'}
|
||||
aria-label={''}
|
||||
zIndex={1}
|
||||
boxShadow={'2px 2px 6px #85b1ff'}
|
||||
onClick={() => {
|
||||
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Container {...data} />
|
||||
|
||||
<ModuleTemplateList isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
||||
</Box>
|
||||
);
|
||||
}, [data, isOpenTemplate, onCloseTemplate, onOpenTemplate]);
|
||||
|
||||
return (
|
||||
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
|
||||
<ReactFlowProvider>
|
||||
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
||||
{Header}
|
||||
{memoRenderContainer}
|
||||
</Flex>
|
||||
</ReactFlowProvider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Flow);
|
||||
|
||||
const FlowController = React.memo(function FlowController() {
|
||||
const { fitView } = useReactFlow();
|
||||
return (
|
||||
<>
|
||||
<MiniMap
|
||||
style={{
|
||||
height: 78,
|
||||
width: 126,
|
||||
marginBottom: 35
|
||||
}}
|
||||
pannable
|
||||
/>
|
||||
<Controls
|
||||
position={'bottom-right'}
|
||||
style={{
|
||||
display: 'flex',
|
||||
marginBottom: 5,
|
||||
background: 'white',
|
||||
borderRadius: '6px',
|
||||
overflow: 'hidden',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.20), 0px 12px 16px -4px rgba(19, 51, 107, 0.20)'
|
||||
}}
|
||||
showInteractive={false}
|
||||
showFitView={false}
|
||||
>
|
||||
<MyTooltip label={'页面居中'}>
|
||||
<ControlButton className="custom-workflow-fix_view" onClick={() => fitView()}>
|
||||
<MyIcon name={'core/modules/fixview'} w={'14px'} />
|
||||
</ControlButton>
|
||||
</MyTooltip>
|
||||
</Controls>
|
||||
<Background />
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { FlowModuleItemType, ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { type Node, type Edge } from 'reactflow';
|
||||
|
||||
export const flowNode2Modules = ({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) => {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
intro: item.data.intro,
|
||||
avatar: item.data.avatar,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((input) => ({
|
||||
...input,
|
||||
connected: false
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected = !!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter((edge) => {
|
||||
if (
|
||||
edge.source === module.moduleId &&
|
||||
edge.sourceHandle === output.key &&
|
||||
edge.targetHandle
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
};
|
||||
|
||||
export const filterExportModules = (modules: ModuleItemType[]) => {
|
||||
modules.forEach((module) => {
|
||||
// dataset - remove select dataset value
|
||||
if (module.flowType === FlowNodeTypeEnum.datasetSearchNode) {
|
||||
module.inputs.forEach((item) => {
|
||||
if (item.key === ModuleInputKeyEnum.datasetSelectList) {
|
||||
item.value = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return JSON.stringify(modules, null, 2);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { AppSchema } from '@fastgpt/global/core/app/type.d';
|
||||
import React, {
|
||||
useMemo,
|
||||
@@ -16,10 +16,16 @@ import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { getGuideModule } from '@fastgpt/global/core/module/utils';
|
||||
import { getGuideModule } from '@fastgpt/global/core/workflow/utils';
|
||||
import { checkChatSupportSelectFileByModules } from '@/web/core/chat/utils';
|
||||
import { ModuleInputKeyEnum } from '@fastgpt/global/core/module/constants';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import {
|
||||
getDefaultEntryNodeIds,
|
||||
initWorkflowEdgeStatus,
|
||||
storeNodes2RuntimeNodes
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
|
||||
export type ChatTestComponentRef = {
|
||||
resetChatTest: () => void;
|
||||
@@ -28,11 +34,13 @@ export type ChatTestComponentRef = {
|
||||
const ChatTest = (
|
||||
{
|
||||
app,
|
||||
modules = [],
|
||||
nodes = [],
|
||||
edges = [],
|
||||
onClose
|
||||
}: {
|
||||
app: AppSchema;
|
||||
modules?: ModuleItemType[];
|
||||
nodes?: StoreNodeItemType[];
|
||||
edges?: StoreEdgeItemType[];
|
||||
onClose: () => void;
|
||||
},
|
||||
ref: ForwardedRef<ChatTestComponentRef>
|
||||
@@ -40,16 +48,17 @@ const ChatTest = (
|
||||
const { t } = useTranslation();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const { userInfo } = useUserStore();
|
||||
const isOpen = useMemo(() => modules && modules.length > 0, [modules]);
|
||||
const isOpen = useMemo(() => nodes && nodes.length > 0, [nodes]);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
/* get histories */
|
||||
let historyMaxLen = 6;
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
nodes.forEach((node) => {
|
||||
node.inputs.forEach((input) => {
|
||||
if (
|
||||
(input.key === ModuleInputKeyEnum.history ||
|
||||
input.key === ModuleInputKeyEnum.historyMaxAmount) &&
|
||||
(input.key === NodeInputKeyEnum.history ||
|
||||
input.key === NodeInputKeyEnum.historyMaxAmount) &&
|
||||
typeof input.value === 'number'
|
||||
) {
|
||||
historyMaxLen = Math.max(historyMaxLen, input.value);
|
||||
@@ -64,10 +73,12 @@ const ChatTest = (
|
||||
data: {
|
||||
history,
|
||||
prompt: chatList[chatList.length - 2].value,
|
||||
modules,
|
||||
nodes: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)),
|
||||
edges: initWorkflowEdgeStatus(edges),
|
||||
variables,
|
||||
appId: app._id,
|
||||
appName: `调试-${app.name}`
|
||||
appName: `调试-${app.name}`,
|
||||
mode: 'test'
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
@@ -75,7 +86,7 @@ const ChatTest = (
|
||||
|
||||
return { responseText, responseData };
|
||||
},
|
||||
[app._id, app.name, modules]
|
||||
[app._id, app.name, edges, nodes]
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@@ -138,14 +149,14 @@ const ChatTest = (
|
||||
appAvatar={app.avatar}
|
||||
userAvatar={userInfo?.avatar}
|
||||
showMarkIcon
|
||||
userGuideModule={getGuideModule(modules)}
|
||||
showFileSelector={checkChatSupportSelectFileByModules(modules)}
|
||||
userGuideModule={getGuideModule(nodes)}
|
||||
showFileSelector={checkChatSupportSelectFileByModules(nodes)}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
{/* <Box
|
||||
<Box
|
||||
zIndex={2}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
position={'fixed'}
|
||||
@@ -154,7 +165,7 @@ const ChatTest = (
|
||||
bottom={0}
|
||||
right={0}
|
||||
onClick={onClose}
|
||||
/> */}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
682
projects/app/src/components/core/workflow/Flow/FlowProvider.tsx
Normal file
@@ -0,0 +1,682 @@
|
||||
import {
|
||||
type Node,
|
||||
type NodeChange,
|
||||
type Edge,
|
||||
type EdgeChange,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
OnConnectStartParams
|
||||
} from 'reactflow';
|
||||
import type {
|
||||
FlowNodeItemType,
|
||||
FlowNodeTemplateType
|
||||
} from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { FlowNodeChangeProps } from '@fastgpt/global/core/workflow/type/fe.d';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
useContext,
|
||||
useCallback,
|
||||
createContext,
|
||||
useRef,
|
||||
useMemo,
|
||||
useState,
|
||||
useEffect
|
||||
} from 'react';
|
||||
import { storeEdgesRenderEdge, storeNode2FlowNode } from '@/web/core/workflow/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NodeOutputKeyEnum, RuntimeEdgeStatusEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { defaultRunningStatus } from '../constants';
|
||||
import { postWorkflowDebug } from '@/web/core/workflow/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { checkNodeRunStatus } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
|
||||
export type useFlowProviderStoreType = {
|
||||
// connect
|
||||
connectingEdge: OnConnectStartParams | undefined;
|
||||
setConnectingEdge: React.Dispatch<React.SetStateAction<OnConnectStartParams | undefined>>;
|
||||
// nodes
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
mode: 'app' | 'plugin';
|
||||
filterAppIds: string[];
|
||||
nodes: Node<FlowNodeItemType, string | undefined>[];
|
||||
nodeList: FlowNodeItemType[];
|
||||
setNodes: Dispatch<SetStateAction<Node<FlowNodeItemType, string | undefined>[]>>;
|
||||
onNodesChange: OnChange<NodeChange>;
|
||||
// debug
|
||||
workflowDebugData:
|
||||
| {
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
nextRunNodes: RuntimeNodeItemType[];
|
||||
}
|
||||
| undefined;
|
||||
onNextNodeDebug: () => Promise<void>;
|
||||
onStartNodeDebug: ({
|
||||
entryNodeId,
|
||||
runtimeNodes,
|
||||
runtimeEdges
|
||||
}: {
|
||||
entryNodeId: string;
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
}) => Promise<void>;
|
||||
onStopNodeDebug: () => void;
|
||||
|
||||
edges: Edge<any>[];
|
||||
setEdges: Dispatch<SetStateAction<Edge<any>[]>>;
|
||||
onEdgesChange: OnChange<EdgeChange>;
|
||||
onFixView: () => void;
|
||||
onChangeNode: (e: FlowNodeChangeProps) => void;
|
||||
onResetNode: (e: { id: string; module: FlowNodeTemplateType }) => void;
|
||||
onDelEdge: (e: {
|
||||
nodeId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}) => void;
|
||||
initData: (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => void;
|
||||
splitToolInputs: (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
nodeId: string
|
||||
) => {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
};
|
||||
hasToolNode: boolean;
|
||||
hoverNodeId: string | undefined;
|
||||
setHoverNodeId: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
onUpdateNodeError: (node: string, isError: Boolean) => void;
|
||||
};
|
||||
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
mode: 'app',
|
||||
filterAppIds: [],
|
||||
nodes: [],
|
||||
setNodes: function (
|
||||
value: React.SetStateAction<Node<FlowNodeItemType, string | undefined>[]>
|
||||
): void {
|
||||
return;
|
||||
},
|
||||
onNodesChange: function (changes: NodeChange[]): void {
|
||||
return;
|
||||
},
|
||||
edges: [],
|
||||
setEdges: function (value: React.SetStateAction<Edge<any>[]>): void {
|
||||
return;
|
||||
},
|
||||
onEdgesChange: function (changes: EdgeChange[]): void {
|
||||
return;
|
||||
},
|
||||
onFixView: function (): void {
|
||||
return;
|
||||
},
|
||||
onChangeNode: function (e: FlowNodeChangeProps): void {
|
||||
return;
|
||||
},
|
||||
onDelEdge: function (e: {
|
||||
nodeId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}): void {
|
||||
return;
|
||||
},
|
||||
onResetNode: function (e): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
splitToolInputs: function (
|
||||
inputs: FlowNodeInputItemType[],
|
||||
nodeId: string
|
||||
): {
|
||||
isTool: boolean;
|
||||
toolInputs: FlowNodeInputItemType[];
|
||||
commonInputs: FlowNodeInputItemType[];
|
||||
} {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
hasToolNode: false,
|
||||
connectingEdge: undefined,
|
||||
basicNodeTemplates: [],
|
||||
initData: function (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
hoverNodeId: undefined,
|
||||
setHoverNodeId: function (value: React.SetStateAction<string | undefined>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onUpdateNodeError: function (nodeId: string, isError: Boolean): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
nodeList: [],
|
||||
workflowDebugData: undefined,
|
||||
onNextNodeDebug: function (): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onStartNodeDebug: function ({
|
||||
entryNodeId,
|
||||
runtimeNodes,
|
||||
runtimeEdges
|
||||
}: {
|
||||
entryNodeId: string;
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
}): Promise<void> {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onStopNodeDebug: function (): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
setConnectingEdge: function (
|
||||
value: React.SetStateAction<OnConnectStartParams | undefined>
|
||||
): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
export const FlowProvider = ({
|
||||
mode,
|
||||
basicNodeTemplates = [],
|
||||
filterAppIds = [],
|
||||
children,
|
||||
appId,
|
||||
pluginId
|
||||
}: {
|
||||
mode: useFlowProviderStoreType['mode'];
|
||||
basicNodeTemplates: FlowNodeTemplateType[];
|
||||
filterAppIds?: string[];
|
||||
children: React.ReactNode;
|
||||
appId?: string;
|
||||
pluginId?: string;
|
||||
}) => {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const { toast } = useToast();
|
||||
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowNodeItemType>([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [hoverNodeId, setHoverNodeId] = useState<string>();
|
||||
const [connectingEdge, setConnectingEdge] = useState<OnConnectStartParams>();
|
||||
|
||||
const stringifyNodes = useMemo(() => JSON.stringify(nodes.map((node) => node.data)), [nodes]);
|
||||
const nodeList = useMemo(
|
||||
() => JSON.parse(stringifyNodes) as FlowNodeItemType[],
|
||||
[stringifyNodes]
|
||||
);
|
||||
|
||||
const hasToolNode = useMemo(() => {
|
||||
return !!nodes.find((node) => node.data.flowNodeType === FlowNodeTypeEnum.tools);
|
||||
}, [nodes]);
|
||||
|
||||
const onFixView = useCallback(() => {
|
||||
const btn = document.querySelector('.custom-workflow-fix_view') as HTMLButtonElement;
|
||||
|
||||
setTimeout(() => {
|
||||
btn && btn.click();
|
||||
}, 100);
|
||||
}, []);
|
||||
|
||||
/* edge */
|
||||
const onDelEdge = useCallback(
|
||||
({
|
||||
nodeId,
|
||||
sourceHandle,
|
||||
targetHandle
|
||||
}: {
|
||||
nodeId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
targetHandle?: string | undefined;
|
||||
}) => {
|
||||
if (!sourceHandle && !targetHandle) return;
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => {
|
||||
if (edge.source === nodeId && edge.sourceHandle === sourceHandle) return false;
|
||||
if (edge.target === nodeId && edge.targetHandle === targetHandle) return false;
|
||||
|
||||
return true;
|
||||
})
|
||||
);
|
||||
},
|
||||
[setEdges]
|
||||
);
|
||||
|
||||
/* node */
|
||||
// reset a node data. delete edge and replace it
|
||||
const onResetNode = useCallback(
|
||||
({ id, module }: { id: string; module: FlowNodeTemplateType }) => {
|
||||
setNodes((state) =>
|
||||
state.map((node) => {
|
||||
if (node.id === id) {
|
||||
// delete edge
|
||||
node.data.inputs.forEach((item) => {
|
||||
onDelEdge({ nodeId: id, targetHandle: item.key });
|
||||
});
|
||||
node.data.outputs.forEach((item) => {
|
||||
onDelEdge({ nodeId: id, sourceHandle: item.key });
|
||||
});
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...module
|
||||
}
|
||||
};
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes]
|
||||
);
|
||||
const onChangeNode = useCallback(
|
||||
(props: FlowNodeChangeProps) => {
|
||||
const { nodeId, type } = props;
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
if (node.id !== nodeId) return node;
|
||||
|
||||
const updateObj: Record<string, any> = {};
|
||||
|
||||
if (type === 'attr') {
|
||||
if (props.key) {
|
||||
updateObj[props.key] = props.value;
|
||||
}
|
||||
} else if (type === 'updateInput') {
|
||||
updateObj.inputs = node.data.inputs.map((item) =>
|
||||
item.key === props.key ? props.value : item
|
||||
);
|
||||
} else if (type === 'replaceInput') {
|
||||
onDelEdge({ nodeId, targetHandle: props.key });
|
||||
const oldInputIndex = node.data.inputs.findIndex((item) => item.key === props.key);
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
index: oldInputIndex,
|
||||
value: props.value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addInput') {
|
||||
const input = node.data.inputs.find((input) => input.key === props.value.key);
|
||||
if (input) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.inputs = node.data.inputs;
|
||||
} else {
|
||||
if (props.index !== undefined) {
|
||||
const inputs = [...node.data.inputs];
|
||||
inputs.splice(props.index, 0, props.value);
|
||||
updateObj.inputs = inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(props.value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delInput') {
|
||||
onDelEdge({ nodeId, targetHandle: props.key });
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== props.key);
|
||||
} else if (type === 'updateOutput') {
|
||||
updateObj.outputs = node.data.outputs.map((item) =>
|
||||
item.key === props.key ? props.value : item
|
||||
);
|
||||
} else if (type === 'replaceOutput') {
|
||||
onDelEdge({ nodeId, sourceHandle: props.key });
|
||||
const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === props.key);
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key);
|
||||
console.log(props.value);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addOutput',
|
||||
index: oldOutputIndex,
|
||||
value: props.value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addOutput') {
|
||||
const output = node.data.outputs.find((output) => output.key === props.value.key);
|
||||
if (output) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.outputs = node.data.outputs;
|
||||
} else {
|
||||
if (props.index !== undefined) {
|
||||
const outputs = [...node.data.outputs];
|
||||
outputs.splice(props.index, 0, props.value);
|
||||
updateObj.outputs = outputs;
|
||||
} else {
|
||||
updateObj.outputs = node.data.outputs.concat(props.value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delOutput') {
|
||||
onDelEdge({ nodeId, sourceHandle: props.key });
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== props.key);
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...updateObj
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes, toast]
|
||||
);
|
||||
const onUpdateNodeError = useCallback(
|
||||
(nodeId: string, isError: Boolean) => {
|
||||
setNodes((nodes) => {
|
||||
return nodes.map((item) => {
|
||||
if (item.data?.nodeId === nodeId) {
|
||||
item.selected = true;
|
||||
//@ts-ignore
|
||||
item.data.isError = isError;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
});
|
||||
},
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
/* Run workflow debug and get next runtime data */
|
||||
const [workflowDebugData, setWorkflowDebugData] = useState<{
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
nextRunNodes: RuntimeNodeItemType[];
|
||||
}>();
|
||||
const onNextNodeDebug = useCallback(
|
||||
async (debugData = workflowDebugData) => {
|
||||
if (!debugData) return;
|
||||
// 1. Cancel node selected status and debugResult.showStatus
|
||||
setNodes((state) =>
|
||||
state.map((node) => ({
|
||||
...node,
|
||||
selected: false,
|
||||
data: {
|
||||
...node.data,
|
||||
debugResult: node.data.debugResult
|
||||
? {
|
||||
...node.data.debugResult,
|
||||
showResult: false,
|
||||
isExpired: true
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
// 2. Set isEntry field and get entryNodes
|
||||
const runtimeNodes = debugData.runtimeNodes.map((item) => ({
|
||||
...item,
|
||||
isEntry: debugData.nextRunNodes.some((node) => node.nodeId === item.nodeId)
|
||||
}));
|
||||
const entryNodes = runtimeNodes.filter((item) => item.isEntry);
|
||||
|
||||
const runtimeNodeStatus: Record<string, string> = entryNodes
|
||||
.map((node) => {
|
||||
const status = checkNodeRunStatus({
|
||||
node,
|
||||
runtimeEdges: debugData?.runtimeEdges || []
|
||||
});
|
||||
|
||||
return {
|
||||
nodeId: node.nodeId,
|
||||
status
|
||||
};
|
||||
})
|
||||
.reduce(
|
||||
(acc, cur) => ({
|
||||
...acc,
|
||||
[cur.nodeId]: cur.status
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
// 3. Set entry node status to running
|
||||
entryNodes.forEach((node) => {
|
||||
if (runtimeNodeStatus[node.nodeId] !== 'wait') {
|
||||
console.log(node.name);
|
||||
onChangeNode({
|
||||
nodeId: node.nodeId,
|
||||
type: 'attr',
|
||||
key: 'debugResult',
|
||||
value: defaultRunningStatus
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// 4. Run one step
|
||||
const { finishedEdges, finishedNodes, nextStepRunNodes, flowResponses } =
|
||||
await postWorkflowDebug({
|
||||
nodes: runtimeNodes,
|
||||
edges: debugData.runtimeEdges,
|
||||
variables: {},
|
||||
appId,
|
||||
pluginId
|
||||
});
|
||||
// console.log({ finishedEdges, finishedNodes, nextStepRunNodes, flowResponses });
|
||||
// 5. Store debug result
|
||||
const newStoreDebugData = {
|
||||
runtimeNodes: finishedNodes,
|
||||
// edges need to save status
|
||||
runtimeEdges: finishedEdges.map((edge) => {
|
||||
const oldEdge = debugData.runtimeEdges.find(
|
||||
(item) => item.source === edge.source && item.target === edge.target
|
||||
);
|
||||
const status =
|
||||
oldEdge?.status && oldEdge.status !== RuntimeEdgeStatusEnum.waiting
|
||||
? oldEdge.status
|
||||
: edge.status;
|
||||
return {
|
||||
...edge,
|
||||
status
|
||||
};
|
||||
}),
|
||||
nextRunNodes: nextStepRunNodes
|
||||
};
|
||||
setWorkflowDebugData(newStoreDebugData);
|
||||
|
||||
// 6. selected entry node and Update entry node debug result
|
||||
setNodes((state) =>
|
||||
state.map((node) => {
|
||||
const isEntryNode = entryNodes.some((item) => item.nodeId === node.data.nodeId);
|
||||
|
||||
if (!isEntryNode || runtimeNodeStatus[node.data.nodeId] === 'wait') return node;
|
||||
|
||||
const result = flowResponses.find((item) => item.nodeId === node.data.nodeId);
|
||||
|
||||
if (runtimeNodeStatus[node.data.nodeId] === 'skip') {
|
||||
return {
|
||||
...node,
|
||||
selected: isEntryNode,
|
||||
data: {
|
||||
...node.data,
|
||||
debugResult: {
|
||||
status: 'skipped',
|
||||
showResult: true,
|
||||
isExpired: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
return {
|
||||
...node,
|
||||
selected: isEntryNode,
|
||||
data: {
|
||||
...node.data,
|
||||
debugResult: {
|
||||
status: 'success',
|
||||
response: result,
|
||||
showResult: true,
|
||||
isExpired: false
|
||||
}
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Check for an empty response
|
||||
if (flowResponses.length === 0 && nextStepRunNodes.length > 0) {
|
||||
onNextNodeDebug(newStoreDebugData);
|
||||
}
|
||||
} catch (error) {
|
||||
entryNodes.forEach((node) => {
|
||||
onChangeNode({
|
||||
nodeId: node.nodeId,
|
||||
type: 'attr',
|
||||
key: 'debugResult',
|
||||
value: {
|
||||
status: 'failed',
|
||||
message: getErrText(error, 'Debug failed'),
|
||||
showResult: true
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(error);
|
||||
}
|
||||
},
|
||||
[appId, onChangeNode, pluginId, setNodes, workflowDebugData]
|
||||
);
|
||||
const onStopNodeDebug = useCallback(() => {
|
||||
setWorkflowDebugData(undefined);
|
||||
setNodes((state) =>
|
||||
state.map((node) => ({
|
||||
...node,
|
||||
selected: false,
|
||||
data: {
|
||||
...node.data,
|
||||
debugResult: undefined
|
||||
}
|
||||
}))
|
||||
);
|
||||
}, [setNodes]);
|
||||
const onStartNodeDebug = useCallback(
|
||||
async ({
|
||||
entryNodeId,
|
||||
runtimeNodes,
|
||||
runtimeEdges
|
||||
}: {
|
||||
entryNodeId: string;
|
||||
runtimeNodes: RuntimeNodeItemType[];
|
||||
runtimeEdges: RuntimeEdgeItemType[];
|
||||
}) => {
|
||||
const data = {
|
||||
runtimeNodes,
|
||||
runtimeEdges,
|
||||
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId)
|
||||
};
|
||||
onStopNodeDebug();
|
||||
setWorkflowDebugData(data);
|
||||
|
||||
onNextNodeDebug(data);
|
||||
},
|
||||
[onNextNodeDebug, onStopNodeDebug]
|
||||
);
|
||||
|
||||
/* If the module is connected by a tool, the tool input and the normal input are separated */
|
||||
const splitToolInputs = useCallback(
|
||||
(inputs: FlowNodeInputItemType[], nodeId: string) => {
|
||||
const isTool = !!edges.find(
|
||||
(edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId
|
||||
);
|
||||
|
||||
return {
|
||||
isTool,
|
||||
toolInputs: inputs.filter((item) => isTool && item.toolDescription),
|
||||
commonInputs: inputs.filter((item) => {
|
||||
if (!isTool) return true;
|
||||
return !item.toolDescription;
|
||||
})
|
||||
};
|
||||
},
|
||||
[edges]
|
||||
);
|
||||
|
||||
const initData = useCallback(
|
||||
(e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {
|
||||
setNodes(e.nodes?.map((item) => storeNode2FlowNode({ item })));
|
||||
|
||||
setEdges(e.edges?.map((item) => storeEdgesRenderEdge({ edge: item })));
|
||||
|
||||
setTimeout(() => {
|
||||
onFixView();
|
||||
}, 100);
|
||||
},
|
||||
[setEdges, setNodes, onFixView]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eventBus.on(EventNameEnum.requestWorkflowStore, () => {
|
||||
eventBus.emit(EventNameEnum.receiveWorkflowStore, {
|
||||
nodes
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
eventBus.off(EventNameEnum.requestWorkflowStore);
|
||||
};
|
||||
}, [nodes]);
|
||||
|
||||
const value = {
|
||||
reactFlowWrapper,
|
||||
mode,
|
||||
filterAppIds,
|
||||
edges,
|
||||
setEdges,
|
||||
onEdgesChange,
|
||||
// nodes
|
||||
nodes,
|
||||
nodeList,
|
||||
setNodes,
|
||||
onNodesChange,
|
||||
hoverNodeId,
|
||||
setHoverNodeId,
|
||||
onUpdateNodeError,
|
||||
workflowDebugData,
|
||||
onNextNodeDebug,
|
||||
onStartNodeDebug,
|
||||
onStopNodeDebug,
|
||||
|
||||
basicNodeTemplates,
|
||||
// connect
|
||||
connectingEdge,
|
||||
setConnectingEdge,
|
||||
onFixView,
|
||||
onChangeNode,
|
||||
onResetNode,
|
||||
onDelEdge,
|
||||
initData,
|
||||
splitToolInputs,
|
||||
hasToolNode
|
||||
};
|
||||
|
||||
return <StateContext.Provider value={value}>{children}</StateContext.Provider>;
|
||||
};
|
||||
|
||||
export default React.memo(FlowProvider);
|
||||
|
||||
type GetWorkflowStoreResponse = {
|
||||
nodes: Node<FlowNodeItemType>[];
|
||||
};
|
||||
export const getWorkflowStore = () =>
|
||||
new Promise<GetWorkflowStoreResponse>((resolve) => {
|
||||
eventBus.on(EventNameEnum.receiveWorkflowStore, (data: GetWorkflowStoreResponse) => {
|
||||
resolve(data);
|
||||
eventBus.off(EventNameEnum.receiveWorkflowStore);
|
||||
});
|
||||
eventBus.emit(EventNameEnum.requestWorkflowStore);
|
||||
});
|
||||