4.8.6 merge (#1943)
* Dataset collection forbid (#1885) * perf: tool call support same id * feat: collection forbid * feat: collection forbid * Inheritance Permission for apps (#1897) * feat: app schema define chore: references of authapp * feat: authApp method inheritance * feat: create and update api * feat: update * feat: inheritance Permission controller for app. * feat: abstract version of inheritPermission * feat: ancestorId for apps * chore: update app * fix: inheritPermission abstract version * feat: update folder defaultPermission * feat: app update api * chore: inheritance frontend * chore: app list api * feat: update defaultPermission in app deatil * feat: backend api finished * feat: app inheritance permission fe * fix: app update defaultpermission causes collaborator miss * fix: ts error * chore: adjust the codes * chore: i18n chore: i18n * chore: fe adjust and i18n * chore: adjust the code * feat: resume api; chore: rewrite update api and inheritPermission methods * chore: something * chore: fe code adjusting * feat: frontend adjusting * chore: fe code adjusting * chore: adjusting the code * perf: fe loading * format * Inheritance fix (#1908) * fix: SlideCard * fix: authapp did not return parent app for inheritance app * fix: fe adjusting * feat: fe adjusing * perf: inherit per ux * doc * fix: ts errors (#1916) * perf: inherit permission * fix: permission inherit * Workflow type (#1938) * perf: workflow type tmp workflow perf: workflow type feat: custom field config * perf: dynamic input * perf: node classify * perf: node classify * perf: node classify * perf: node classify * fix: workflow custom input * feat: text editor and customFeedback move to basic nodes * feat: community system plugin * fix: ts * feat: exprEval plugin * perf: workflow type * perf: plugin important * fix: default templates * perf: markdown hr css * lock * perf: fetch url * perf: new plugin version * fix: chat histories update * fix: collection paths invalid * perf: app card ui --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
## 插件类型
|
||||
|
||||
xxx.json 文件
|
||||
|
||||
```ts
|
||||
type TemplateType =
|
||||
| 'userGuide'
|
||||
| 'systemInput'
|
||||
| 'tools'
|
||||
| 'textAnswer'
|
||||
| 'functionCall'
|
||||
| 'externalCall'
|
||||
| 'other';
|
||||
|
||||
type pluginType = {
|
||||
author: string; // 填写作者信息
|
||||
templateType: FlowNodeTemplateType['templateType'];
|
||||
name: string;
|
||||
avatar: string;
|
||||
intro: string;
|
||||
showStatus?: boolean; // 是否需要展示组件运行状态
|
||||
modules: []; //直接从高级编排导出配置复制过来;
|
||||
};
|
||||
```
|
||||
|
||||
## 额外代码怎么写?
|
||||
|
||||
参考 `TFSwitch` 和 `TextEditor`,通过 HTTP 模块将数据转到一个接口中实现。提交到社区的插件,务必将所有代码都放置在 FastGPT 仓库中,可以在 `projects/app/src/pages/api/plugins` 下新建一个与**插件文件名相同**的子目录进行接口编辑。
|
||||
|
||||
## 需要装包怎么办?
|
||||
|
||||
可以在 `packages/plugins` 下创建一个与**插件文件名相同**的子目录进行编写,可在 plugins 目录下安装相关依赖。然后在 FastGPT 主项目的接口中通过 `@fastgpt/plugins/xxx` 引入。
|
||||
@@ -8,227 +8,295 @@
|
||||
"showStatus": false,
|
||||
"isTool": false,
|
||||
"weight": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 541.7107154264237,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "自定义输入变量",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": ["string"],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
}
|
||||
},
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
{
|
||||
"key": "反馈内容",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
"label": "反馈内容",
|
||||
"renderTypeList": ["textarea", "reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"maxLength": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "反馈内容",
|
||||
"valueType": "string",
|
||||
"label": "反馈内容",
|
||||
"renderTypeList": ["textarea"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "ILc8GS7iU53M",
|
||||
"key": "反馈内容",
|
||||
"valueType": "string",
|
||||
"label": "反馈内容",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "2LCxDnOSculb",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["CRT7oIEU8v2P", "pYKS0LB9gAr3"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "CRT7oIEU8v2P",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "/imgs/workflow/http.png",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1070.8458389994719,
|
||||
"y": -415.09022555407836
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"],
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"value": "/api/plugins/customFeedback/v2"
|
||||
},
|
||||
{
|
||||
"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 \"customFeedback\":\"{{customFeedback}}\",\r\n \"customInputs\": {{customInputs}},\r\n \"appId\": \"{{appId}}\",\r\n \"chatId\": \"{{chatId}}\",\r\n \"responseChatItemId\": \"{{responseChatItemId}}\"\r\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "customFeedback",
|
||||
"valueType": "string",
|
||||
"label": "customFeedback",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "ILc8GS7iU53M"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "dynamic",
|
||||
"canEdit": true,
|
||||
"key": "customInputs",
|
||||
"label": "customInputs",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "ILc8GS7iU53M",
|
||||
"key": "反馈内容",
|
||||
"valueType": "string",
|
||||
"label": "反馈内容",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "2LCxDnOSculb",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
"value": ["CRT7oIEU8v2P", "pYKS0LB9gAr3"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "CRT7oIEU8v2P",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "/imgs/workflow/http.png",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1070.8458389994719,
|
||||
"y": -415.09022555407836
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"]
|
||||
},
|
||||
{
|
||||
"key": "customFeedback",
|
||||
"valueType": "string",
|
||||
"label": "customFeedback",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "ILc8GS7iU53M"]
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"value": "/api/plugins/customFeedback/v2"
|
||||
},
|
||||
{
|
||||
"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 \"customFeedback\":\"{{customFeedback}}\",\r\n \"system_addInputParam\": {{system_addInputParam}}\r\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "pYKS0LB9gAr3",
|
||||
"type": "dynamic",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "pYKS0LB9gAr3",
|
||||
"type": "dynamic",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,125 +8,127 @@
|
||||
"showStatus": false,
|
||||
"isTool": true,
|
||||
"weight": 10,
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "time",
|
||||
"valueType": "string",
|
||||
"label": "time",
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["WNUvWwYBUfEr", "HdIl1GWi0tnc"]
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "WNUvWwYBUfEr",
|
||||
"name": "文本加工",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
"flowNodeType": "pluginModule",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1030.0794269310472,
|
||||
"y": -350.61013802286885
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "{{cTime}}",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "time",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
"label": "time",
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["WNUvWwYBUfEr", "HdIl1GWi0tnc"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
],
|
||||
"outputs": []
|
||||
},
|
||||
{
|
||||
"nodeId": "WNUvWwYBUfEr",
|
||||
"name": "文本加工",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
"flowNodeType": "pluginModule",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1030.0794269310472,
|
||||
"y": -350.61013802286885
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": false,
|
||||
"value": "{{cTime}}",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,244 +1,314 @@
|
||||
{
|
||||
"author": "FastGPT Team",
|
||||
"version": "481",
|
||||
"templateType": "tools",
|
||||
"name": "文本加工",
|
||||
"avatar": "/imgs/workflow/textEditor.svg",
|
||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||
"showStatus": false,
|
||||
"isTool": false,
|
||||
"weight": 100,
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 616.4226348688949,
|
||||
"y": -165.05298493910115
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
|
||||
"isTool": false,
|
||||
"templateType": "tools",
|
||||
|
||||
"workflow": {
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 449.3089648771014,
|
||||
"y": -139.7660480324482
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "自定义输入变量",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": ["string"],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": false
|
||||
}
|
||||
},
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"required": true
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"maxLength": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"renderTypeList": ["textarea"],
|
||||
"required": true,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"value": "",
|
||||
"editField": {
|
||||
"key": true
|
||||
},
|
||||
"maxLength": "",
|
||||
"dynamicParamDefaultValue": {
|
||||
"inputType": "reference",
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "ILc8GS7iU53M",
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "2LCxDnOSculb",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "system_addInputParam",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["CRT7oIEU8v2P", "vooswj3VxKW8"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "CRT7oIEU8v2P",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "/imgs/workflow/http.png",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1070.8458389994719,
|
||||
"y": -415.09022555407836
|
||||
},
|
||||
"version": "486",
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"],
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"value": "/api/plugins/textEditor/v2"
|
||||
},
|
||||
{
|
||||
"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 \"customInputs\": {{customInputs}}\r\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "ILc8GS7iU53M"]
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "dynamic",
|
||||
"canEdit": true,
|
||||
"key": "customInputs",
|
||||
"label": "customInputs",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"required": true,
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "ILc8GS7iU53M",
|
||||
"key": "文本",
|
||||
"valueType": "string",
|
||||
"label": "文本",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "2LCxDnOSculb",
|
||||
"key": "system_addInputParam",
|
||||
"valueType": "dynamic",
|
||||
"label": "动态外部数据",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1607.7142331269126,
|
||||
"y": -151.8669210746189
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"required": false,
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"description": true,
|
||||
"valueType": true
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"customFieldConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
"value": ["CRT7oIEU8v2P", "vooswj3VxKW8"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "HdIl1GWi0tnc",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "CRT7oIEU8v2P",
|
||||
"name": "HTTP 请求",
|
||||
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
|
||||
"avatar": "/imgs/workflow/http.png",
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1070.8458389994719,
|
||||
"y": -415.09022555407836
|
||||
},
|
||||
"inputs": [
|
||||
{
|
||||
"key": "system_addInputParam",
|
||||
"renderTypeList": ["addInputParam"],
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "2LCxDnOSculb"]
|
||||
},
|
||||
{
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text",
|
||||
"renderTypeList": ["reference"],
|
||||
"description": "",
|
||||
"canEdit": true,
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
"value": ["lmpb9v2lo2lk", "ILc8GS7iU53M"]
|
||||
},
|
||||
{
|
||||
"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,
|
||||
"value": "/api/plugins/textEditor/v2"
|
||||
},
|
||||
{
|
||||
"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": [
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
"type": "dynamic",
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"editField": {
|
||||
"key": true,
|
||||
"valueType": true
|
||||
{
|
||||
"id": "vooswj3VxKW8",
|
||||
"type": "dynamic",
|
||||
"key": "text",
|
||||
"valueType": "string",
|
||||
"label": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "vooswj3VxKW8",
|
||||
"type": "dynamic",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"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,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.8.5",
|
||||
"version": "4.8.6",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
### FastGPT V4.8.5
|
||||
### FastGPT V4.8.6
|
||||
|
||||
1. 新增 - 应用使用新权限系统。
|
||||
2. 新增 - 应用支持文件夹。
|
||||
3. 优化 - 文本分割增加连续换行、制表符清除,避免大文本性能问题。
|
||||
4. 重要修复 - 修复系统插件运行池数据污染问题,由于从内存获取,会导致全局污染。
|
||||
5. 修复 - Debug 模式下,相同 source 和 target 内容,导致连线显示异常。
|
||||
6. 修复 - 定时执行初始化错误。
|
||||
7. 修复 - 应用调用传参异常。
|
||||
8. 修复 - ctrl + cv 复杂节点时,nodeId错误。
|
||||
9. 调整组件库全局theme。
|
||||
1. 新增 - 知识库支持单个集合禁用功能
|
||||
2. 新增 - 文件夹权限继承
|
||||
3. 新增 - 网页抓取和数学计算器系统插件
|
||||
4. 新增 - 移动文本加工和自定义反馈到基础节点中
|
||||
5. 修复 - 工作流中团队插件加载异常
|
||||
|
||||
------
|
||||
|
||||
|
||||
1
projects/app/public/imgs/workflow/fetchUrl.svg
Normal file
1
projects/app/public/imgs/workflow/fetchUrl.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1719897603573" class="icon" viewBox="0 0 1025 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4511" width="200" height="200"><path d="M717.312 476.16c-7.68-10.24-14.848-20.48-22.528-30.208-36.352-48.64-72.192-97.792-108.544-146.432-10.24-13.824-14.336-28.16-9.728-45.056 9.216-31.744 50.176-41.984 73.728-17.92 15.872 16.384 29.184 34.816 43.52 52.736 40.96 50.688 81.92 101.376 122.368 152.064 1.024 1.024 1.024 2.56 2.048 4.608 4.096-4.608 1.536-6.656 0.512-8.704-20.992-43.008-41.984-86.016-62.976-128.512-7.68-15.872-8.704-31.232 0-46.592 17.408-29.696 61.44-28.16 79.872 3.072 31.232 54.272 61.952 109.056 93.184 163.84 20.48 35.84 39.936 71.68 60.928 107.008 49.152 81.408 36.352 200.192-53.248 267.264-49.152 36.864-103.936 56.832-165.376 54.272-45.056-2.048-89.088-12.288-133.12-23.04-33.28-8.192-65.536-18.432-96.256-33.792-5.12-2.56-10.24-5.632-14.848-8.704-18.944-12.8-24.576-25.6-19.968-45.568 4.608-20.48 19.968-35.328 39.936-38.4 8.704-1.536 16.384-0.512 24.576 2.56 17.92 6.144 35.84 12.288 54.272 18.944 5.632 2.048 11.264 2.048 16.384-1.536 9.216-5.632 10.752-15.36 3.584-23.552-5.632-6.656-12.288-12.288-19.456-17.408-53.76-40.448-104.96-84.48-157.696-126.464-22.528-17.92-44.544-35.84-66.56-54.272-19.456-15.872-24.064-43.52-10.24-62.976 13.824-19.456 40.96-24.576 62.464-10.24 36.352 24.064 72.704 48.64 109.056 72.704 23.04 15.36 45.568 30.72 68.608 46.08 1.536 1.024 3.584 2.048 5.12 3.584 2.048 1.024 4.096 3.072 6.144 1.024 2.56-2.56-0.512-4.096-2.048-5.632-10.24-10.752-20.992-22.016-31.232-32.768-54.784-56.32-109.056-112.64-163.84-168.96-17.92-18.944-18.432-46.08-1.024-64 17.92-17.92 45.056-18.432 64.512 0 36.864 34.816 73.728 69.12 111.104 103.936 31.232 29.696 62.976 58.88 94.208 88.576 1.536-0.512 2.048-1.024 2.56-1.536z" fill="#4D81B7" p-id="4512"></path><path d="M471.04 82.944h384c48.64 0 84.48 35.84 84.48 84.48 0 52.224-0.512 104.96 0 157.184 0 10.24-6.144 7.68-11.776 8.192-6.656 0.512-9.728-0.512-9.728-8.704 0.512-52.224 0-104.448 0-156.672 0-37.376-27.136-64.512-64.512-64.512h-767.488c-37.376 0-65.024 27.136-65.024 64.512v587.264c0 37.888 27.136 64.512 64.512 64.512h371.2c3.584 0 8.704-2.56 9.728 2.56 1.536 5.632 1.536 11.264-0.512 16.896 0 1.024-2.56 1.536-3.584 1.536h-374.784c-51.712 0-87.552-35.84-87.552-87.04V167.424c0-49.664 36.352-86.016 86.016-86.016 129.024 1.536 257.024 1.536 385.024 1.536z" fill="#808080" p-id="4513"></path><path d="M345.088 438.272v193.024c0 24.576-22.528 40.96-44.032 31.744-14.848-6.656-19.968-19.968-19.968-35.328v-154.624-219.648c0-8.192 1.024-16.896 5.12-24.064 7.168-12.8 20.992-18.432 35.84-14.848 13.824 3.584 23.552 16.384 23.552 31.744v173.056c-0.512 5.632-0.512 12.288-0.512 18.944zM141.824 506.368v-126.976c0-20.992 16.384-31.744 31.744-32.256 16.896-0.512 30.72 14.336 30.72 31.744v253.952c0 18.432-13.824 32.256-31.232 32.256-18.432 0-31.232-13.312-31.232-32.256v-126.464z" fill="#CFE5F6" p-id="4514"></path><path d="M471.04 947.2h-265.728c-3.072 0-8.192 2.048-8.704-2.56-1.024-5.632-2.048-11.776 0.512-16.896 1.024-2.56 5.12-1.024 8.192-1.024h355.84c58.368 0 116.224 0.512 174.592 0 9.728 0 9.216 4.096 9.728 11.264 0.512 8.192-2.048 10.24-10.24 10.24-88.064-1.024-176.128-1.024-264.192-1.024z" fill="#808080" p-id="4515"></path></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
1
projects/app/public/imgs/workflow/mathExprEval.svg
Normal file
1
projects/app/public/imgs/workflow/mathExprEval.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1719901713739" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4531" width="200" height="200"><path d="M322.953846 207.425641h525.128205c28.882051 0 52.512821 23.630769 52.512821 52.512821v105.025641c0 28.882051-23.630769 52.512821-52.512821 52.51282h-525.128205c-28.882051 0-52.512821-23.630769-52.51282-52.51282v-105.025641c0-28.882051 23.630769-52.512821 52.51282-52.512821z" fill="#20DAB4" p-id="4532"></path><path d="M827.076923 1024H196.923077c-73.517949 0-131.282051-57.764103-131.282051-131.282051V131.282051c0-73.517949 57.764103-131.282051 131.282051-131.282051h630.153846c73.517949 0 131.282051 57.764103 131.282051 131.282051v761.435898c0 73.517949-60.389744 131.282051-131.282051 131.282051zM196.923077 52.512821C152.287179 52.512821 118.153846 86.646154 118.153846 131.282051v761.435898c0 44.635897 34.133333 78.769231 78.769231 78.76923h630.153846c44.635897 0 78.769231-34.133333 78.769231-78.76923V131.282051c0-44.635897-34.133333-78.769231-78.769231-78.76923H196.923077z" fill="#106D5A" p-id="4533"></path><path d="M270.441026 414.851282c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.256411-26.25641-10.502564-26.256411-26.256411 10.502564-26.25641 26.256411-26.25641m0-52.51282c-44.635897 0-78.769231 34.133333-78.769231 78.76923s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.76923-78.769231-36.758974-78.769231-78.76923-78.76923zM506.748718 414.851282c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.256411-26.25641-10.502564-26.25641-26.256411 10.502564-26.25641 26.25641-26.25641m0-52.51282c-44.635897 0-78.769231 34.133333-78.769231 78.76923s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231-36.758974-78.769231-78.769231-78.76923zM743.05641 414.851282c15.753846 0 26.25641 10.502564 26.256411 26.25641s-10.502564 26.25641-26.256411 26.256411-26.25641-10.502564-26.25641-26.256411 10.502564-26.25641 26.25641-26.25641m0-52.51282c-44.635897 0-78.769231 34.133333-78.769231 78.76923s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231-36.758974-78.769231-78.769231-78.76923zM270.441026 598.646154c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.25641-26.25641-10.502564-26.256411-26.25641 10.502564-26.25641 26.256411-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.76923-78.769231-36.758974-78.769231-78.76923-78.769231zM506.748718 598.646154c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.25641-26.25641-10.502564-26.25641-26.25641 10.502564-26.25641 26.25641-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231-36.758974-78.769231-78.769231-78.769231zM743.05641 598.646154c15.753846 0 26.25641 10.502564 26.256411 26.25641s-10.502564 26.25641-26.256411 26.25641-26.25641-10.502564-26.25641-26.25641 10.502564-26.25641 26.25641-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231-36.758974-78.769231-78.769231-78.769231zM270.441026 782.441026c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.25641-26.25641-10.502564-26.256411-26.25641c0-13.128205 10.502564-26.25641 26.256411-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.76923-78.769231c0-42.010256-36.758974-78.769231-78.76923-78.769231zM506.748718 782.441026c15.753846 0 26.25641 10.502564 26.25641 26.25641s-10.502564 26.25641-26.25641 26.25641-26.25641-10.502564-26.25641-26.25641c0-13.128205 10.502564-26.25641 26.25641-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231c0-42.010256-36.758974-78.769231-78.769231-78.769231zM743.05641 782.441026c15.753846 0 26.25641 10.502564 26.256411 26.25641s-10.502564 26.25641-26.256411 26.25641-26.25641-10.502564-26.25641-26.25641c0-13.128205 10.502564-26.25641 26.25641-26.25641m0-52.512821c-44.635897 0-78.769231 34.133333-78.769231 78.769231s34.133333 78.769231 78.769231 78.769231 78.769231-34.133333 78.769231-78.769231c0-42.010256-36.758974-78.769231-78.769231-78.769231zM769.312821 181.169231v105.025641h-525.128206v-105.025641h525.128206m0-52.512821h-525.128206c-28.882051 0-52.512821 23.630769-52.51282 52.512821v105.025641c0 28.882051 23.630769 52.512821 52.51282 52.51282h525.128206c28.882051 0 52.512821-23.630769 52.51282-52.51282v-105.025641c0-28.882051-23.630769-52.512821-52.51282-52.512821z" fill="#106D5A" p-id="4534"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -8,7 +8,7 @@ const Avatar = ({ w = '30px', src, ...props }: ImageProps) => {
|
||||
<Image
|
||||
fallbackSrc={LOGO_ICON}
|
||||
fallbackStrategy={'onError'}
|
||||
borderRadius={'md'}
|
||||
// borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
alt=""
|
||||
w={w}
|
||||
|
||||
@@ -15,7 +15,7 @@ const ChatAvatar = ({ src, type }: { src?: string; type: 'Human' | 'AI' }) => {
|
||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||
bg={type === 'Human' ? 'white' : 'primary.50'}
|
||||
>
|
||||
<Avatar src={src} w={'100%'} h={'100%'} />
|
||||
<Avatar src={src} w={'100%'} h={'100%'} borderRadius={'sm'} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -158,7 +158,7 @@ ${JSON.stringify(questionGuides)}`;
|
||||
bg: 'auto'
|
||||
}}
|
||||
>
|
||||
<Avatar src={tool.toolAvatar} borderRadius={'md'} w={'1rem'} mr={2} />
|
||||
<Avatar src={tool.toolAvatar} w={'1rem'} mr={2} />
|
||||
<Box mr={1} fontSize={'sm'}>
|
||||
{tool.toolName}
|
||||
</Box>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { useCallback } from 'react';
|
||||
import { htmlTemplate } from '@/web/core/chat/constants';
|
||||
import { fileDownload } from '@/web/common/file/utils';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
|
||||
export const useChatBox = () => {
|
||||
const onExportChat = useCallback(
|
||||
@@ -40,8 +41,30 @@ export const useChatBox = () => {
|
||||
|
||||
const map: Record<ExportChatType, () => void> = {
|
||||
md: () => {
|
||||
console.log(history);
|
||||
fileDownload({
|
||||
text: history.map((item) => item.value).join('\n\n'),
|
||||
text: history
|
||||
.map((item) => {
|
||||
let result = `Role: ${item.obj}\n`;
|
||||
const content = item.value.map((item) => {
|
||||
if (item.type === ChatItemValueTypeEnum.text) {
|
||||
return item.text?.content;
|
||||
} else if (item.type === ChatItemValueTypeEnum.file) {
|
||||
return `
|
||||

|
||||
`;
|
||||
} else if (item.type === ChatItemValueTypeEnum.tool) {
|
||||
return `
|
||||
\`\`\`Toll
|
||||
${JSON.stringify(item.tools, null, 2)}
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
});
|
||||
|
||||
return result + content;
|
||||
})
|
||||
.join('\n\n-------\n\n'),
|
||||
type: 'text/markdown',
|
||||
filename: 'chat.md'
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ const CommunityModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
return (
|
||||
<MyModal isOpen={true} onClose={onClose} iconSrc="modal/concat" title={t('home.Community')}>
|
||||
<MyModal isOpen={true} onClose={onClose} iconSrc="modal/concat" title={t('system.Concat us')}>
|
||||
<ModalBody textAlign={'center'}>
|
||||
<Markdown source={feConfigs?.concatMd || ''} />
|
||||
</ModalBody>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Skeleton } from '@chakra-ui/react';
|
||||
import MyPhotoView from '@fastgpt/web/components/common/Image/PhotoView';
|
||||
import { useBoolean } from 'ahooks';
|
||||
@@ -6,11 +6,13 @@ import { useBoolean } from 'ahooks';
|
||||
const MdImage = ({ src }: { src?: string }) => {
|
||||
const [isLoaded, { setTrue }] = useBoolean(false);
|
||||
|
||||
const [renderSrc, setRenderSrc] = useState(src);
|
||||
|
||||
return (
|
||||
<Skeleton isLoaded={isLoaded}>
|
||||
<MyPhotoView
|
||||
borderRadius={'md'}
|
||||
src={src}
|
||||
src={renderSrc}
|
||||
alt={''}
|
||||
fallbackSrc={'/imgs/errImg.png'}
|
||||
fallbackStrategy={'onError'}
|
||||
@@ -21,10 +23,12 @@ const MdImage = ({ src }: { src?: string }) => {
|
||||
minH={'120px'}
|
||||
maxH={'500px'}
|
||||
my={1}
|
||||
mx={'auto'}
|
||||
onLoad={() => {
|
||||
setTrue();
|
||||
}}
|
||||
onError={() => {
|
||||
setRenderSrc('/imgs/errImg.png');
|
||||
setTrue();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
}
|
||||
.markdown hr {
|
||||
margin: 10px 0;
|
||||
border-color: var(--chakra-colors-gray-300);
|
||||
}
|
||||
.markdown {
|
||||
tab-size: 4;
|
||||
|
||||
@@ -39,7 +39,7 @@ const FolderPath = (props: {
|
||||
{concatPaths.map((item, i) => (
|
||||
<Flex key={item.parentId || i} alignItems={'center'}>
|
||||
<Box
|
||||
fontSize={['sm', fontSize || 'sm']}
|
||||
fontSize={['xs', fontSize || 'sm']}
|
||||
py={0.5}
|
||||
px={1.5}
|
||||
borderRadius={'md'}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FolderIcon } from '@fastgpt/global/common/file/image/constants';
|
||||
@@ -13,6 +14,8 @@ import CollaboratorContextProvider, {
|
||||
} from '../../support/permission/MemberManager/context';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
|
||||
|
||||
const FolderSlideCard = ({
|
||||
refreshDeps,
|
||||
@@ -24,7 +27,11 @@ const FolderSlideCard = ({
|
||||
onDelete,
|
||||
|
||||
defaultPer,
|
||||
managePer
|
||||
managePer,
|
||||
isInheritPermission,
|
||||
resumeInheritPermission,
|
||||
hasParent,
|
||||
refetchResource
|
||||
}: {
|
||||
refreshDeps?: any[];
|
||||
name: string;
|
||||
@@ -40,9 +47,16 @@ const FolderSlideCard = ({
|
||||
onChange: (v: PermissionValueType) => Promise<any>;
|
||||
};
|
||||
managePer: MemberManagerInputPropsType;
|
||||
|
||||
isInheritPermission?: boolean;
|
||||
resumeInheritPermission?: () => Promise<void>;
|
||||
hasParent?: boolean;
|
||||
refetchResource?: () => Promise<any>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { commonT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
type: 'delete',
|
||||
@@ -118,6 +132,12 @@ const FolderSlideCard = ({
|
||||
<Box>
|
||||
<FormLabel>{t('support.permission.Permission')}</FormLabel>
|
||||
|
||||
{!isInheritPermission && (
|
||||
<Box mt={2}>
|
||||
<ResumeInherit onResume={() => resumeInheritPermission?.().then(refetchResource)} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{managePer.permission.hasManagePer && (
|
||||
<Box mt={5}>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
@@ -127,12 +147,20 @@ const FolderSlideCard = ({
|
||||
mt="1"
|
||||
per={defaultPer.value}
|
||||
defaultPer={defaultPer.defaultValue}
|
||||
onChange={defaultPer.onChange}
|
||||
isInheritPermission={isInheritPermission}
|
||||
onChange={(v) => defaultPer.onChange(v)}
|
||||
hasParent={hasParent}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={6}>
|
||||
<CollaboratorContextProvider {...managePer} refreshDeps={refreshDeps}>
|
||||
<CollaboratorContextProvider
|
||||
{...managePer}
|
||||
refreshDeps={refreshDeps}
|
||||
refetchResource={refetchResource}
|
||||
isInheritPermission={isInheritPermission}
|
||||
hasParent={hasParent}
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, DragEvent, useCallback } from 'react';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { useBoolean } from 'ahooks';
|
||||
|
||||
export const useFolderDrag = ({
|
||||
onDrop,
|
||||
@@ -10,11 +11,13 @@ export const useFolderDrag = ({
|
||||
}) => {
|
||||
const [dragId, setDragId] = useState<string>();
|
||||
const [targetId, setTargetId] = useState<string>();
|
||||
const [isDropping, { setTrue, setFalse }] = useBoolean();
|
||||
|
||||
const getBoxProps = useCallback(
|
||||
({ dataId, isFolder }: { dataId: string; isFolder: boolean }) => {
|
||||
return {
|
||||
draggable: true,
|
||||
userSelect: 'none' as any,
|
||||
'data-drag-id': isFolder ? dataId : undefined,
|
||||
onDragStart: (e: DragEvent<HTMLDivElement>) => {
|
||||
setDragId(dataId);
|
||||
@@ -29,15 +32,19 @@ export const useFolderDrag = ({
|
||||
e.preventDefault();
|
||||
setTargetId(undefined);
|
||||
},
|
||||
onDrop: (e: DragEvent<HTMLDivElement>) => {
|
||||
onDrop: async (e: DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
setTrue();
|
||||
|
||||
if (targetId && dragId && targetId !== dragId) {
|
||||
onDrop(dragId, targetId);
|
||||
}
|
||||
try {
|
||||
if (targetId && dragId && targetId !== dragId) {
|
||||
await onDrop(dragId, targetId);
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
setTargetId(undefined);
|
||||
setDragId(undefined);
|
||||
setFalse();
|
||||
},
|
||||
...(activeStyles &&
|
||||
targetId === dataId && {
|
||||
@@ -45,10 +52,11 @@ export const useFolderDrag = ({
|
||||
})
|
||||
};
|
||||
},
|
||||
[activeStyles, dragId, onDrop, targetId]
|
||||
[activeStyles, dragId, onDrop, setFalse, setTrue, targetId]
|
||||
);
|
||||
|
||||
return {
|
||||
getBoxProps
|
||||
getBoxProps,
|
||||
isDropping
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,9 @@ import { Box, Button, Flex, HStack, ModalBody } from '@chakra-ui/react';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import DefaultPermissionList from '../DefaultPerList';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import ResumeInherit from '../ResumeInheritText';
|
||||
|
||||
export type ConfigPerModalProps = {
|
||||
avatar?: string;
|
||||
@@ -18,6 +21,10 @@ export type ConfigPerModalProps = {
|
||||
onChange: (v: PermissionValueType) => Promise<any>;
|
||||
};
|
||||
managePer: MemberManagerInputPropsType;
|
||||
isInheritPermission?: boolean;
|
||||
resumeInheritPermission?: () => void;
|
||||
hasParent?: boolean;
|
||||
refetchResource?: () => void;
|
||||
};
|
||||
|
||||
const ConfigPerModal = ({
|
||||
@@ -25,71 +32,90 @@ const ConfigPerModal = ({
|
||||
name,
|
||||
defaultPer,
|
||||
managePer,
|
||||
onClose
|
||||
isInheritPermission,
|
||||
resumeInheritPermission,
|
||||
hasParent,
|
||||
onClose,
|
||||
refetchResource
|
||||
}: ConfigPerModalProps & {
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/modal/key.svg"
|
||||
onClose={onClose}
|
||||
title={t('permission.Permission config')}
|
||||
>
|
||||
<ModalBody>
|
||||
<HStack>
|
||||
<Avatar src={avatar} w={'1.75rem'} />
|
||||
<Box>{name}</Box>
|
||||
</HStack>
|
||||
<Box mt={6}>
|
||||
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="1"
|
||||
per={defaultPer.value}
|
||||
defaultPer={defaultPer.defaultValue}
|
||||
onChange={defaultPer.onChange}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<CollaboratorContextProvider {...managePer}>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<Box fontSize={'sm'}>{t('permission.Collaborator')}</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common.Add')}
|
||||
</Button>
|
||||
<>
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="/imgs/modal/key.svg"
|
||||
onClose={onClose}
|
||||
title={t('permission.Permission config')}
|
||||
>
|
||||
<ModalBody>
|
||||
<HStack>
|
||||
<Avatar src={avatar} w={'1.75rem'} />
|
||||
<Box>{name}</Box>
|
||||
</HStack>
|
||||
{!isInheritPermission && (
|
||||
<Box mt={3}>
|
||||
<ResumeInherit onResume={resumeInheritPermission} />
|
||||
</Box>
|
||||
)}
|
||||
<Box mt={5}>
|
||||
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="1"
|
||||
per={defaultPer.value}
|
||||
defaultPer={defaultPer.defaultValue}
|
||||
isInheritPermission={isInheritPermission}
|
||||
onChange={(v) => defaultPer.onChange(v)}
|
||||
hasParent={hasParent}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={4}>
|
||||
<CollaboratorContextProvider
|
||||
{...managePer}
|
||||
refetchResource={refetchResource}
|
||||
isInheritPermission={isInheritPermission}
|
||||
hasParent={hasParent}
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<Box fontSize={'sm'}>{t('permission.Collaborator')}</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="common/settingLight" />}
|
||||
onClick={onOpenManageModal}
|
||||
>
|
||||
{t('permission.Manage')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="whitePrimary"
|
||||
leftIcon={<MyIcon w="4" name="support/permission/collaborator" />}
|
||||
onClick={onOpenAddMember}
|
||||
>
|
||||
{t('common.Add')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</CollaboratorContextProvider>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
<MemberListCard mt={2} p={1.5} bg="myGray.100" borderRadius="md" />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</CollaboratorContextProvider>
|
||||
</Box>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import React from 'react';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
export enum defaultPermissionEnum {
|
||||
private = 'private',
|
||||
@@ -18,6 +19,8 @@ type Props = Omit<BoxProps, 'onChange'> & {
|
||||
readPer?: PermissionValueType;
|
||||
writePer?: PermissionValueType;
|
||||
onChange: (v: PermissionValueType) => Promise<any> | any;
|
||||
isInheritPermission?: boolean;
|
||||
hasParent?: boolean;
|
||||
};
|
||||
|
||||
const DefaultPermissionList = ({
|
||||
@@ -26,28 +29,45 @@ const DefaultPermissionList = ({
|
||||
readPer = ReadPermissionVal,
|
||||
writePer = WritePermissionVal,
|
||||
onChange,
|
||||
isInheritPermission = false,
|
||||
hasParent,
|
||||
...styles
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
const { commonT } = useI18n();
|
||||
|
||||
const defaultPermissionSelectList = [
|
||||
{ label: '仅协作者访问', value: defaultPer },
|
||||
{ label: '团队可访问', value: readPer },
|
||||
{ label: '团队可编辑', value: writePer }
|
||||
];
|
||||
|
||||
const { runAsync: onRequestChange, loading } = useRequest2(async (v: PermissionValueType) =>
|
||||
const { runAsync: onRequestChange, loading } = useRequest2((v: PermissionValueType) =>
|
||||
onChange(v)
|
||||
);
|
||||
|
||||
return (
|
||||
<Box {...styles}>
|
||||
<MySelect
|
||||
isLoading={loading}
|
||||
list={defaultPermissionSelectList}
|
||||
value={per}
|
||||
onchange={onRequestChange}
|
||||
/>
|
||||
</Box>
|
||||
<>
|
||||
<Box {...styles}>
|
||||
<MySelect
|
||||
isLoading={loading}
|
||||
list={defaultPermissionSelectList}
|
||||
value={per}
|
||||
onchange={(per) => {
|
||||
if (isInheritPermission && hasParent) {
|
||||
openConfirm(
|
||||
() => onRequestChange(per),
|
||||
undefined,
|
||||
commonT('permission.Remove InheritPermission Confirm')
|
||||
)();
|
||||
} else {
|
||||
return onRequestChange(per);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BoxProps, useDisclosure } from '@chakra-ui/react';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
import {
|
||||
CollaboratorItemType,
|
||||
UpdateClbPermissionProps
|
||||
@@ -13,6 +13,8 @@ import dynamic from 'next/dynamic';
|
||||
import MemberListCard, { MemberListCardProps } from './MemberListCard';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
const AddMemberModal = dynamic(() => import('./AddMemberModal'));
|
||||
const ManageModal = dynamic(() => import('./ManageModal'));
|
||||
|
||||
@@ -66,12 +68,28 @@ const CollaboratorContextProvider = ({
|
||||
permissionList,
|
||||
onUpdateCollaborators,
|
||||
onDelOneCollaborator,
|
||||
children,
|
||||
refetchResource,
|
||||
refreshDeps = [],
|
||||
children
|
||||
isInheritPermission,
|
||||
hasParent
|
||||
}: MemberManagerInputPropsType & {
|
||||
children: (props: ChildrenProps) => ReactNode;
|
||||
refetchResource?: () => void;
|
||||
isInheritPermission?: boolean;
|
||||
hasParent?: boolean;
|
||||
}) => {
|
||||
const onUpdateCollaboratorsThen = async (props: UpdateClbPermissionProps) => {
|
||||
await onUpdateCollaborators(props);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThen = async (tmbId: string) => {
|
||||
await onDelOneCollaborator(tmbId);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { commonT } = useI18n();
|
||||
|
||||
const {
|
||||
data: collaboratorList = [],
|
||||
@@ -86,19 +104,10 @@ const CollaboratorContextProvider = ({
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps
|
||||
refreshDeps: refreshDeps
|
||||
}
|
||||
);
|
||||
|
||||
const onUpdateCollaboratorsThen = async (props: UpdateClbPermissionProps) => {
|
||||
await onUpdateCollaborators(props);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
const onDelOneCollaboratorThen = async (tmbId: string) => {
|
||||
await onDelOneCollaborator(tmbId);
|
||||
refetchCollaboratorList();
|
||||
};
|
||||
|
||||
const getPerLabelList = useCallback(
|
||||
(per: PermissionValueType) => {
|
||||
const Per = new Permission({ per });
|
||||
@@ -125,6 +134,7 @@ const CollaboratorContextProvider = ({
|
||||
[permissionList]
|
||||
);
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
const {
|
||||
isOpen: isOpenAddMember,
|
||||
onOpen: onOpenAddMember,
|
||||
@@ -147,11 +157,57 @@ const CollaboratorContextProvider = ({
|
||||
onDelOneCollaborator: onDelOneCollaboratorThen,
|
||||
getPerLabelList
|
||||
};
|
||||
|
||||
const onOpenAddMemberModal = () => {
|
||||
if (isInheritPermission && hasParent) {
|
||||
openConfirm(
|
||||
() => {
|
||||
onOpenAddMember();
|
||||
},
|
||||
undefined,
|
||||
commonT('permission.Remove InheritPermission Confirm')
|
||||
)();
|
||||
} else {
|
||||
onOpenAddMember();
|
||||
}
|
||||
};
|
||||
const onOpenManageModalModal = () => {
|
||||
if (isInheritPermission && hasParent) {
|
||||
openConfirm(
|
||||
() => {
|
||||
onOpenManageModal();
|
||||
},
|
||||
undefined,
|
||||
commonT('permission.Remove InheritPermission Confirm')
|
||||
)();
|
||||
} else {
|
||||
onOpenManageModal();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<CollaboratorContext.Provider value={contextValue}>
|
||||
{children({ onOpenAddMember, onOpenManageModal, MemberListCard })}
|
||||
{isOpenAddMember && <AddMemberModal onClose={onCloseAddMember} />}
|
||||
{isOpenManageModal && <ManageModal onClose={onCloseManageModal} />}
|
||||
{children({
|
||||
onOpenAddMember: onOpenAddMemberModal,
|
||||
onOpenManageModal: onOpenManageModalModal,
|
||||
MemberListCard
|
||||
})}
|
||||
{isOpenAddMember && (
|
||||
<AddMemberModal
|
||||
onClose={() => {
|
||||
onCloseAddMember();
|
||||
refetchResource?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isOpenManageModal && (
|
||||
<ManageModal
|
||||
onClose={() => {
|
||||
onCloseManageModal();
|
||||
refetchResource?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
</CollaboratorContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import React from 'react';
|
||||
|
||||
const ResumeInherit = ({
|
||||
onResume,
|
||||
...props
|
||||
}: BoxProps & {
|
||||
onResume?: () => Promise<any> | any;
|
||||
}) => {
|
||||
const { commonT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const { ConfirmModal: CommonConfirmModal, openConfirm: openCommonConfirm } = useConfirm({});
|
||||
|
||||
return onResume ? (
|
||||
<Box display={'inline'} fontSize={'sm'} {...props}>
|
||||
{commonT('permission.No InheritPermission')}
|
||||
<Box
|
||||
display={'inline'}
|
||||
textDecoration={'underline'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.600' }}
|
||||
onClick={() => {
|
||||
openCommonConfirm(
|
||||
() =>
|
||||
onResume()?.then(() => {
|
||||
toast({
|
||||
title: commonT('permission.Resume InheritPermission Success'),
|
||||
status: 'success'
|
||||
});
|
||||
}),
|
||||
undefined,
|
||||
commonT('permission.Resume InheritPermission Confirm')
|
||||
)();
|
||||
}}
|
||||
>
|
||||
{commonT('Click to resume')}
|
||||
</Box>
|
||||
|
||||
<CommonConfirmModal />
|
||||
</Box>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default ResumeInherit;
|
||||
10
projects/app/src/global/core/api/datasetReq.d.ts
vendored
10
projects/app/src/global/core/api/datasetReq.d.ts
vendored
@@ -22,14 +22,4 @@ export type GetDatasetCollectionsProps = RequestPaging & {
|
||||
selectFolder?: boolean;
|
||||
};
|
||||
|
||||
export type UpdateDatasetCollectionParams = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
/* ==== data ===== */
|
||||
export type GetDatasetDataListProps = RequestPaging & {
|
||||
searchText?: string;
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
1
projects/app/src/global/core/chat/api.d.ts
vendored
1
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -1,5 +1,4 @@
|
||||
import type { AppChatConfigType, AppTTSConfigType } from '@fastgpt/global/core/app/type.d';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { AdminFbkType, ChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat.d';
|
||||
|
||||
|
||||
@@ -37,15 +37,6 @@ export type InsertOneDatasetDataProps = PushDatasetDataChunkProps & {
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
export type UpdateDatasetDataProps = {
|
||||
id: string;
|
||||
q?: string; // embedding content
|
||||
a?: string; // bonus content
|
||||
indexes: (Omit<DatasetDataIndexItemType, 'dataId'> & {
|
||||
dataId?: string; // pg data id
|
||||
})[];
|
||||
};
|
||||
|
||||
export type GetTrainingQueueProps = {
|
||||
vectorModel: string;
|
||||
agentModel: string;
|
||||
|
||||
19
projects/app/src/global/core/dataset/type.d.ts
vendored
19
projects/app/src/global/core/dataset/type.d.ts
vendored
@@ -10,16 +10,21 @@ import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/co
|
||||
/* ================= collection ===================== */
|
||||
export type DatasetCollectionsListItemType = {
|
||||
_id: string;
|
||||
parentId?: string;
|
||||
tmbId: string;
|
||||
name: string;
|
||||
parentId?: DatasetCollectionSchemaType['parentId'];
|
||||
tmbId: DatasetCollectionSchemaType['tmbId'];
|
||||
name: DatasetCollectionSchemaType['name'];
|
||||
type: DatasetCollectionSchemaType['type'];
|
||||
updateTime: Date;
|
||||
dataAmount: number;
|
||||
trainingAmount: number;
|
||||
createTime: DatasetCollectionSchemaType['createTime'];
|
||||
updateTime: DatasetCollectionSchemaType['updateTime'];
|
||||
forbid?: DatasetCollectionSchemaType['forbid'];
|
||||
trainingType?: DatasetCollectionSchemaType['trainingType'];
|
||||
|
||||
fileId?: string;
|
||||
rawLink?: string;
|
||||
permission: DatasetPermission;
|
||||
|
||||
dataAmount: number;
|
||||
trainingAmount: number;
|
||||
};
|
||||
|
||||
/* ================= data ===================== */
|
||||
@@ -30,5 +35,5 @@ export type DatasetDataListItemType = {
|
||||
q: string; // embedding content
|
||||
a: string; // bonus content
|
||||
chunkIndex?: number;
|
||||
indexes: DatasetDataSchemaType['indexes'];
|
||||
// indexes: DatasetDataSchemaType['indexes'];
|
||||
};
|
||||
|
||||
@@ -37,17 +37,21 @@ import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
const BillTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [billType, setBillType] = useState<`${BillTypeEnum}` | ''>('');
|
||||
const [billType, setBillType] = useState<BillTypeEnum | ''>('');
|
||||
const [billDetail, setBillDetail] = useState<BillSchemaType>();
|
||||
|
||||
const billTypeList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(billTypeMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
],
|
||||
() =>
|
||||
[
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(billTypeMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
] as {
|
||||
label: string;
|
||||
value: BillTypeEnum | '';
|
||||
}[],
|
||||
[t]
|
||||
);
|
||||
|
||||
@@ -106,7 +110,7 @@ const BillTable = () => {
|
||||
<Tr>
|
||||
<Th>#</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
<MySelect<BillTypeEnum | ''>
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
size={'sm'}
|
||||
|
||||
@@ -661,7 +661,7 @@ const Other = () => {
|
||||
h={'48px'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
联系我们
|
||||
{t('system.Concat us')}
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
@@ -40,19 +40,23 @@ const UsageTable = () => {
|
||||
from: addDays(new Date(), -7),
|
||||
to: new Date()
|
||||
});
|
||||
const [usageSource, setUsageSource] = useState<`${UsageSourceEnum}` | ''>('');
|
||||
const [usageSource, setUsageSource] = useState<UsageSourceEnum | ''>('');
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const [usageDetail, setUsageDetail] = useState<UsageItemType>();
|
||||
|
||||
const sourceList = useMemo(
|
||||
() => [
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
],
|
||||
() =>
|
||||
[
|
||||
{ label: t('common.All'), value: '' },
|
||||
...Object.entries(UsageSourceMap).map(([key, value]) => ({
|
||||
label: t(value.label),
|
||||
value: key
|
||||
}))
|
||||
] as {
|
||||
label: never;
|
||||
value: UsageSourceEnum | '';
|
||||
}[],
|
||||
[t]
|
||||
);
|
||||
|
||||
@@ -144,7 +148,7 @@ const UsageTable = () => {
|
||||
{/* <Th>{t('user.team.Member Name')}</Th> */}
|
||||
<Th>{t('user.Time')}</Th>
|
||||
<Th>
|
||||
<MySelect
|
||||
<MySelect<UsageSourceEnum | ''>
|
||||
list={sourceList}
|
||||
value={usageSource}
|
||||
size={'sm'}
|
||||
|
||||
34
projects/app/src/pages/api/admin/initv486.ts
Normal file
34
projects/app/src/pages/api/admin/initv486.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { PgClient } from '@fastgpt/service/common/vectorStore/pg';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
|
||||
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
await MongoApp.updateMany(
|
||||
{},
|
||||
{
|
||||
$set: {
|
||||
inheritPermission: true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
jsonRes(res, {
|
||||
message: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { exit } from 'process';
|
||||
import { FastGPTProUrl } from '@fastgpt/service/common/system/constants';
|
||||
import { initFastGPTConfig } from '@fastgpt/service/common/system/tools';
|
||||
import json5 from 'json5';
|
||||
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await getInitConfig();
|
||||
@@ -69,10 +70,6 @@ export async function getInitConfig() {
|
||||
// abandon
|
||||
getSystemPluginV1()
|
||||
]);
|
||||
|
||||
console.log({
|
||||
communityPlugins: global.communityPlugins
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Load init config error', error);
|
||||
global.systemInitd = false;
|
||||
@@ -155,16 +152,17 @@ function getSystemPlugin() {
|
||||
const filterFiles = files.filter((item) => item.endsWith('.json'));
|
||||
|
||||
// read json file
|
||||
const fileTemplates: (PluginTemplateType & { weight: number })[] = filterFiles.map((filename) => {
|
||||
const fileTemplates = filterFiles.map<SystemPluginTemplateItemType>((filename) => {
|
||||
const content = readFileSync(`${basePath}/${filename}`, 'utf-8');
|
||||
return {
|
||||
...json5.parse(content),
|
||||
id: `${PluginSourceEnum.community}-${filename.replace('.json', '')}`,
|
||||
source: PluginSourceEnum.community
|
||||
originCost: 0,
|
||||
currentCost: 0,
|
||||
id: `${PluginSourceEnum.community}-${filename.replace('.json', '')}`
|
||||
};
|
||||
});
|
||||
|
||||
fileTemplates.sort((a, b) => b.weight - a.weight);
|
||||
fileTemplates.sort((a, b) => (b.weight || 0) - (a.weight || 0));
|
||||
|
||||
global.communityPlugins = fileTemplates;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { checkTeamAppLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
@@ -14,6 +12,8 @@ import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { ClientSession } from '@fastgpt/service/common/mongo';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
|
||||
export type CreateAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -24,15 +24,20 @@ export type CreateAppBody = {
|
||||
edges?: AppSchema['edges'];
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<CreateAppBody>, res: NextApiResponse<any>) {
|
||||
async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
const { parentId, name, avatar, type, modules, edges } = req.body;
|
||||
|
||||
if (!name || !type || !Array.isArray(modules)) {
|
||||
throw new Error('缺少参数');
|
||||
return Promise.reject(CommonErrEnum.inheritPermissionError);
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
if (parentId) {
|
||||
// if it is not a root app
|
||||
// check the parent folder permission
|
||||
await authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true });
|
||||
}
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
@@ -49,9 +54,7 @@ async function handler(req: ApiRequestProps<CreateAppBody>, res: NextApiResponse
|
||||
tmbId
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: appId
|
||||
});
|
||||
return appId;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -102,7 +105,7 @@ export const onCreateApp = async ({
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (type !== AppTypeEnum.folder && type !== AppTypeEnum.httpPlugin) {
|
||||
if (!AppFolderTypeList.includes(type!)) {
|
||||
await MongoAppVersion.create(
|
||||
[
|
||||
{
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
throw new Error('参数错误');
|
||||
Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
// 凭证校验
|
||||
const { app } = await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import {
|
||||
PerResourceTypeEnum,
|
||||
WritePermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { FolderImgUrl } from '@fastgpt/global/common/file/image/constants';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { syncCollaborators } from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { getResourceAllClbs } from '@fastgpt/service/support/permission/controller';
|
||||
|
||||
export type CreateAppFolderBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -15,25 +23,59 @@ export type CreateAppFolderBody = {
|
||||
intro?: string;
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<CreateAppFolderBody>, res: NextApiResponse<any>) {
|
||||
async function handler(req: ApiRequestProps<CreateAppFolderBody>) {
|
||||
const { name, intro, parentId } = req.body;
|
||||
|
||||
if (!name) {
|
||||
throw new Error('缺少参数');
|
||||
Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
const parentApp = await (async () => {
|
||||
if (parentId) {
|
||||
// if it is not a root folder
|
||||
return (
|
||||
await authApp({
|
||||
req,
|
||||
appId: parentId,
|
||||
per: WritePermissionVal,
|
||||
authToken: true
|
||||
})
|
||||
).app; // check the parent folder permission
|
||||
}
|
||||
})();
|
||||
|
||||
// Create app
|
||||
await MongoApp.create({
|
||||
...parseParentIdInMongo(parentId),
|
||||
avatar: FolderImgUrl,
|
||||
name,
|
||||
intro,
|
||||
teamId,
|
||||
tmbId,
|
||||
type: AppTypeEnum.folder
|
||||
await mongoSessionRun(async (session) => {
|
||||
const app = await MongoApp.create({
|
||||
...parseParentIdInMongo(parentId),
|
||||
avatar: FolderImgUrl,
|
||||
name,
|
||||
intro,
|
||||
teamId,
|
||||
tmbId,
|
||||
type: AppTypeEnum.folder,
|
||||
// inheritPermission: !!parentApp ? true : false,
|
||||
defaultPermission: !!parentApp ? parentApp.defaultPermission : AppDefaultPermissionVal
|
||||
});
|
||||
|
||||
if (parentId) {
|
||||
const parentClbs = await getResourceAllClbs({
|
||||
teamId,
|
||||
resourceId: parentId,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
session
|
||||
});
|
||||
|
||||
await syncCollaborators({
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
teamId,
|
||||
resourceId: app._id,
|
||||
collaborators: parentClbs,
|
||||
session
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
|
||||
import {
|
||||
@@ -12,8 +10,10 @@ import { AppPermission } from '@fastgpt/global/support/permission/app/controller
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
|
||||
export type ListAppBody = {
|
||||
parentId?: ParentIdType;
|
||||
@@ -22,22 +22,34 @@ export type ListAppBody = {
|
||||
searchKey?: string;
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<ListAppBody>,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<AppListItemType[]> {
|
||||
async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemType[]> {
|
||||
const { parentId, type, getRecentlyChat, searchKey } = req.body;
|
||||
|
||||
// 凭证校验
|
||||
const {
|
||||
teamId,
|
||||
app: ParentApp,
|
||||
tmbId,
|
||||
teamId,
|
||||
permission: tmbPer
|
||||
} = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const { parentId, type, getRecentlyChat, searchKey } = req.body;
|
||||
} = await (async () => {
|
||||
if (parentId) {
|
||||
return await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId: parentId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
...(await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
per: ReadPermissionVal
|
||||
})),
|
||||
app: undefined
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
const findAppsQuery = (() => {
|
||||
const searchMatch = searchKey
|
||||
@@ -71,7 +83,7 @@ async function handler(
|
||||
const [myApps, rpList] = await Promise.all([
|
||||
MongoApp.find(
|
||||
findAppsQuery,
|
||||
'_id avatar type name intro tmbId updateTime pluginData defaultPermission'
|
||||
'_id parentId avatar type name intro tmbId updateTime pluginData defaultPermission inheritPermission'
|
||||
)
|
||||
.sort({
|
||||
updateTime: -1
|
||||
@@ -87,11 +99,29 @@ async function handler(
|
||||
|
||||
const filterApps = myApps
|
||||
.map((app) => {
|
||||
const perVal = rpList.find((item) => String(item.resourceId) === String(app._id))?.permission;
|
||||
const Per = new AppPermission({
|
||||
per: perVal ?? app.defaultPermission,
|
||||
isOwner: String(app.tmbId) === tmbId || tmbPer.isOwner
|
||||
});
|
||||
const Per = (() => {
|
||||
// Inherit app
|
||||
if (app.inheritPermission && ParentApp && !AppFolderTypeList.includes(app.type)) {
|
||||
// get its parent's permission as its permission
|
||||
app.defaultPermission = ParentApp.defaultPermission;
|
||||
const perVal = rpList.find(
|
||||
(item) => String(item.resourceId) === String(ParentApp._id)
|
||||
)?.permission;
|
||||
|
||||
return new AppPermission({
|
||||
per: perVal ?? app.defaultPermission,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || tmbPer.isOwner
|
||||
});
|
||||
} else {
|
||||
const perVal = rpList.find(
|
||||
(item) => String(item.resourceId) === String(app._id)
|
||||
)?.permission;
|
||||
return new AppPermission({
|
||||
per: perVal ?? app.defaultPermission,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || tmbPer.isOwner
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
...app,
|
||||
@@ -112,7 +142,8 @@ async function handler(
|
||||
updateTime: app.updateTime,
|
||||
permission: app.permission,
|
||||
defaultPermission: app.defaultPermission || AppDefaultPermissionVal,
|
||||
pluginData: app.pluginData
|
||||
pluginData: app.pluginData,
|
||||
inheritPermission: app.inheritPermission ?? true
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
getPluginPreviewNode,
|
||||
splitCombinePluginId
|
||||
} from '@fastgpt/service/core/app/plugin/controller';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { getCommunityPluginsTemplateList } from '@fastgpt/plugins/register';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
await authCert({ req, authToken: true });
|
||||
async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>
|
||||
): Promise<NodeTemplateListItemType[]> {
|
||||
await authCert({ req, authToken: true });
|
||||
|
||||
const data: FlowNodeTemplateType[] =
|
||||
global.communityPlugins?.map((plugin) => ({
|
||||
id: plugin.id,
|
||||
pluginId: plugin.id,
|
||||
templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
|
||||
flowNodeType: FlowNodeTypeEnum.pluginModule,
|
||||
avatar: plugin.avatar,
|
||||
name: plugin.name,
|
||||
intro: plugin.intro,
|
||||
showStatus: true,
|
||||
isTool: plugin.isTool,
|
||||
version: defaultNodeVersion,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
})) || [];
|
||||
// const data: NodeTemplateListItemType[] =
|
||||
// global.communityPlugins?.map((plugin) => ({
|
||||
// id: plugin.id,
|
||||
// templateType: plugin.templateType ?? FlowNodeTemplateTypeEnum.other,
|
||||
// flowNodeType: FlowNodeTypeEnum.pluginModule,
|
||||
// avatar: plugin.avatar,
|
||||
// name: plugin.name,
|
||||
// intro: plugin.intro
|
||||
// })) || [];
|
||||
|
||||
jsonRes<FlowNodeTemplateType[]>(res, {
|
||||
data
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
return getCommunityPluginsTemplateList();
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
PerResourceTypeEnum
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { resumeInheritPermission } from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
export type ResumeInheritPermissionQuery = {
|
||||
appId: string;
|
||||
};
|
||||
export type ResumeInheritPermissionBody = {};
|
||||
// resume the app's inherit permission.
|
||||
async function handler(
|
||||
req: ApiRequestProps<ResumeInheritPermissionBody, ResumeInheritPermissionQuery>
|
||||
) {
|
||||
const { appId } = req.query;
|
||||
const { app } = await authApp({
|
||||
appId,
|
||||
req,
|
||||
authToken: true,
|
||||
per: ManagePermissionVal
|
||||
});
|
||||
|
||||
if (app.parentId) {
|
||||
await resumeInheritPermission({
|
||||
resource: app,
|
||||
folderTypeList: AppFolderTypeList,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
resourceModel: MongoApp
|
||||
});
|
||||
} else {
|
||||
await MongoApp.updateOne(
|
||||
{
|
||||
_id: appId
|
||||
},
|
||||
{
|
||||
inheritPermission: true
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
export default NextAPI(handler);
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import type { AppUpdateParams } from '@/global/core/app/api';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
@@ -6,12 +5,37 @@ import { beforeUpdateAppFormat } from '@fastgpt/service/core/app/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import {
|
||||
ManagePermissionVal,
|
||||
PerResourceTypeEnum,
|
||||
WritePermissionVal
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import {
|
||||
syncChildrenPermission,
|
||||
syncCollaborators
|
||||
} from '@fastgpt/service/support/permission/inheritPermission';
|
||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||
import { ClientSession } from 'mongoose';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { getResourceAllClbs } from '@fastgpt/service/support/permission/controller';
|
||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||
|
||||
/* 获取我的模型 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
/*
|
||||
修改默认权限
|
||||
1. 继承态目录:关闭继承态,修改权限,同步子目录默认权限
|
||||
2. 继承态资源:关闭继承态,修改权限, 复制父级协作者。
|
||||
3. 非继承目录:修改权限,同步子目录默认权限
|
||||
4. 非继承资源:修改权限
|
||||
|
||||
移动
|
||||
1. 继承态目录:改 parentId, 修改成父的默认权限,同步子目录默认权限和协作者
|
||||
2. 继承态资源:改 parentId
|
||||
3. 非继承:改 parentId
|
||||
*/
|
||||
|
||||
async function handler(req: ApiRequestProps<AppUpdateParams, { appId: string }>) {
|
||||
const {
|
||||
parentId,
|
||||
name,
|
||||
@@ -24,40 +48,152 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
teamTags,
|
||||
defaultPermission
|
||||
} = req.body as AppUpdateParams;
|
||||
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
throw new Error('appId is empty');
|
||||
Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
if (defaultPermission) {
|
||||
await authApp({ req, authToken: true, appId, per: ManagePermissionVal });
|
||||
} else {
|
||||
await authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
}
|
||||
const { app } = await (async () => {
|
||||
if (defaultPermission !== undefined) {
|
||||
// if defaultPermission or inheritPermission is set, then need manage permission
|
||||
return authApp({ req, authToken: true, appId, per: ManagePermissionVal });
|
||||
} else {
|
||||
return authApp({ req, authToken: true, appId, per: WritePermissionVal });
|
||||
}
|
||||
})();
|
||||
|
||||
// format nodes data
|
||||
// 1. dataset search limit, less than model quoteMaxToken
|
||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||
const isDefaultPermissionChanged =
|
||||
defaultPermission !== undefined && defaultPermission !== app.defaultPermission;
|
||||
const isFolder = AppFolderTypeList.includes(app.type);
|
||||
|
||||
// 更新模型
|
||||
await MongoApp.findByIdAndUpdate(appId, {
|
||||
...parseParentIdInMongo(parentId),
|
||||
...(name && { name }),
|
||||
...(type && { type }),
|
||||
...(avatar && { avatar }),
|
||||
...(intro !== undefined && { intro }),
|
||||
...(defaultPermission !== undefined && { defaultPermission }),
|
||||
...(teamTags && { teamTags }),
|
||||
...(formatNodes && {
|
||||
modules: formatNodes
|
||||
}),
|
||||
...(edges && {
|
||||
edges
|
||||
}),
|
||||
...(chatConfig && { chatConfig })
|
||||
});
|
||||
const onUpdate = async (
|
||||
session?: ClientSession,
|
||||
updatedDefaultPermission?: PermissionValueType
|
||||
) => {
|
||||
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });
|
||||
|
||||
return MongoApp.findByIdAndUpdate(
|
||||
appId,
|
||||
{
|
||||
...parseParentIdInMongo(parentId),
|
||||
...(name && { name }),
|
||||
...(type && { type }),
|
||||
...(avatar && { avatar }),
|
||||
...(intro !== undefined && { intro }),
|
||||
// update default permission(Maybe move update)
|
||||
...(updatedDefaultPermission !== undefined && {
|
||||
defaultPermission: updatedDefaultPermission
|
||||
}),
|
||||
// Not root, update default permission
|
||||
...(isDefaultPermissionChanged && { inheritPermission: false }),
|
||||
...(teamTags && { teamTags }),
|
||||
...(formatNodes && {
|
||||
modules: formatNodes
|
||||
}),
|
||||
...(edges && {
|
||||
edges
|
||||
}),
|
||||
...(chatConfig && { chatConfig })
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
};
|
||||
|
||||
// Move
|
||||
if (parentId !== undefined) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
// Auth
|
||||
const parentDefaultPermission = await (async () => {
|
||||
if (parentId) {
|
||||
const { app: parentApp } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId: parentId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
return parentApp.defaultPermission;
|
||||
}
|
||||
|
||||
return AppDefaultPermissionVal;
|
||||
})();
|
||||
|
||||
// Inherit folder: Sync children permission and it's clbs
|
||||
if (isFolder && app.inheritPermission) {
|
||||
const parentClbs = await getResourceAllClbs({
|
||||
teamId: app.teamId,
|
||||
resourceId: parentId,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
session
|
||||
});
|
||||
// sync self
|
||||
await syncCollaborators({
|
||||
resourceId: app._id,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
collaborators: parentClbs,
|
||||
session,
|
||||
teamId: app.teamId
|
||||
});
|
||||
// sync the children
|
||||
await syncChildrenPermission({
|
||||
resource: app,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
resourceModel: MongoApp,
|
||||
folderTypeList: AppFolderTypeList,
|
||||
defaultPermission: parentDefaultPermission,
|
||||
collaborators: parentClbs,
|
||||
session
|
||||
});
|
||||
|
||||
return onUpdate(session, parentDefaultPermission);
|
||||
}
|
||||
|
||||
return onUpdate(session);
|
||||
});
|
||||
}
|
||||
|
||||
// Update default permission
|
||||
if (isDefaultPermissionChanged) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
if (isFolder) {
|
||||
// Sync children default permission
|
||||
await syncChildrenPermission({
|
||||
resource: {
|
||||
_id: app._id,
|
||||
type: app.type,
|
||||
teamId: app.teamId,
|
||||
parentId: app.parentId
|
||||
},
|
||||
folderTypeList: AppFolderTypeList,
|
||||
resourceModel: MongoApp,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
session,
|
||||
defaultPermission
|
||||
});
|
||||
} else if (app.inheritPermission && app.parentId) {
|
||||
// Inherit app
|
||||
const parentClbs = await getResourceAllClbs({
|
||||
teamId: app.teamId,
|
||||
resourceId: app.parentId,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
session
|
||||
});
|
||||
await syncCollaborators({
|
||||
resourceId: app._id,
|
||||
resourceType: PerResourceTypeEnum.app,
|
||||
collaborators: parentClbs,
|
||||
session,
|
||||
teamId: app.teamId
|
||||
});
|
||||
}
|
||||
|
||||
return onUpdate(session, defaultPermission);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
|
||||
export type getLatestVersionQuery = {
|
||||
appId: string;
|
||||
|
||||
@@ -10,8 +10,9 @@ import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/sc
|
||||
import { startTrainingQueue } from '@/service/core/dataset/training/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { PagingData } from '@/types';
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
async function handler(req: NextApiRequest): Promise<PagingData<DatasetCollectionsListItemType>> {
|
||||
let {
|
||||
pageNum = 1,
|
||||
pageSize = 10,
|
||||
@@ -45,9 +46,24 @@ async function handler(req: NextApiRequest) {
|
||||
: {})
|
||||
};
|
||||
|
||||
const selectField = {
|
||||
_id: 1,
|
||||
parentId: 1,
|
||||
tmbId: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
forbid: 1,
|
||||
createTime: 1,
|
||||
updateTime: 1,
|
||||
trainingType: 1,
|
||||
fileId: 1,
|
||||
rawLink: 1
|
||||
};
|
||||
|
||||
// not count data amount
|
||||
if (simple) {
|
||||
const collections = await MongoDatasetCollection.find(match, '_id parentId type name')
|
||||
const collections = await MongoDatasetCollection.find(match)
|
||||
.select(selectField)
|
||||
.sort({
|
||||
updateTime: -1
|
||||
})
|
||||
@@ -123,15 +139,7 @@ async function handler(req: NextApiRequest) {
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
parentId: 1,
|
||||
tmbId: 1,
|
||||
name: 1,
|
||||
type: 1,
|
||||
status: 1,
|
||||
updateTime: 1,
|
||||
fileId: 1,
|
||||
rawLink: 1,
|
||||
...selectField,
|
||||
dataAmount: {
|
||||
$ifNull: [{ $arrayElemAt: ['$dataCount.count', 0] }, 0]
|
||||
},
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import { getDatasetCollectionPaths } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
|
||||
export default async function handler(req: NextApiRequest) {
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { parentId } = req.query as { parentId: string };
|
||||
|
||||
if (!parentId) {
|
||||
@@ -16,9 +18,34 @@ export default async function handler(req: NextApiRequest) {
|
||||
collectionId: parentId,
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
const paths = await getDatasetCollectionPaths({
|
||||
parentId
|
||||
});
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
export async function getDatasetCollectionPaths({
|
||||
parentId = ''
|
||||
}: {
|
||||
parentId?: string;
|
||||
}): Promise<ParentTreePathItemType[]> {
|
||||
async function find(parentId?: string): Promise<ParentTreePathItemType[]> {
|
||||
if (!parentId) {
|
||||
return [];
|
||||
}
|
||||
const parent = await MongoDatasetCollection.findOne({ _id: parentId }, 'name parentId');
|
||||
|
||||
if (!parent) return [];
|
||||
|
||||
const paths = await find(parent.parentId);
|
||||
paths.push({ parentId, parentName: parent.name });
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
return find(parentId);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import type { UpdateDatasetCollectionParams } from '@/global/core/api/datasetReq.d';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { getCollectionUpdateTime } from '@fastgpt/service/core/dataset/collection/utils';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { id, parentId, name } = req.body as UpdateDatasetCollectionParams;
|
||||
export type UpdateDatasetCollectionParams = {
|
||||
id: string;
|
||||
parentId?: string;
|
||||
name?: string;
|
||||
forbid?: boolean;
|
||||
};
|
||||
|
||||
async function handler(req: ApiRequestProps<UpdateDatasetCollectionParams>) {
|
||||
const { id, parentId, name, forbid } = req.body;
|
||||
|
||||
if (!id) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
@@ -25,7 +31,8 @@ async function handler(req: NextApiRequest) {
|
||||
|
||||
const updateFields: Record<string, any> = {
|
||||
...(parentId !== undefined && { parentId: parentId || null }),
|
||||
...(name && { name, updateTime: getCollectionUpdateTime({ name }) })
|
||||
...(name && { name, updateTime: getCollectionUpdateTime({ name }) }),
|
||||
...(forbid !== undefined && { forbid })
|
||||
};
|
||||
|
||||
await MongoDatasetCollection.findByIdAndUpdate(id, {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import type { GetDatasetDataListProps } from '@/global/core/api/datasetReq';
|
||||
import { authDatasetCollection } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
import { replaceRegChars } from '@fastgpt/global/common/string/tools';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { PagingData, RequestPaging } from '@/types';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { DatasetDataListItemType } from '@/global/core/dataset/type';
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
let {
|
||||
pageNum = 1,
|
||||
pageSize = 10,
|
||||
searchText = '',
|
||||
collectionId
|
||||
} = req.body as GetDatasetDataListProps;
|
||||
export type GetDatasetDataListProps = RequestPaging & {
|
||||
searchText?: string;
|
||||
collectionId: string;
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<GetDatasetDataListProps>
|
||||
): Promise<PagingData<DatasetDataListItemType>> {
|
||||
let { pageNum = 1, pageSize = 10, searchText = '', collectionId } = req.body;
|
||||
|
||||
pageSize = Math.min(pageSize, 30);
|
||||
|
||||
@@ -25,15 +28,14 @@ async function handler(req: NextApiRequest) {
|
||||
per: ReadPermissionVal
|
||||
});
|
||||
|
||||
searchText = replaceRegChars(searchText).replace(/'/g, '');
|
||||
|
||||
const queryReg = new RegExp(`${replaceRegChars(searchText)}`, 'i');
|
||||
const match = {
|
||||
teamId,
|
||||
datasetId: collection.datasetId._id,
|
||||
collectionId,
|
||||
...(searchText
|
||||
...(searchText.trim()
|
||||
? {
|
||||
$or: [{ q: new RegExp(searchText, 'i') }, { a: new RegExp(searchText, 'i') }]
|
||||
$or: [{ q: queryReg }, { a: queryReg }]
|
||||
}
|
||||
: {})
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import { updateData2Dataset } from '@/service/core/dataset/data/controller';
|
||||
import { pushGenerateVectorUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UpdateDatasetDataProps } from '@/global/core/dataset/api';
|
||||
import { checkDatasetLimit } from '@fastgpt/service/support/permission/teamLimit';
|
||||
import { UpdateDatasetDataProps } from '@fastgpt/global/core/dataset/controller';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { authDatasetData } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema';
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { id, q = '', a, indexes = [] } = req.body as UpdateDatasetDataProps;
|
||||
async function handler(req: ApiRequestProps<UpdateDatasetDataProps>) {
|
||||
const { dataId, q, a, indexes = [] } = req.body;
|
||||
|
||||
// auth data permission
|
||||
const {
|
||||
@@ -21,30 +21,30 @@ async function handler(req: NextApiRequest) {
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
dataId: id,
|
||||
dataId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
// auth team balance
|
||||
await checkDatasetLimit({
|
||||
teamId,
|
||||
insertLen: 1
|
||||
});
|
||||
if (q || a || indexes.length > 0) {
|
||||
const { tokens } = await updateData2Dataset({
|
||||
dataId,
|
||||
q,
|
||||
a,
|
||||
indexes,
|
||||
model: vectorModel
|
||||
});
|
||||
|
||||
const { tokens } = await updateData2Dataset({
|
||||
dataId: id,
|
||||
q,
|
||||
a,
|
||||
indexes,
|
||||
model: vectorModel
|
||||
});
|
||||
|
||||
pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
tokens,
|
||||
model: vectorModel
|
||||
});
|
||||
pushGenerateVectorUsage({
|
||||
teamId,
|
||||
tmbId,
|
||||
tokens,
|
||||
model: vectorModel
|
||||
});
|
||||
} else {
|
||||
// await MongoDatasetData.findByIdAndUpdate(dataId, {
|
||||
// ...(forbid !== undefined && { forbid })
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
||||
@@ -27,7 +27,7 @@ async function handler(
|
||||
}
|
||||
|
||||
/* user auth */
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
const [{ teamId, tmbId }, { app }] = await Promise.all([
|
||||
authCert({
|
||||
req,
|
||||
authToken: true
|
||||
@@ -38,12 +38,6 @@ async function handler(
|
||||
// auth balance
|
||||
const { user } = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
||||
|
||||
const app = {
|
||||
...defaultApp,
|
||||
teamId,
|
||||
tmbId
|
||||
};
|
||||
|
||||
/* start process */
|
||||
const { flowUsages, flowResponses, debugResponse } = await dispatchWorkFlow({
|
||||
res,
|
||||
|
||||
@@ -3,17 +3,25 @@ import type { HttpBodyType } from '@fastgpt/global/core/workflow/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { addCustomFeedbacks } from '@fastgpt/service/core/chat/controller';
|
||||
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { SystemVariablesType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
customFeedback: string;
|
||||
}>;
|
||||
type Props = HttpBodyType<
|
||||
SystemVariablesType & {
|
||||
customFeedback: string;
|
||||
customInputs: Record<string, any>;
|
||||
}
|
||||
>;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const {
|
||||
customFeedback,
|
||||
[NodeInputKeyEnum.addInputParam]: { appId, chatId, responseChatItemId: chatItemId }
|
||||
appId,
|
||||
chatId,
|
||||
responseChatItemId: chatItemId,
|
||||
customInputs
|
||||
} = req.body as Props;
|
||||
|
||||
await authRequestFromLocal({ req });
|
||||
@@ -22,25 +30,27 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
return res.json({});
|
||||
}
|
||||
|
||||
const feedbackText = replaceVariable(customFeedback, customInputs);
|
||||
|
||||
// wait the chat finish
|
||||
setTimeout(() => {
|
||||
addCustomFeedbacks({
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
feedbacks: [customFeedback]
|
||||
feedbacks: [feedbackText]
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
if (!chatId || !chatItemId) {
|
||||
return res.json({
|
||||
[NodeOutputKeyEnum.answerText]: `\\n\\n**自动反馈调试**: "${customFeedback}"\\n\\n`,
|
||||
text: customFeedback
|
||||
[NodeOutputKeyEnum.answerText]: `\\n\\n**自动反馈调试**: "${feedbackText}"\\n\\n`,
|
||||
text: feedbackText
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
text: customFeedback
|
||||
text: feedbackText
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import type { HttpBodyType } from '@fastgpt/global/core/workflow/api.d';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { authRequestFromLocal } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
type Props = HttpBodyType<{
|
||||
text: string;
|
||||
[key: string]: any;
|
||||
customInputs: Record<string, any>;
|
||||
}>;
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
export default async function handler(req: ApiRequestProps<Props>, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { text, [NodeInputKeyEnum.addInputParam]: obj } = req.body as Props;
|
||||
const { text, customInputs: obj = {} } = req.body;
|
||||
|
||||
await authRequestFromLocal({ req });
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@@ -15,7 +15,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { compressImgFileAndUpload } from '@/web/common/file/controller';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -32,15 +32,19 @@ import {
|
||||
AppDefaultPermissionVal,
|
||||
AppPermissionList
|
||||
} from '@fastgpt/global/support/permission/app/constant';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import DefaultPermissionList from '@/components/support/permission/DefaultPerList';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { UpdateClbPermissionProps } from '@fastgpt/global/support/permission/collaborator';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { resumeInheritPer } from '@/web/core/app/api';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import ResumeInherit from '@/components/support/permission/ResumeInheritText';
|
||||
|
||||
const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { commonT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const { updateAppDetail, appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { updateAppDetail, appDetail, reloadApp } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: '.jpg,.png',
|
||||
@@ -52,12 +56,10 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
handleSubmit,
|
||||
watch
|
||||
handleSubmit
|
||||
} = useForm({
|
||||
defaultValues: appDetail
|
||||
});
|
||||
const defaultPermission = watch('defaultPermission');
|
||||
const avatar = getValues('avatar');
|
||||
|
||||
// submit config
|
||||
@@ -76,6 +78,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
title: t('common.Update Success'),
|
||||
status: 'success'
|
||||
});
|
||||
reloadApp();
|
||||
},
|
||||
errorToast: t('common.Update Failed')
|
||||
});
|
||||
@@ -138,6 +141,17 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const { runAsync: resumeInheritPermission } = useRequest2(
|
||||
() => resumeInheritPer(appDetail._id),
|
||||
// () => putAppById(appDetail._id, { inheritPermission: true }),
|
||||
{
|
||||
errorToast: '恢复失败',
|
||||
onSuccess: () => {
|
||||
reloadApp();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
@@ -182,13 +196,23 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
{/* role */}
|
||||
{appDetail.permission.hasManagePer && (
|
||||
<>
|
||||
{!appDetail.inheritPermission && appDetail.parentId && (
|
||||
<Box mt={3}>
|
||||
<ResumeInherit onResume={resumeInheritPermission} />
|
||||
</Box>
|
||||
)}
|
||||
<Box mt="4">
|
||||
<Box fontSize={'sm'}>{t('permission.Default permission')}</Box>
|
||||
<DefaultPermissionList
|
||||
mt="2"
|
||||
per={defaultPermission}
|
||||
per={appDetail.defaultPermission}
|
||||
defaultPer={AppDefaultPermissionVal}
|
||||
onChange={(v) => setValue('defaultPermission', v)}
|
||||
isInheritPermission={appDetail.inheritPermission}
|
||||
onChange={(v) => {
|
||||
setValue('defaultPermission', v);
|
||||
handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)();
|
||||
}}
|
||||
hasParent={!!appDetail.parentId}
|
||||
/>
|
||||
</Box>
|
||||
<Box mt={6}>
|
||||
@@ -198,6 +222,9 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
permissionList={AppPermissionList}
|
||||
onUpdateCollaborators={onUpdateCollaborators}
|
||||
onDelOneCollaborator={onDelCollaborator}
|
||||
refreshDeps={[appDetail.inheritPermission]}
|
||||
isInheritPermission={appDetail.inheritPermission}
|
||||
hasParent={!!appDetail.parentId}
|
||||
>
|
||||
{({ MemberListCard, onOpenManageModal, onOpenAddMember }) => {
|
||||
return (
|
||||
@@ -208,7 +235,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<Box fontSize={'sm'}>协作者</Box>
|
||||
<Box fontSize={'sm'}>{commonT('permission.Collaborator')}</Box>
|
||||
<Flex flexDirection="row" gap="2">
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -54,7 +54,6 @@ const Edit = ({
|
||||
flex={'1 0 0'}
|
||||
h={0}
|
||||
pt={[2, 1.5]}
|
||||
pl={[2, 1]}
|
||||
gap={1}
|
||||
borderRadius={'lg'}
|
||||
overflowY={['auto', 'unset']}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useTransition } from 'react';
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
Button,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { AddIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useFieldArray, UseFormReturn } from 'react-hook-form';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -468,7 +467,7 @@ const EditForm = ({
|
||||
onRemoveTool={(e) => {
|
||||
setAppForm((state) => ({
|
||||
...state,
|
||||
selectedTools: state.selectedTools.filter((item) => item.id !== e.id)
|
||||
selectedTools: state.selectedTools.filter((item) => item.pluginId !== e.id)
|
||||
}));
|
||||
}}
|
||||
onClose={onCloseToolsSelect}
|
||||
|
||||
@@ -89,7 +89,7 @@ const Header = ({
|
||||
<RouteTab />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex pl={2} pt={[2, 3]} alignItems={'flex-start'} position={'relative'}>
|
||||
<Flex pt={[2, 3]} alignItems={'flex-start'} position={'relative'}>
|
||||
<Box flex={'1'}>
|
||||
<FolderPath paths={paths} hoverStyle={{ color: 'primary.600' }} onClick={onclickRoute} />
|
||||
</Box>
|
||||
@@ -102,20 +102,23 @@ const Header = ({
|
||||
<Flex alignItems={'center'}>
|
||||
{!historiesDefaultData && (
|
||||
<>
|
||||
<MyTag
|
||||
mr={3}
|
||||
type={'borderFill'}
|
||||
showDot
|
||||
colorSchema={
|
||||
isPublished
|
||||
? publishStatusStyle.published.colorSchema
|
||||
: publishStatusStyle.unPublish.colorSchema
|
||||
}
|
||||
>
|
||||
{isPublished
|
||||
? publishStatusStyle.published.text
|
||||
: publishStatusStyle.unPublish.text}
|
||||
</MyTag>
|
||||
{isPc && (
|
||||
<MyTag
|
||||
mr={3}
|
||||
type={'borderFill'}
|
||||
showDot
|
||||
colorSchema={
|
||||
isPublished
|
||||
? publishStatusStyle.published.colorSchema
|
||||
: publishStatusStyle.unPublish.colorSchema
|
||||
}
|
||||
>
|
||||
{isPublished
|
||||
? publishStatusStyle.published.text
|
||||
: publishStatusStyle.unPublish.text}
|
||||
</MyTag>
|
||||
)}
|
||||
|
||||
<IconButton
|
||||
mr={[2, 4]}
|
||||
icon={<MyIcon name={'history'} w={'18px'} />}
|
||||
|
||||
@@ -22,14 +22,16 @@ import {
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import {
|
||||
FlowNodeTemplateType,
|
||||
NodeTemplateListItemType
|
||||
} from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api/plugin';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { PluginTypeEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
@@ -43,7 +45,7 @@ import FolderPath from '@/components/common/folder/Path';
|
||||
type Props = {
|
||||
selectedTools: FlowNodeTemplateType[];
|
||||
onAddTool: (tool: FlowNodeTemplateType) => void;
|
||||
onRemoveTool: (tool: FlowNodeTemplateType) => void;
|
||||
onRemoveTool: (tool: NodeTemplateListItemType) => void;
|
||||
};
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
@@ -160,7 +162,7 @@ const RenderList = React.memo(function RenderList({
|
||||
onRemoveTool,
|
||||
setParentId
|
||||
}: Props & {
|
||||
templates: FlowNodeTemplateType[];
|
||||
templates: NodeTemplateListItemType[];
|
||||
isLoadingData: boolean;
|
||||
setParentId: React.Dispatch<React.SetStateAction<ParentIdType>>;
|
||||
}) {
|
||||
@@ -170,29 +172,10 @@ const RenderList = React.memo(function RenderList({
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, reset } = useForm<Record<string, any>>({});
|
||||
|
||||
const checkToolInputValid = useCallback((tool: FlowNodeTemplateType) => {
|
||||
for (const input of tool.inputs) {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
if (renderType === FlowNodeInputTypeEnum.addInputParam) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const filterValidTools = useMemo(
|
||||
() => templates.filter(checkToolInputValid),
|
||||
[checkToolInputValid, templates]
|
||||
);
|
||||
|
||||
const { mutate: onClickAdd, isLoading } = useRequest({
|
||||
mutationFn: async (template: FlowNodeTemplateType) => {
|
||||
const res = await getPreviewPluginNode({ appId: template.id });
|
||||
|
||||
if (!checkToolInputValid(res)) {
|
||||
return Promise.reject(t('core.app.ToolCall.This plugin cannot be called as a tool'));
|
||||
}
|
||||
|
||||
// All input is tool params
|
||||
if (res.inputs.every((input) => input.toolDescription)) {
|
||||
onAddTool(res);
|
||||
@@ -204,12 +187,12 @@ const RenderList = React.memo(function RenderList({
|
||||
errorToast: t('core.module.templates.Load plugin error')
|
||||
});
|
||||
|
||||
return filterValidTools.length === 0 && !isLoadingData ? (
|
||||
return templates.length === 0 && !isLoadingData ? (
|
||||
<EmptyTip text={t('core.app.ToolCall.No plugin')} />
|
||||
) : (
|
||||
<MyBox>
|
||||
{filterValidTools.map((item, i) => {
|
||||
const selected = selectedTools.some((tool) => tool.pluginId === item.pluginId);
|
||||
{templates.map((item, i) => {
|
||||
const selected = selectedTools.some((tool) => tool.pluginId === item.id);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -247,8 +230,7 @@ const RenderList = React.memo(function RenderList({
|
||||
>
|
||||
{t('common.Remove')}
|
||||
</Button>
|
||||
) : item.pluginType === PluginTypeEnum.folder ||
|
||||
item.pluginType === AppTypeEnum.httpPlugin ? (
|
||||
) : item.isFolder ? (
|
||||
<Button size={'sm'} variant={'whiteBase'} onClick={() => setParentId(item.id)}>
|
||||
{t('common.Open')}
|
||||
</Button>
|
||||
|
||||
@@ -24,7 +24,7 @@ const SimpleEdit = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex h={'100%'} flexDirection={'column'} pr={3} pb={3}>
|
||||
<Flex h={'100%'} flexDirection={'column'} px={[3, 0]} pr={[3, 3]} pb={3}>
|
||||
<Header appForm={appForm} setAppForm={setAppForm} />
|
||||
{currentTab === TabEnum.appEdit ? (
|
||||
<Edit appForm={appForm} setAppForm={setAppForm} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import React, { forwardRef, ForwardedRef } from 'react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { Box, Flex, IconButton, Input, InputGroup, InputLeftElement, css } from '@chakra-ui/react';
|
||||
import type {
|
||||
FlowNodeTemplateType,
|
||||
nodeTemplateListType
|
||||
} from '@fastgpt/global/core/workflow/type/index.d';
|
||||
NodeTemplateListItemType,
|
||||
NodeTemplateListType
|
||||
} from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
@@ -30,13 +30,15 @@ import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import FolderPath from '@/components/common/folder/Path';
|
||||
import { getAppFolderPath } from '@/web/core/app/api/app';
|
||||
import { useWorkflowUtils } from './hooks/useUtils';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
type ModuleTemplateListProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
type RenderListProps = {
|
||||
templates: FlowNodeTemplateType[];
|
||||
templates: NodeTemplateListItemType[];
|
||||
onClose: () => void;
|
||||
parentId: ParentIdType;
|
||||
setParentId: React.Dispatch<React.SetStateAction<ParentIdType>>;
|
||||
@@ -56,73 +58,44 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
const [parentId, setParentId] = useState<ParentIdType>('');
|
||||
const [searchKey, setSearchKey] = useState('');
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { basicNodeTemplates, hasToolNode, nodeList } = useContextSelector(
|
||||
const { basicNodeTemplates, hasToolNode, nodeList, appId } = useContextSelector(
|
||||
WorkflowContext,
|
||||
(v) => v
|
||||
);
|
||||
const [pluginBuffer, setPluginBuffer] = useState<{
|
||||
systemPlugin: FlowNodeTemplateType[];
|
||||
teamPlugin: FlowNodeTemplateType[];
|
||||
}>({
|
||||
[TemplateTypeEnum.systemPlugin]: [],
|
||||
[TemplateTypeEnum.teamPlugin]: []
|
||||
});
|
||||
|
||||
const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic);
|
||||
|
||||
const { data: templates = [], loading } = useRequest2(
|
||||
const { data: basicNodes } = useRequest2(
|
||||
async () => {
|
||||
if (templateType === TemplateTypeEnum.basic) {
|
||||
return basicNodeTemplates.filter((item) => {
|
||||
// unique node filter
|
||||
if (item.unique) {
|
||||
const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType);
|
||||
if (nodeExist) {
|
||||
return basicNodeTemplates
|
||||
.filter((item) => {
|
||||
// unique node filter
|
||||
if (item.unique) {
|
||||
const nodeExist = nodeList.some((node) => node.flowNodeType === item.flowNodeType);
|
||||
if (nodeExist) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// special node filter
|
||||
if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// special node filter
|
||||
if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) {
|
||||
return false;
|
||||
}
|
||||
// tool stop
|
||||
if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// tool stop
|
||||
if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
.map<NodeTemplateListItemType>((item) => ({
|
||||
id: item.id,
|
||||
flowNodeType: item.flowNodeType,
|
||||
templateType: item.templateType,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
intro: item.intro
|
||||
}));
|
||||
}
|
||||
if (templateType === TemplateTypeEnum.systemPlugin) {
|
||||
if (pluginBuffer.systemPlugin.length === 0) {
|
||||
return getSystemPlugTemplates().then((res) => {
|
||||
setPluginBuffer((state) => ({
|
||||
...state,
|
||||
systemPlugin: res
|
||||
}));
|
||||
return res;
|
||||
});
|
||||
} else {
|
||||
return pluginBuffer.systemPlugin;
|
||||
}
|
||||
}
|
||||
if (templateType === TemplateTypeEnum.teamPlugin) {
|
||||
if (pluginBuffer.teamPlugin.length === 0) {
|
||||
return getTeamPlugTemplates({
|
||||
parentId,
|
||||
searchKey,
|
||||
type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin]
|
||||
}).then((res) => {
|
||||
setPluginBuffer((state) => ({
|
||||
...state,
|
||||
teamPlugin: res
|
||||
}));
|
||||
return res;
|
||||
});
|
||||
} else {
|
||||
return pluginBuffer.teamPlugin;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -130,6 +103,39 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
refreshDeps: [basicNodeTemplates, nodeList, hasToolNode, templateType, searchKey, parentId]
|
||||
}
|
||||
);
|
||||
const { data: teamApps, loading: isLoadingTeamApp } = useRequest2(
|
||||
async () => {
|
||||
if (templateType === TemplateTypeEnum.teamPlugin) {
|
||||
return getTeamPlugTemplates({
|
||||
parentId,
|
||||
searchKey,
|
||||
type: [AppTypeEnum.folder, AppTypeEnum.httpPlugin, AppTypeEnum.plugin]
|
||||
}).then((res) => res.filter((app) => app.id !== appId));
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
throttleWait: 300,
|
||||
refreshDeps: [templateType, searchKey, parentId]
|
||||
}
|
||||
);
|
||||
const { data: systemPlugins, loading: isLoadingSystemPlugins } = useRequest2(
|
||||
async () => {
|
||||
if (templateType === TemplateTypeEnum.systemPlugin) {
|
||||
return getSystemPlugTemplates();
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [templateType]
|
||||
}
|
||||
);
|
||||
|
||||
const isLoading = isLoadingTeamApp || isLoadingSystemPlugins;
|
||||
const templates = useMemo(
|
||||
() => basicNodes || teamApps || systemPlugins || [],
|
||||
[basicNodes, systemPlugins, teamApps]
|
||||
);
|
||||
|
||||
const { data: paths = [] } = useRequest2(() => getAppFolderPath(parentId), {
|
||||
manual: false,
|
||||
@@ -151,7 +157,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
isLoading={isLoading}
|
||||
display={'flex'}
|
||||
zIndex={3}
|
||||
flexDirection={'column'}
|
||||
@@ -246,7 +252,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
</MyBox>
|
||||
</>
|
||||
);
|
||||
}, [isOpen, onClose, loading, t, templateType, searchKey, parentId, paths, templates, router]);
|
||||
}, [isOpen, onClose, isLoading, t, templateType, searchKey, parentId, paths, templates, router]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
@@ -270,8 +276,8 @@ const RenderList = React.memo(function RenderList({
|
||||
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
|
||||
const { computedNewNodeName } = useWorkflowUtils();
|
||||
|
||||
const formatTemplates = useMemo<nodeTemplateListType>(() => {
|
||||
const copy: nodeTemplateListType = JSON.parse(JSON.stringify(workflowNodeTemplateList(t)));
|
||||
const formatTemplates = useMemo<NodeTemplateListType>(() => {
|
||||
const copy: NodeTemplateListType = cloneDeep(workflowNodeTemplateList(t));
|
||||
templates.forEach((item) => {
|
||||
const index = copy.findIndex((template) => template.type === item.templateType);
|
||||
if (index === -1) return;
|
||||
@@ -282,7 +288,13 @@ const RenderList = React.memo(function RenderList({
|
||||
}, [templates, parentId]);
|
||||
|
||||
const onAddNode = useCallback(
|
||||
async ({ template, position }: { template: FlowNodeTemplateType; position: XYPosition }) => {
|
||||
async ({
|
||||
template,
|
||||
position
|
||||
}: {
|
||||
template: NodeTemplateListItemType;
|
||||
position: XYPosition;
|
||||
}) => {
|
||||
if (!reactFlowWrapper?.current) return;
|
||||
|
||||
const templateNode = await (async () => {
|
||||
@@ -297,7 +309,11 @@ const RenderList = React.memo(function RenderList({
|
||||
}
|
||||
|
||||
// base node
|
||||
return { ...template };
|
||||
const baseTemplate = moduleTemplatesFlat.find((item) => item.id === template.id);
|
||||
if (!baseTemplate) {
|
||||
throw new Error('baseTemplate not found');
|
||||
}
|
||||
return { ...baseTemplate };
|
||||
} catch (e) {
|
||||
toast({
|
||||
status: 'error',
|
||||
@@ -390,7 +406,7 @@ const RenderList = React.memo(function RenderList({
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'sm'}
|
||||
draggable={template.pluginType !== AppTypeEnum.folder}
|
||||
draggable={!template.isFolder}
|
||||
onDragEnd={(e) => {
|
||||
if (e.clientX < sliderWidth) return;
|
||||
onAddNode({
|
||||
@@ -399,10 +415,7 @@ const RenderList = React.memo(function RenderList({
|
||||
});
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (
|
||||
template.pluginType === AppTypeEnum.folder ||
|
||||
template.pluginType === AppTypeEnum.httpPlugin
|
||||
) {
|
||||
if (template.isFolder) {
|
||||
return setParentId(template.id);
|
||||
}
|
||||
if (isPc) {
|
||||
@@ -412,7 +425,7 @@ const RenderList = React.memo(function RenderList({
|
||||
});
|
||||
}
|
||||
onAddNode({
|
||||
template: template,
|
||||
template,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
onClose();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ModalBody, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/runApp/type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import SelectOneResource from '@/components/common/folder/SelectOneResource';
|
||||
import {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { Box, Flex, FlexProps } from '@chakra-ui/react';
|
||||
|
||||
const IOTitle = ({ text }: { text?: 'Input' | 'Output' | string }) => {
|
||||
const IOTitle = ({ text, ...props }: { text?: 'Input' | 'Output' | string } & FlexProps) => {
|
||||
return (
|
||||
<Flex fontSize={'md'} alignItems={'center'} fontWeight={'medium'} mb={3}>
|
||||
<Flex fontSize={'md'} alignItems={'center'} fontWeight={'medium'} mb={3} {...props}>
|
||||
<Box w={'3px'} h={'14px'} borderRadius={'13px'} bg={'primary.600'} mr={1.5} />
|
||||
{text}
|
||||
</Flex>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { RuntimeEdgeItemType, StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Node } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext, getWorkflowStore } from '../../context';
|
||||
import { useWorkflowUtils } from './useUtils';
|
||||
|
||||
@@ -18,7 +18,7 @@ import ButtonEdge from './components/ButtonEdge';
|
||||
import NodeTemplatesModal from './NodeTemplatesModal';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { connectionLineStyle, defaultEdgeOptions } from '../constants';
|
||||
@@ -30,6 +30,8 @@ const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
|
||||
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
||||
[FlowNodeTypeEnum.emptyNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.globalVariable]: NodeSimple,
|
||||
[FlowNodeTypeEnum.textEditor]: NodeSimple,
|
||||
[FlowNodeTypeEnum.customFeedback]: NodeSimple,
|
||||
[FlowNodeTypeEnum.systemConfig]: dynamic(() => import('./nodes/NodeSystemConfig')),
|
||||
[FlowNodeTypeEnum.workflowStart]: dynamic(() => import('./nodes/NodeWorkflowStart')),
|
||||
[FlowNodeTypeEnum.chatNode]: NodeSimple,
|
||||
@@ -40,8 +42,8 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest468]: dynamic(() => import('./nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./nodes/NodePluginIO/PluginInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./nodes/NodePluginIO/PluginOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple,
|
||||
[FlowNodeTypeEnum.queryExtension]: NodeSimple,
|
||||
[FlowNodeTypeEnum.tools]: dynamic(() => import('./nodes/NodeTools')),
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import RenderToolInput from './render/RenderToolInput';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import IOTitle from '../components/IOTitle';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
|
||||
@@ -14,16 +13,15 @@ const NodeAnswer = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs } = data;
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
<Container>
|
||||
{toolInputs.length > 0 && (
|
||||
{isTool && (
|
||||
<>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<Container>
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -2,10 +2,10 @@ import React, { useMemo } from 'react';
|
||||
import { NodeProps, Position } from 'reactflow';
|
||||
import { Box, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { ClassifyQuestionAgentItemType } from '@fastgpt/global/core/workflow/template/system/classifyQuestion/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
@@ -24,7 +24,7 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
content: workflowT('code.Reset template confirm')
|
||||
});
|
||||
@@ -79,11 +79,10 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
{toolInputs.length > 0 && (
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
WorkflowIOValueTypeEnum,
|
||||
NodeInputKeyEnum,
|
||||
VARIABLE_NODE_ID
|
||||
} from '@fastgpt/global/core/workflow/constants';
|
||||
import { getOneQuoteInputTemplate } from '@fastgpt/global/core/workflow/template/system/datasetConcat';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MySlider from '@/components/Slider';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
ReferenceValueProps
|
||||
} from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
import IOTitle from '../components/IOTitle';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { ReferSelector, useReference } from './render/RenderInput/templates/Reference';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import ValueTypeLabel from './render/ValueTypeLabel';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
|
||||
const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -25,10 +37,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const quotes = useMemo(
|
||||
() => inputs.filter((item) => item.valueType === WorkflowIOValueTypeEnum.datasetQuote),
|
||||
[inputs]
|
||||
);
|
||||
const quoteList = useMemo(() => inputs.filter((item) => item.canEdit), [inputs]);
|
||||
|
||||
const tokenLimit = useMemo(() => {
|
||||
let maxTokens = 3000;
|
||||
@@ -47,14 +56,6 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return maxTokens;
|
||||
}, [llmModelList, nodeList]);
|
||||
|
||||
const onAddField = useCallback(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: getOneQuoteInputTemplate({ index: quotes.length + 1 })
|
||||
});
|
||||
}, [nodeId, onChangeNode, quotes.length]);
|
||||
|
||||
const CustomComponent = useMemo(() => {
|
||||
return {
|
||||
[NodeInputKeyEnum.datasetMaxTokens]: (item: FlowNodeInputItemType) => (
|
||||
@@ -83,26 +84,42 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
customComponent: (item: FlowNodeInputItemType) => (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
{t('core.workflow.Dataset quote')}
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
mr={'-5px'}
|
||||
onClick={onAddField}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)
|
||||
[NodeInputKeyEnum.datasetQuoteList]: (item: FlowNodeInputItemType) => {
|
||||
return (
|
||||
<>
|
||||
<HStack className="nodrag" cursor={'default'} position={'relative'}>
|
||||
<HStack spacing={1} position={'relative'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
<Box>{t('core.workflow.Dataset quote')}</Box>
|
||||
</HStack>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: getOneQuoteInputTemplate({ index: quoteList.length + 1 })
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</HStack>
|
||||
<Box mt={2}>
|
||||
{quoteList.map((children) => (
|
||||
<Box key={children.key} _notLast={{ mb: 3 }}>
|
||||
<Reference nodeId={nodeId} inputChildren={children} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [nodeId, onAddField, onChangeNode, t, tokenLimit]);
|
||||
}, [nodeId, onChangeNode, quoteList, t, tokenLimit]);
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} selected={selected} {...data}>
|
||||
@@ -118,3 +135,79 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeDatasetConcat);
|
||||
|
||||
function Reference({
|
||||
nodeId,
|
||||
inputChildren
|
||||
}: {
|
||||
nodeId: string;
|
||||
inputChildren: FlowNodeInputItemType;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const { referenceList, formatValue } = useReference({
|
||||
nodeId,
|
||||
valueType: inputChildren.valueType,
|
||||
value: inputChildren.value
|
||||
});
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: ReferenceValueProps) => {
|
||||
const workflowStartNode = nodeList.find(
|
||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||
);
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: inputChildren.key,
|
||||
value: {
|
||||
...inputChildren,
|
||||
value:
|
||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
||||
? [VARIABLE_NODE_ID, e[1]]
|
||||
: e
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputChildren, nodeId, nodeList, onChangeNode]
|
||||
);
|
||||
|
||||
const onDel = useCallback(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key: inputChildren.key
|
||||
});
|
||||
}, [inputChildren.key, nodeId, onChangeNode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<FormLabel required={inputChildren.required}>{inputChildren.label}</FormLabel>
|
||||
{/* value */}
|
||||
<ValueTypeLabel valueType={inputChildren.valueType} />
|
||||
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={onDel}
|
||||
/>
|
||||
</Flex>
|
||||
<ReferSelector
|
||||
placeholder={t(inputChildren.referencePlaceholder || '选择知识库引用')}
|
||||
list={referenceList}
|
||||
value={formatValue}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
|
||||
const NodeEmpty = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return <NodeCard selected={selected} {...data}></NodeCard>;
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export const defaultField: ContextExtractAgentItemType = {
|
||||
valueType: 'string',
|
||||
@@ -74,11 +74,11 @@ const ExtractFieldModal = ({
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<FormLabel flex={['0 0 80px', '0 0 100px']}>{t('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={'1 0 0'}>
|
||||
<MySelect
|
||||
<MySelect<string>
|
||||
list={fnValueTypeSelect}
|
||||
value={valueType}
|
||||
onchange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
onchange={(e) => {
|
||||
setValue('valueType', e as any);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -12,13 +12,13 @@ import {
|
||||
Flex
|
||||
} from '@chakra-ui/react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import Container from '../../components/Container';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { ContextExtractAgentItemType } from '@fastgpt/global/core/workflow/template/system/contextExtract/type';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import ExtractFieldModal, { defaultField } from './ExtractFieldModal';
|
||||
@@ -42,7 +42,7 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
|
||||
const CustomComponent = useMemo(
|
||||
@@ -144,11 +144,10 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
{toolInputs.length > 0 && (
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
@@ -174,8 +173,10 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
defaultField={editExtractFiled}
|
||||
onClose={() => setEditExtractField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
const extracts: ContextExtractAgentItemType[] =
|
||||
inputs.find((item) => item.key === NodeInputKeyEnum.extractKeys)?.value || [];
|
||||
const input = inputs.find(
|
||||
(input) => input.key === NodeInputKeyEnum.extractKeys
|
||||
) as FlowNodeInputItemType;
|
||||
const extracts: ContextExtractAgentItemType[] = input.value || [];
|
||||
|
||||
const exists = extracts.find((item) => item.key === editExtractFiled.key);
|
||||
|
||||
@@ -188,7 +189,7 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.extractKeys,
|
||||
value: {
|
||||
...inputs.find((input) => input.key === NodeInputKeyEnum.extractKeys),
|
||||
...input,
|
||||
value: newInputs
|
||||
}
|
||||
});
|
||||
@@ -203,7 +204,10 @@ const NodeExtract = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
|
||||
if (exists) {
|
||||
if (editExtractFiled.key === data.key) {
|
||||
const output = outputs.find((output) => output.key === data.key);
|
||||
const output = outputs.find(
|
||||
(output) => output.key === data.key
|
||||
) as FlowNodeOutputItemType;
|
||||
|
||||
// update
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
|
||||
@@ -47,6 +47,8 @@ const CurlImportModal = ({
|
||||
const headers = inputs.find((item) => item.key === NodeInputKeyEnum.httpHeaders);
|
||||
const jsonBody = inputs.find((item) => item.key === NodeInputKeyEnum.httpJsonBody);
|
||||
|
||||
if (!requestUrl || !requestMethod || !params || !headers || !jsonBody) return;
|
||||
|
||||
const parsed = parse(content);
|
||||
if (!parsed.url) {
|
||||
throw new Error('url not found');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState, useTransition } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../../components/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
@@ -106,8 +106,12 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
|
||||
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
|
||||
|
||||
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod);
|
||||
const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl);
|
||||
const requestMethods = inputs.find(
|
||||
(item) => item.key === NodeInputKeyEnum.httpMethod
|
||||
) as FlowNodeInputItemType;
|
||||
const requestUrl = inputs.find(
|
||||
(item) => item.key === NodeInputKeyEnum.httpReqUrl
|
||||
) as FlowNodeInputItemType;
|
||||
|
||||
const onChangeUrl = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChangeNode({
|
||||
@@ -251,6 +255,8 @@ export function RenderHttpProps({
|
||||
const { t } = useTranslation();
|
||||
const [selectedTab, setSelectedTab] = useState(TabEnum.params);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const getNodeDynamicInputs = useContextSelector(WorkflowContext, (v) => v.getNodeDynamicInputs);
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const requestMethods = inputs.find((item) => item.key === NodeInputKeyEnum.httpMethod)?.value;
|
||||
@@ -269,17 +275,10 @@ export function RenderHttpProps({
|
||||
t
|
||||
});
|
||||
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.canEdit || input.toolDescription)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
const nodeVariables = formatEditorVariablePickerIcon(getNodeDynamicInputs(nodeId));
|
||||
|
||||
return [...moduleVariables, ...globalVariables];
|
||||
}, [appDetail.chatConfig, inputs, nodeList, t]);
|
||||
return [...nodeVariables, ...globalVariables];
|
||||
}, [appDetail.chatConfig, getNodeDynamicInputs, nodeId, nodeList, t]);
|
||||
|
||||
const variableText = useMemo(() => {
|
||||
return variables
|
||||
@@ -637,7 +636,7 @@ const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (v) => v.splitToolInputs);
|
||||
const { toolInputs, commonInputs, isTool } = splitToolInputs(inputs, nodeId);
|
||||
const { commonInputs, isTool } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
const HttpMethodAndUrl = useMemoizedFn(() => (
|
||||
<RenderHttpMethodAndUrl nodeId={nodeId} inputs={inputs} />
|
||||
@@ -656,8 +655,7 @@ const NodeHttp = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} canEdit />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeProps, Position } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/system/ifElse/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import { Box, Button, Center, Flex, useDisclosure } from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum, NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getLafAppDetail } from '@/web/support/laf/api';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
@@ -24,7 +24,7 @@ import RenderToolInput from './render/RenderToolInput';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
@@ -34,6 +34,7 @@ import IOTitle from '../components/IOTitle';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
import { putUpdateTeam } from '@/web/support/user/team/api';
|
||||
import { nodeLafCustomInputConfig } from '@fastgpt/global/core/workflow/template/system/laf';
|
||||
|
||||
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
|
||||
|
||||
@@ -46,7 +47,9 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const requestUrl = inputs.find((item) => item.key === NodeInputKeyEnum.httpReqUrl);
|
||||
const requestUrl = inputs.find(
|
||||
(item) => item.key === NodeInputKeyEnum.httpReqUrl
|
||||
) as FlowNodeInputItemType;
|
||||
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
|
||||
@@ -122,8 +125,8 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
[lafFunctionSelectList, requestUrl?.value]
|
||||
);
|
||||
|
||||
const { mutate: onSyncParams, isLoading: isSyncing } = useRequest({
|
||||
mutationFn: async () => {
|
||||
const { run: onSyncParams, loading: isSyncing } = useRequest2(
|
||||
async () => {
|
||||
await refetchFunction();
|
||||
const lafFunction = lafData?.lafFunctions.find(
|
||||
(item) => item.requestUrl === selectedFunction
|
||||
@@ -144,10 +147,10 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
// add input variables
|
||||
const bodyParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.properties || {};
|
||||
|
||||
const requiredParams =
|
||||
lafFunction?.request?.content?.['application/json']?.schema?.required || [];
|
||||
|
||||
// add new params
|
||||
const allParams = [
|
||||
...Object.keys(bodyParams).map((key) => ({
|
||||
name: key,
|
||||
@@ -166,13 +169,11 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: param.required,
|
||||
description: param.desc || '',
|
||||
toolDescription: param.desc || '未设置参数描述',
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
valueType: true
|
||||
}
|
||||
toolDescription: param.desc || param.name,
|
||||
customInputConfig: nodeLafCustomInputConfig,
|
||||
canEdit: true
|
||||
};
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
@@ -211,8 +212,10 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
});
|
||||
});
|
||||
},
|
||||
successToast: t('common.Sync success')
|
||||
});
|
||||
{
|
||||
successToast: t('common.Sync success')
|
||||
}
|
||||
);
|
||||
|
||||
// not config laf
|
||||
if (!token || !appid) {
|
||||
@@ -299,19 +302,18 @@ const ConfigLaf = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const RenderIO = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const RenderIO = ({ data }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const { commonInputs, toolInputs, isTool } = splitToolInputs(inputs, nodeId);
|
||||
const { commonInputs, isTool } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} canEdit />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea,
|
||||
Stack,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import MultipleSelect from '@fastgpt/web/components/common/MySelect/MultipleSelect';
|
||||
import { useBoolean } from 'ahooks';
|
||||
|
||||
const MyNumberInput = dynamic(
|
||||
() => import('@fastgpt/web/components/common/Input/NumberInput/index')
|
||||
);
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
|
||||
export const defaultInput: FlowNodeInputItemType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference], // Can only choose one here
|
||||
selectedTypeIndex: 0,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
key: '',
|
||||
label: ''
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
defaultValue,
|
||||
keys = [],
|
||||
hasDynamicInput,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
defaultValue: FlowNodeInputItemType;
|
||||
keys: string[];
|
||||
hasDynamicInput: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: FlowNodeInputItemType; isChangeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
|
||||
const inputTypeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('core.workflow.inputType.Reference'),
|
||||
value: FlowNodeInputTypeEnum.reference,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.JSON Editor'),
|
||||
value: FlowNodeInputTypeEnum.JSONEditor,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.number input'),
|
||||
value: FlowNodeInputTypeEnum.numberInput,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectApp'),
|
||||
value: FlowNodeInputTypeEnum.selectApp,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.selectApp
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectLLMModel'),
|
||||
value: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.selectDataset
|
||||
},
|
||||
...(hasDynamicInput
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: t('core.workflow.inputType.dynamicTargetInput'),
|
||||
value: FlowNodeInputTypeEnum.addInputParam,
|
||||
defaultValueType: WorkflowIOValueTypeEnum.dynamic
|
||||
}
|
||||
])
|
||||
],
|
||||
[hasDynamicInput, t]
|
||||
);
|
||||
|
||||
const isEdit = !!defaultValue.key;
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm({
|
||||
defaultValues: defaultValue
|
||||
});
|
||||
|
||||
const inputType = watch('renderTypeList.0') || FlowNodeInputTypeEnum.reference;
|
||||
const valueType = watch('valueType');
|
||||
|
||||
const [isToolInput, { toggle: setIsToolInput }] = useBoolean(!!getValues('toolDescription'));
|
||||
|
||||
const maxLength = watch('maxLength');
|
||||
const max = watch('max');
|
||||
const min = watch('min');
|
||||
const selectValueTypeList = watch('customInputConfig.selectValueTypeList');
|
||||
|
||||
const showValueTypeSelect = inputType === FlowNodeInputTypeEnum.reference;
|
||||
|
||||
// input type config
|
||||
const showRequired = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.addInputParam];
|
||||
return !list.includes(inputType);
|
||||
}, [inputType]);
|
||||
const showDefaultValue = useMemo(() => {
|
||||
const list = [
|
||||
FlowNodeInputTypeEnum.input,
|
||||
FlowNodeInputTypeEnum.textarea,
|
||||
FlowNodeInputTypeEnum.JSONEditor,
|
||||
FlowNodeInputTypeEnum.numberInput,
|
||||
FlowNodeInputTypeEnum.switch
|
||||
];
|
||||
|
||||
return list.includes(inputType);
|
||||
}, [inputType]);
|
||||
const showMaxLenInput = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.textarea];
|
||||
|
||||
return list.includes(inputType);
|
||||
}, [inputType]);
|
||||
const showMinMaxInput = useMemo(() => {
|
||||
const list = [FlowNodeInputTypeEnum.numberInput];
|
||||
return list.includes(inputType);
|
||||
}, [inputType]);
|
||||
|
||||
const valueTypeSelectList = Object.values(FlowValueTypeMap).map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
const defaultValueType =
|
||||
inputTypeList.find((item) => item.value === inputType)?.defaultValueType ||
|
||||
WorkflowIOValueTypeEnum.string;
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: FlowNodeInputItemType) => {
|
||||
data.key = data?.key?.trim();
|
||||
|
||||
if (!data.key) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Name Cannot Be Empty')
|
||||
});
|
||||
}
|
||||
|
||||
if (data.renderTypeList[0] !== FlowNodeInputTypeEnum.reference) {
|
||||
data.valueType = defaultValueType;
|
||||
}
|
||||
|
||||
const isChangeKey = defaultValue.key !== data.key;
|
||||
// create check key
|
||||
if (keys.includes(data.key)) {
|
||||
if (!isEdit || isChangeKey) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: workflowT('Field Name already exists')
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isToolInput) {
|
||||
data.toolDescription = data.description;
|
||||
}
|
||||
|
||||
data.label = data.key;
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
isChangeKey
|
||||
});
|
||||
onClose();
|
||||
},
|
||||
[
|
||||
defaultValue.key,
|
||||
defaultValueType,
|
||||
isEdit,
|
||||
isToolInput,
|
||||
keys,
|
||||
onClose,
|
||||
onSubmit,
|
||||
t,
|
||||
toast,
|
||||
workflowT
|
||||
]
|
||||
);
|
||||
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/workflow/extract.png"
|
||||
title={isEdit ? workflowT('Edit input') : workflowT('Add new input')}
|
||||
maxW={['90vw', '800px']}
|
||||
w={'100%'}
|
||||
>
|
||||
<ModalBody display={'flex'} gap={8} flexDirection={['column', 'row']}>
|
||||
<Stack flex={1} gap={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Input Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect<FlowNodeInputTypeEnum>
|
||||
list={inputTypeList}
|
||||
value={inputType}
|
||||
onchange={(e) => {
|
||||
setValue('renderTypeList.0', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Field Name')}</FormLabel>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Box alignItems={'flex-start'}>
|
||||
<FormLabel flex={'0 0 70px'} mb={'1px'}>
|
||||
{workflowT('Field description')}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
placeholder={workflowT('Field description placeholder')}
|
||||
rows={4}
|
||||
{...register('description', { required: isToolInput ? true : false })}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
{/* input type config */}
|
||||
<Stack flex={1} gap={5}>
|
||||
{/* value type */}
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Data Type')}</FormLabel>
|
||||
{showValueTypeSelect ? (
|
||||
<Box flex={1}>
|
||||
<MySelect<WorkflowIOValueTypeEnum>
|
||||
list={valueTypeSelectList}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
defaultValueType
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{showRequired && (
|
||||
<Flex alignItems={'center'} minH={'40px'}>
|
||||
<FormLabel flex={'1'}>{workflowT('Field required')}</FormLabel>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* reference */}
|
||||
{inputType === FlowNodeInputTypeEnum.reference && (
|
||||
<>
|
||||
<Flex alignItems={'center'} minH={'40px'}>
|
||||
<FormLabel flex={'1'}>{workflowT('Field used as tool input')}</FormLabel>
|
||||
<Switch
|
||||
isChecked={isToolInput}
|
||||
onChange={(e) => {
|
||||
setIsToolInput();
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showMaxLenInput && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Max Length')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('core.module.Max Length placeholder')}
|
||||
value={maxLength}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('maxLength', e || '');
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{showMinMaxInput && (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Max Value')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
value={watch('max')}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('max', e || '');
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Min Value')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
value={watch('min')}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('min', e || '');
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showDefaultValue && (
|
||||
<Flex alignItems={'center'} minH={'40px'}>
|
||||
<FormLabel flex={inputType === FlowNodeInputTypeEnum.switch ? 1 : '0 0 70px'}>
|
||||
{t('core.module.Default Value')}
|
||||
</FormLabel>
|
||||
{inputType === FlowNodeInputTypeEnum.numberInput && (
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
max={max}
|
||||
min={min}
|
||||
type={'number'}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.input && (
|
||||
<Input bg={'myGray.50'} maxLength={maxLength} {...register('defaultValue')} />
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.textarea && (
|
||||
<Textarea bg={'myGray.50'} maxLength={maxLength} {...register('defaultValue')} />
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.JSONEditor && (
|
||||
<JsonEditor
|
||||
bg={'myGray.50'}
|
||||
resize
|
||||
w={'full'}
|
||||
onChange={(e) => {
|
||||
setValue('defaultValue', e);
|
||||
}}
|
||||
defaultValue={String(getValues('defaultValue'))}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.switch && (
|
||||
<Switch {...register('defaultValue')} />
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{inputType === FlowNodeInputTypeEnum.addInputParam && (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Input Type')}</FormLabel>
|
||||
<Box flex={1} fontWeight={'bold'}>
|
||||
{workflowT('Only the Reference type is supported')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box>
|
||||
<HStack mb={1}>
|
||||
<FormLabel>{workflowT('Optional value type')}</FormLabel>
|
||||
<QuestionTip label={workflowT('Optional value type tip')} />
|
||||
</HStack>
|
||||
<MultipleSelect<WorkflowIOValueTypeEnum>
|
||||
list={valueTypeSelectList}
|
||||
bg={'myGray.50'}
|
||||
value={selectValueTypeList || []}
|
||||
onSelect={(e) => {
|
||||
setValue('customInputConfig.selectValueTypeList', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</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);
|
||||
@@ -0,0 +1,169 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { Box, Button, HStack } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import Container from '../../components/Container';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
FlowNodeInputMap,
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import VariableTable from './VariableTable';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import IOTitle from '../../components/IOTitle';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { defaultInput } from './InputEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./InputEditModal'));
|
||||
|
||||
/*
|
||||
1. When the plug-in is called, the input of the rendering node is customized.
|
||||
2. Customize input nodes. Input and output must be symmetrical.
|
||||
3. When the plug-in is run, the external will calculate the value of the custom input and throw it to the output of the custom input node to start running the plug-in.
|
||||
*/
|
||||
|
||||
const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs = [], outputs } = data;
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
const onSubmit = ({ data }: { data: FlowNodeInputItemType; isChangeKey: boolean }) => {
|
||||
if (!editField) return;
|
||||
|
||||
if (editField?.key) {
|
||||
const output = outputs.find((output) => output.key === editField.key);
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...(output as FlowNodeOutputItemType),
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
id: data.key,
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.hidden
|
||||
};
|
||||
|
||||
// Add new input
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: data
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addOutput',
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<NodeCard
|
||||
minW={'300px'}
|
||||
selected={selected}
|
||||
menuForbid={{
|
||||
rename: true,
|
||||
copy: true,
|
||||
delete: true
|
||||
}}
|
||||
{...data}
|
||||
>
|
||||
<Container mt={1}>
|
||||
<HStack className="nodrag" cursor={'default'} mb={3}>
|
||||
<IOTitle text={t('core.workflow.Custom inputs')} mb={0} />
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => setEditField(defaultInput)}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</HStack>
|
||||
<VariableTable
|
||||
variables={inputs.map((input) => {
|
||||
const inputType = input.renderTypeList[0];
|
||||
return {
|
||||
icon: FlowNodeInputMap[inputType]?.icon as string,
|
||||
label: t(input.label),
|
||||
type: input.valueType ? t(FlowValueTypeMap[input.valueType]?.label) : '-',
|
||||
isTool: !!input.toolDescription,
|
||||
key: input.key
|
||||
};
|
||||
})}
|
||||
onEdit={(key) => {
|
||||
const input = inputs.find((input) => input.key === key);
|
||||
if (!input) return;
|
||||
setEditField(input);
|
||||
}}
|
||||
onDelete={(key) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delOutput',
|
||||
key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
}, [data, inputs, nodeId, onChangeNode, selected, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{Render}
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
defaultValue={editField}
|
||||
keys={inputs.map((item) => item.key)}
|
||||
hasDynamicInput={
|
||||
!!inputs.find(
|
||||
(input) =>
|
||||
input.key !== editField.key &&
|
||||
input.renderTypeList.includes(FlowNodeInputTypeEnum.addInputParam)
|
||||
)
|
||||
}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodePluginInput);
|
||||
@@ -0,0 +1,221 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import Container from '../../components/Container';
|
||||
import { FlowNodeInputItemType, ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { VARIABLE_NODE_ID, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import IOTitle from '../../components/IOTitle';
|
||||
import { ReferSelector, useReference } from '../render/RenderInput/templates/Reference';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import ValueTypeLabel from '../render/ValueTypeLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
import { defaultInput } from '../render/RenderInput/FieldEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../render/RenderInput/FieldEditModal'));
|
||||
|
||||
const customInputConfig = {
|
||||
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
|
||||
showDescription: true,
|
||||
showDefaultValue: true
|
||||
};
|
||||
|
||||
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs } = data;
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
return (
|
||||
<NodeCard
|
||||
minW={'300px'}
|
||||
selected={selected}
|
||||
menuForbid={{
|
||||
debug: true,
|
||||
rename: true,
|
||||
copy: true,
|
||||
delete: true
|
||||
}}
|
||||
{...data}
|
||||
>
|
||||
<Container mt={1}>
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<IOTitle mb={0} text={t('core.workflow.Custom outputs')}></IOTitle>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => setEditField(defaultInput)}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* render input */}
|
||||
<Box mt={2}>
|
||||
{inputs.map((input) => (
|
||||
<Box key={input.key} _notLast={{ mb: 3 }}>
|
||||
<Reference nodeId={nodeId} keys={inputs.map((input) => input.key)} input={input} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
customInputConfig={customInputConfig}
|
||||
defaultInput={editField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: data
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodePluginOutput);
|
||||
|
||||
function Reference({
|
||||
nodeId,
|
||||
keys,
|
||||
input
|
||||
}: {
|
||||
nodeId: string;
|
||||
keys: string[];
|
||||
input: FlowNodeInputItemType;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
type: 'delete',
|
||||
content: workflowT('Confirm delete field tip')
|
||||
});
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: ReferenceValueProps) => {
|
||||
const workflowStartNode = nodeList.find(
|
||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||
);
|
||||
|
||||
const value =
|
||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
||||
? [VARIABLE_NODE_ID, e[1]]
|
||||
: e;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: input.key,
|
||||
value: {
|
||||
...input,
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[input, nodeId, nodeList, onChangeNode]
|
||||
);
|
||||
|
||||
const { referenceList, formatValue } = useReference({
|
||||
nodeId,
|
||||
valueType: input.valueType,
|
||||
value: input.value
|
||||
});
|
||||
|
||||
const onUpdateField = useCallback(
|
||||
({ data }: { data: FlowNodeInputItemType }) => {
|
||||
if (!data.key) return;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: input.key,
|
||||
value: data
|
||||
});
|
||||
},
|
||||
[input.key, nodeId, onChangeNode]
|
||||
);
|
||||
const onDel = useCallback(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key: input.key
|
||||
});
|
||||
}, [input.key, nodeId, onChangeNode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<FormLabel required={input.required}>{input.label}</FormLabel>
|
||||
{input.description && <QuestionTip label={input.description}></QuestionTip>}
|
||||
{/* value */}
|
||||
<ValueTypeLabel valueType={input.valueType} />
|
||||
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() => setEditField(input)}
|
||||
/>
|
||||
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={openConfirm(onDel)}
|
||||
/>
|
||||
</Flex>
|
||||
<ReferSelector
|
||||
placeholder={t(input.referencePlaceholder || '选择引用变量')}
|
||||
list={referenceList}
|
||||
value={formatValue}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
defaultInput={editField}
|
||||
customInputConfig={customInputConfig}
|
||||
keys={keys}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onUpdateField}
|
||||
/>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
const VariableTable = ({
|
||||
variables = [],
|
||||
onEdit,
|
||||
onDelete
|
||||
}: {
|
||||
variables: { icon?: string; label: string; type: string; key: string; isTool?: boolean }[];
|
||||
onEdit: (key: string) => void;
|
||||
onDelete: (key: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const showToolColumn = variables.some((item) => item.isTool);
|
||||
|
||||
return (
|
||||
<Box bg={'white'} borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('core.module.variable.variable name')}
|
||||
</Th>
|
||||
<Th>{t('core.workflow.Value type')}</Th>
|
||||
{showToolColumn && <Th>{workflowT('Tool input')}</Th>}
|
||||
<Th borderBottomRightRadius={'none !important'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{!!item.icon && <MyIcon name={item.icon as any} w={'14px'} mr={1} />}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
{showToolColumn && <Th>{item.isTool ? '✅' : '-'}</Th>}
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => onEdit(item.key)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete(item.key);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VariableTable);
|
||||
@@ -1,225 +0,0 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import Container from '../components/Container';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
import type {
|
||||
EditInputFieldMapType,
|
||||
EditNodeFieldType
|
||||
} from '@fastgpt/global/core/workflow/node/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
FlowNodeInputMap,
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import VariableTable from './render/VariableTable';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
inputType: FlowNodeInputTypeEnum.reference,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
required: true
|
||||
};
|
||||
const createEditField: EditInputFieldMapType = {
|
||||
key: true,
|
||||
description: true,
|
||||
required: true,
|
||||
valueType: true,
|
||||
inputType: true
|
||||
};
|
||||
const dynamicInputEditField: EditInputFieldMapType = {
|
||||
key: true
|
||||
};
|
||||
|
||||
const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<NodeCard
|
||||
minW={'300px'}
|
||||
selected={selected}
|
||||
menuForbid={{
|
||||
rename: true,
|
||||
copy: true,
|
||||
delete: true
|
||||
}}
|
||||
{...data}
|
||||
>
|
||||
<Container mt={1}>
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} mb={3}>
|
||||
<Box>{t('core.workflow.Custom inputs')}</Box>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => setCreateField(defaultCreateField)}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<VariableTable
|
||||
fieldEditType={createEditField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onCloseFieldEdit={() => {
|
||||
setCreateField(undefined);
|
||||
setEditField(undefined);
|
||||
}}
|
||||
variables={inputs.map((input) => {
|
||||
const inputType = input.renderTypeList[0];
|
||||
return {
|
||||
icon: FlowNodeInputMap[inputType]?.icon as string,
|
||||
label: t(input.label),
|
||||
type: input.valueType ? t(FlowValueTypeMap[input.valueType]?.label) : '-',
|
||||
key: input.key
|
||||
};
|
||||
})}
|
||||
createField={createField}
|
||||
onCreate={({ data }) => {
|
||||
if (!data.key || !data.inputType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label || '',
|
||||
renderTypeList: [data.inputType],
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
canEdit: true,
|
||||
value: data.defaultValue,
|
||||
editField: dynamicInputEditField,
|
||||
maxLength: data.maxLength,
|
||||
max: data.max,
|
||||
min: data.min,
|
||||
dynamicParamDefaultValue: data.dynamicParamDefaultValue
|
||||
};
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: newInput
|
||||
});
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
id: getNanoid(),
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
type: FlowNodeOutputTypeEnum.static
|
||||
};
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addOutput',
|
||||
value: newOutput
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
editField={editField}
|
||||
onStartEdit={(key) => {
|
||||
const input = inputs.find((input) => input.key === key);
|
||||
if (!input) return;
|
||||
|
||||
setEditField({
|
||||
...input,
|
||||
inputType: input.renderTypeList[0],
|
||||
isToolInput: !!input.toolDescription
|
||||
});
|
||||
}}
|
||||
onEdit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !editField?.key) return;
|
||||
|
||||
const output = outputs.find((output) => output.key === editField.key);
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...data,
|
||||
key: data.key,
|
||||
label: data.label || '',
|
||||
renderTypeList: [data.inputType],
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
canEdit: true,
|
||||
value: data.defaultValue,
|
||||
editField: dynamicInputEditField
|
||||
};
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...(output as FlowNodeOutputItemType),
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
onDelete={(key) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delOutput',
|
||||
key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
}, [createField, data, editField, inputs, nodeId, onChangeNode, outputs, selected, t]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
export default React.memo(NodePluginInput);
|
||||
@@ -1,105 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import Container from '../components/Container';
|
||||
import { EditInputFieldMapType, EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../context';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./render/FieldEditModal'));
|
||||
|
||||
const defaultCreateField: EditNodeFieldType = {
|
||||
inputType: FlowNodeInputTypeEnum.reference,
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
};
|
||||
const createEditField: EditInputFieldMapType = {
|
||||
key: true,
|
||||
description: true,
|
||||
valueType: true
|
||||
};
|
||||
|
||||
const NodePluginOutput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, inputs } = data;
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard
|
||||
minW={'300px'}
|
||||
selected={selected}
|
||||
menuForbid={{
|
||||
debug: true,
|
||||
rename: true,
|
||||
copy: true,
|
||||
delete: true
|
||||
}}
|
||||
{...data}
|
||||
>
|
||||
<Container mt={1}>
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Box position={'relative'} fontWeight={'medium'}>
|
||||
{t('core.workflow.Custom outputs')}
|
||||
</Box>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => setCreateField(defaultCreateField)}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<RenderInput nodeId={nodeId} flowInputList={inputs} />
|
||||
</Container>
|
||||
{!!createField && (
|
||||
<FieldEditModal
|
||||
editField={createEditField}
|
||||
defaultField={createField}
|
||||
keys={inputs.map((input) => input.key)}
|
||||
onClose={() => setCreateField(undefined)}
|
||||
onSubmit={({ data }) => {
|
||||
if (!data.key || !data.label) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: false,
|
||||
description: data.description,
|
||||
canEdit: true,
|
||||
editField: createEditField
|
||||
};
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: newInput
|
||||
});
|
||||
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodePluginOutput);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
@@ -21,17 +21,16 @@ const NodeSimple = ({
|
||||
const { t } = useTranslation();
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const { nodeId, inputs, outputs } = data;
|
||||
const { toolInputs, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
const filterHiddenInputs = useMemo(() => commonInputs.filter((item) => true), [commonInputs]);
|
||||
|
||||
return (
|
||||
<NodeCard minW={minW} maxW={maxW} selected={selected} {...data}>
|
||||
{toolInputs.length > 0 && (
|
||||
{isTool && (
|
||||
<>
|
||||
<Container>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={toolInputs} />
|
||||
<RenderToolInput nodeId={nodeId} inputs={inputs} />
|
||||
</Container>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Dispatch, useMemo, useTransition } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
|
||||
import QGSwitch from '@/components/core/app/QGSwitch';
|
||||
import TTSSelect from '@/components/core/app/TTSSelect';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Divider from '../components/Divider';
|
||||
import Container from '../components/Container';
|
||||
import RenderInput from './render/RenderInput';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import {
|
||||
Box,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from './render/NodeCard';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import Container from '../components/Container';
|
||||
import RenderOutput from './render/RenderOutput';
|
||||
import IOTitle from '../components/IOTitle';
|
||||
|
||||
@@ -1,530 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea,
|
||||
Stack
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
EditInputFieldMapType,
|
||||
EditNodeFieldType
|
||||
} from '@fastgpt/global/core/workflow/node/type.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput/index';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
|
||||
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
|
||||
const EmptyTip = dynamic(() => import('@fastgpt/web/components/common/EmptyTip'));
|
||||
|
||||
const defaultValue: EditNodeFieldType = {
|
||||
inputType: FlowNodeInputTypeEnum.reference,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
key: '',
|
||||
label: '',
|
||||
description: '',
|
||||
isToolInput: false,
|
||||
defaultValue: '',
|
||||
maxLength: undefined,
|
||||
max: undefined,
|
||||
min: undefined,
|
||||
editField: {},
|
||||
dynamicParamDefaultValue: {
|
||||
inputType: FlowNodeInputTypeEnum.reference,
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
required: true
|
||||
}
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
editField = {
|
||||
key: true
|
||||
},
|
||||
defaultField,
|
||||
keys = [],
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
editField?: EditInputFieldMapType;
|
||||
defaultField: EditNodeFieldType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const showDynamicInputSelect =
|
||||
!keys.includes(NodeInputKeyEnum.addInputParam) ||
|
||||
defaultField.key === NodeInputKeyEnum.addInputParam;
|
||||
|
||||
const inputTypeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('core.workflow.inputType.Reference'),
|
||||
value: FlowNodeInputTypeEnum.reference,
|
||||
defaultValue: {}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.input'),
|
||||
value: FlowNodeInputTypeEnum.input,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.textarea'),
|
||||
value: FlowNodeInputTypeEnum.textarea,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.JSON Editor'),
|
||||
value: FlowNodeInputTypeEnum.JSONEditor,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.number input'),
|
||||
value: FlowNodeInputTypeEnum.numberInput,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.number
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.switch'),
|
||||
value: FlowNodeInputTypeEnum.switch,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.boolean
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectApp'),
|
||||
value: FlowNodeInputTypeEnum.selectApp,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.selectApp
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectLLMModel'),
|
||||
value: FlowNodeInputTypeEnum.selectLLMModel,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('core.workflow.inputType.selectDataset'),
|
||||
value: FlowNodeInputTypeEnum.selectDataset,
|
||||
defaultValue: {
|
||||
valueType: WorkflowIOValueTypeEnum.selectDataset
|
||||
}
|
||||
},
|
||||
...(showDynamicInputSelect
|
||||
? [
|
||||
{
|
||||
label: t('core.workflow.inputType.dynamicTargetInput'),
|
||||
value: FlowNodeInputTypeEnum.addInputParam,
|
||||
defaultValue: {
|
||||
label: t('core.workflow.inputType.dynamicTargetInput'),
|
||||
valueType: WorkflowIOValueTypeEnum.dynamic,
|
||||
key: NodeInputKeyEnum.addInputParam,
|
||||
required: false
|
||||
}
|
||||
}
|
||||
]
|
||||
: [])
|
||||
],
|
||||
[showDynamicInputSelect, t]
|
||||
);
|
||||
|
||||
const { register, getValues, setValue, handleSubmit, watch } = useForm<EditNodeFieldType>({
|
||||
defaultValues: {
|
||||
...defaultValue,
|
||||
...defaultField,
|
||||
valueType: defaultField.valueType ?? WorkflowIOValueTypeEnum.string
|
||||
}
|
||||
});
|
||||
const inputType = watch('inputType');
|
||||
const valueType = watch('valueType');
|
||||
|
||||
const isToolInput = watch('isToolInput');
|
||||
const maxLength = watch('maxLength');
|
||||
const max = watch('max');
|
||||
const min = watch('min');
|
||||
const defaultInputValueType = watch('dynamicParamDefaultValue.valueType');
|
||||
|
||||
const showKeyInput = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.addInputParam) return false;
|
||||
|
||||
return editField.key;
|
||||
}, [editField.key, inputType]);
|
||||
|
||||
const showInputTypeSelect = useMemo(() => {
|
||||
return editField.inputType;
|
||||
}, [editField.inputType]);
|
||||
|
||||
const showDescriptionInput = useMemo(() => {
|
||||
return editField.description;
|
||||
}, [editField.description]);
|
||||
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
if (!editField.valueType) return false;
|
||||
if (inputType !== FlowNodeInputTypeEnum.reference) return false;
|
||||
|
||||
return true;
|
||||
}, [editField.valueType, inputType]);
|
||||
|
||||
// input type config
|
||||
const showToolInput = useMemo(() => {
|
||||
return inputType === FlowNodeInputTypeEnum.reference;
|
||||
}, [inputType]);
|
||||
|
||||
const showDefaultValue = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.input) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.textarea) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.JSONEditor) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.numberInput) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.switch) return true;
|
||||
|
||||
return false;
|
||||
}, [inputType]);
|
||||
|
||||
const showMaxLenInput = useMemo(() => {
|
||||
if (inputType === FlowNodeInputTypeEnum.input) return true;
|
||||
if (inputType === FlowNodeInputTypeEnum.textarea) return true;
|
||||
|
||||
return false;
|
||||
}, [inputType]);
|
||||
|
||||
const showMinMaxInput = useMemo(
|
||||
() => inputType === FlowNodeInputTypeEnum.numberInput,
|
||||
[inputType]
|
||||
);
|
||||
|
||||
const showDynamicInput = useMemo(() => {
|
||||
return inputType === FlowNodeInputTypeEnum.addInputParam;
|
||||
}, [inputType]);
|
||||
|
||||
const slicedTypeMap = Object.values(FlowValueTypeMap).slice(0, -1);
|
||||
|
||||
const dataTypeSelectList = slicedTypeMap.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: EditNodeFieldType) => {
|
||||
data.key = data?.key?.trim();
|
||||
// add default value
|
||||
const inputTypeConfig = inputTypeList.find((item) => item.value === data.inputType);
|
||||
if (inputTypeConfig?.defaultValue) {
|
||||
data.label = data.key;
|
||||
for (const key in inputTypeConfig.defaultValue) {
|
||||
// @ts-ignore
|
||||
data[key] = inputTypeConfig.defaultValue[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.key) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Name Cannot Be Empty')
|
||||
});
|
||||
}
|
||||
|
||||
// create check key
|
||||
if (!defaultField.key && keys.includes(data.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Already Exist')
|
||||
});
|
||||
}
|
||||
// edit check repeat key
|
||||
if (defaultField.key && defaultField.key !== data.key && keys.includes(data.key)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('core.module.edit.Field Already Exist')
|
||||
});
|
||||
}
|
||||
if (showValueTypeSelect && !data.valueType) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: '数据类型不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
changeKey: !keys.includes(data.key)
|
||||
});
|
||||
},
|
||||
[defaultField.key, inputTypeList, keys, onSubmit, showValueTypeSelect, 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/workflow/extract.png"
|
||||
title={t('core.module.edit.Field Edit')}
|
||||
maxW={['90vw', showInputTypeSelect ? '800px' : '400px']}
|
||||
w={'100%'}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalBody overflow={'visible'}>
|
||||
<Flex gap={8} flexDirection={['column', 'row']}>
|
||||
<Stack flex={1} gap={5}>
|
||||
{showInputTypeSelect && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Input Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect
|
||||
list={inputTypeList}
|
||||
value={inputType}
|
||||
onchange={(e: string) => {
|
||||
const type = e as FlowNodeInputTypeEnum;
|
||||
|
||||
setValue('inputType', type);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{showValueTypeSelect && !showInputTypeSelect && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect
|
||||
w={'full'}
|
||||
list={dataTypeSelectList}
|
||||
value={valueType}
|
||||
onchange={(e: string) => {
|
||||
const type = e as WorkflowIOValueTypeEnum;
|
||||
setValue('valueType', type);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{showKeyInput && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Field Name')}</FormLabel>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showDescriptionInput && (
|
||||
<Box alignItems={'flex-start'}>
|
||||
<FormLabel flex={'0 0 70px'} mb={'1px'}>
|
||||
{t('core.module.Field Description')}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
placeholder={
|
||||
isToolInput ? t('core.module.Plugin tool Description') : t('common.choosable')
|
||||
}
|
||||
rows={5}
|
||||
{...register('description', { required: isToolInput ? true : false })}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
{/* input type config */}
|
||||
{showInputTypeSelect && (
|
||||
<Stack flex={1} gap={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{workflowT('Field required')}</FormLabel>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
{showToolInput && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>工具参数</FormLabel>
|
||||
<Switch {...register('isToolInput')} />
|
||||
</Flex>
|
||||
)}
|
||||
{showValueTypeSelect && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect
|
||||
w={'full'}
|
||||
list={dataTypeSelectList}
|
||||
value={valueType}
|
||||
onchange={(e: string) => {
|
||||
const type = e as WorkflowIOValueTypeEnum;
|
||||
setValue('valueType', type);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{showDefaultValue && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Default Value')}</FormLabel>
|
||||
{inputType === FlowNodeInputTypeEnum.numberInput && (
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
max={max}
|
||||
min={min}
|
||||
type={'number'}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.input && (
|
||||
<Input bg={'myGray.50'} maxLength={maxLength} {...register('defaultValue')} />
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.textarea && (
|
||||
<Textarea
|
||||
bg={'myGray.50'}
|
||||
maxLength={maxLength}
|
||||
{...register('defaultValue')}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.JSONEditor && (
|
||||
<JsonEditor
|
||||
resize
|
||||
w={'full'}
|
||||
onChange={(e) => {
|
||||
setValue('defaultValue', e);
|
||||
}}
|
||||
defaultValue={String(getValues('defaultValue'))}
|
||||
/>
|
||||
)}
|
||||
{inputType === FlowNodeInputTypeEnum.switch && (
|
||||
<Switch {...register('defaultValue')} />
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
{showMaxLenInput && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Max Length')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
placeholder={t('core.module.Max Length placeholder')}
|
||||
value={maxLength}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('maxLength', e);
|
||||
}}
|
||||
// {...register('maxLength')}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
{showMinMaxInput && (
|
||||
<>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Max Value')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
value={watch('max')}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('max', e);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Min Value')}</FormLabel>
|
||||
<MyNumberInput
|
||||
flex={'1 0 0'}
|
||||
bg={'myGray.50'}
|
||||
value={watch('min')}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore
|
||||
setValue('min', e);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{showDynamicInput && (
|
||||
<Stack gap={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Input Type')}</FormLabel>
|
||||
<Box flex={1} fontWeight={'bold'}>
|
||||
{t('core.workflow.inputType.Reference')}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect
|
||||
list={dataTypeSelectList}
|
||||
value={defaultInputValueType}
|
||||
onchange={(e) => {
|
||||
setValue(
|
||||
'dynamicParamDefaultValue.valueType',
|
||||
e as WorkflowIOValueTypeEnum
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{t('core.workflow.inputType.Required')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<Switch {...register('dynamicParamDefaultValue.required')} />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
)}
|
||||
{!showToolInput &&
|
||||
!showValueTypeSelect &&
|
||||
!showDefaultValue &&
|
||||
!showMaxLenInput &&
|
||||
!showMinMaxInput &&
|
||||
!showDynamicInput && <EmptyTip text={t('core.module.No Config Tips')} />}
|
||||
</Stack>
|
||||
)}
|
||||
</Flex>
|
||||
</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);
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Box, Button, Card, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -111,7 +111,7 @@ const NodeCard = (props: Props) => {
|
||||
const onClickSyncVersion = useCallback(async () => {
|
||||
try {
|
||||
if (!node || !template) return;
|
||||
if (node?.flowNodeType === 'pluginModule') {
|
||||
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) {
|
||||
if (!node.pluginId) return;
|
||||
onResetNode({
|
||||
id: nodeId,
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Stack,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
CustomFieldConfigType,
|
||||
FlowNodeInputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMount } from 'ahooks';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '../../../../context';
|
||||
|
||||
const FieldModal = ({
|
||||
customInputConfig,
|
||||
defaultInput,
|
||||
keys,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
customInputConfig: CustomFieldConfigType;
|
||||
defaultInput: FlowNodeInputItemType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: FlowNodeInputItemType; isChangeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT, commonT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const isEdit = !!defaultInput.key;
|
||||
|
||||
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
|
||||
defaultValues: defaultInput
|
||||
});
|
||||
const inputType = FlowNodeInputTypeEnum.reference;
|
||||
|
||||
// value type select
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
if (!customInputConfig.selectValueTypeList || customInputConfig.selectValueTypeList.length <= 1)
|
||||
return false;
|
||||
if (inputType === FlowNodeInputTypeEnum.reference) return true;
|
||||
|
||||
return false;
|
||||
}, [customInputConfig.selectValueTypeList, inputType]);
|
||||
const valueTypeSelectLit = useMemo(() => {
|
||||
if (!customInputConfig.selectValueTypeList) return [];
|
||||
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap).map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
return dataTypeSelectList.filter((item) =>
|
||||
customInputConfig.selectValueTypeList?.includes(item.value)
|
||||
);
|
||||
}, [customInputConfig.selectValueTypeList, t]);
|
||||
const valueType = watch('valueType');
|
||||
useMount(() => {
|
||||
if (
|
||||
customInputConfig.selectValueTypeList &&
|
||||
customInputConfig.selectValueTypeList.length > 0 &&
|
||||
!valueType
|
||||
) {
|
||||
setValue('valueType', customInputConfig.selectValueTypeList[0]);
|
||||
}
|
||||
});
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: FlowNodeInputItemType) => {
|
||||
const isChangeKey = defaultInput.key !== data.key;
|
||||
|
||||
if (keys.includes(data.key)) {
|
||||
// 只要编辑状态,且未改变key,就不提示
|
||||
if (!isEdit || isChangeKey) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: workflowT('Field Name already exists')
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
data.key = data?.key?.trim();
|
||||
data.label = data.key;
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
isChangeKey
|
||||
});
|
||||
onClose();
|
||||
},
|
||||
[defaultInput.key, isEdit, keys, onClose, onSubmit, toast, workflowT]
|
||||
);
|
||||
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/workflow/extract.png"
|
||||
title={isEdit ? workflowT('Edit input') : workflowT('Add new input')}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalBody w={'100%'} overflow={'auto'} display={'flex'} flexDirection={['column', 'row']}>
|
||||
<Stack w={'100%'} spacing={3}>
|
||||
{showValueTypeSelect && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{commonT('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect<WorkflowIOValueTypeEnum>
|
||||
w={'full'}
|
||||
list={valueTypeSelectLit}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{/* key */}
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'} required>
|
||||
{t('core.module.Field Name')}
|
||||
</FormLabel>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
{customInputConfig.showDescription && (
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{workflowT('Input description')}</FormLabel>
|
||||
<Textarea bg={'myGray.50'} {...register('description', {})} />
|
||||
</Flex>
|
||||
)}
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={3}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{commonT('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmitSuccess, onSubmitError)}>
|
||||
{commonT('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FieldModal;
|
||||
|
||||
export const defaultInput: FlowNodeInputItemType = {
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
canEdit: true,
|
||||
key: '',
|
||||
label: ''
|
||||
};
|
||||
@@ -1,20 +1,15 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
import NodeInputSelect from '@fastgpt/web/components/core/workflow/NodeInputSelect';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import ValueTypeLabel from '../ValueTypeLabel';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
const FieldEditModal = dynamic(() => import('../FieldEditModal'));
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@@ -26,18 +21,7 @@ const InputLabel = ({ nodeId, input }: Props) => {
|
||||
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const {
|
||||
description,
|
||||
toolDescription,
|
||||
required,
|
||||
label,
|
||||
selectedTypeIndex,
|
||||
renderTypeList,
|
||||
valueType,
|
||||
canEdit,
|
||||
key
|
||||
} = input;
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
const { description, required, label, selectedTypeIndex, renderTypeList, valueType } = input;
|
||||
|
||||
const onChangeRenderType = useCallback(
|
||||
(e: string) => {
|
||||
@@ -68,63 +52,12 @@ const InputLabel = ({ nodeId, input }: Props) => {
|
||||
fontWeight={'medium'}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{required && (
|
||||
<Box position={'absolute'} left={-2} top={-1} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
{t(label)}
|
||||
<FormLabel required={required}>{t(label)}</FormLabel>
|
||||
{description && <QuestionTip ml={1} label={t(description)}></QuestionTip>}
|
||||
</Flex>
|
||||
{/* value type */}
|
||||
{renderType === FlowNodeInputTypeEnum.reference && <ValueTypeLabel valueType={valueType} />}
|
||||
{/* edit config */}
|
||||
{canEdit && (
|
||||
<>
|
||||
{input.editField && Object.keys(input.editField).length > 0 && (
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...input,
|
||||
inputType: renderTypeList[0],
|
||||
valueType: valueType,
|
||||
key,
|
||||
label,
|
||||
description,
|
||||
isToolInput: !!toolDescription
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key: key
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delOutput',
|
||||
key: key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* input type select */}
|
||||
{renderTypeList && renderTypeList.length > 1 && (
|
||||
<Box ml={2}>
|
||||
@@ -135,67 +68,16 @@ const InputLabel = ({ nodeId, input }: Props) => {
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!!editField?.key && (
|
||||
<FieldEditModal
|
||||
editField={input.editField}
|
||||
keys={[editField.key]}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={({ data, changeKey }) => {
|
||||
if (!data.inputType || !data.key || !data.label || !editField.key) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...input,
|
||||
renderTypeList: [data.inputType],
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
required: data.required,
|
||||
label: data.label,
|
||||
description: data.description,
|
||||
toolDescription: data.isToolInput ? data.description : undefined,
|
||||
maxLength: data.maxLength,
|
||||
value: data.defaultValue,
|
||||
max: data.max,
|
||||
min: data.min
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: newInput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: newInput.key,
|
||||
value: newInput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}, [
|
||||
canEdit,
|
||||
description,
|
||||
editField,
|
||||
input,
|
||||
key,
|
||||
label,
|
||||
nodeId,
|
||||
onChangeNode,
|
||||
onChangeRenderType,
|
||||
renderTypeList,
|
||||
required,
|
||||
selectedTypeIndex,
|
||||
t,
|
||||
toolDescription,
|
||||
valueType
|
||||
]);
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ const RenderList: {
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.addInputParam],
|
||||
Component: dynamic(() => import('./templates/AddInputParam'))
|
||||
Component: dynamic(() => import('./templates/DynamicInputs/index'))
|
||||
},
|
||||
{
|
||||
types: [FlowNodeInputTypeEnum.JSONEditor],
|
||||
@@ -76,10 +76,7 @@ type Props = {
|
||||
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
|
||||
const copyInputs = useMemo(() => JSON.stringify(flowInputList), [flowInputList]);
|
||||
const filterInputs = useMemo(() => {
|
||||
const parseSortInputs = JSON.parse(copyInputs) as FlowNodeInputItemType[];
|
||||
return parseSortInputs.filter((input) => {
|
||||
return true;
|
||||
});
|
||||
return JSON.parse(copyInputs) as FlowNodeInputItemType[];
|
||||
}, [copyInputs]);
|
||||
|
||||
const memoCustomComponent = useMemo(() => CustomComponent || {}, [CustomComponent]);
|
||||
@@ -87,6 +84,8 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
const Render = useMemo(() => {
|
||||
return filterInputs.map((input) => {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
const isDynamic = !!input.canEdit;
|
||||
|
||||
const RenderComponent = (() => {
|
||||
if (renderType === FlowNodeInputTypeEnum.custom && memoCustomComponent[input.key]) {
|
||||
return <>{memoCustomComponent[input.key]({ ...input })}</>;
|
||||
@@ -98,7 +97,7 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
return <Component inputs={filterInputs} item={input} nodeId={nodeId} />;
|
||||
})();
|
||||
|
||||
return renderType !== FlowNodeInputTypeEnum.hidden ? (
|
||||
return renderType !== FlowNodeInputTypeEnum.hidden && !isDynamic ? (
|
||||
<Box key={input.key} _notLast={{ mb }} position={'relative'}>
|
||||
{!!input.label && !hideLabelTypeList.includes(renderType) && (
|
||||
<InputLabel nodeId={nodeId} input={input} />
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import Reference from './Reference';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import { AppContext } from '@/pages/app/detail/components/context';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const AddInputParam = (props: RenderInputProps) => {
|
||||
const { item, inputs, nodeId } = props;
|
||||
const { t } = useTranslation();
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const inputValue = useMemo(() => (item.value || []) as FlowNodeInputItemType[], [item.value]);
|
||||
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
const inputIndex = useMemo(
|
||||
() => inputs?.findIndex((input) => input.key === item.key),
|
||||
[inputs, item.key]
|
||||
);
|
||||
|
||||
const onAddField = useCallback(
|
||||
({ data }: { data: EditNodeFieldType }) => {
|
||||
if (!data.key) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.label || '',
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
required: data.required,
|
||||
description: data.description,
|
||||
canEdit: true,
|
||||
editField: item.editField
|
||||
};
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
index: inputIndex ? inputIndex + 1 : 1,
|
||||
value: newInput
|
||||
});
|
||||
setEditField(undefined);
|
||||
},
|
||||
[inputIndex, item, nodeId, onChangeNode]
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
fontWeight={'medium'}
|
||||
color={'myGray.600'}
|
||||
>
|
||||
{t('core.workflow.Custom variable')}
|
||||
{item.description && <QuestionTip ml={1} label={t(item.description)} />}
|
||||
</Flex>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => setEditField(item.dynamicParamDefaultValue ?? {})}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
{appDetail.type === AppTypeEnum.plugin && (
|
||||
<Box mt={1}>
|
||||
<Reference {...props} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
editField={item.editField}
|
||||
defaultField={editField}
|
||||
keys={inputValue.map((input) => input.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onAddField}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
appDetail.type,
|
||||
editField,
|
||||
inputValue,
|
||||
item.description,
|
||||
item.dynamicParamDefaultValue,
|
||||
item.editField,
|
||||
onAddField,
|
||||
props,
|
||||
t
|
||||
]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
|
||||
export default React.memo(AddInputParam);
|
||||
@@ -0,0 +1,229 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../../type';
|
||||
import { Box, Button, Flex, HStack } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { FlowNodeInputItemType, ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import { defaultInput } from '../../FieldEditModal';
|
||||
import { getInputComponentProps } from '@fastgpt/global/core/workflow/node/io/utils';
|
||||
import { VARIABLE_NODE_ID } from '@fastgpt/global/core/workflow/constants';
|
||||
import { ReferSelector, useReference } from '../Reference';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import ValueTypeLabel from '../../../ValueTypeLabel';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { isWorkflowStartOutput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../../FieldEditModal'));
|
||||
|
||||
const DynamicInputs = (props: RenderInputProps) => {
|
||||
const { item, inputs = [], nodeId } = props;
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const dynamicInputs = useMemo(() => inputs.filter((item) => item.canEdit), [inputs]);
|
||||
const keys = useMemo(() => {
|
||||
return inputs.map((input) => input.key);
|
||||
}, [inputs]);
|
||||
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
const onAddField = useCallback(
|
||||
({ data }: { data: FlowNodeInputItemType }) => {
|
||||
if (!data.key) return;
|
||||
|
||||
const newInput: FlowNodeInputItemType = {
|
||||
...data,
|
||||
required: true
|
||||
};
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addInput',
|
||||
value: newInput
|
||||
});
|
||||
},
|
||||
[nodeId, onChangeNode]
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<Box borderBottom={'base'} pb={3}>
|
||||
<HStack className="nodrag" cursor={'default'} position={'relative'}>
|
||||
<HStack spacing={1} position={'relative'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
<Box>{item.label || workflowT('Custom input')}</Box>
|
||||
{item.description && <QuestionTip label={t(item.description)} />}
|
||||
</HStack>
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...defaultInput,
|
||||
...getInputComponentProps(item)
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</HStack>
|
||||
{/* field render */}
|
||||
<Box mt={2}>
|
||||
{dynamicInputs.map((children) => (
|
||||
<Box key={children.key} _notLast={{ mb: 3 }}>
|
||||
<Reference {...props} inputChildren={children} />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{!!editField && !!item.customInputConfig && (
|
||||
<FieldEditModal
|
||||
defaultInput={editField}
|
||||
customInputConfig={item.customInputConfig}
|
||||
keys={keys}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onAddField}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}, [editField, dynamicInputs, item, keys, onAddField, props, t, workflowT]);
|
||||
|
||||
return Render;
|
||||
};
|
||||
|
||||
export default React.memo(DynamicInputs);
|
||||
|
||||
function Reference({
|
||||
inputChildren,
|
||||
...props
|
||||
}: RenderInputProps & {
|
||||
inputChildren: FlowNodeInputItemType;
|
||||
}) {
|
||||
const { nodeId, inputs = [], item } = props;
|
||||
const { t } = useTranslation();
|
||||
const { workflowT } = useI18n();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
type: 'delete',
|
||||
content: workflowT('Confirm delete field tip')
|
||||
});
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
|
||||
const keys = useMemo(() => {
|
||||
return inputs.map((input) => input.key);
|
||||
}, [inputs]);
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: ReferenceValueProps) => {
|
||||
const workflowStartNode = nodeList.find(
|
||||
(node) => node.flowNodeType === FlowNodeTypeEnum.workflowStart
|
||||
);
|
||||
|
||||
const newValue =
|
||||
e[0] === workflowStartNode?.id && !isWorkflowStartOutput(e[1])
|
||||
? [VARIABLE_NODE_ID, e[1]]
|
||||
: e;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: inputChildren.key,
|
||||
value: {
|
||||
...inputChildren,
|
||||
value: newValue
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputChildren, nodeId, nodeList, onChangeNode]
|
||||
);
|
||||
|
||||
const { referenceList, formatValue } = useReference({
|
||||
nodeId,
|
||||
valueType: inputChildren.valueType,
|
||||
value: inputChildren.value
|
||||
});
|
||||
|
||||
const onUpdateField = useCallback(
|
||||
({ data }: { data: FlowNodeInputItemType }) => {
|
||||
if (!data.key) return;
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceInput',
|
||||
key: inputChildren.key,
|
||||
value: data
|
||||
});
|
||||
},
|
||||
[inputChildren.key, nodeId, onChangeNode]
|
||||
);
|
||||
const onDel = useCallback(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'delInput',
|
||||
key: inputChildren.key
|
||||
});
|
||||
}, [inputChildren.key, nodeId, onChangeNode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex alignItems={'center'} mb={1}>
|
||||
<FormLabel required={inputChildren.required}>{inputChildren.label}</FormLabel>
|
||||
{inputChildren.description && (
|
||||
<QuestionTip ml={1} label={inputChildren.description}></QuestionTip>
|
||||
)}
|
||||
{/* value */}
|
||||
<ValueTypeLabel valueType={inputChildren.valueType} />
|
||||
|
||||
<MyIcon
|
||||
name={'common/settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() => setEditField(inputChildren)}
|
||||
/>
|
||||
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
color={'myGray.500'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={openConfirm(onDel)}
|
||||
/>
|
||||
</Flex>
|
||||
<ReferSelector
|
||||
placeholder={t(inputChildren.referencePlaceholder || '选择引用变量')}
|
||||
list={referenceList}
|
||||
value={formatValue}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
|
||||
{!!editField && !!item.customInputConfig && (
|
||||
<FieldEditModal
|
||||
defaultInput={editField}
|
||||
customInputConfig={item.customInputConfig}
|
||||
keys={keys}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onUpdateField}
|
||||
/>
|
||||
)}
|
||||
<ConfirmModal />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,8 @@ const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const getNodeDynamicInputs = useContextSelector(WorkflowContext, (v) => v.getNodeDynamicInputs);
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
// get variable
|
||||
@@ -23,16 +25,9 @@ const JsonEditor = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
t
|
||||
});
|
||||
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.canEdit)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
const nodeVariables = formatEditorVariablePickerIcon(getNodeDynamicInputs(nodeId));
|
||||
|
||||
return [...globalVariables, ...moduleVariables];
|
||||
return [...globalVariables, ...nodeVariables];
|
||||
}, [inputs, nodeList]);
|
||||
|
||||
const update = useCallback(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
import { SelectAppItemType } from '@fastgpt/global/core/workflow/template/system/runApp/type';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import SelectAppModal from '../../../../SelectAppModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -13,6 +13,8 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const getNodeDynamicInputs = useContextSelector(WorkflowContext, (v) => v.getNodeDynamicInputs);
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
// get variable
|
||||
@@ -23,16 +25,14 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
t
|
||||
});
|
||||
|
||||
const moduleVariables = formatEditorVariablePickerIcon(
|
||||
inputs
|
||||
.filter((input) => input.canEdit)
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
const nodeVariables = formatEditorVariablePickerIcon(
|
||||
getNodeDynamicInputs(nodeId).map((item) => ({
|
||||
key: item.key,
|
||||
label: item.label
|
||||
}))
|
||||
);
|
||||
|
||||
return [...globalVariables, ...moduleVariables];
|
||||
return [...globalVariables, ...nodeVariables];
|
||||
}, [nodeList, inputs, t]);
|
||||
|
||||
const onChange = useCallback(
|
||||
@@ -49,6 +49,7 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
|
||||
},
|
||||
[item, nodeId, onChangeNode]
|
||||
);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
return (
|
||||
<PromptEditor
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowValueTypeMap
|
||||
} from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Stack,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import {
|
||||
CustomFieldConfigType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useMount } from 'ahooks';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
|
||||
const FieldModal = ({
|
||||
customFieldConfig,
|
||||
defaultValue,
|
||||
keys,
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
customFieldConfig: CustomFieldConfigType;
|
||||
defaultValue: FlowNodeOutputItemType;
|
||||
keys: string[];
|
||||
onClose: () => void;
|
||||
onSubmit: (e: { data: FlowNodeOutputItemType; isChangeKey: boolean }) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { workflowT, commonT } = useI18n();
|
||||
const { toast } = useToast();
|
||||
const isEdit = !!defaultValue.key;
|
||||
|
||||
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeOutputItemType>({
|
||||
defaultValues: defaultValue
|
||||
});
|
||||
|
||||
const valueType = watch('valueType');
|
||||
// value type select
|
||||
const showValueTypeSelect = useMemo(() => {
|
||||
if (!customFieldConfig.selectValueTypeList || customFieldConfig.selectValueTypeList.length <= 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}, [customFieldConfig.selectValueTypeList]);
|
||||
const valueTypeSelectLit = useMemo(() => {
|
||||
if (!customFieldConfig.selectValueTypeList) return [];
|
||||
|
||||
const dataTypeSelectList = Object.values(FlowValueTypeMap)
|
||||
.slice(0, -1)
|
||||
.map((item) => ({
|
||||
label: t(item.label),
|
||||
value: item.value
|
||||
}));
|
||||
|
||||
return dataTypeSelectList.filter((item) =>
|
||||
customFieldConfig.selectValueTypeList?.includes(item.value)
|
||||
);
|
||||
}, [customFieldConfig.selectValueTypeList, t]);
|
||||
|
||||
const onSubmitSuccess = useCallback(
|
||||
(data: FlowNodeOutputItemType) => {
|
||||
const isChangeKey = defaultValue.key !== data.key;
|
||||
|
||||
if (keys.includes(data.key)) {
|
||||
// 只要编辑状态,且未改变key,就不提示
|
||||
if (!isEdit || isChangeKey) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: workflowT('Field Name already exists')
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
data.id = data.id || getNanoid();
|
||||
data.key = data?.key?.trim();
|
||||
data.label = data.key;
|
||||
|
||||
onSubmit({
|
||||
data,
|
||||
isChangeKey
|
||||
});
|
||||
onClose();
|
||||
},
|
||||
[defaultValue.key, isEdit, keys, onClose, onSubmit, toast, workflowT]
|
||||
);
|
||||
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/workflow/extract.png"
|
||||
title={isEdit ? workflowT('Edit input') : workflowT('Add new input')}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalBody w={'100%'} overflow={'auto'} display={'flex'} flexDirection={['column', 'row']}>
|
||||
<Stack w={'100%'} spacing={3}>
|
||||
{showValueTypeSelect && (
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{commonT('core.module.Data Type')}</FormLabel>
|
||||
<Box flex={1}>
|
||||
<MySelect<WorkflowIOValueTypeEnum>
|
||||
w={'full'}
|
||||
list={valueTypeSelectLit}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{/* key */}
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'} required>
|
||||
{t('core.module.Field Name')}
|
||||
</FormLabel>
|
||||
<Input
|
||||
bg={'myGray.50'}
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
{customFieldConfig.showDescription && (
|
||||
<Flex mt={3} alignItems={'center'}>
|
||||
<FormLabel flex={'0 0 70px'}>{workflowT('Input description')}</FormLabel>
|
||||
<Textarea bg={'myGray.50'} {...register('description', {})} />
|
||||
</Flex>
|
||||
)}
|
||||
</Stack>
|
||||
</ModalBody>
|
||||
<ModalFooter gap={3}>
|
||||
<Button variant={'whiteBase'} onClick={onClose}>
|
||||
{commonT('common.Close')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmitSuccess, onSubmitError)}>
|
||||
{commonT('common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FieldModal;
|
||||
|
||||
export const defaultOutput: FlowNodeOutputItemType = {
|
||||
id: '',
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.dynamic,
|
||||
key: '',
|
||||
label: ''
|
||||
};
|
||||
@@ -7,13 +7,16 @@ import OutputLabel from './Label';
|
||||
import { RenderOutputProps } from './type';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import VariableTable from '../VariableTable';
|
||||
import { EditNodeFieldType } from '@fastgpt/global/core/workflow/node/type';
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import VariableTable from '../../NodePluginIO/VariableTable';
|
||||
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { defaultOutput } from './FieldEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./FieldEditModal'));
|
||||
|
||||
const RenderList: {
|
||||
types: FlowNodeOutputTypeEnum[];
|
||||
@@ -32,25 +35,40 @@ const RenderOutput = ({
|
||||
|
||||
const outputString = useMemo(() => JSON.stringify(flowOutputList), [flowOutputList]);
|
||||
const copyOutputs = useMemo(() => {
|
||||
const parseOutputs = JSON.parse(outputString) as FlowNodeOutputItemType[];
|
||||
|
||||
return parseOutputs;
|
||||
return JSON.parse(outputString) as FlowNodeOutputItemType[];
|
||||
}, [outputString]);
|
||||
|
||||
const [createField, setCreateField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<EditNodeFieldType>();
|
||||
const [editField, setEditField] = useState<FlowNodeOutputItemType>();
|
||||
|
||||
const RenderDynamicOutputs = useMemo(() => {
|
||||
const dynamicOutputs = copyOutputs.filter(
|
||||
(item) => item.type === FlowNodeOutputTypeEnum.dynamic
|
||||
);
|
||||
|
||||
const addOutput = dynamicOutputs.find((item) => item.key === NodeOutputKeyEnum.addOutputParam);
|
||||
const filterAddOutput = dynamicOutputs.filter(
|
||||
(item) => item.key !== NodeOutputKeyEnum.addOutputParam
|
||||
);
|
||||
|
||||
return dynamicOutputs.length === 0 || !addOutput ? null : (
|
||||
const onSubmit = ({ data }: { data: FlowNodeOutputItemType }) => {
|
||||
if (!editField) return;
|
||||
|
||||
if (editField.key) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addOutput',
|
||||
value: data
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return !addOutput?.customFieldConfig ? null : (
|
||||
<Box mb={5}>
|
||||
<Flex
|
||||
mb={2}
|
||||
@@ -62,7 +80,7 @@ const RenderOutput = ({
|
||||
<Box position={'relative'} fontWeight={'medium'}>
|
||||
{t('core.workflow.Custom outputs')}
|
||||
</Box>
|
||||
<QuestionTip ml={1} label={addOutput.description} />
|
||||
{addOutput.description && <QuestionTip ml={1} label={addOutput.description} />}
|
||||
<Box flex={'1 0 0'} />
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
@@ -70,86 +88,22 @@ const RenderOutput = ({
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
onClick={() => {
|
||||
setCreateField({});
|
||||
setEditField(defaultOutput);
|
||||
}}
|
||||
>
|
||||
{t('common.Add New')}
|
||||
</Button>
|
||||
</Flex>
|
||||
<VariableTable
|
||||
fieldEditType={addOutput.editField}
|
||||
keys={copyOutputs.map((output) => output.key)}
|
||||
onCloseFieldEdit={() => {
|
||||
setCreateField(undefined);
|
||||
setEditField(undefined);
|
||||
}}
|
||||
variables={filterAddOutput.map((output) => ({
|
||||
label: output.label || '-',
|
||||
type: output.valueType ? t(FlowValueTypeMap[output.valueType]?.label) : '-',
|
||||
key: output.key
|
||||
}))}
|
||||
createField={createField}
|
||||
onCreate={({ data }) => {
|
||||
if (!data.key) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
id: getNanoid(),
|
||||
type: FlowNodeOutputTypeEnum.dynamic,
|
||||
key: data.key,
|
||||
valueType: data.valueType,
|
||||
label: data.key
|
||||
};
|
||||
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'addOutput',
|
||||
value: newOutput
|
||||
});
|
||||
setCreateField(undefined);
|
||||
}}
|
||||
editField={editField}
|
||||
onStartEdit={(e) => {
|
||||
const output = copyOutputs.find((output) => output.key === e);
|
||||
onEdit={(key) => {
|
||||
const output = copyOutputs.find((output) => output.key === key);
|
||||
if (!output) return;
|
||||
setEditField({
|
||||
valueType: output.valueType,
|
||||
required: output.required,
|
||||
key: output.key,
|
||||
label: output.label,
|
||||
description: output.description
|
||||
});
|
||||
}}
|
||||
onEdit={({ data, changeKey }) => {
|
||||
if (!data.key || !editField?.key) return;
|
||||
|
||||
const output = copyOutputs.find((output) => output.key === editField.key);
|
||||
|
||||
const newOutput: FlowNodeOutputItemType = {
|
||||
...(output as FlowNodeOutputItemType),
|
||||
valueType: data.valueType,
|
||||
key: data.key,
|
||||
label: data.label,
|
||||
description: data.description
|
||||
};
|
||||
|
||||
if (changeKey) {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: newOutput
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateOutput',
|
||||
key: newOutput.key,
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
setEditField(output);
|
||||
}}
|
||||
onDelete={(key) => {
|
||||
onChangeNode({
|
||||
@@ -159,9 +113,19 @@ const RenderOutput = ({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
customFieldConfig={addOutput?.customFieldConfig}
|
||||
defaultValue={editField}
|
||||
keys={copyOutputs.map((output) => output.key)}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={onSubmit}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}, [copyOutputs, createField, editField, nodeId, onChangeNode, t]);
|
||||
}, [copyOutputs, editField, nodeId, onChangeNode, t]);
|
||||
|
||||
const RenderCommonOutputs = useMemo(() => {
|
||||
const renderOutputs = copyOutputs.filter(
|
||||
@@ -172,14 +136,14 @@ const RenderOutput = ({
|
||||
<>
|
||||
{renderOutputs.map((output) => {
|
||||
return output.label ? (
|
||||
<Box key={output.key} _notLast={{ mb: 5 }} position={'relative'}>
|
||||
{output.required && (
|
||||
<Box position={'absolute'} left={'-6px'} top={-1} color={'red.600'}>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
<FormLabel
|
||||
key={output.key}
|
||||
required={output.required}
|
||||
_notLast={{ mb: 5 }}
|
||||
position={'relative'}
|
||||
>
|
||||
<OutputLabel nodeId={nodeId} output={output} />
|
||||
</Box>
|
||||
</FormLabel>
|
||||
) : null;
|
||||
})}
|
||||
</>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { defaultEditFormData } from './constants';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -21,6 +20,8 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
|
||||
const EditFieldModal = ({
|
||||
defaultValue = defaultEditFormData,
|
||||
@@ -38,8 +39,11 @@ const EditFieldModal = ({
|
||||
|
||||
const { mutate: onclickSubmit } = useRequest({
|
||||
mutationFn: async (e: FlowNodeInputItemType) => {
|
||||
e.key = e.key.trim();
|
||||
|
||||
const inputConfig: FlowNodeInputItemType = {
|
||||
...e,
|
||||
description: e.toolDescription,
|
||||
label: e.key
|
||||
};
|
||||
if (defaultValue.key) {
|
||||
@@ -136,3 +140,17 @@ const EditFieldModal = ({
|
||||
};
|
||||
|
||||
export default React.memo(EditFieldModal);
|
||||
|
||||
export const defaultEditFormData: FlowNodeInputItemType = {
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
key: '',
|
||||
label: '',
|
||||
toolDescription: '',
|
||||
required: true,
|
||||
canEdit: true,
|
||||
customInputConfig: {
|
||||
selectValueTypeList: Object.values(fnValueTypeSelect).map((item) => item.value),
|
||||
showDescription: true
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
|
||||
export const defaultEditFormData: FlowNodeInputItemType = {
|
||||
valueType: WorkflowIOValueTypeEnum.string,
|
||||
renderTypeList: [FlowNodeInputTypeEnum.reference],
|
||||
key: '',
|
||||
label: '',
|
||||
toolDescription: '',
|
||||
required: true,
|
||||
canEdit: true,
|
||||
editField: {
|
||||
key: true,
|
||||
description: true
|
||||
}
|
||||
};
|
||||
|
||||
export default function Dom() {
|
||||
return <></>;
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type {
|
||||
FlowNodeInputItemType,
|
||||
FlowNodeOutputItemType
|
||||
} from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -13,52 +10,56 @@ import {
|
||||
Th,
|
||||
Td,
|
||||
TableContainer,
|
||||
Flex
|
||||
Flex,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { defaultEditFormData } from './constants';
|
||||
import { defaultEditFormData } from './EditFieldModal';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
|
||||
import IOTitle from '../../../components/IOTitle';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
const EditFieldModal = dynamic(() => import('./EditFieldModal'));
|
||||
|
||||
const RenderToolInput = ({
|
||||
nodeId,
|
||||
inputs,
|
||||
canEdit = false
|
||||
inputs
|
||||
}: {
|
||||
nodeId: string;
|
||||
inputs: FlowNodeInputItemType[];
|
||||
canEdit?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
|
||||
const { toolInputs } = splitToolInputs(inputs, nodeId);
|
||||
|
||||
const dynamicInput = useMemo(() => {
|
||||
return inputs.find((item) => item.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam);
|
||||
}, [inputs]);
|
||||
|
||||
const [editField, setEditField] = useState<FlowNodeInputItemType>();
|
||||
|
||||
return (
|
||||
<>
|
||||
{canEdit && (
|
||||
<Flex mb={2} alignItems={'center'}>
|
||||
<Box flex={'1 0 0'} fontWeight={'medium'} color={'myGray.600'}>
|
||||
{t('common.Field')}
|
||||
</Box>
|
||||
<HStack mb={2} justifyContent={'space-between'}>
|
||||
<IOTitle text={t('core.module.tool.Tool input')} mb={0} />
|
||||
{dynamicInput && (
|
||||
<Button
|
||||
variant={'unstyled'}
|
||||
leftIcon={<MyIcon name={'common/addLight'} w={'14px'} />}
|
||||
variant={'whiteBase'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
iconSpacing={1}
|
||||
size={'sm'}
|
||||
px={3}
|
||||
_hover={{
|
||||
bg: 'myGray.150'
|
||||
}}
|
||||
onClick={() => setEditField(defaultEditFormData)}
|
||||
>
|
||||
{t('core.module.extract.Add field')}
|
||||
{t('Add new')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
<Box borderRadius={'md'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
@@ -66,11 +67,11 @@ const RenderToolInput = ({
|
||||
<Th>字段名</Th>
|
||||
<Th>字段描述</Th>
|
||||
<Th>必须</Th>
|
||||
{canEdit && <Th></Th>}
|
||||
{dynamicInput && <Th></Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{inputs.map((item, index) => (
|
||||
{toolInputs.map((item, index) => (
|
||||
<Tr
|
||||
key={index}
|
||||
position={'relative'}
|
||||
@@ -80,7 +81,7 @@ const RenderToolInput = ({
|
||||
<Td>{item.key}</Td>
|
||||
<Td>{item.toolDescription}</Td>
|
||||
<Td>{item.required ? '✔' : ''}</Td>
|
||||
{canEdit && (
|
||||
{dynamicInput && (
|
||||
<Td whiteSpace={'nowrap'}>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FlowValueTypeMap } from '@/web/core/workflow/constants/dataType';
|
||||
import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type {
|
||||
EditInputFieldMapType,
|
||||
EditNodeFieldType
|
||||
} from '@fastgpt/global/core/workflow/node/type';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./FieldEditModal'));
|
||||
|
||||
const VariableTable = ({
|
||||
fieldEditType,
|
||||
variables = [],
|
||||
keys,
|
||||
|
||||
createField,
|
||||
onCreate,
|
||||
|
||||
editField,
|
||||
onStartEdit,
|
||||
onEdit,
|
||||
|
||||
onCloseFieldEdit,
|
||||
onDelete
|
||||
}: {
|
||||
fieldEditType?: EditInputFieldMapType;
|
||||
variables: { icon?: string; label: string; type: string; key: string }[];
|
||||
keys: string[];
|
||||
|
||||
createField?: EditNodeFieldType;
|
||||
onCreate?: (e: { data: EditNodeFieldType }) => void;
|
||||
|
||||
editField?: EditNodeFieldType;
|
||||
onStartEdit: (key: string) => void;
|
||||
onEdit?: (e: { data: EditNodeFieldType; changeKey: boolean }) => void;
|
||||
|
||||
onCloseFieldEdit: () => void;
|
||||
onDelete?: (key: string) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const fileEditData = (createField || editField) as EditNodeFieldType | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
bg={'white'}
|
||||
borderRadius={'md'}
|
||||
overflow={'hidden'}
|
||||
borderWidth={'1px'}
|
||||
borderBottom={'none'}
|
||||
>
|
||||
<TableContainer>
|
||||
<Table bg={'white'}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th borderBottomLeftRadius={'none !important'}>
|
||||
{t('core.module.variable.variable name')}
|
||||
</Th>
|
||||
<Th>{t('core.workflow.Value type')}</Th>
|
||||
<Th borderBottomRightRadius={'none !important'}></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{variables.map((item) => (
|
||||
<Tr key={item.key}>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
{!!item.icon && <MyIcon name={item.icon as any} w={'14px'} mr={1} />}
|
||||
{item.label || item.key}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>{item.type}</Td>
|
||||
<Td>
|
||||
<MyIcon
|
||||
mr={3}
|
||||
name={'common/settingLight'}
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => onStartEdit(item.key)}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
ml={2}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onDelete?.(item.key);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Box>
|
||||
{!!fileEditData && (
|
||||
<FieldEditModal
|
||||
editField={fieldEditType}
|
||||
defaultField={fileEditData}
|
||||
keys={keys}
|
||||
onClose={onCloseFieldEdit}
|
||||
onSubmit={(e) => {
|
||||
if (!!createField && onCreate) {
|
||||
onCreate(e);
|
||||
} else if (!!editField && onEdit) {
|
||||
onEdit(e);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(VariableTable);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user