mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
Plugin support select file (#2756)
* feat: plugin support upload files (#2716) * feat: plugin support file upload * file history * fix history & chattest * chore: code * plugin config icon & file preview padding * perf: undefined fn * fix: plugin file numbers & plugin template add config (#2743) * perf: run plugin without human message (#2749) * perf: run plugin without human message * fix build * fix build * rename node * perf: ui * perf: plugin rerun with last params & plugin log add file (#2755) * perf: plugin run with last params & plugin log add file * delete console * perf: plugin refresh code * fix: ts --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
@@ -68,7 +68,7 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "lmpb9v2lo2lk",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/workflow/input.png",
|
||||
"flowNodeType": "pluginInput",
|
||||
@@ -71,7 +71,7 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "i7uow4wj2wdp",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"flowNodeType": "pluginOutput",
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"flowType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "m8dupj",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "/imgs/module/input.png",
|
||||
"flowType": "pluginInput",
|
||||
@@ -48,7 +48,7 @@
|
||||
},
|
||||
{
|
||||
"moduleId": "bjsa7r",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/module/output.png",
|
||||
"flowType": "pluginOutput",
|
||||
|
@@ -10,7 +10,7 @@
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"flowType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
@@ -94,7 +94,7 @@
|
||||
},
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"flowType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"modules": [
|
||||
{
|
||||
"moduleId": "w90mfp",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"flowType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
{
|
||||
"moduleId": "tze1ju",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"flowType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
|
@@ -9,14 +9,14 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 351.2046235980429,
|
||||
"y": -77.41739975794749
|
||||
"x": 503.3030871469042,
|
||||
"y": -91.64434154072819
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -44,14 +44,14 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1983.6911708285384,
|
||||
"y": -95.86447885674228
|
||||
"x": 1876.2082565873427,
|
||||
"y": -110.14434154072819
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -95,7 +95,7 @@
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
@@ -106,6 +106,7 @@
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
@@ -115,7 +116,9 @@
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
@@ -123,17 +126,33 @@
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpTimeout",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "number",
|
||||
"label": "",
|
||||
"value": 30,
|
||||
"min": 5,
|
||||
"max": 600,
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"description": "common:core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "https://fal.run/fal-ai/flux-pro"
|
||||
"value": "https://fal.run/fal-ai/flux-pro",
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
@@ -147,9 +166,11 @@
|
||||
}
|
||||
],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
"description": "common:core.module.input.description.Http Request Header",
|
||||
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
@@ -157,7 +178,9 @@
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
@@ -165,7 +188,29 @@
|
||||
"valueType": "any",
|
||||
"value": "{\n \"prompt\": \"{{prompt}}\",\n \"image_size\": \"landscape_4_3\",\n \"num_inference_steps\": 28,\n \"guidance_scale\": 3.5\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpFormBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpContentType",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"value": "json",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
@@ -183,6 +228,7 @@
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
@@ -201,7 +247,7 @@
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"label": "workflow:request_error",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
@@ -209,8 +255,8 @@
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"label": "workflow:raw_response",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
@@ -250,6 +296,20 @@
|
||||
"label": "images[0].url"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "lSYsc889IXDr",
|
||||
"name": "系统配置",
|
||||
"intro": "",
|
||||
"avatar": "core/workflow/template/systemConfig",
|
||||
"flowNodeType": "pluginConfig",
|
||||
"position": {
|
||||
"x": 45.52914573588026,
|
||||
"y": -110.14434154072819
|
||||
},
|
||||
"version": "4811",
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
|
@@ -9,14 +9,14 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 412.7756423516722,
|
||||
"y": -99.80686112290361
|
||||
"x": 421.97302886868476,
|
||||
"y": -89.7785530936485
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -44,14 +44,14 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1822.7195641525896,
|
||||
"y": -193.54601587659562
|
||||
"x": 1785.9300180845394,
|
||||
"y": -108.2785530936485
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -95,7 +95,7 @@
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input",
|
||||
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
@@ -106,6 +106,7 @@
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
@@ -115,7 +116,9 @@
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
@@ -123,17 +126,33 @@
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpTimeout",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "number",
|
||||
"label": "",
|
||||
"value": 30,
|
||||
"min": 5,
|
||||
"max": 600,
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Url",
|
||||
"description": "common:core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "https://api.openai.com/v1/images/generations"
|
||||
"value": "https://api.openai.com/v1/images/generations",
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
@@ -147,9 +166,11 @@
|
||||
}
|
||||
],
|
||||
"label": "",
|
||||
"description": "core.module.input.description.Http Request Header",
|
||||
"placeholder": "core.module.input.description.Http Request Header",
|
||||
"required": false
|
||||
"description": "common:core.module.input.description.Http Request Header",
|
||||
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
@@ -157,7 +178,9 @@
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
@@ -165,7 +188,29 @@
|
||||
"valueType": "any",
|
||||
"value": "{\n \"model\": \"dall-e-3\",\n \"prompt\": \"{{prompt}}\",\n \"n\": 1,\n \"size\": \"1024x1024\"\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpFormBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpContentType",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"value": "json",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["reference"],
|
||||
@@ -183,6 +228,7 @@
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
@@ -201,7 +247,7 @@
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"label": "workflow:request_error",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
@@ -209,8 +255,8 @@
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"label": "workflow:raw_response",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
@@ -250,6 +296,20 @@
|
||||
"label": "data[0].url"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "c7tRU2qAQoAf",
|
||||
"name": "系统配置",
|
||||
"intro": "",
|
||||
"avatar": "core/workflow/template/systemConfig",
|
||||
"flowNodeType": "pluginConfig",
|
||||
"position": {
|
||||
"x": -46.476647046261974,
|
||||
"y": -89.7785530936485
|
||||
},
|
||||
"version": "4811",
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
|
@@ -9,14 +9,14 @@
|
||||
"nodes": [
|
||||
{
|
||||
"nodeId": "pluginInput",
|
||||
"name": "自定义插件输入",
|
||||
"name": "插件开始",
|
||||
"intro": "自定义配置外部输入,使用插件时,仅暴露自定义配置的输入",
|
||||
"avatar": "core/workflow/template/workflowStart",
|
||||
"flowNodeType": "pluginInput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 517.5620777851774,
|
||||
"y": -173.55711888178655
|
||||
"x": 535.7465806305546,
|
||||
"y": -201.26482361861054
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -79,14 +79,14 @@
|
||||
},
|
||||
{
|
||||
"nodeId": "pluginOutput",
|
||||
"name": "自定义插件输出",
|
||||
"name": "插件输出",
|
||||
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
|
||||
"avatar": "/imgs/workflow/output.png",
|
||||
"avatar": "core/workflow/template/pluginOutput",
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 1668.9410524554828,
|
||||
"y": -153.47815316221283
|
||||
"x": 1776.027569211593,
|
||||
"y": -58.264823618610535
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [],
|
||||
@@ -111,7 +111,116 @@
|
||||
"valueType": "dynamic",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"description": "core.module.input.description.HTTP Dynamic Input"
|
||||
"description": "common:core.module.input.description.HTTP Dynamic Input",
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
},
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpMethod",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"value": "POST",
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpTimeout",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "number",
|
||||
"label": "",
|
||||
"value": 30,
|
||||
"min": 5,
|
||||
"max": 600,
|
||||
"required": true,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpReqUrl",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"label": "",
|
||||
"description": "common:core.module.input.description.Http Request Url",
|
||||
"placeholder": "https://api.ai.com/getInventory",
|
||||
"required": false,
|
||||
"value": "{{url}}",
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpHeader",
|
||||
"renderTypeList": ["custom"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"description": "common:core.module.input.description.Http Request Header",
|
||||
"placeholder": "common:core.module.input.description.Http Request Header",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpParams",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": "{\r\n \"msg_type\": \"text\",\r\n \"content\": {\r\n \"text\": \"{{text}}\"\r\n }\r\n}",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpFormBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "any",
|
||||
"value": [],
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "system_httpContentType",
|
||||
"renderTypeList": ["hidden"],
|
||||
"valueType": "string",
|
||||
"value": "json",
|
||||
"label": "",
|
||||
"required": false,
|
||||
"debugLabel": "",
|
||||
"toolDescription": ""
|
||||
},
|
||||
{
|
||||
"key": "text",
|
||||
@@ -124,7 +233,28 @@
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["pluginInput", "p0m68Dv5KaIp"]
|
||||
"value": ["pluginInput", "p0m68Dv5KaIp"],
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"key": "url",
|
||||
@@ -137,54 +267,48 @@
|
||||
"key": true,
|
||||
"valueType": true
|
||||
},
|
||||
"value": ["pluginInput", "mv52BrPVE6bm"]
|
||||
},
|
||||
{
|
||||
"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": "{{url}}"
|
||||
},
|
||||
{
|
||||
"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 \"msg_type\": \"text\",\r\n \"content\": {\r\n \"text\": \"{{text}}\"\r\n }\r\n}",
|
||||
"label": "",
|
||||
"required": false
|
||||
"value": ["pluginInput", "mv52BrPVE6bm"],
|
||||
"customInputConfig": {
|
||||
"selectValueTypeList": [
|
||||
"string",
|
||||
"number",
|
||||
"boolean",
|
||||
"object",
|
||||
"arrayString",
|
||||
"arrayNumber",
|
||||
"arrayBoolean",
|
||||
"arrayObject",
|
||||
"arrayAny",
|
||||
"any",
|
||||
"chatHistory",
|
||||
"datasetQuote",
|
||||
"dynamic",
|
||||
"selectApp",
|
||||
"selectDataset"
|
||||
],
|
||||
"showDescription": false,
|
||||
"showDefaultValue": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "workflow:request_error",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"required": true,
|
||||
"label": "workflow:raw_response",
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "system_addOutputParam",
|
||||
"key": "system_addOutputParam",
|
||||
@@ -195,25 +319,22 @@
|
||||
"key": true,
|
||||
"valueType": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "error",
|
||||
"key": "error",
|
||||
"label": "请求错误",
|
||||
"description": "HTTP请求错误信息,成功时返回空",
|
||||
"valueType": "object",
|
||||
"type": "static"
|
||||
},
|
||||
{
|
||||
"id": "httpRawResponse",
|
||||
"key": "httpRawResponse",
|
||||
"label": "原始响应",
|
||||
"required": true,
|
||||
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
|
||||
"valueType": "any",
|
||||
"type": "static"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nodeId": "q3ccNXiZIHoS",
|
||||
"name": "系统配置",
|
||||
"intro": "",
|
||||
"avatar": "core/workflow/template/systemConfig",
|
||||
"flowNodeType": "pluginConfig",
|
||||
"position": {
|
||||
"x": 99.73879703925843,
|
||||
"y": -201.26482361861054
|
||||
},
|
||||
"version": "4811",
|
||||
"inputs": [],
|
||||
"outputs": []
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
|
@@ -8,7 +8,8 @@ import {
|
||||
useDisclosure,
|
||||
HStack,
|
||||
Switch,
|
||||
ModalFooter
|
||||
ModalFooter,
|
||||
BoxProps
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -25,8 +26,9 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
const FileSelect = ({
|
||||
forbidVision = false,
|
||||
value = defaultAppSelectFileConfig,
|
||||
onChange
|
||||
}: {
|
||||
onChange,
|
||||
...labelStyle
|
||||
}: Omit<BoxProps, 'onChange'> & {
|
||||
forbidVision?: boolean;
|
||||
value?: AppFileSelectConfigType;
|
||||
onChange: (e: AppFileSelectConfigType) => void;
|
||||
@@ -57,7 +59,7 @@ const FileSelect = ({
|
||||
return (
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/simpleMode/file'} mr={2} w={'20px'} />
|
||||
<FormLabel>{t('app:file_upload')}</FormLabel>
|
||||
<FormLabel {...labelStyle}>{t('app:file_upload')}</FormLabel>
|
||||
<ChatFunctionTip type={'file'} />
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={t('app:config_file_upload')}>
|
||||
|
@@ -1,34 +1,21 @@
|
||||
import { useSpeech } from '@/web/common/hooks/useSpeech';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { Box, CircularProgress, Flex, HStack, Image, Spinner, Textarea } from '@chakra-ui/react';
|
||||
import { Box, Flex, Spinner, Textarea } from '@chakra-ui/react';
|
||||
import React, { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { uploadFile2DB } from '@/web/common/file/controller';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
ChatBoxInputFormType,
|
||||
ChatBoxInputType,
|
||||
SendPromptFnType,
|
||||
UserInputFileItemType
|
||||
} from '../type';
|
||||
import { ChatBoxInputFormType, ChatBoxInputType, SendPromptFnType } from '../type';
|
||||
import { textareaMinH } from '../constants';
|
||||
import { UseFormReturn, useFieldArray } from 'react-hook-form';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { documentFileType } from '@fastgpt/global/common/file/constants';
|
||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { clone } from 'lodash';
|
||||
import { formatFileSize } from '@fastgpt/global/common/file/tools';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import FilePreview from '../../components/FilePreview';
|
||||
import { useFileUpload } from '../hooks/useFileUpload';
|
||||
import ComplianceTip from '@/components/common/ComplianceTip/index';
|
||||
|
||||
const InputGuideBox = dynamic(() => import('./InputGuideBox'));
|
||||
@@ -56,21 +43,10 @@ const ChatInput = ({
|
||||
appId: string;
|
||||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const { setValue, watch, control } = chatForm;
|
||||
const inputValue = watch('input');
|
||||
const {
|
||||
update: updateFiles,
|
||||
remove: removeFiles,
|
||||
fields: fileList,
|
||||
replace: replaceFiles
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'files'
|
||||
});
|
||||
|
||||
const {
|
||||
chatId,
|
||||
@@ -82,86 +58,32 @@ const ChatInput = ({
|
||||
fileSelectConfig
|
||||
} = useContextSelector(ChatBoxContext, (v) => v);
|
||||
|
||||
const {
|
||||
File,
|
||||
onOpenSelectFile,
|
||||
fileList,
|
||||
onSelectFile,
|
||||
uploadFiles,
|
||||
selectFileIcon,
|
||||
selectFileLabel,
|
||||
showSelectFile,
|
||||
showSelectImg,
|
||||
removeFiles,
|
||||
replaceFiles
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig,
|
||||
control
|
||||
});
|
||||
const havInput = !!inputValue || fileList.length > 0;
|
||||
const hasFileUploading = fileList.some((item) => !item.url);
|
||||
const canSendMessage = havInput && !hasFileUploading;
|
||||
|
||||
const showSelectFile = fileSelectConfig.canSelectFile;
|
||||
const showSelectImg = fileSelectConfig.canSelectImg;
|
||||
const maxSelectFiles = fileSelectConfig.maxFiles ?? 10;
|
||||
const maxSize = (feConfigs?.uploadFileMaxSize || 1024) * 1024 * 1024; // nkb
|
||||
const { icon: selectFileIcon, tooltip: selectFileTip } = useMemo(() => {
|
||||
if (showSelectFile) {
|
||||
return {
|
||||
icon: 'core/chat/fileSelect',
|
||||
tooltip: t('chat:select_file')
|
||||
};
|
||||
} else if (showSelectImg) {
|
||||
return {
|
||||
icon: 'core/chat/fileSelect',
|
||||
tooltip: t('chat:select_img')
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}, [showSelectFile, showSelectImg, t]);
|
||||
|
||||
/* file selector and upload */
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: `${showSelectImg ? 'image/*,' : ''} ${showSelectFile ? documentFileType : ''}`,
|
||||
multiple: true,
|
||||
maxCount: maxSelectFiles
|
||||
});
|
||||
// Upload files
|
||||
useRequest2(
|
||||
async () => {
|
||||
const filterFiles = fileList.filter((item) => item.status === 0);
|
||||
|
||||
if (filterFiles.length === 0) return;
|
||||
|
||||
replaceFiles(fileList.map((item) => ({ ...item, status: 1 })));
|
||||
let errorFileIndex: number[] = [];
|
||||
|
||||
await Promise.allSettled(
|
||||
filterFiles.map(async (file) => {
|
||||
const copyFile = clone(file);
|
||||
copyFile.status = 1;
|
||||
if (!copyFile.rawFile) return;
|
||||
|
||||
try {
|
||||
const fileIndex = fileList.findIndex((item) => item.id === file.id)!;
|
||||
|
||||
// Start upload and update process
|
||||
const { previewUrl } = await uploadFile2DB({
|
||||
file: copyFile.rawFile,
|
||||
bucketName: 'chat',
|
||||
outLinkAuthData,
|
||||
metadata: {
|
||||
chatId
|
||||
},
|
||||
percentListen(e) {
|
||||
copyFile.process = e;
|
||||
if (!copyFile.url) {
|
||||
updateFiles(fileIndex, copyFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update file url
|
||||
copyFile.url = `${location.origin}${previewUrl}`;
|
||||
updateFiles(fileIndex, copyFile);
|
||||
} catch (error) {
|
||||
errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t(
|
||||
getErrText(error, t('common:error.upload_file_error_filename', { name: file.name }))
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
removeFiles(errorFileIndex);
|
||||
uploadFiles();
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
@@ -169,78 +91,6 @@ const ChatInput = ({
|
||||
refreshDeps: [fileList, outLinkAuthData, chatId]
|
||||
}
|
||||
);
|
||||
const onSelectFile = useCallback(
|
||||
async (files: File[]) => {
|
||||
if (!files || files.length === 0) {
|
||||
return;
|
||||
}
|
||||
// filter max files
|
||||
if (fileList.length + files.length > maxSelectFiles) {
|
||||
files = files.slice(0, maxSelectFiles - fileList.length);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('chat:file_amount_over', { max: maxSelectFiles })
|
||||
});
|
||||
}
|
||||
|
||||
const filterFilesByMaxSize = files.filter((file) => file.size <= maxSize);
|
||||
if (filterFilesByMaxSize.length < files.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('file:some_file_size_exceeds_limit', { maxSize: formatFileSize(maxSize) })
|
||||
});
|
||||
}
|
||||
|
||||
const loadFiles = await Promise.all(
|
||||
filterFilesByMaxSize.map(
|
||||
(file) =>
|
||||
new Promise<UserInputFileItemType>((resolve, reject) => {
|
||||
if (file.type.includes('image')) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const item: UserInputFileItemType = {
|
||||
id: getNanoid(6),
|
||||
rawFile: file,
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: file.name,
|
||||
icon: reader.result as string,
|
||||
status: 0
|
||||
};
|
||||
resolve(item);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject(reader.error);
|
||||
};
|
||||
} else {
|
||||
resolve({
|
||||
id: getNanoid(6),
|
||||
rawFile: file,
|
||||
type: ChatFileTypeEnum.file,
|
||||
name: file.name,
|
||||
icon: getFileIcon(file.name),
|
||||
status: 0
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Document, image
|
||||
const concatFileList = clone(
|
||||
fileList.concat(loadFiles).sort((a, b) => {
|
||||
if (a.type === ChatFileTypeEnum.image && b.type === ChatFileTypeEnum.file) {
|
||||
return 1;
|
||||
} else if (a.type === ChatFileTypeEnum.file && b.type === ChatFileTypeEnum.image) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
);
|
||||
replaceFiles(concatFileList);
|
||||
},
|
||||
[fileList, maxSelectFiles, replaceFiles, toast, t, maxSize]
|
||||
);
|
||||
|
||||
/* on send */
|
||||
const handleSend = useCallback(
|
||||
@@ -330,91 +180,7 @@ const ChatInput = ({
|
||||
),
|
||||
[isSpeaking, isTransCription, t]
|
||||
);
|
||||
const RenderFilePreview = useMemo(
|
||||
() =>
|
||||
fileList.length > 0 ? (
|
||||
<Flex
|
||||
maxH={'250px'}
|
||||
overflowY={'auto'}
|
||||
wrap={'wrap'}
|
||||
px={[2, 4]}
|
||||
pt={3}
|
||||
userSelect={'none'}
|
||||
gap={2}
|
||||
mb={fileList.length > 0 ? 2 : 0}
|
||||
>
|
||||
{fileList.map((item, index) => (
|
||||
<MyBox
|
||||
key={index}
|
||||
border={'sm'}
|
||||
boxShadow={
|
||||
'0px 2.571px 6.429px 0px rgba(19, 51, 107, 0.08), 0px 0px 0.643px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
rounded={'md'}
|
||||
position={'relative'}
|
||||
_hover={{
|
||||
'.close-icon': { display: 'block' }
|
||||
}}
|
||||
>
|
||||
<MyIcon
|
||||
name={'closeSolid'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
color={'myGray.700'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
position={'absolute'}
|
||||
bg={'white'}
|
||||
right={'-8px'}
|
||||
top={'-8px'}
|
||||
onClick={() => removeFiles(index)}
|
||||
className="close-icon"
|
||||
display={['', 'none']}
|
||||
zIndex={10}
|
||||
/>
|
||||
{item.type === ChatFileTypeEnum.image && (
|
||||
<Image
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
w={['2rem', '3rem']}
|
||||
h={['2rem', '3rem']}
|
||||
borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
)}
|
||||
{item.type === ChatFileTypeEnum.file && (
|
||||
<HStack minW={['100px', '150px']} maxW={'250px'} p={2}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
{/* Process */}
|
||||
{!item.url && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
inset="0"
|
||||
bg="rgba(255,255,255,0.4)"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<CircularProgress
|
||||
value={item.process}
|
||||
color="primary.600"
|
||||
bg={'white'}
|
||||
size={isPc ? '30px' : '35px'}
|
||||
>
|
||||
{/* <CircularProgressLabel>{item.process ?? 0}%</CircularProgressLabel> */}
|
||||
</CircularProgress>
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
))}
|
||||
</Flex>
|
||||
) : null,
|
||||
[fileList, isPc, removeFiles]
|
||||
);
|
||||
|
||||
const RenderTextarea = useMemo(
|
||||
() => (
|
||||
<Flex alignItems={'flex-end'} mt={fileList.length > 0 ? 1 : 0} pl={[2, 4]}>
|
||||
@@ -431,10 +197,10 @@ const ChatInput = ({
|
||||
onOpenSelectFile();
|
||||
}}
|
||||
>
|
||||
<MyTooltip label={selectFileTip}>
|
||||
<MyTooltip label={selectFileLabel}>
|
||||
<MyIcon name={selectFileIcon as any} w={'18px'} color={'myGray.600'} />
|
||||
</MyTooltip>
|
||||
<File onSelect={onSelectFile} />
|
||||
<File onSelect={(files) => onSelectFile({ files, fileList })} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -510,7 +276,7 @@ const ChatInput = ({
|
||||
.filter((file) => {
|
||||
return file && fileTypeFilter(file);
|
||||
}) as File[];
|
||||
onSelectFile(files);
|
||||
onSelectFile({ files, fileList });
|
||||
|
||||
if (files.length > 0) {
|
||||
e.stopPropagation();
|
||||
@@ -636,7 +402,7 @@ const ChatInput = ({
|
||||
[
|
||||
File,
|
||||
TextareaDom,
|
||||
fileList.length,
|
||||
fileList,
|
||||
handleSend,
|
||||
hasFileUploading,
|
||||
havInput,
|
||||
@@ -650,7 +416,7 @@ const ChatInput = ({
|
||||
onStop,
|
||||
onWhisperRecord,
|
||||
selectFileIcon,
|
||||
selectFileTip,
|
||||
selectFileLabel,
|
||||
setValue,
|
||||
showSelectFile,
|
||||
showSelectImg,
|
||||
@@ -700,7 +466,9 @@ const ChatInput = ({
|
||||
{RenderTranslateLoading}
|
||||
|
||||
{/* file preview */}
|
||||
{RenderFilePreview}
|
||||
<Box px={[2, 4]}>
|
||||
<FilePreview fileList={fileList} removeFiles={removeFiles} />
|
||||
</Box>
|
||||
|
||||
{RenderTextarea}
|
||||
</Box>
|
||||
|
@@ -20,6 +20,7 @@ import { createContext } from 'use-context-selector';
|
||||
import { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { getChatResData } from '@/web/core/chat/api';
|
||||
import { ChatBoxInputFormType } from './type';
|
||||
|
||||
export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
appAvatar?: string;
|
||||
@@ -29,7 +30,7 @@ export type ChatProviderProps = OutLinkChatAuthProps & {
|
||||
chatHistories: ChatSiteItemType[];
|
||||
setChatHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
|
||||
variablesForm: UseFormReturn<FieldValues, any>;
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
|
||||
// not chat test params
|
||||
chatId?: string;
|
||||
|
@@ -0,0 +1,214 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSelectFile } from '@/web/common/file/hooks/useSelectFile';
|
||||
import { uploadFile2DB } from '@/web/common/file/controller';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||
import { formatFileSize } from '@fastgpt/global/common/file/tools';
|
||||
import { clone } from 'lodash';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { Control, useFieldArray } from 'react-hook-form';
|
||||
import { ChatBoxInputFormType, UserInputFileItemType } from '../type';
|
||||
import { AppFileSelectConfigType } from '@fastgpt/global/core/app/type';
|
||||
import { documentFileType } from '@fastgpt/global/common/file/constants';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
interface UseFileUploadOptions {
|
||||
outLinkAuthData: any;
|
||||
chatId: string;
|
||||
fileSelectConfig: AppFileSelectConfigType;
|
||||
control: Control<ChatBoxInputFormType, any>;
|
||||
}
|
||||
|
||||
export const useFileUpload = (props: UseFileUploadOptions) => {
|
||||
const { outLinkAuthData, chatId, fileSelectConfig, control } = props;
|
||||
const { toast } = useToast();
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const {
|
||||
update: updateFiles,
|
||||
remove: removeFiles,
|
||||
fields: fileList,
|
||||
replace: replaceFiles
|
||||
} = useFieldArray({
|
||||
control: control,
|
||||
name: 'files'
|
||||
});
|
||||
|
||||
const showSelectFile = fileSelectConfig?.canSelectFile;
|
||||
const showSelectImg = fileSelectConfig?.canSelectImg;
|
||||
const maxSelectFiles = fileSelectConfig?.maxFiles ?? 10;
|
||||
const maxSize = (feConfigs?.uploadFileMaxSize || 1024) * 1024 * 1024; // nkb
|
||||
|
||||
const { icon: selectFileIcon, label: selectFileLabel } = useMemo(() => {
|
||||
if (showSelectFile && showSelectImg) {
|
||||
return {
|
||||
icon: 'core/chat/fileSelect',
|
||||
label: t('chat:select_file_img')
|
||||
};
|
||||
} else if (showSelectFile) {
|
||||
return {
|
||||
icon: 'core/chat/fileSelect',
|
||||
label: t('chat:select_file')
|
||||
};
|
||||
} else if (showSelectImg) {
|
||||
return {
|
||||
icon: 'core/chat/imgSelect',
|
||||
label: t('chat:select_img')
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}, [showSelectFile, showSelectImg, t]);
|
||||
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
fileType: `${showSelectImg ? 'image/*,' : ''} ${showSelectFile ? documentFileType : ''}`,
|
||||
multiple: true,
|
||||
maxCount: maxSelectFiles
|
||||
});
|
||||
|
||||
const onSelectFile = useCallback(
|
||||
async ({ files, fileList }: { files: File[]; fileList: UserInputFileItemType[] }) => {
|
||||
if (!files || files.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter max files
|
||||
if (files.length > maxSelectFiles) {
|
||||
files = files.slice(0, maxSelectFiles);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('chat:file_amount_over', { max: maxSelectFiles })
|
||||
});
|
||||
}
|
||||
|
||||
// Filter files by max size
|
||||
const filterFilesByMaxSize = files.filter((file) => file.size <= maxSize);
|
||||
if (filterFilesByMaxSize.length < files.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('file:some_file_size_exceeds_limit', { maxSize: formatFileSize(maxSize) })
|
||||
});
|
||||
}
|
||||
|
||||
// Convert files to UserInputFileItemType
|
||||
const loadFiles = await Promise.all(
|
||||
filterFilesByMaxSize.map(
|
||||
(file) =>
|
||||
new Promise<UserInputFileItemType>((resolve, reject) => {
|
||||
if (file.type.includes('image')) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const item: UserInputFileItemType = {
|
||||
id: getNanoid(6),
|
||||
rawFile: file,
|
||||
type: ChatFileTypeEnum.image,
|
||||
name: file.name,
|
||||
icon: reader.result as string,
|
||||
status: 0
|
||||
};
|
||||
resolve(item);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
reject(reader.error);
|
||||
};
|
||||
} else {
|
||||
resolve({
|
||||
id: getNanoid(6),
|
||||
rawFile: file,
|
||||
type: ChatFileTypeEnum.file,
|
||||
name: file.name,
|
||||
icon: getFileIcon(file.name),
|
||||
status: 0
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
// Document, image
|
||||
const concatFileList = clone(
|
||||
fileList.concat(loadFiles).sort((a, b) => {
|
||||
if (a.type === ChatFileTypeEnum.image && b.type === ChatFileTypeEnum.file) {
|
||||
return 1;
|
||||
} else if (a.type === ChatFileTypeEnum.file && b.type === ChatFileTypeEnum.image) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
);
|
||||
replaceFiles(concatFileList);
|
||||
|
||||
return loadFiles;
|
||||
},
|
||||
[maxSelectFiles, replaceFiles, toast, t, maxSize]
|
||||
);
|
||||
|
||||
const uploadFiles = async () => {
|
||||
const filterFiles = fileList.filter((item) => item.status === 0);
|
||||
|
||||
if (filterFiles.length === 0) return;
|
||||
|
||||
replaceFiles(fileList.map((item) => ({ ...item, status: 1 })));
|
||||
let errorFileIndex: number[] = [];
|
||||
|
||||
await Promise.allSettled(
|
||||
filterFiles.map(async (file) => {
|
||||
const copyFile = clone(file);
|
||||
copyFile.status = 1;
|
||||
if (!copyFile.rawFile) return;
|
||||
|
||||
try {
|
||||
const fileIndex = fileList.findIndex((item) => item.id === file.id)!;
|
||||
|
||||
// Start upload and update process
|
||||
const { previewUrl } = await uploadFile2DB({
|
||||
file: copyFile.rawFile,
|
||||
bucketName: 'chat',
|
||||
outLinkAuthData,
|
||||
metadata: {
|
||||
chatId
|
||||
},
|
||||
percentListen(e) {
|
||||
copyFile.process = e;
|
||||
if (!copyFile.url) {
|
||||
updateFiles(fileIndex, copyFile);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Update file url
|
||||
copyFile.url = `${location.origin}${previewUrl}`;
|
||||
updateFiles(fileIndex, copyFile);
|
||||
} catch (error) {
|
||||
errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!);
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t(
|
||||
getErrText(error, t('common:error.upload_file_error_filename', { name: file.name }))
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
removeFiles(errorFileIndex);
|
||||
};
|
||||
|
||||
return {
|
||||
File,
|
||||
onOpenSelectFile,
|
||||
fileList,
|
||||
onSelectFile,
|
||||
uploadFiles,
|
||||
selectFileIcon,
|
||||
selectFileLabel,
|
||||
showSelectFile,
|
||||
showSelectImg,
|
||||
removeFiles,
|
||||
replaceFiles
|
||||
};
|
||||
};
|
@@ -430,7 +430,8 @@ const ChatBox = (
|
||||
file: {
|
||||
type: file.type,
|
||||
name: file.name,
|
||||
url: file.url || ''
|
||||
url: file.url || '',
|
||||
icon: file.icon || ''
|
||||
}
|
||||
})),
|
||||
...(text
|
||||
|
@@ -22,6 +22,7 @@ export type ChatBoxInputFormType = {
|
||||
input: string;
|
||||
files: UserInputFileItemType[];
|
||||
chatStarted: boolean;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type ChatBoxInputType = {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import RenderPluginInput from './renderPluginInput';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
@@ -6,11 +6,19 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { PluginRunContext } from '../context';
|
||||
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { isEqual } from 'lodash';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useFileUpload } from '../../ChatBox/hooks/useFileUpload';
|
||||
import FilePreview from '../../components/FilePreview';
|
||||
import { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { ChatBoxInputFormType, UserInputFileItemType } from '../../ChatBox/type';
|
||||
|
||||
const RenderInput = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
pluginInputs,
|
||||
variablesForm,
|
||||
@@ -19,10 +27,13 @@ const RenderInput = () => {
|
||||
onNewChat,
|
||||
onSubmit,
|
||||
isChatting,
|
||||
chatConfig
|
||||
chatConfig,
|
||||
chatId,
|
||||
outLinkAuthData,
|
||||
restartInputStore,
|
||||
setRestartInputStore
|
||||
} = useContextSelector(PluginRunContext, (v) => v);
|
||||
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
@@ -31,46 +42,102 @@ const RenderInput = () => {
|
||||
formState: { errors }
|
||||
} = variablesForm;
|
||||
|
||||
const defaultFormValues = useMemo(() => {
|
||||
return pluginInputs.reduce(
|
||||
const {
|
||||
File,
|
||||
onOpenSelectFile,
|
||||
fileList,
|
||||
onSelectFile,
|
||||
uploadFiles,
|
||||
selectFileIcon,
|
||||
showSelectFile,
|
||||
showSelectImg,
|
||||
removeFiles,
|
||||
replaceFiles
|
||||
} = useFileUpload({
|
||||
outLinkAuthData,
|
||||
chatId: chatId || '',
|
||||
fileSelectConfig: chatConfig?.fileSelectConfig,
|
||||
control
|
||||
});
|
||||
const isDisabledInput = histories.length > 0;
|
||||
|
||||
const onClickNewChat = useCallback(
|
||||
(e: ChatBoxInputFormType, files: UserInputFileItemType[] = []) => {
|
||||
setRestartInputStore({
|
||||
...e,
|
||||
files
|
||||
});
|
||||
onNewChat?.();
|
||||
},
|
||||
[onNewChat, setRestartInputStore]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// Set last run value
|
||||
if (!isDisabledInput && restartInputStore) {
|
||||
reset(restartInputStore);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set history to default value
|
||||
|
||||
const defaultFormValues = pluginInputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
}, [pluginInputs]);
|
||||
|
||||
const historyFormValues = useMemo(() => {
|
||||
if (histories.length === 0) return undefined;
|
||||
const historyFormValues = (() => {
|
||||
if (!isDisabledInput) return undefined;
|
||||
const historyValue = histories[0].value;
|
||||
try {
|
||||
const inputValueString = historyValue.find((item) => item.type === 'text')?.text?.content;
|
||||
return (
|
||||
inputValueString &&
|
||||
JSON.parse(inputValueString).reduce(
|
||||
(
|
||||
acc: Record<string, any>,
|
||||
{
|
||||
key,
|
||||
value
|
||||
}: {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
) => ({ ...acc, [key]: value }),
|
||||
{}
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse input value:', error);
|
||||
return undefined;
|
||||
}
|
||||
})();
|
||||
|
||||
try {
|
||||
const inputValueString = histories[0].value[0].text?.content || '[]';
|
||||
return JSON.parse(inputValueString).reduce(
|
||||
(
|
||||
acc: Record<string, any>,
|
||||
{
|
||||
key,
|
||||
value
|
||||
}: {
|
||||
key: string;
|
||||
value: any;
|
||||
}
|
||||
) => ({ ...acc, [key]: value }),
|
||||
{}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse input value:', error);
|
||||
return undefined;
|
||||
}
|
||||
}, [histories]);
|
||||
// Parse history file
|
||||
const historyFileList = (() => {
|
||||
if (!isDisabledInput) return [];
|
||||
const historyValue = histories[0].value as UserChatItemValueItemType[];
|
||||
return historyValue.filter((item) => item.type === 'file').map((item) => item.file);
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (isEqual(getValues(), defaultFormValues)) return;
|
||||
reset(historyFormValues || defaultFormValues);
|
||||
}, [defaultFormValues, getValues, historyFormValues, reset]);
|
||||
reset({
|
||||
...(historyFormValues || defaultFormValues),
|
||||
files: historyFileList
|
||||
});
|
||||
}, [getValues, histories, isDisabledInput, pluginInputs, replaceFiles, reset, restartInputStore]);
|
||||
|
||||
const isDisabledInput = histories.length > 0;
|
||||
const hasFileUploading = useMemo(() => {
|
||||
return fileList.some((item) => !item.url);
|
||||
}, [fileList]);
|
||||
|
||||
useRequest2(uploadFiles, {
|
||||
manual: false,
|
||||
errorToast: t('common:upload_file_error'),
|
||||
refreshDeps: [fileList, outLinkAuthData, chatId]
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -88,7 +155,35 @@ const RenderInput = () => {
|
||||
<Markdown source={chatConfig.instruction} />
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* file select */}
|
||||
{(showSelectFile || showSelectImg) && (
|
||||
<Box mb={5}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel fontSize={'md'} fontWeight={'medium'}>
|
||||
{t('chat:file_input')}
|
||||
</FormLabel>
|
||||
<QuestionTip ml={1} label={t('chat:file_input_tip')} />
|
||||
<Box flex={1} />
|
||||
{histories.length === 0 && (
|
||||
<Button
|
||||
leftIcon={<MyIcon name={selectFileIcon as any} w={'16px'} />}
|
||||
variant={'whiteBase'}
|
||||
onClick={() => {
|
||||
onOpenSelectFile();
|
||||
}}
|
||||
>
|
||||
{t('chat:select')}
|
||||
</Button>
|
||||
)}
|
||||
<File onSelect={(files) => onSelectFile({ files, fileList })} />
|
||||
</Flex>
|
||||
<FilePreview
|
||||
fileList={fileList}
|
||||
removeFiles={isDisabledInput ? undefined : removeFiles}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{/* Filed */}
|
||||
{pluginInputs.map((input) => {
|
||||
return (
|
||||
<Controller
|
||||
@@ -118,18 +213,22 @@ const RenderInput = () => {
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{/* Run Button */}
|
||||
{onStartChat && onNewChat && (
|
||||
<Flex justifyContent={'end'} mt={8}>
|
||||
<Button
|
||||
isLoading={isChatting}
|
||||
isLoading={isChatting || hasFileUploading}
|
||||
onClick={() => {
|
||||
if (histories.length > 0) {
|
||||
return onNewChat();
|
||||
}
|
||||
handleSubmit(onSubmit)();
|
||||
handleSubmit((e) => {
|
||||
if (isDisabledInput) {
|
||||
onClickNewChat(e, fileList);
|
||||
} else {
|
||||
onSubmit(e, fileList);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{histories.length > 0 ? t('common:common.Restart') : t('common:common.Run')}
|
||||
{isDisabledInput ? t('common:common.Restart') : t('common:common.Run')}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
|
@@ -1,8 +1,12 @@
|
||||
import React, { ReactNode, useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { PluginRunBoxProps } from './type';
|
||||
import { AIChatItemValueItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import {
|
||||
AIChatItemValueItemType,
|
||||
ChatSiteItemType,
|
||||
RuntimeUserPromptType
|
||||
} from '@fastgpt/global/core/chat/type';
|
||||
import { FieldValues, useForm } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './constants';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -10,17 +14,23 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { generatingMessageProps } from '../type';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { getPluginRunContent } from '@fastgpt/global/core/app/plugin/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
type PluginRunContextType = PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: FieldValues) => Promise<any>;
|
||||
};
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { ChatBoxInputFormType, UserInputFileItemType } from '../ChatBox/type';
|
||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
||||
|
||||
type PluginRunContextType = OutLinkChatAuthProps &
|
||||
PluginRunBoxProps & {
|
||||
isChatting: boolean;
|
||||
onSubmit: (e: ChatBoxInputFormType, files?: UserInputFileItemType[]) => Promise<any>;
|
||||
outLinkAuthData: OutLinkChatAuthProps;
|
||||
restartInputStore?: ChatBoxInputFormType;
|
||||
setRestartInputStore: React.Dispatch<React.SetStateAction<ChatBoxInputFormType | undefined>>;
|
||||
};
|
||||
|
||||
export const PluginRunContext = createContext<PluginRunContextType>({
|
||||
pluginInputs: [],
|
||||
//@ts-ignore
|
||||
variablesForm: undefined,
|
||||
histories: [],
|
||||
setHistories: function (value: React.SetStateAction<ChatSiteItemType[]>): void {
|
||||
throw new Error('Function not implemented.');
|
||||
@@ -33,15 +43,24 @@ export const PluginRunContext = createContext<PluginRunContextType>({
|
||||
isChatting: false,
|
||||
onSubmit: function (e: FieldValues): Promise<any> {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
},
|
||||
outLinkAuthData: {},
|
||||
//@ts-ignore
|
||||
variablesForm: undefined
|
||||
});
|
||||
|
||||
const PluginRunContextProvider = ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken,
|
||||
children,
|
||||
...props
|
||||
}: PluginRunBoxProps & { children: ReactNode }) => {
|
||||
const { pluginInputs, onStartChat, setHistories, histories, setTab } = props;
|
||||
|
||||
const [restartInputStore, setRestartInputStore] = useState<ChatBoxInputFormType>();
|
||||
|
||||
const { toast } = useToast();
|
||||
const chatController = useRef(new AbortController());
|
||||
const { t } = useTranslation();
|
||||
@@ -50,6 +69,22 @@ const PluginRunContextProvider = ({
|
||||
chatController.current?.abort('stop');
|
||||
}, []);
|
||||
|
||||
const outLinkAuthData = useMemo(
|
||||
() => ({
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
teamToken
|
||||
}),
|
||||
[shareId, outLinkUid, teamId, teamToken]
|
||||
);
|
||||
|
||||
const variablesForm = useForm<ChatBoxInputFormType>({
|
||||
defaultValues: {
|
||||
files: []
|
||||
}
|
||||
});
|
||||
|
||||
const generatingMessage = useCallback(
|
||||
({ event, text = '', status, name, tool }: generatingMessageProps) => {
|
||||
setHistories((state) =>
|
||||
@@ -144,90 +179,99 @@ const PluginRunContextProvider = ({
|
||||
[histories]
|
||||
);
|
||||
|
||||
const { runAsync: onSubmit } = useRequest2(async (e: FieldValues) => {
|
||||
if (!onStartChat) return;
|
||||
if (isChatting) {
|
||||
toast({
|
||||
title: t('chat:is_chatting'),
|
||||
status: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
setTab(PluginRunBoxTabEnum.output);
|
||||
const { runAsync: onSubmit } = useRequest2(
|
||||
async (e: ChatBoxInputFormType, files?: UserInputFileItemType[]) => {
|
||||
if (!onStartChat) return;
|
||||
if (isChatting) {
|
||||
toast({
|
||||
title: t('chat:is_chatting'),
|
||||
status: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// reset controller
|
||||
abortRequest();
|
||||
const abortSignal = new AbortController();
|
||||
chatController.current = abortSignal;
|
||||
// reset controller
|
||||
abortRequest();
|
||||
const abortSignal = new AbortController();
|
||||
chatController.current = abortSignal;
|
||||
|
||||
setHistories([
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
status: 'finish',
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: getPluginRunContent({
|
||||
pluginInputs,
|
||||
variables: e
|
||||
})
|
||||
setHistories([
|
||||
{
|
||||
...getPluginRunUserQuery({
|
||||
pluginInputs,
|
||||
variables: e,
|
||||
files: files as RuntimeUserPromptType['files']
|
||||
}),
|
||||
status: 'finish'
|
||||
},
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: [
|
||||
],
|
||||
status: 'loading'
|
||||
}
|
||||
]);
|
||||
setTab(PluginRunBoxTabEnum.output);
|
||||
|
||||
const messages = chats2GPTMessages({
|
||||
messages: [
|
||||
{
|
||||
type: ChatItemValueTypeEnum.text,
|
||||
text: {
|
||||
content: ''
|
||||
}
|
||||
dataId: getNanoid(24),
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: []
|
||||
}
|
||||
],
|
||||
status: 'loading'
|
||||
}
|
||||
]);
|
||||
|
||||
try {
|
||||
const { responseData } = await onStartChat({
|
||||
messages: [],
|
||||
controller: chatController.current,
|
||||
generatingMessage,
|
||||
variables: e
|
||||
reserveId: true
|
||||
});
|
||||
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish',
|
||||
responseData
|
||||
};
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
toast({ title: err.message, status: 'error' });
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish'
|
||||
};
|
||||
})
|
||||
);
|
||||
try {
|
||||
const { responseData } = await onStartChat({
|
||||
messages: messages,
|
||||
controller: chatController.current,
|
||||
generatingMessage,
|
||||
variables: e
|
||||
});
|
||||
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish',
|
||||
responseData
|
||||
};
|
||||
})
|
||||
);
|
||||
} catch (err: any) {
|
||||
toast({ title: err.message, status: 'error' });
|
||||
setHistories((state) =>
|
||||
state.map((item, index) => {
|
||||
if (index !== state.length - 1) return item;
|
||||
return {
|
||||
...item,
|
||||
status: 'finish'
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const contextValue: PluginRunContextType = {
|
||||
...props,
|
||||
isChatting,
|
||||
onSubmit
|
||||
onSubmit,
|
||||
outLinkAuthData,
|
||||
variablesForm,
|
||||
restartInputStore,
|
||||
setRestartInputStore
|
||||
};
|
||||
return <PluginRunContext.Provider value={contextValue}>{children}</PluginRunContext.Provider>;
|
||||
};
|
||||
|
@@ -5,10 +5,11 @@ import { PluginRunBoxTabEnum } from './constants';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import React from 'react';
|
||||
import { onStartChatType } from '../type';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
|
||||
export type PluginRunBoxProps = OutLinkChatAuthProps & {
|
||||
pluginInputs: FlowNodeInputItemType[];
|
||||
variablesForm: UseFormReturn<FieldValues, any>;
|
||||
variablesForm: UseFormReturn<ChatBoxInputFormType, any>;
|
||||
histories: ChatSiteItemType[]; // chatHistories[1] is the response
|
||||
setHistories: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
|
||||
|
||||
|
@@ -0,0 +1,121 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { FieldArrayWithId } from 'react-hook-form';
|
||||
import { ChatBoxInputFormType } from '../ChatBox/type';
|
||||
import { Box, CircularProgress, Flex, HStack, Image } from '@chakra-ui/react';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { ChatFileTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
|
||||
const RenderFilePreview = ({
|
||||
fileList,
|
||||
removeFiles
|
||||
}: {
|
||||
fileList: FieldArrayWithId<ChatBoxInputFormType, 'files', 'id'>[];
|
||||
removeFiles?: (index?: number | number[]) => void;
|
||||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
|
||||
return fileList.length > 0 ? (
|
||||
<Flex
|
||||
maxH={'250px'}
|
||||
overflowY={'auto'}
|
||||
wrap={'wrap'}
|
||||
pt={3}
|
||||
userSelect={'none'}
|
||||
mb={fileList.length > 0 ? 2 : 0}
|
||||
pr={0.5}
|
||||
>
|
||||
{fileList.map((item, index) => {
|
||||
const isFile = item.type === ChatFileTypeEnum.file;
|
||||
const isImage = item.type === ChatFileTypeEnum.image;
|
||||
return (
|
||||
<MyBox
|
||||
key={index}
|
||||
maxW={isFile ? 56 : 14}
|
||||
w={isFile ? '50%' : '12.5%'}
|
||||
aspectRatio={isFile ? 4 : 1}
|
||||
pr={1.5}
|
||||
pb={1.5}
|
||||
mb={0.5}
|
||||
>
|
||||
<Box
|
||||
border={'sm'}
|
||||
boxShadow={
|
||||
'0px 2.571px 6.429px 0px rgba(19, 51, 107, 0.08), 0px 0px 0.643px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
rounded={'md'}
|
||||
position={'relative'}
|
||||
_hover={{
|
||||
'.close-icon': { display: 'block' }
|
||||
}}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
alignItems={'center'}
|
||||
pl={isFile ? 1 : 0}
|
||||
>
|
||||
{removeFiles && (
|
||||
<MyIcon
|
||||
name={'closeSolid'}
|
||||
w={'16px'}
|
||||
h={'16px'}
|
||||
color={'myGray.700'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'red.500' }}
|
||||
position={'absolute'}
|
||||
rounded={'full'}
|
||||
bg={'white'}
|
||||
right={'-8px'}
|
||||
top={'-8px'}
|
||||
onClick={() => removeFiles(index)}
|
||||
className="close-icon"
|
||||
display={['', 'none']}
|
||||
zIndex={10}
|
||||
/>
|
||||
)}
|
||||
{isImage && (
|
||||
<Image
|
||||
alt={'img'}
|
||||
src={item.icon}
|
||||
w={'full'}
|
||||
h={'full'}
|
||||
borderRadius={'md'}
|
||||
objectFit={'contain'}
|
||||
/>
|
||||
)}
|
||||
{isFile && (
|
||||
<HStack alignItems={'center'} h={'full'}>
|
||||
<MyIcon name={item.icon as any} w={['1.5rem', '2rem']} h={['1.5rem', '2rem']} />
|
||||
<Box flex={'1 0 0'} pr={2} className="textEllipsis" fontSize={'xs'}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
{/* Process */}
|
||||
{!item.url && (
|
||||
<Flex
|
||||
position={'absolute'}
|
||||
inset="0"
|
||||
bg="rgba(255,255,255,0.4)"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<CircularProgress
|
||||
value={item.process}
|
||||
color="primary.600"
|
||||
bg={'white'}
|
||||
size={isPc ? '30px' : '35px'}
|
||||
>
|
||||
{/* <CircularProgressLabel>{item.process ?? 0}%</CircularProgressLabel> */}
|
||||
</CircularProgress>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
</MyBox>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default React.memo(RenderFilePreview);
|
@@ -2,14 +2,18 @@ import { ChatSiteItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { PluginRunBoxTabEnum } from './PluginRunBox/constants';
|
||||
import { ComponentRef as ChatComponentRef, SendPromptFnType } from './ChatBox/type';
|
||||
import {
|
||||
ChatBoxInputFormType,
|
||||
ComponentRef as ChatComponentRef,
|
||||
SendPromptFnType
|
||||
} from './ChatBox/type';
|
||||
import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
||||
|
||||
export const useChat = () => {
|
||||
const ChatBoxRef = useRef<ChatComponentRef>(null);
|
||||
|
||||
const [chatRecords, setChatRecords] = useState<ChatSiteItemType[]>([]);
|
||||
const variablesForm = useForm();
|
||||
const variablesForm = useForm<ChatBoxInputFormType>();
|
||||
// plugin
|
||||
const [pluginRunTab, setPluginRunTab] = useState<PluginRunBoxTabEnum>(PluginRunBoxTabEnum.input);
|
||||
|
||||
|
@@ -4,7 +4,7 @@ import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/cons
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
||||
import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants';
|
||||
import type { UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||
import type { UserChatItemType } from '@fastgpt/global/core/chat/type';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { dispatchWorkFlow } from '@fastgpt/service/core/workflow/dispatch';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
@@ -13,7 +13,10 @@ import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
||||
import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||
import {
|
||||
getPluginRunUserQuery,
|
||||
updatePluginInputByVariables
|
||||
} from '@fastgpt/global/core/workflow/utils';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type';
|
||||
@@ -28,6 +31,7 @@ import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
|
||||
export type Props = {
|
||||
messages: ChatCompletionMessageParam[];
|
||||
@@ -65,8 +69,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
throw new Error('Edges is not array');
|
||||
}
|
||||
const chatMessages = GPTMessages2Chats(messages);
|
||||
const userInput = chatMessages.pop()?.value as UserChatItemValueItemType[] | undefined;
|
||||
|
||||
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
|
||||
|
||||
/* user auth */
|
||||
@@ -82,6 +84,22 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
|
||||
const isPlugin = app.type === AppTypeEnum.plugin;
|
||||
|
||||
const userQuestion: UserChatItemType = (() => {
|
||||
if (isPlugin) {
|
||||
return getPluginRunUserQuery({
|
||||
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
|
||||
variables,
|
||||
files: variables.files
|
||||
});
|
||||
}
|
||||
|
||||
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
|
||||
if (!latestHumanChat) {
|
||||
throw new Error('User question is empty');
|
||||
}
|
||||
return latestHumanChat;
|
||||
})();
|
||||
|
||||
let runtimeNodes = storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes, chatMessages));
|
||||
|
||||
// Plugin need to replace inputs
|
||||
@@ -89,7 +107,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
runtimeNodes = updatePluginInputByVariables(runtimeNodes, variables);
|
||||
variables = {};
|
||||
} else {
|
||||
if (!userInput) {
|
||||
if (!userQuestion.value) {
|
||||
throw new Error('Params Error');
|
||||
}
|
||||
}
|
||||
@@ -117,7 +135,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
runtimeNodes,
|
||||
runtimeEdges: initWorkflowEdgeStatus(edges, chatMessages),
|
||||
variables,
|
||||
query: removeEmptyUserInput(userInput),
|
||||
query: removeEmptyUserInput(userQuestion.value),
|
||||
chatConfig,
|
||||
histories: chatMessages,
|
||||
stream: true,
|
||||
|
@@ -49,13 +49,16 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
|
||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { updatePluginInputByVariables } from '@fastgpt/global/core/workflow/utils';
|
||||
import {
|
||||
getPluginRunUserQuery,
|
||||
updatePluginInputByVariables
|
||||
} from '@fastgpt/global/core/workflow/utils';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { getSystemTime } from '@fastgpt/global/common/time/timezone';
|
||||
import { rewriteNodeOutputByHistories } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { getWorkflowResponseWrite } from '@fastgpt/service/core/workflow/dispatch/utils';
|
||||
import { getPluginRunUserQuery } from '@fastgpt/service/core/workflow/utils';
|
||||
import { WORKFLOW_MAX_RUN_TIMES } from '@fastgpt/service/core/workflow/constants';
|
||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||
|
||||
type FastGptWebChatProps = {
|
||||
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
|
||||
@@ -185,8 +188,11 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
// Get obj=Human history
|
||||
const userQuestion: UserChatItemType = (() => {
|
||||
if (isPlugin) {
|
||||
// TODO:get plugin files from variables
|
||||
return getPluginRunUserQuery({ nodes: app.modules, variables });
|
||||
return getPluginRunUserQuery({
|
||||
pluginInputs: getPluginInputsFromStoreNodes(app.modules),
|
||||
variables,
|
||||
files: variables.files
|
||||
});
|
||||
}
|
||||
|
||||
const latestHumanChat = chatMessages.pop() as UserChatItemType | undefined;
|
||||
|
@@ -145,6 +145,7 @@ const DetailLogsModal = ({
|
||||
{isPlugin ? (
|
||||
<Box px={5} pt={2} h={'100%'}>
|
||||
<PluginRunBox
|
||||
chatConfig={chat?.app?.chatConfig}
|
||||
pluginInputs={chat?.app.pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
histories={chatRecords}
|
||||
|
@@ -408,10 +408,11 @@ export const useWorkflow = () => {
|
||||
/* node */
|
||||
const handleRemoveNode = useMemoizedFn((change: NodeRemoveChange, node: Node) => {
|
||||
if (node.data.forbidDelete) {
|
||||
return toast({
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.workflow.Can not delete node')
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the node has child nodes, remove the child nodes
|
||||
@@ -438,6 +439,8 @@ export const useWorkflow = () => {
|
||||
setEdges((state) =>
|
||||
state.filter((edge) => edge.source !== change.id && edge.target !== change.id)
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
const handleSelectNode = useMemoizedFn((change: NodeSelectionChange) => {
|
||||
// If the node is not selected and the Ctrl key is pressed, select the node
|
||||
@@ -506,8 +509,9 @@ export const useWorkflow = () => {
|
||||
for (const change of changes) {
|
||||
if (change.type === 'remove') {
|
||||
const node = nodes.find((n) => n.id === change.id);
|
||||
if (node) {
|
||||
handleRemoveNode(change, node);
|
||||
// 如果删除失败,则不继续执行
|
||||
if (node && !handleRemoveNode(change, node)) {
|
||||
return;
|
||||
}
|
||||
} else if (change.type === 'select') {
|
||||
handleSelectNode(change);
|
||||
|
@@ -13,6 +13,11 @@ import { getAppChatConfig } from '@fastgpt/global/core/workflow/utils';
|
||||
import { useCreation } from 'ahooks';
|
||||
import ChatFunctionTip from '@/components/core/app/Tip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import { WorkflowContext } from '../../../context';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import FileSelect from '@/components/core/app/FileSelect';
|
||||
import { userFilesInput } from '@fastgpt/global/core/workflow/template/system/workflowStart';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
type ComponentProps = {
|
||||
chatConfig: AppChatConfigType;
|
||||
@@ -60,6 +65,9 @@ const NodePluginConfig = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
>
|
||||
<Container w={'360px'}>
|
||||
<Instruction {...componentsProps} />
|
||||
<Box pt={4}>
|
||||
<FileSelectConfig {...componentsProps} />
|
||||
</Box>
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
@@ -72,6 +80,7 @@ function Instruction({ chatConfig: { instruction }, setAppDetail }: ComponentPro
|
||||
return (
|
||||
<>
|
||||
<Flex>
|
||||
<MyIcon name={'core/app/simpleMode/chat'} mr={2} w={'20px'} />
|
||||
<FormLabel color={'myGray.600'} fontWeight={'medium'} fontSize={'14px'}>
|
||||
{t('workflow:plugin.Instructions')}
|
||||
</FormLabel>
|
||||
@@ -100,3 +109,48 @@ function Instruction({ chatConfig: { instruction }, setAppDetail }: ComponentPro
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FileSelectConfig({ chatConfig: { fileSelectConfig }, setAppDetail }: ComponentProps) {
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const nodes = useContextSelector(WorkflowContext, (v) => v.nodes);
|
||||
const pluginInputNode = nodes.find((item) => item.type === FlowNodeTypeEnum.pluginInput)!;
|
||||
|
||||
return (
|
||||
<FileSelect
|
||||
value={fileSelectConfig}
|
||||
color={'myGray.600'}
|
||||
fontWeight={'medium'}
|
||||
fontSize={'14px'}
|
||||
onChange={(e) => {
|
||||
setAppDetail((state) => ({
|
||||
...state,
|
||||
chatConfig: {
|
||||
...state.chatConfig,
|
||||
fileSelectConfig: e
|
||||
}
|
||||
}));
|
||||
|
||||
// Dynamic add or delete userFilesInput
|
||||
const canUploadFiles = e.canSelectFile || e.canSelectImg;
|
||||
const repeatKey = pluginInputNode?.data.outputs.find(
|
||||
(item) => item.key === userFilesInput.key
|
||||
);
|
||||
if (canUploadFiles) {
|
||||
!repeatKey &&
|
||||
onChangeNode({
|
||||
nodeId: pluginInputNode.id,
|
||||
type: 'addOutput',
|
||||
value: userFilesInput
|
||||
});
|
||||
} else {
|
||||
repeatKey &&
|
||||
onChangeNode({
|
||||
nodeId: pluginInputNode.id,
|
||||
type: 'delOutput',
|
||||
key: userFilesInput.key
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import { WorkflowContext } from '../../../context';
|
||||
import IOTitle from '../../components/IOTitle';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { defaultInput } from './InputEditModal';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('./InputEditModal'));
|
||||
|
||||
@@ -140,9 +141,15 @@ const NodePluginInput = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
}}
|
||||
/>
|
||||
</Container>
|
||||
{!!outputs.length && (
|
||||
<Container>
|
||||
<IOTitle text={t('common:common.Output')} />
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
}, [data, inputs, nodeId, onChangeNode, selected, t]);
|
||||
}, [data, inputs, nodeId, onChangeNode, outputs, selected, t]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -72,7 +72,7 @@ export const useChatTest = ({
|
||||
|
||||
const CustomChatContainer = useMemoizedFn(() =>
|
||||
appDetail.type === AppTypeEnum.plugin ? (
|
||||
<Box p={3}>
|
||||
<Box p={5}>
|
||||
<PluginRunBox
|
||||
pluginInputs={pluginInputs}
|
||||
variablesForm={variablesForm}
|
||||
|
@@ -17,7 +17,6 @@ import { useTranslation } from 'next-i18next';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import ComplianceTip from '@/components/common/ComplianceTip/index';
|
||||
|
@@ -360,7 +360,7 @@ export const emptyTemplates: Record<
|
||||
nodes: [
|
||||
{
|
||||
nodeId: 'pluginInput',
|
||||
name: i18nT('common:core.module.template.self_input'),
|
||||
name: i18nT('workflow:template.plugin_start'),
|
||||
avatar: 'core/workflow/template/workflowStart',
|
||||
flowNodeType: FlowNodeTypeEnum.pluginInput,
|
||||
showStatus: false,
|
||||
@@ -385,6 +385,20 @@ export const emptyTemplates: Record<
|
||||
version: '481',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
nodeId: 'pluginConfig',
|
||||
name: i18nT('common:core.module.template.system_config'),
|
||||
intro: '',
|
||||
avatar: 'core/workflow/template/systemConfig',
|
||||
flowNodeType: FlowNodeTypeEnum.pluginConfig,
|
||||
position: {
|
||||
x: 184.66337662472682,
|
||||
y: -216.05298493910115
|
||||
},
|
||||
version: '4811',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
}
|
||||
],
|
||||
edges: []
|
||||
|
Reference in New Issue
Block a user