diff --git a/docSite/content/zh-cn/docs/development/upgrading/4812.md b/docSite/content/zh-cn/docs/development/upgrading/4812.md
index 3c544e0db..226c51a20 100644
--- a/docSite/content/zh-cn/docs/development/upgrading/4812.md
+++ b/docSite/content/zh-cn/docs/development/upgrading/4812.md
@@ -9,8 +9,12 @@ weight: 812
## 更新说明
-1. 新增 - 全局变量支持更多数据类型
+1. 新增 - 全局变量支持数字类型,并且支持配置默认值和部分输入框参数。
2. 新增 - FE_DOMAIN 环境变量,配置该环境变量后,上传文件/图片会补全后缀后得到完整地址。(可解决 docx 文件图片链接,有时会无法被模型识别问题)
3. 新增 - 工具调用支持交互模式
-4. 修复 - 文件后缀判断,去除 query 影响。
-5. 修复 - AI 响应为空时,会造成 LLM 历史记录合并。
\ No newline at end of file
+4. 新增 - Debug 模式支持输入全局变量
+5. 新增 - chat openapi 文档
+6. 新增 - wiki 搜索插件
+7. 修复 - 文件后缀判断,去除 query 影响。
+8. 修复 - AI 响应为空时,会造成 LLM 历史记录合并。
+9. 修复 - 用户交互节点未阻塞流程。
diff --git a/files/docker/docker-compose-milvus.yml b/files/docker/docker-compose-milvus.yml
index 9c466d24f..4703bdddf 100644
--- a/files/docker/docker-compose-milvus.yml
+++ b/files/docker/docker-compose-milvus.yml
@@ -154,7 +154,7 @@ services:
- MILVUS_TOKEN=none
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
- # 前端地址
+ # 前端地址: http://localhost:3000
- FE_DOMAIN=
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
diff --git a/files/docker/docker-compose-pgvector.yml b/files/docker/docker-compose-pgvector.yml
index 92b1a8ace..558fee914 100644
--- a/files/docker/docker-compose-pgvector.yml
+++ b/files/docker/docker-compose-pgvector.yml
@@ -111,7 +111,7 @@ services:
- PG_URL=postgresql://username:password@pg:5432/postgres
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
- # 前端地址
+ # 前端地址: http://localhost:3000
- FE_DOMAIN=
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
diff --git a/files/docker/docker-compose-zilliz.yml b/files/docker/docker-compose-zilliz.yml
index e641a0bfb..6796dc7a7 100644
--- a/files/docker/docker-compose-zilliz.yml
+++ b/files/docker/docker-compose-zilliz.yml
@@ -92,7 +92,7 @@ services:
- MILVUS_TOKEN=zilliz_cloud_token
# sandbox 地址
- SANDBOX_URL=http://sandbox:3000
- # 前端地址
+ # 前端地址: http://localhost:3000
- FE_DOMAIN=
# 日志等级: debug, info, warn, error
- LOG_LEVEL=info
diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts
index 089c2c352..234920e20 100644
--- a/packages/global/core/app/type.d.ts
+++ b/packages/global/core/app/type.d.ts
@@ -13,6 +13,7 @@ import { StoreEdgeItemType } from '../workflow/type/edge';
import { PermissionSchemaType, PermissionValueType } from '../../support/permission/type';
import { AppPermission } from '../../support/permission/app/controller';
import { ParentIdType } from '../../common/parentFolder/type';
+import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant';
export type AppSchema = {
_id: string;
@@ -114,11 +115,19 @@ export type VariableItemType = {
id: string;
key: string;
label: string;
- type: `${VariableInputEnum}`;
+ type: VariableInputEnum;
required: boolean;
- maxLen: number;
- enums: { value: string }[];
- valueType: WorkflowIOValueTypeEnum;
+ description: string;
+ valueType?: WorkflowIOValueTypeEnum;
+ defaultValue?: any;
+
+ // input
+ maxLength?: number;
+ // numberInput
+ max?: number;
+ min?: number;
+ // select
+ enums?: { value: string; label: string }[];
};
// tts
export type AppTTSConfigType = {
diff --git a/packages/global/core/chat/adapt.ts b/packages/global/core/chat/adapt.ts
index d126d19fc..9b9c34d32 100644
--- a/packages/global/core/chat/adapt.ts
+++ b/packages/global/core/chat/adapt.ts
@@ -122,6 +122,9 @@ export const chats2GPTMessages = ({
value.type === ChatItemValueTypeEnum.text &&
typeof value.text?.content === 'string'
) {
+ if (!value.text.content && item.value.length > 1) {
+ return;
+ }
// Concat text
const lastValue = item.value[i - 1];
const lastResult = aiResults[aiResults.length - 1];
diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts
index 3cde9035d..49481e077 100644
--- a/packages/global/core/workflow/constants.ts
+++ b/packages/global/core/workflow/constants.ts
@@ -267,29 +267,51 @@ export enum NodeOutputKeyEnum {
export enum VariableInputEnum {
input = 'input',
textarea = 'textarea',
+ numberInput = 'numberInput',
select = 'select',
custom = 'custom'
}
-export const variableMap = {
+export const variableMap: Record<
+ VariableInputEnum,
+ {
+ icon: string;
+ label: string;
+ value: VariableInputEnum;
+ defaultValueType: WorkflowIOValueTypeEnum;
+ description?: string;
+ }
+> = {
[VariableInputEnum.input]: {
- icon: 'core/app/variable/input',
- title: i18nT('common:core.module.variable.input type'),
- desc: ''
+ icon: 'core/workflow/inputType/input',
+ label: i18nT('common:core.workflow.inputType.input'),
+ value: VariableInputEnum.input,
+ defaultValueType: WorkflowIOValueTypeEnum.string
},
[VariableInputEnum.textarea]: {
- icon: 'core/app/variable/textarea',
- title: i18nT('common:core.module.variable.textarea type'),
- desc: i18nT('app:variable.textarea_type_desc')
+ icon: 'core/workflow/inputType/textarea',
+ label: i18nT('common:core.workflow.inputType.textarea'),
+ value: VariableInputEnum.textarea,
+ defaultValueType: WorkflowIOValueTypeEnum.string,
+ description: i18nT('app:variable.textarea_type_desc')
+ },
+ [VariableInputEnum.numberInput]: {
+ icon: 'core/workflow/inputType/numberInput',
+ label: i18nT('common:core.workflow.inputType.number input'),
+ value: VariableInputEnum.numberInput,
+ defaultValueType: WorkflowIOValueTypeEnum.number
},
[VariableInputEnum.select]: {
- icon: 'core/app/variable/select',
- title: i18nT('common:core.module.variable.select type'),
- desc: ''
+ icon: 'core/workflow/inputType/option',
+ label: i18nT('common:core.workflow.inputType.select'),
+ value: VariableInputEnum.select,
+ defaultValueType: WorkflowIOValueTypeEnum.string
},
[VariableInputEnum.custom]: {
- icon: 'core/app/variable/external',
- title: i18nT('common:core.module.variable.Custom type'),
- desc: i18nT('app:variable.select type_desc')
+ icon: 'core/workflow/inputType/customVariable',
+ label: i18nT('common:core.workflow.inputType.custom'),
+ value: VariableInputEnum.custom,
+ defaultValueType: WorkflowIOValueTypeEnum.string,
+ description: i18nT('app:variable.select type_desc')
}
};
diff --git a/packages/global/core/workflow/template/system/aiChat/index.ts b/packages/global/core/workflow/template/system/aiChat/index.ts
index 99c606939..0bc4e19ab 100644
--- a/packages/global/core/workflow/template/system/aiChat/index.ts
+++ b/packages/global/core/workflow/template/system/aiChat/index.ts
@@ -54,6 +54,7 @@ export const AiChatModule: FlowNodeTemplateType = {
intro: i18nT('workflow:template.ai_chat_intro'),
showStatus: true,
isTool: true,
+ courseUrl: '/docs/workflow/modules/ai_chat/',
version: '481',
inputs: [
Input_Template_SettingAiModel,
diff --git a/packages/global/core/workflow/template/system/assignedAnswer.ts b/packages/global/core/workflow/template/system/assignedAnswer.ts
index d0eae9c70..14f344be4 100644
--- a/packages/global/core/workflow/template/system/assignedAnswer.ts
+++ b/packages/global/core/workflow/template/system/assignedAnswer.ts
@@ -17,7 +17,7 @@ export const AssignedAnswerModule: FlowNodeTemplateType = {
avatar: 'core/workflow/template/reply',
name: i18nT('workflow:assigned_reply'),
intro: i18nT('workflow:intro_assigned_reply'),
-
+ courseUrl: '/docs/workflow/modules/reply/',
version: '481',
isTool: true,
inputs: [
diff --git a/packages/global/core/workflow/template/system/classifyQuestion/index.ts b/packages/global/core/workflow/template/system/classifyQuestion/index.ts
index c0531f0f1..8326d823a 100644
--- a/packages/global/core/workflow/template/system/classifyQuestion/index.ts
+++ b/packages/global/core/workflow/template/system/classifyQuestion/index.ts
@@ -31,6 +31,7 @@ export const ClassifyQuestionModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_question_classification'),
showStatus: true,
version: '481',
+ courseUrl: '/docs/workflow/modules/question_classify/',
inputs: [
{
...Input_Template_SelectAIModel,
diff --git a/packages/global/core/workflow/template/system/contextExtract/index.ts b/packages/global/core/workflow/template/system/contextExtract/index.ts
index 073173ea0..5d0333c42 100644
--- a/packages/global/core/workflow/template/system/contextExtract/index.ts
+++ b/packages/global/core/workflow/template/system/contextExtract/index.ts
@@ -26,6 +26,7 @@ export const ContextExtractModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_text_content_extraction'),
showStatus: true,
isTool: true,
+ courseUrl: '/docs/workflow/modules/content_extract/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/customFeedback.ts b/packages/global/core/workflow/template/system/customFeedback.ts
index 811ce9540..f3d5dd9c3 100644
--- a/packages/global/core/workflow/template/system/customFeedback.ts
+++ b/packages/global/core/workflow/template/system/customFeedback.ts
@@ -17,6 +17,7 @@ export const CustomFeedbackNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/customFeedback',
name: i18nT('workflow:custom_feedback'),
intro: i18nT('workflow:intro_custom_feedback'),
+ courseUrl: '/docs/workflow/modules/custom_feedback/',
version: '486',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/datasetSearch.ts b/packages/global/core/workflow/template/system/datasetSearch.ts
index bb89f4a9b..d83cf2f82 100644
--- a/packages/global/core/workflow/template/system/datasetSearch.ts
+++ b/packages/global/core/workflow/template/system/datasetSearch.ts
@@ -29,6 +29,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
intro: Dataset_SEARCH_DESC,
showStatus: true,
isTool: true,
+ courseUrl: '/docs/workflow/modules/dataset_search/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/http468.ts b/packages/global/core/workflow/template/system/http468.ts
index fab83fe6b..a31a9cbef 100644
--- a/packages/global/core/workflow/template/system/http468.ts
+++ b/packages/global/core/workflow/template/system/http468.ts
@@ -27,6 +27,7 @@ export const HttpNode468: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_http_request'),
showStatus: true,
isTool: true,
+ courseUrl: '/docs/workflow/modules/http/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/ifElse/index.ts b/packages/global/core/workflow/template/system/ifElse/index.ts
index fd0c49076..b50e9989f 100644
--- a/packages/global/core/workflow/template/system/ifElse/index.ts
+++ b/packages/global/core/workflow/template/system/ifElse/index.ts
@@ -23,6 +23,7 @@ export const IfElseNode: FlowNodeTemplateType = {
name: i18nT('workflow:condition_checker'),
intro: i18nT('workflow:execute_different_branches_based_on_conditions'),
showStatus: true,
+ courseUrl: '/docs/workflow/modules/tfswitch/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/laf.ts b/packages/global/core/workflow/template/system/laf.ts
index 0b2d9a3d5..61dadb191 100644
--- a/packages/global/core/workflow/template/system/laf.ts
+++ b/packages/global/core/workflow/template/system/laf.ts
@@ -32,6 +32,7 @@ export const LafModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_laf_function_call'),
showStatus: true,
isTool: true,
+ courseUrl: '/docs/workflow/modules/laf/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/sandbox/index.ts b/packages/global/core/workflow/template/system/sandbox/index.ts
index 5a9614a56..b66ac4bcc 100644
--- a/packages/global/core/workflow/template/system/sandbox/index.ts
+++ b/packages/global/core/workflow/template/system/sandbox/index.ts
@@ -26,6 +26,7 @@ export const CodeNode: FlowNodeTemplateType = {
name: i18nT('workflow:code_execution'),
intro: i18nT('workflow:execute_a_simple_script_code_usually_for_complex_data_processing'),
showStatus: true,
+ courseUrl: '/docs/workflow/modules/sandbox/',
version: '482',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/textEditor.ts b/packages/global/core/workflow/template/system/textEditor.ts
index 1dc17a2b1..c7f97d2e9 100644
--- a/packages/global/core/workflow/template/system/textEditor.ts
+++ b/packages/global/core/workflow/template/system/textEditor.ts
@@ -23,6 +23,7 @@ export const TextEditorNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/textConcat',
name: i18nT('workflow:text_concatenation'),
intro: i18nT('workflow:intro_text_concatenation'),
+ courseUrl: '/docs/workflow/modules/text_editor/',
version: '486',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/tools.ts b/packages/global/core/workflow/template/system/tools.ts
index 6c3315abb..bd91596d7 100644
--- a/packages/global/core/workflow/template/system/tools.ts
+++ b/packages/global/core/workflow/template/system/tools.ts
@@ -31,6 +31,7 @@ export const ToolModule: FlowNodeTemplateType = {
name: i18nT('workflow:template.tool_call'),
intro: i18nT('workflow:template.tool_call_intro'),
showStatus: true,
+ courseUrl: '/docs/workflow/modules/tool/',
version: '481',
inputs: [
{
diff --git a/packages/global/core/workflow/template/system/workflowStart.ts b/packages/global/core/workflow/template/system/workflowStart.ts
index 5f14b5aa9..930ced6ca 100644
--- a/packages/global/core/workflow/template/system/workflowStart.ts
+++ b/packages/global/core/workflow/template/system/workflowStart.ts
@@ -30,6 +30,7 @@ export const WorkflowStart: FlowNodeTemplateType = {
intro: '',
forbidDelete: true,
unique: true,
+ courseUrl: '/docs/workflow/modules/input/',
version: '481',
inputs: [{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }],
outputs: [
diff --git a/packages/global/core/workflow/type/index.d.ts b/packages/global/core/workflow/type/index.d.ts
index 3c300a470..99b1f67a8 100644
--- a/packages/global/core/workflow/type/index.d.ts
+++ b/packages/global/core/workflow/type/index.d.ts
@@ -35,7 +35,7 @@ export type WorkflowTemplateType = {
avatar: string;
intro?: string;
author?: string;
- inputExplanationUrl?: string;
+ courseUrl?: string;
version: string;
showStatus?: boolean;
diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts
index 332c4c119..227e9f4f2 100644
--- a/packages/global/core/workflow/type/node.d.ts
+++ b/packages/global/core/workflow/type/node.d.ts
@@ -32,7 +32,6 @@ export type FlowNodeCommonType = {
avatar?: string;
name: string;
intro?: string; // template list intro
- inputExplanationUrl?: string;
showStatus?: boolean; // chatting response step status
version: string;
@@ -69,6 +68,7 @@ export type FlowNodeTemplateType = FlowNodeCommonType & {
unique?: boolean;
diagram?: string; // diagram url
+ courseUrl?: string; // course url
};
export type NodeTemplateListItemType = {
diff --git a/packages/global/core/workflow/utils.ts b/packages/global/core/workflow/utils.ts
index aa7e66dd1..6fbb98138 100644
--- a/packages/global/core/workflow/utils.ts
+++ b/packages/global/core/workflow/utils.ts
@@ -230,6 +230,7 @@ export const appData2FlowNodeIO = ({
FlowNodeInputTypeEnum.textarea,
FlowNodeInputTypeEnum.reference
],
+ [VariableInputEnum.numberInput]: [FlowNodeInputTypeEnum.numberInput],
[VariableInputEnum.select]: [FlowNodeInputTypeEnum.select],
[VariableInputEnum.custom]: [
FlowNodeInputTypeEnum.input,
@@ -246,7 +247,7 @@ export const appData2FlowNodeIO = ({
description: '',
valueType: WorkflowIOValueTypeEnum.any,
required: item.required,
- list: item.enums.map((enumItem) => ({
+ list: item.enums?.map((enumItem) => ({
label: enumItem.value,
value: enumItem.value
}))
@@ -391,7 +392,13 @@ export function replaceEditorVariable({
}
];
}
- return [];
+ return [
+ {
+ id: item.key,
+ value: item.value,
+ nodeId: runningNode.nodeId
+ }
+ ];
});
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
diff --git a/packages/plugins/src/Doc2X/FileImg2text/template.json b/packages/plugins/src/Doc2X/FileImg2text/template.json
index f47b89853..37d992a3f 100644
--- a/packages/plugins/src/Doc2X/FileImg2text/template.json
+++ b/packages/plugins/src/Doc2X/FileImg2text/template.json
@@ -4,7 +4,7 @@
"name": "Doc2X 图像(文件)识别",
"avatar": "plugins/doc2x",
"intro": "将上传的图片文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
- "inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
+ "courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true,
"weight": 10,
diff --git a/packages/plugins/src/Doc2X/FilePDF2text/template.json b/packages/plugins/src/Doc2X/FilePDF2text/template.json
index 575060c6b..4fa3f0908 100644
--- a/packages/plugins/src/Doc2X/FilePDF2text/template.json
+++ b/packages/plugins/src/Doc2X/FilePDF2text/template.json
@@ -4,7 +4,7 @@
"name": "Doc2X PDF文件(文件)识别",
"avatar": "plugins/doc2x",
"intro": "将上传的PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
- "inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
+ "courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true,
"weight": 10,
diff --git a/packages/plugins/src/Doc2X/URLImg2text/template.json b/packages/plugins/src/Doc2X/URLImg2text/template.json
index f3ea27508..6afbb76bf 100644
--- a/packages/plugins/src/Doc2X/URLImg2text/template.json
+++ b/packages/plugins/src/Doc2X/URLImg2text/template.json
@@ -4,7 +4,7 @@
"name": "Doc2X 图像(URL)识别",
"avatar": "plugins/doc2x",
"intro": "从URL下载图片并发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
- "inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
+ "courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true,
"weight": 10,
diff --git a/packages/plugins/src/Doc2X/URLPDF2text/template.json b/packages/plugins/src/Doc2X/URLPDF2text/template.json
index 32db81c90..6d0496d05 100644
--- a/packages/plugins/src/Doc2X/URLPDF2text/template.json
+++ b/packages/plugins/src/Doc2X/URLPDF2text/template.json
@@ -4,7 +4,7 @@
"name": "Doc2X PDF文件(URL)识别",
"avatar": "plugins/doc2x",
"intro": "从URL下载PDF文件,并发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本",
- "inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
+ "courseUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true,
"weight": 10,
diff --git a/packages/plugins/src/feishu/template.json b/packages/plugins/src/feishu/template.json
index 8728f5790..947eb4793 100644
--- a/packages/plugins/src/feishu/template.json
+++ b/packages/plugins/src/feishu/template.json
@@ -4,7 +4,7 @@
"name": "飞书机器人 webhook",
"avatar": "/appMarketTemplates/plugin-feishu/avatar.svg",
"intro": "向飞书机器人发起 webhook 请求。",
- "inputExplanationUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
+ "courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
"showStatus": false,
"weight": 10,
diff --git a/packages/service/core/app/plugin/controller.ts b/packages/service/core/app/plugin/controller.ts
index 9b2e7d9a3..e94ce67a0 100644
--- a/packages/service/core/app/plugin/controller.ts
+++ b/packages/service/core/app/plugin/controller.ts
@@ -96,7 +96,7 @@ export async function getChildAppPreviewNode({
avatar: app.avatar,
name: app.name,
intro: app.intro,
- inputExplanationUrl: app.inputExplanationUrl,
+ courseUrl: app.courseUrl,
showStatus: app.showStatus,
isTool: true,
version: app.version,
diff --git a/packages/service/core/workflow/dispatch/agent/runTool/index.ts b/packages/service/core/workflow/dispatch/agent/runTool/index.ts
index 8f52c84c3..587f04d13 100644
--- a/packages/service/core/workflow/dispatch/agent/runTool/index.ts
+++ b/packages/service/core/workflow/dispatch/agent/runTool/index.ts
@@ -150,7 +150,11 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
assistantResponses = [], // FastGPT system store assistant.value response
runTimes
} = await (async () => {
- const adaptMessages = chats2GPTMessages({ messages, reserveId: false });
+ const adaptMessages = chats2GPTMessages({
+ messages,
+ reserveId: false,
+ reserveTool: !!toolModel.toolChoice
+ });
if (toolModel.toolChoice) {
return runToolWithToolChoice({
diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts
index 85e9a27a0..23ea01118 100644
--- a/packages/web/components/common/Icon/constants.ts
+++ b/packages/web/components/common/Icon/constants.ts
@@ -275,6 +275,7 @@ export const iconPaths = {
'core/workflow/versionHistories': () => import('./icons/core/workflow/versionHistories.svg'),
date: () => import('./icons/date.svg'),
delete: () => import('./icons/delete.svg'),
+ drag: () => import('./icons/drag.svg'),
edit: () => import('./icons/edit.svg'),
empty: () => import('./icons/empty.svg'),
export: () => import('./icons/export.svg'),
diff --git a/packages/web/components/common/Icon/icons/drag.svg b/packages/web/components/common/Icon/icons/drag.svg
new file mode 100644
index 000000000..15c0fe534
--- /dev/null
+++ b/packages/web/components/common/Icon/icons/drag.svg
@@ -0,0 +1,8 @@
+
diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json
index 5329ddc7a..c5165d3cd 100644
--- a/packages/web/i18n/en/common.json
+++ b/packages/web/i18n/en/common.json
@@ -21,6 +21,7 @@
"Login": "Login",
"Move": "Move",
"Name": "Name",
+ "None": "None",
"Rename": "Rename",
"Resume": "Resume",
"Running": "Running",
@@ -750,7 +751,6 @@
"core.module.variable.select type": "Dropdown Single Select",
"core.module.variable.text max length": "Max Length",
"core.module.variable.textarea type": "Paragraph",
- "core.module.variable.variable name": "Variable Name",
"core.module.variable.variable name is required": "Variable Name Cannot Be Empty",
"core.module.variable.variable option is required": "Options Cannot Be All Empty",
"core.module.variable.variable option is value is required": "Option Content Cannot Be Empty",
@@ -781,7 +781,6 @@
"core.workflow.Stop debug": "Stop Debugging",
"core.workflow.Success": "Run Successful",
"core.workflow.Value type": "Data Type",
- "core.workflow.Variable.Variable type": "Variable Type",
"core.workflow.debug.Done": "Debugging Completed",
"core.workflow.debug.Hide result": "Hide Result",
"core.workflow.debug.Not result": "No Run Result",
diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json
index 3a51af540..338a5909c 100644
--- a/packages/web/i18n/en/workflow.json
+++ b/packages/web/i18n/en/workflow.json
@@ -2,7 +2,11 @@
"Array_element": "Array element",
"Code": "Code",
"Confirm_sync_node": "It will be updated to the latest node configuration and fields that do not exist in the template will be deleted (including all custom fields).\n\nIf the fields are complex, it is recommended that you copy a node first and then update the original node to facilitate parameter copying.",
+ "Node_variables": "Node variables",
+ "Node.Open_Node_Course": "Open node course",
"Quote_prompt_setting": "Quote prompt",
+ "Variable.Variable type": "Variable type",
+ "Variable_name": "Variable name",
"add_new_input": "Add New Input",
"add_new_output": "New output",
"append_application_reply_to_history_as_new_context": "Append the application's reply to the history as new context",
@@ -179,6 +183,7 @@
"user_question": "User Question",
"user_question_tool_desc": "User input questions (questions need to be improved)",
"value_type": "Value type",
+ "variable_description": "Variable description",
"variable_picker_tips": "Type node name or variable name to search",
"variable_update": "Variable Update",
"workflow.My edit": "My Edit",
diff --git a/packages/web/i18n/zh/common.json b/packages/web/i18n/zh/common.json
index adba6fb9b..ec5102b77 100644
--- a/packages/web/i18n/zh/common.json
+++ b/packages/web/i18n/zh/common.json
@@ -21,6 +21,7 @@
"Login": "登录",
"Move": "移动",
"Name": "名称",
+ "None": "无",
"Rename": "重命名",
"Resume": "恢复",
"Running": "运行中",
@@ -755,7 +756,6 @@
"core.module.variable.select type": "下拉单选",
"core.module.variable.text max length": "最大长度",
"core.module.variable.textarea type": "段落",
- "core.module.variable.variable name": "变量名",
"core.module.variable.variable name is required": "变量名不能为空",
"core.module.variable.variable option is required": "选项不能全空",
"core.module.variable.variable option is value is required": "选项内容不能为空",
@@ -786,7 +786,6 @@
"core.workflow.Stop debug": "停止调试",
"core.workflow.Success": "运行成功",
"core.workflow.Value type": "数据类型",
- "core.workflow.Variable.Variable type": "变量类型",
"core.workflow.debug.Done": "完成调试",
"core.workflow.debug.Hide result": "隐藏结果",
"core.workflow.debug.Not result": "无运行结果",
diff --git a/packages/web/i18n/zh/workflow.json b/packages/web/i18n/zh/workflow.json
index 9d78b18cc..4b5f5dc16 100644
--- a/packages/web/i18n/zh/workflow.json
+++ b/packages/web/i18n/zh/workflow.json
@@ -2,7 +2,11 @@
"Array_element": "数组元素",
"Code": "代码",
"Confirm_sync_node": "将会更新至最新的节点配置,不存在模板中的字段将会被删除(包括所有自定义字段)。\n如果字段较为复杂,建议您先复制一份节点,再更新原来的节点,便于参数复制。",
+ "Node_variables": "节点变量",
+ "Node.Open_Node_Course": "查看节点教程",
"Quote_prompt_setting": "引用提示词配置",
+ "Variable.Variable type": "变量类型",
+ "Variable_name": "变量名",
"add_new_input": "新增输入",
"add_new_output": "新增输出",
"append_application_reply_to_history_as_new_context": "将该应用回复内容拼接到历史记录中,作为新的上下文返回",
@@ -180,6 +184,7 @@
"user_question": "用户问题",
"user_question_tool_desc": "用户输入的问题(问题需要完善)",
"value_type": "数据类型",
+ "variable_description": "变量描述",
"variable_picker_tips": "可输入节点名或变量名搜索",
"variable_update": "变量更新",
"workflow.My edit": "我的编辑",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1466d1749..3e2c1cc35 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2710,28 +2710,24 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@next/swc-linux-arm64-musl@14.2.5':
resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@next/swc-linux-x64-gnu@14.2.5':
resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@next/swc-linux-x64-musl@14.2.5':
resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- libc: [musl]
'@next/swc-win32-arm64-msvc@14.2.5':
resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==}
@@ -2792,28 +2788,24 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@node-rs/jieba-linux-arm64-musl@1.10.0':
resolution: {integrity: sha512-gxqoAVOQsn9sgYK6mFO9dsMZ/yOMvVecLZW5rGvLErjiugVvYUlESXIvCqxp2GSws8RtTqJj6p9u/lBmCCuvaw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@node-rs/jieba-linux-x64-gnu@1.10.0':
resolution: {integrity: sha512-rS5Shs8JITxJjFIjoIZ5a9O+GO21TJgKu03g2qwFE3QaN5ZOvXtz+/AqqyfT4GmmMhCujD83AGqfOGXDmItF9w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@node-rs/jieba-linux-x64-musl@1.10.0':
resolution: {integrity: sha512-BvSiF2rR8Birh2oEVHcYwq0WGC1cegkEdddWsPrrSmpKmukJE2zyjcxaOOggq2apb8fIRsjyeeUh6X3R5AgjvA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- libc: [musl]
'@node-rs/jieba-wasm32-wasi@1.10.0':
resolution: {integrity: sha512-EzeAAbRrFTdYw61rd8Mfwdp/fA21d58z9vLY06CDbI+dqANfMFn1IUdwzKWi8S5J/MRhvbzonbbh3yHlz6F43Q==}
@@ -2967,55 +2959,46 @@ packages:
resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==}
cpu: [arm]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.18.1':
resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==}
cpu: [arm]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.18.1':
resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==}
cpu: [arm64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.18.1':
resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==}
cpu: [arm64]
os: [linux]
- libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.18.1':
resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==}
cpu: [ppc64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.18.1':
resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==}
cpu: [riscv64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.18.1':
resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==}
cpu: [s390x]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.18.1':
resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==}
cpu: [x64]
os: [linux]
- libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.18.1':
resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==}
cpu: [x64]
os: [linux]
- libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.18.1':
resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==}
diff --git a/projects/app/src/components/core/app/VariableEdit.tsx b/projects/app/src/components/core/app/VariableEdit.tsx
index a21072725..58a18c706 100644
--- a/projects/app/src/components/core/app/VariableEdit.tsx
+++ b/projects/app/src/components/core/app/VariableEdit.tsx
@@ -2,17 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import {
Box,
Button,
- ModalFooter,
- ModalBody,
- NumberInput,
- NumberInputField,
- NumberInputStepper,
- NumberIncrementStepper,
- NumberDecrementStepper,
Flex,
- Switch,
- Input,
- FormControl,
Table,
Thead,
Tbody,
@@ -20,7 +10,7 @@ import {
Th,
Td,
TableContainer,
- useDisclosure
+ Stack
} from '@chakra-ui/react';
import { SmallAddIcon } from '@chakra-ui/icons';
import {
@@ -31,38 +21,34 @@ import {
import type { VariableItemType } from '@fastgpt/global/core/app/type.d';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useForm } from 'react-hook-form';
-import { useFieldArray } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
-import MyRadio from '@/components/common/MyRadio';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import ChatFunctionTip from './Tip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
-import { FlowValueTypeMap } from '@fastgpt/global/core/workflow/node/constant';
-import MySelect from '@fastgpt/web/components/common/MySelect';
+import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
+import InputTypeConfig from '@/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig';
export const defaultVariable: VariableItemType = {
id: nanoid(),
- key: 'key',
- label: 'label',
+ key: '',
+ label: '',
type: VariableInputEnum.input,
+ description: '',
required: true,
- maxLen: 50,
- enums: [{ value: '' }],
valueType: WorkflowIOValueTypeEnum.string
};
-export const addVariable = () => {
- const newVariable = { ...defaultVariable, key: '', id: '' };
- return newVariable;
+
+type InputItemType = VariableItemType & {
+ list: { label: string; value: string }[];
};
-const valueTypeMap = {
- [VariableInputEnum.input]: WorkflowIOValueTypeEnum.string,
- [VariableInputEnum.select]: WorkflowIOValueTypeEnum.string,
- [VariableInputEnum.textarea]: WorkflowIOValueTypeEnum.string,
- [VariableInputEnum.custom]: WorkflowIOValueTypeEnum.any
+
+export const addVariable = () => {
+ const newVariable = { ...defaultVariable, key: '', id: '', list: [{ value: '', label: '' }] };
+ return newVariable;
};
const VariableEdit = ({
@@ -74,46 +60,37 @@ const VariableEdit = ({
}) => {
const { t } = useTranslation();
const { toast } = useToast();
- const [refresh, setRefresh] = useState(false);
- const VariableTypeList = useMemo(
+ const form = useForm();
+ const { setValue, reset, watch, getValues } = form;
+ const value = getValues();
+ const type = watch('type');
+ const valueType = watch('valueType');
+ const max = watch('max');
+ const min = watch('min');
+ const defaultValue = watch('defaultValue');
+
+ const inputTypeList = useMemo(
() =>
- Object.entries(variableMap).map(([key, value]) => ({
- title: t(value.title as any),
- icon: value.icon,
- value: key
+ Object.values(variableMap).map((item) => ({
+ icon: item.icon,
+ label: t(item.label as any),
+ value: item.value,
+ defaultValueType: item.defaultValueType,
+ description: item.description ? t(item.description as any) : ''
})),
[t]
);
- const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure();
- const {
- setValue,
- reset: resetEdit,
- register: registerEdit,
- getValues: getValuesEdit,
- setValue: setValuesEdit,
- control: editVariableController,
- handleSubmit: handleSubmitEdit,
- watch
- } = useForm<{ variable: VariableItemType }>();
-
- const variableType = watch('variable.type');
- const valueType = watch('variable.valueType');
-
- const {
- fields: selectEnums,
- append: appendEnums,
- remove: removeEnums
- } = useFieldArray({
- control: editVariableController,
- name: 'variable.enums'
- });
+ const defaultValueType = useMemo(() => {
+ const item = inputTypeList.find((item) => item.value === type);
+ return item?.defaultValueType;
+ }, [inputTypeList, type]);
const formatVariables = useMemo(() => {
const results = formatEditorVariablePickerIcon(variables);
- return results.map((item) => {
- const variable = variables.find((variable) => variable.key === item.key);
+ return results.map((item) => {
+ const variable = variables.find((variable) => variable.key === item.key)!;
return {
...variable,
icon: item.icon
@@ -121,45 +98,12 @@ const VariableEdit = ({
});
}, [variables]);
- const valueTypeSelectList = useMemo(
- () =>
- Object.values(FlowValueTypeMap)
- .map((item) => ({
- label: t(item.label as any),
- value: item.value
- }))
- .filter(
- (item) =>
- ![
- WorkflowIOValueTypeEnum.arrayAny,
- WorkflowIOValueTypeEnum.selectApp,
- WorkflowIOValueTypeEnum.selectDataset,
- WorkflowIOValueTypeEnum.dynamic
- ].includes(item.value)
- ),
- [t]
- );
- const showValueTypeSelect = variableType === VariableInputEnum.custom;
+ const onSubmitSuccess = useCallback(
+ (data: InputItemType, action: 'confirm' | 'continue') => {
+ data.label = data?.label?.trim();
- const onSubmit = useCallback(
- ({ variable }: { variable: VariableItemType }) => {
- variable.key = variable.key.trim();
-
- // check select
- if (variable.type === VariableInputEnum.select) {
- const enums = variable.enums.filter((item) => item.value);
- if (enums.length === 0) {
- toast({
- status: 'warning',
- title: t('common:core.module.variable.variable option is required')
- });
- return;
- }
- }
-
- // check repeat key
const existingVariable = variables.find(
- (item) => item.key === variable.key && item.id !== variable.id
+ (item) => item.label === data.label && item.id !== data.id
);
if (existingVariable) {
toast({
@@ -169,32 +113,59 @@ const VariableEdit = ({
return;
}
- // set valuetype based on variable.type
- variable.valueType =
- variable.type === VariableInputEnum.custom
- ? variable.valueType
- : valueTypeMap[variable.type];
+ data.key = data.label;
+ data.enums = data.list;
- // set default required value based on variableType
- if (variable.type === VariableInputEnum.custom) {
- variable.required = false;
+ if (data.type === VariableInputEnum.custom) {
+ data.required = false;
+ }
+
+ if (data.type === VariableInputEnum.numberInput) {
+ data.valueType = WorkflowIOValueTypeEnum.number;
}
const onChangeVariable = [...variables];
- // update
- if (variable.id) {
- const index = variables.findIndex((item) => item.id === variable.id);
- onChangeVariable[index] = variable;
+ if (data.id) {
+ const index = variables.findIndex((item) => item.id === data.id);
+ onChangeVariable[index] = data;
} else {
onChangeVariable.push({
- ...variable,
+ ...data,
id: nanoid()
});
}
- onChange(onChangeVariable);
- onCloseEdit();
+
+ if (action === 'confirm') {
+ onChange(onChangeVariable);
+ reset({});
+ } else if (action === 'continue') {
+ onChange(onChangeVariable);
+ toast({
+ status: 'success',
+ title: t('common:common.Add Success')
+ });
+ reset({
+ ...addVariable(),
+ defaultValue: ''
+ });
+ }
},
- [onChange, onCloseEdit, t, toast, variables]
+ [variables, toast, t, onChange, reset]
+ );
+
+ const onSubmitError = useCallback(
+ (e: Object) => {
+ for (const item of Object.values(e)) {
+ if (item.message) {
+ toast({
+ status: 'warning',
+ title: item.message
+ });
+ break;
+ }
+ }
+ },
+ [toast]
);
return (
@@ -212,8 +183,7 @@ const VariableEdit = ({
size={'sm'}
mr={'-5px'}
onClick={() => {
- resetEdit({ variable: addVariable() });
- onOpenEdit();
+ reset(addVariable());
}}
>
{t('common:common.Add New')}
@@ -232,7 +202,7 @@ const VariableEdit = ({
w={'18px !important'}
p={0}
/>
- {t('common:core.module.variable.variable name')} |
+ {t('workflow:Variable_name')} |
{t('common:core.module.variable.key')} |
{t('common:common.Require Input')} |
|
@@ -241,8 +211,8 @@ const VariableEdit = ({
{formatVariables.map((item) => (
-
-
+ |
+
|
{item.label} |
{item.key} |
@@ -254,8 +224,11 @@ const VariableEdit = ({
w={'16px'}
cursor={'pointer'}
onClick={() => {
- resetEdit({ variable: item });
- onOpenEdit();
+ const formattedItem = {
+ ...item,
+ list: item.enums || []
+ };
+ reset(formattedItem);
}}
/>
)}
+
{/* Edit modal */}
-
-
- {variableType !== VariableInputEnum.custom && (
-
- {t('common:common.Require Input')}
-
-
- )}
-
- {t('common:core.module.variable.variable name')}
-
-
-
- {t('common:core.module.variable.key')}
-
-
-
- {t('workflow:value_type')}
- {showValueTypeSelect ? (
-
-
- list={valueTypeSelectList.filter(
- (item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
- )}
- value={valueType}
- onchange={(e) => {
- setValue('variable.valueType', e);
- }}
- />
-
- ) : (
- {valueTypeMap[variableType]}
- )}
-
-
-
- {t('common:core.workflow.Variable.Variable type')}
-
- {
- setValuesEdit('variable.type', e as any);
- setRefresh(!refresh);
- }}
- />
-
- {/* desc */}
- {variableMap[variableType]?.desc && (
-
- {t(variableMap[variableType].desc as any)}
-
- )}
-
- {variableType === VariableInputEnum.input && (
- <>
-
- {t('common:core.module.variable.text max length')}
+ {!!Object.keys(value).length && (
+ reset({})}
+ maxW={['90vw', '928px']}
+ w={'100%'}
+ isCentered
+ >
+
+
+
+ {t('workflow:Variable.Variable type')}
-
-
-
-
-
-
-
-
-
- >
- )}
-
- {variableType === VariableInputEnum.select && (
- <>
-
- {t('common:core.module.variable.variable options')}
-
-
- {selectEnums.map((item, i) => (
-
-
-
-
- {selectEnums.length > 1 && (
+
+
+ {inputTypeList.map((item) => {
+ const isSelected = type === item.value;
+ return (
+ svg': {
+ color: 'primary.600'
+ },
+ '& > span': {
+ color: 'myGray.900'
+ },
+ border: '1px solid #3370FF',
+ boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)'
+ }}
+ onClick={() => {
+ const defaultValIsNumber = !isNaN(Number(value.defaultValue));
+ // 如果切换到 numberInput,不是数字,则清空
+ if (
+ item.value === VariableInputEnum.select ||
+ (item.value === VariableInputEnum.numberInput && !defaultValIsNumber)
+ ) {
+ setValue('defaultValue', '');
+ }
+ setValue('type', item.value);
+ }}
+ >
removeEnums(i)}
+ name={item.icon as any}
+ w={'20px'}
+ mr={1.5}
+ color={isSelected ? 'primary.600' : 'myGray.400'}
/>
- )}
-
- ))}
+
+ {item.label}
+
+ {item.description && (
+
+ )}
+
+ );
+ })}
- }
- bg={'myGray.100 !important'}
- onClick={() => appendEnums({ value: '' })}
- >
- {t('common:core.module.variable add option')}
-
- >
- )}
-
-
-
-
-
-
-
+
+ reset({})}
+ onSubmitSuccess={onSubmitSuccess}
+ onSubmitError={onSubmitError}
+ />
+
+
+ )}
);
};
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx
index 23bea55aa..305639f76 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Input/ChatInput.tsx
@@ -81,16 +81,11 @@ const ChatInput = ({
const canSendMessage = havInput && !hasFileUploading;
// Upload files
- useRequest2(
- async () => {
- uploadFiles();
- },
- {
- manual: false,
- errorToast: t('common:upload_file_error'),
- refreshDeps: [fileList, outLinkAuthData, chatId]
- }
- );
+ useRequest2(uploadFiles, {
+ manual: false,
+ errorToast: t('common:upload_file_error'),
+ refreshDeps: [fileList, outLinkAuthData, chatId]
+ });
/* on send */
const handleSend = useCallback(
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/VariableInput.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/VariableInput.tsx
index 641603a54..ab3e8d70b 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/VariableInput.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/VariableInput.tsx
@@ -1,7 +1,18 @@
-import React from 'react';
+import React, { useCallback, useEffect, useMemo } from 'react';
import { Controller, UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'next-i18next';
-import { Box, Button, Card, FormControl, Input, Textarea } from '@chakra-ui/react';
+import {
+ Box,
+ Button,
+ Card,
+ Input,
+ NumberDecrementStepper,
+ NumberIncrementStepper,
+ NumberInput,
+ NumberInputField,
+ NumberInputStepper,
+ Textarea
+} from '@chakra-ui/react';
import ChatAvatar from './ChatAvatar';
import { MessageCardStyle } from '../constants';
import { VariableInputEnum } from '@fastgpt/global/core/workflow/constants';
@@ -10,6 +21,113 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { ChatBoxInputFormType } from '../type.d';
import { useContextSelector } from 'use-context-selector';
import { ChatBoxContext } from '../Provider';
+import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
+import { useDeepCompareEffect } from 'ahooks';
+import { VariableItemType } from '@fastgpt/global/core/app/type';
+
+export const VariableInputItem = ({
+ item,
+ variablesForm
+}: {
+ item: VariableItemType;
+ variablesForm: UseFormReturn;
+}) => {
+ const { register, control, setValue } = variablesForm;
+
+ return (
+
+
+ {item.label}
+ {item.required && (
+
+ *
+
+ )}
+ {item.description && }
+
+ {item.type === VariableInputEnum.input && (
+
+ )}
+ {item.type === VariableInputEnum.textarea && (
+
+ )}
+ {item.type === VariableInputEnum.select && (
+ {
+ return (
+ ({
+ label: item.value,
+ value: item.value
+ }))}
+ value={value}
+ onchange={(e) => setValue(item.key, e)}
+ />
+ );
+ }}
+ />
+ )}
+ {item.type === VariableInputEnum.numberInput && (
+ (
+ onChange(Number(valueString))}
+ >
+
+
+
+
+
+
+ )}
+ />
+ )}
+
+ );
+};
const VariableInput = ({
chatForm,
@@ -21,13 +139,22 @@ const VariableInput = ({
const { t } = useTranslation();
const { appAvatar, variableList, variablesForm } = useContextSelector(ChatBoxContext, (v) => v);
- const { register, setValue, handleSubmit: handleSubmitChat, control } = variablesForm;
+ const { reset, handleSubmit: handleSubmitChat } = variablesForm;
+
+ const defaultValues = useMemo(() => {
+ return variableList.reduce((acc: Record, item) => {
+ acc[item.key] = item.defaultValue;
+ return acc;
+ }, {});
+ }, [variableList]);
+
+ useDeepCompareEffect(() => {
+ reset(defaultValues);
+ }, [defaultValues]);
return (
- {/* avatar */}
- {/* message */}
{variableList.map((item) => (
-
-
- {item.label}
- {item.required && (
-
- *
-
- )}
-
- {item.type === VariableInputEnum.input && (
-
- )}
- {item.type === VariableInputEnum.textarea && (
-
- )}
- {item.type === VariableInputEnum.select && (
- {
- return (
- ({
- label: item.value,
- value: item.value
- }))}
- value={value}
- onchange={(e) => setValue(item.key, e)}
- />
- );
- }}
- />
- )}
-
+
))}
{!chatStarted && (
- }
- size={'sm'}
- maxW={'100px'}
- onClick={handleSubmitChat(() => {
- chatForm.setValue('chatStarted', true);
- })}
- >
- {t('common:core.chat.Start Chat')}
-
+
+ }
+ size={'sm'}
+ maxW={'100px'}
+ onClick={handleSubmitChat(() => {
+ chatForm.setValue('chatStarted', true);
+ })}
+ >
+ {t('common:core.chat.Start Chat')}
+
+
)}
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx
index 33b485f60..d58b33106 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/hooks/useFileUpload.tsx
@@ -181,7 +181,7 @@ export const useFileUpload = (props: UseFileUploadOptions) => {
});
// Update file url
- copyFile.url = `${location.origin}${previewUrl}`;
+ copyFile.url = previewUrl;
updateFiles(fileIndex, copyFile);
} catch (error) {
errorFileIndex.push(fileList.findIndex((item) => item.id === file.id)!);
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
index 48b93d84a..7d4700b81 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx
@@ -495,7 +495,8 @@ const ChatBox = (
// 这里,无论是否为交互模式,最后都是 Human 的消息。
const messages = chats2GPTMessages({
messages: newChatList.slice(0, -1),
- reserveId: true
+ reserveId: true,
+ reserveTool: true
});
const {
diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts b/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
index cb1d10e43..b090f38d6 100644
--- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
+++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/utils.ts
@@ -57,9 +57,9 @@ export const checkIsInteractiveByHistories = (chatHistories: ChatSiteItemType[])
) {
const params = lastMessageValue.interactive.params;
// 如果用户选择了,则不认为是交互模式(可能是上一轮以交互结尾,发起的新的一轮对话)
- if ('userSelectOptions' in params && 'userSelectedVal' in params) {
+ if ('userSelectOptions' in params) {
return !params.userSelectedVal;
- } else if ('inputForm' in params && 'submitted' in params) {
+ } else if ('inputForm' in params) {
return !params.submitted;
}
}
diff --git a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx
index ac753cb8f..b9b8e58cc 100644
--- a/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx
+++ b/projects/app/src/components/core/chat/ChatContainer/PluginRunBox/context.tsx
@@ -228,7 +228,8 @@ const PluginRunContextProvider = ({
value: []
}
],
- reserveId: true
+ reserveId: true,
+ reserveTool: true
});
try {
diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx
index 474fc2310..ed258b571 100644
--- a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx
+++ b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx
@@ -354,11 +354,11 @@ const RenderList = React.memo(function RenderList({
{t('app:tool_input_param_tip')}
- {configTool.inputExplanationUrl && (
+ {configTool.courseUrl && (
window.open(configTool.inputExplanationUrl, '_blank')}
+ onClick={() => window.open(configTool.courseUrl, '_blank')}
>
{t('app:workflow.Input guide')}
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
index 475f10259..b75fe37d6 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/IOTitle.tsx
@@ -1,29 +1,12 @@
import React from 'react';
import { Box, StackProps, HStack } from '@chakra-ui/react';
-import { useTranslation } from 'next-i18next';
-
-const IOTitle = ({
- text,
- inputExplanationUrl,
- ...props
-}: { text?: 'Input' | 'Output' | string; inputExplanationUrl?: string } & StackProps) => {
- const { t } = useTranslation();
+import MyIcon from '@fastgpt/web/components/common/Icon';
+const IOTitle = ({ text, ...props }: { text?: 'Input' | 'Output' | string } & StackProps) => {
return (
{text}
-
-
- {inputExplanationUrl && (
- window.open(inputExplanationUrl, '_blank')}
- >
- {t('app:workflow.Input guide')}
-
- )}
);
};
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
index 32e82823d..25ae91fa6 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useDebug.tsx
@@ -1,7 +1,7 @@
import { storeNodes2RuntimeNodes } from '@fastgpt/global/core/workflow/runtime/utils';
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 { useCallback, useState, useMemo, useEffect } from 'react';
import { checkWorkflowNodeAndConnection } from '@/web/core/workflow/utils';
import { useTranslation } from 'next-i18next';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -21,19 +21,30 @@ import {
NumberInputStepper,
Switch
} from '@chakra-ui/react';
-import { useForm } from 'react-hook-form';
-import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
+import { FieldErrors, useForm } from 'react-hook-form';
+import {
+ VariableInputEnum,
+ WorkflowIOValueTypeEnum
+} from '@fastgpt/global/core/workflow/constants';
import { checkInputIsReference } from '@fastgpt/global/core/workflow/utils';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext, getWorkflowStore } from '../../context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
+import { AppContext } from '../../../context';
+import { VariableInputItem } from '@/components/core/chat/ChatContainer/ChatBox/components/VariableInput';
+import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
const MyRightDrawer = dynamic(
() => import('@fastgpt/web/components/common/MyDrawer/MyRightDrawer')
);
const JsonEditor = dynamic(() => import('@fastgpt/web/components/common/Textarea/JsonEditor'));
+enum TabEnum {
+ global = 'global',
+ node = 'node'
+}
+
export const useDebug = () => {
const { t } = useTranslation();
const { toast } = useToast();
@@ -43,6 +54,23 @@ export const useDebug = () => {
const edges = useContextSelector(WorkflowContext, (v) => v.edges);
const onStartNodeDebug = useContextSelector(WorkflowContext, (v) => v.onStartNodeDebug);
+ const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
+
+ const filteredVar = useMemo(() => {
+ const variables = appDetail.chatConfig.variables;
+ return variables?.filter((item) => item.type !== VariableInputEnum.custom) || [];
+ }, [appDetail.chatConfig.variables]);
+
+ const [defaultGlobalVariables, setDefaultGlobalVariables] = useState>(
+ filteredVar.reduce(
+ (acc, item) => {
+ acc[item.key] = item.defaultValue;
+ return acc;
+ },
+ {} as Record
+ )
+ );
+
const [runtimeNodeId, setRuntimeNodeId] = useState();
const [runtimeNodes, setRuntimeNodes] = useState();
const [runtimeEdges, setRuntimeEdges] = useState();
@@ -108,6 +136,8 @@ export const useDebug = () => {
const DebugInputModal = useCallback(() => {
if (!runtimeNodes || !runtimeEdges) return <>>;
+ const [currentTab, setCurrentTab] = useState(TabEnum.node);
+
const runtimeNode = runtimeNodes.find((node) => node.nodeId === runtimeNodeId);
if (!runtimeNode) return <>>;
@@ -117,20 +147,24 @@ export const useDebug = () => {
if (input.required && !input.value) return true;
});
- const { register, getValues, setValue, handleSubmit } = useForm>({
- defaultValues: renderInputs.reduce((acc: Record, input) => {
- const isReference = checkInputIsReference(input);
- if (isReference) {
- acc[input.key] = undefined;
- } else if (typeof input.value === 'object') {
- acc[input.key] = JSON.stringify(input.value, null, 2);
- } else {
- acc[input.key] = input.value;
- }
+ const variablesForm = useForm>({
+ defaultValues: {
+ nodeVariables: renderInputs.reduce((acc: Record, input) => {
+ const isReference = checkInputIsReference(input);
+ if (isReference) {
+ acc[input.key] = undefined;
+ } else if (typeof input.value === 'object') {
+ acc[input.key] = JSON.stringify(input.value, null, 2);
+ } else {
+ acc[input.key] = input.value;
+ }
- return acc;
- }, {})
+ return acc;
+ }, {}),
+ globalVariables: defaultGlobalVariables
+ }
});
+ const { register, getValues, setValue, handleSubmit } = variablesForm;
const onClose = () => {
setRuntimeNodeId(undefined);
@@ -152,12 +186,13 @@ export const useDebug = () => {
input.valueType === WorkflowIOValueTypeEnum.string ||
input.valueType === WorkflowIOValueTypeEnum.number ||
input.valueType === WorkflowIOValueTypeEnum.boolean
- )
- return data[input.key];
+ ) {
+ return data.nodeVariables[input.key];
+ }
- return JSON.parse(data[input.key]);
+ return JSON.parse(data.nodeVariables[input.key]);
} catch (e) {
- return data[input.key];
+ return data.nodeVariables[input.key];
}
})();
@@ -169,11 +204,33 @@ export const useDebug = () => {
}
: node
),
- runtimeEdges: runtimeEdges
+ runtimeEdges: runtimeEdges,
+ variables: data.globalVariables
});
+
+ // Filter global variables and set them as default global variable values
+ setDefaultGlobalVariables(data.globalVariables);
+
onClose();
};
+ const onCheckRunError = useCallback((e: FieldErrors>) => {
+ const hasRequiredNodeVar =
+ e.nodeVariables && Object.values(e.nodeVariables).some((item) => item.type === 'required');
+
+ if (hasRequiredNodeVar) {
+ return setCurrentTab(TabEnum.node);
+ }
+
+ const hasRequiredGlobalVar =
+ e.globalVariables &&
+ Object.values(e.globalVariables).some((item) => item.type === 'required');
+
+ if (hasRequiredGlobalVar) {
+ setCurrentTab(TabEnum.global);
+ }
+ }, []);
+
return (
{
px={0}
>
- {renderInputs.map((input) => {
- const required = input.required || false;
+ {filteredVar.length > 0 && (
+
+ gap={3}
+ ml={-2}
+ mb={5}
+ inlineStyles={{}}
+ list={[
+ { label: t('workflow:Node_variables'), value: TabEnum.node },
+ { label: t('common:core.module.Variable'), value: TabEnum.global }
+ ]}
+ value={currentTab}
+ onChange={setCurrentTab}
+ />
+ )}
+
+ {filteredVar.map((item) => (
+
+ ))}
+
+
+ {renderInputs.map((input) => {
+ const required = input.required || false;
+
+ const RenderInput = (() => {
+ if (input.valueType === WorkflowIOValueTypeEnum.string) {
+ return (
+
+ );
+ }
+ if (input.valueType === WorkflowIOValueTypeEnum.number) {
+ return (
+
+
+
+
+
+
+
+ );
+ }
+ if (input.valueType === WorkflowIOValueTypeEnum.boolean) {
+ return (
+
+
+
+ );
+ }
+
+ let value = getValues(input.key) || '';
+ if (typeof value !== 'string') {
+ value = JSON.stringify(value, null, 2);
+ }
- const RenderInput = (() => {
- if (input.valueType === WorkflowIOValueTypeEnum.string) {
return (
-
-
+
);
- }, [onStartNodeDebug, runtimeEdges, runtimeNodeId, runtimeNodes, t]);
+ }, [
+ defaultGlobalVariables,
+ filteredVar,
+ onStartNodeDebug,
+ runtimeEdges,
+ runtimeNodeId,
+ runtimeNodes,
+ t
+ ]);
return {
DebugInputModal,
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
index 976b8b2ff..5adca23f5 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/hooks/useWorkflow.tsx
@@ -412,41 +412,37 @@ export const useWorkflow = () => {
});
/* node */
+ // Remove change node and its child nodes and edges
const handleRemoveNode = useMemoizedFn((change: NodeRemoveChange, nodeId: string) => {
// If the node has child nodes, remove the child nodes
+ const deletedNodeIdList = [nodeId];
+ const deletedEdgeIdList = edges
+ .filter((edge) => edge.source === nodeId || edge.target === nodeId)
+ .map((edge) => edge.id);
+
const childNodes = nodes.filter((n) => n.data.parentNodeId === nodeId);
if (childNodes.length > 0) {
const childNodeIds = childNodes.map((node) => node.id);
+ deletedNodeIdList.push(...childNodeIds);
+
const childEdges = edges.filter(
(edge) => childNodeIds.includes(edge.source) || childNodeIds.includes(edge.target)
);
-
- onNodesChange(
- childNodes.map((node) => ({
- type: 'remove',
- id: node.id
- }))
- );
- onEdgesChange(
- childEdges.map((edge) => ({
- type: 'remove',
- id: edge.id
- }))
- );
+ deletedEdgeIdList.push(...childEdges.map((edge) => edge.id));
}
- onNodesChange([change]);
-
- // Remove the edges connected to the node
- const nodeEdges = edges.filter((edge) => edge.source === nodeId || edge.target === nodeId);
- onEdgesChange(
- nodeEdges.map((edge) => ({
+ onNodesChange(
+ deletedNodeIdList.map((id) => ({
type: 'remove',
- id: edge.id
+ id
+ }))
+ );
+ onEdgesChange(
+ deletedEdgeIdList.map((id) => ({
+ type: 'remove',
+ id
}))
);
-
- return;
});
const handleSelectNode = useMemoizedFn((change: NodeSelectionChange) => {
// If the node is not selected and the Ctrl key is pressed, select the node
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
index 4c4d4f564..a0c80f9a6 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/Loop/NodeLoopStart.tsx
@@ -110,7 +110,7 @@ const NodeLoopStart = ({ data, selected }: NodeProps) => {
- {t('common:core.module.variable.variable name')}
+ {t('workflow:Variable_name')}
|
{t('common:core.workflow.Value type')} |
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeComment.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeComment.tsx
index fd623c2c0..1604ce8db 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeComment.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeComment.tsx
@@ -80,12 +80,13 @@ const NodeComment = ({ data }: NodeProps) => {
menuForbid={{
debug: true
}}
- border={'none'}
- rounded={'none'}
- bg={'#D8E9FF'}
- boxShadow={
- '0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
- }
+ customStyle={{
+ border: 'none',
+ rounded: 'none',
+ bg: '#D8E9FF',
+ boxShadow:
+ '0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
+ }}
>
v.onChangeNode);
+ const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
+ const edges = useContextSelector(WorkflowContext, (v) => v.edges);
+ const { appDetail } = useContextSelector(AppContext, (v) => v);
const { isOpen: isOpenCurl, onOpen: onOpenCurl, onClose: onCloseCurl } = useDisclosure();
@@ -91,19 +94,18 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
(item) => item.key === NodeInputKeyEnum.httpReqUrl
) as FlowNodeInputItemType;
- const onChangeUrl = (e: React.ChangeEvent) => {
+ const onChangeUrl = (value: string) => {
onChangeNode({
nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.httpReqUrl,
value: {
...requestUrl,
- value: e.target.value
+ value
}
});
};
- const onBlurUrl = (e: React.ChangeEvent) => {
- const val = e.target.value;
+ const onBlurUrl = (val: string) => {
// 拆分params和url
const url = val.split('?')[0];
const params = val.split('?')[1];
@@ -154,6 +156,16 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
}
};
+ const variables = useCreation(() => {
+ return getEditorVariables({
+ nodeId,
+ nodeList,
+ edges,
+ appDetail,
+ t
+ });
+ }, [nodeId, nodeList, edges, appDetail, t]);
+
return (
@@ -166,7 +178,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
-
+ ml={2}
+ >
+
+
{isOpenCurl && }
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx
index fb3298a51..c50492984 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputEditModal.tsx
@@ -22,7 +22,8 @@ export const defaultInput: FlowNodeInputItemType = {
key: '',
label: '',
description: '',
- defaultValue: ''
+ defaultValue: '',
+ list: [{ label: '', value: '' }]
};
const FieldEditModal = ({
@@ -133,10 +134,7 @@ const FieldEditModal = ({
const isEdit = !!defaultValue.key;
const form = useForm({
- defaultValues: {
- ...defaultValue,
- list: defaultValue.list?.length ? defaultValue.list : [{ label: '', value: '' }]
- }
+ defaultValues: defaultValue
});
const { getValues, setValue, watch, reset } = form;
@@ -149,7 +147,7 @@ const FieldEditModal = ({
const max = watch('max');
const min = watch('min');
const selectValueTypeList = watch('customInputConfig.selectValueTypeList');
- const defaultJsonValue = watch('defaultValue');
+ const defaultInputValue = watch('defaultValue');
const defaultValueType =
inputTypeList.flat().find((item) => item.value === inputType)?.defaultValueType ||
@@ -157,9 +155,9 @@ const FieldEditModal = ({
const onSubmitSuccess = useCallback(
(data: FlowNodeInputItemType, action: 'confirm' | 'continue') => {
- data.key = data?.key?.trim();
+ data.label = data?.label?.trim();
- if (!data.key) {
+ if (!data.label) {
return toast({
status: 'warning',
title: t('common:core.module.edit.Field Name Cannot Be Empty')
@@ -199,7 +197,7 @@ const FieldEditModal = ({
data.toolDescription = undefined;
}
- data.label = data.key;
+ data.key = data.label;
if (action === 'confirm') {
onSubmit(data);
@@ -327,7 +325,7 @@ const FieldEditModal = ({
max={max}
min={min}
selectValueTypeList={selectValueTypeList}
- defaultJsonValue={defaultJsonValue}
+ defaultValue={defaultInputValue}
isToolInput={isToolInput}
setIsToolInput={setIsToolInput}
valueType={valueType}
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx
index 0a18e5148..3b9282bb1 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/InputTypeConfig.tsx
@@ -6,11 +6,19 @@ import {
FormLabel,
HStack,
Input,
+ NumberDecrementStepper,
+ NumberIncrementStepper,
+ NumberInput,
+ NumberInputField,
+ NumberInputStepper,
Stack,
Switch,
Textarea
} from '@chakra-ui/react';
-import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
+import {
+ VariableInputEnum,
+ WorkflowIOValueTypeEnum
+} from '@fastgpt/global/core/workflow/constants';
import {
FlowNodeInputTypeEnum,
FlowValueTypeMap
@@ -25,6 +33,9 @@ import React, { useMemo } from 'react';
import { useFieldArray, UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
+import DndDrag, { Draggable } from '@fastgpt/web/components/common/DndDrag';
+
+type ListValueType = { id: string; value: string; label: string }[];
const InputTypeConfig = ({
form,
@@ -36,7 +47,7 @@ const InputTypeConfig = ({
max,
min,
selectValueTypeList,
- defaultJsonValue,
+ defaultValue,
isToolInput,
setIsToolInput,
valueType,
@@ -48,15 +59,15 @@ const InputTypeConfig = ({
form: UseFormReturn;
isEdit: boolean;
onClose: () => void;
- type: 'plugin' | 'formInput';
- inputType: FlowNodeInputTypeEnum;
+ type: 'plugin' | 'formInput' | 'variable';
+ inputType: FlowNodeInputTypeEnum | VariableInputEnum;
maxLength?: number;
max?: number;
min?: number;
selectValueTypeList?: WorkflowIOValueTypeEnum[];
- defaultJsonValue?: string;
+ defaultValue?: string;
// Plugin-specific fields
isToolInput?: boolean;
@@ -69,8 +80,23 @@ const InputTypeConfig = ({
onSubmitError: (e: Object) => void;
}) => {
const { t } = useTranslation();
+ const defaultListValue = { label: t('common:None'), value: '' };
- const { register, setValue, handleSubmit, control } = form;
+ const { register, setValue, handleSubmit, control, watch } = form;
+ const listValue: ListValueType = watch('list');
+
+ const typeLabels = {
+ name: {
+ formInput: t('common:core.module.input_name'),
+ plugin: t('common:core.module.Field Name'),
+ variable: t('workflow:Variable_name')
+ },
+ description: {
+ formInput: t('common:core.module.input_description'),
+ plugin: t('workflow:field_description'),
+ variable: t('workflow:variable_description')
+ }
+ };
const {
fields: selectEnums,
@@ -81,6 +107,11 @@ const InputTypeConfig = ({
name: 'list'
});
+ const mergedSelectEnums = selectEnums.map((field, index) => ({
+ ...field,
+ ...listValue[index]
+ }));
+
const valueTypeSelectList = Object.values(FlowValueTypeMap).map((item) => ({
label: t(item.label as any),
value: item.value
@@ -88,21 +119,26 @@ const InputTypeConfig = ({
const showValueTypeSelect =
inputType === FlowNodeInputTypeEnum.reference ||
- inputType === FlowNodeInputTypeEnum.customVariable;
+ inputType === FlowNodeInputTypeEnum.customVariable ||
+ inputType === VariableInputEnum.custom;
const showRequired = useMemo(() => {
- const list = [FlowNodeInputTypeEnum.addInputParam, FlowNodeInputTypeEnum.customVariable];
+ const list = [
+ FlowNodeInputTypeEnum.addInputParam,
+ FlowNodeInputTypeEnum.customVariable,
+ VariableInputEnum.custom
+ ];
return !list.includes(inputType);
}, [inputType]);
const showMaxLenInput = useMemo(() => {
const list = [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.textarea];
- return list.includes(inputType);
+ return list.includes(inputType as FlowNodeInputTypeEnum);
}, [inputType]);
const showMinMaxInput = useMemo(() => {
const list = [FlowNodeInputTypeEnum.numberInput];
- return list.includes(inputType);
+ return list.includes(inputType as FlowNodeInputTypeEnum);
}, [inputType]);
const showDefaultValue = useMemo(() => {
@@ -111,34 +147,31 @@ const InputTypeConfig = ({
FlowNodeInputTypeEnum.textarea,
FlowNodeInputTypeEnum.JSONEditor,
FlowNodeInputTypeEnum.numberInput,
- FlowNodeInputTypeEnum.switch
+ FlowNodeInputTypeEnum.switch,
+ FlowNodeInputTypeEnum.select
];
- return list.includes(inputType);
+ return list.includes(inputType as FlowNodeInputTypeEnum);
}, [inputType]);
return (
-
+
- {type === 'formInput'
- ? t('common:core.module.input_name')
- : t('common:core.module.Field Name')}
+ {typeLabels.name[type] || typeLabels.name.formInput}
- {type === 'formInput'
- ? t('common:core.module.input_description')
- : t('workflow:field_description')}
+ {typeLabels.description[type] || typeLabels.description.plugin}
) : (
- {defaultValueType}
+
+ {defaultValueType}
+
)}
)}
{showRequired && (
-
+
{t('workflow:field_required')}
@@ -191,7 +226,6 @@ const InputTypeConfig = ({
isChecked={isToolInput}
onChange={(e) => {
setIsToolInput && setIsToolInput();
- console.log(isToolInput);
}}
/>
@@ -208,6 +242,7 @@ const InputTypeConfig = ({
bg={'myGray.50'}
placeholder={t('common:core.module.Max Length placeholder')}
value={maxLength}
+ max={50000}
onChange={(e) => {
// @ts-ignore
setValue('maxLength', e || '');
@@ -258,13 +293,18 @@ const InputTypeConfig = ({
{t('common:core.module.Default Value')}
{inputType === FlowNodeInputTypeEnum.numberInput && (
-
+
+
+
+
+
+
+
)}
{inputType === FlowNodeInputTypeEnum.input && (
@@ -280,10 +320,29 @@ const InputTypeConfig = ({
onChange={(e) => {
setValue('defaultValue', e);
}}
- defaultValue={String(defaultJsonValue)}
+ defaultValue={defaultValue}
/>
)}
{inputType === FlowNodeInputTypeEnum.switch && }
+ {inputType === FlowNodeInputTypeEnum.select && (
+
+ list={[defaultListValue, ...listValue]
+ .filter((item) => item.label !== '')
+ .map((item) => ({
+ label: item.label,
+ value: item.value
+ }))}
+ value={
+ defaultValue && listValue.map((item) => item.value).includes(defaultValue)
+ ? defaultValue
+ : ''
+ }
+ onchange={(e) => {
+ setValue('defaultValue', e);
+ }}
+ w={'200px'}
+ />
+ )}
)}
@@ -314,40 +373,116 @@ const InputTypeConfig = ({
{inputType === FlowNodeInputTypeEnum.select && (
<>
-
- {selectEnums.map((item, i) => (
-
-
- {`${t('common:core.module.variable.variable options')} ${i + 1}`}
-
-
- {
- setValue(`list.${i}.value`, e.target.value);
- }
- })}
- />
-
- {selectEnums.length > 1 && (
- removeEnums(i)}
- />
- )}
-
- ))}
-
+
+ onDragEndCb={(list) => {
+ const newOrder = list.map((item) => item.id);
+ const newSelectEnums = newOrder
+ .map((id) => mergedSelectEnums.find((item) => item.id === id))
+ .filter(Boolean) as { id: string; value: string }[];
+ removeEnums();
+ newSelectEnums.forEach((item) => appendEnums(item));
+
+ // 防止最后一个元素被focus
+ setTimeout(() => {
+ if (document.activeElement instanceof HTMLElement) {
+ document.activeElement.blur();
+ }
+ }, 0);
+ }}
+ dataList={mergedSelectEnums}
+ renderClone={(provided, snapshot, rubric) => {
+ return (
+
+ {mergedSelectEnums[rubric.source.index].value}
+
+ );
+ }}
+ >
+ {(provided) => (
+
+ {mergedSelectEnums.map((item, i) => (
+
+ {(provided, snapshot) => (
+
+
+
+ {`${t('common:core.module.variable.variable options')} ${i + 1}`}
+
+
+ {
+ setValue(`list.${i}.value`, e.target.value);
+ }
+ })}
+ />
+
+ {selectEnums.length > 1 && (
+
+ removeEnums(i)}
+ />
+
+
+
+
+ )}
+
+
+ )}
+
+ ))}
+
+ {provided.placeholder}
+
+
+ )}
+
}
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/VariableTable.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/VariableTable.tsx
index 0bf15c292..3107e1497 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/VariableTable.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodePluginIO/VariableTable.tsx
@@ -21,9 +21,7 @@ const VariableTable = ({
-
- {t('common:core.module.variable.variable name')}
- |
+ {t('workflow:Variable_name')} |
{t('common:core.workflow.Value type')} |
{showToolColumn && {t('workflow:tool_input')} | }
|
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx
index 0d9e6d661..f2f018b0b 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeSimple.tsx
@@ -38,10 +38,7 @@ const NodeSimple = ({
{filterHiddenInputs.length > 0 && (
<>
-
+
>
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
index 3a18c962f..16ba84247 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx
@@ -25,6 +25,8 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
import { WholeResponseContent } from '@/components/core/chat/components/WholeResponseModal';
+import { useSystemStore } from '@/web/common/system/useSystemStore';
+import { getDocPath } from '@/web/common/system/doc';
type Props = FlowNodeItemType & {
children?: React.ReactNode | React.ReactNode[] | string;
@@ -39,7 +41,8 @@ type Props = FlowNodeItemType & {
copy?: boolean;
delete?: boolean;
};
-} & Omit;
+ customStyle?: FlexProps;
+};
const NodeCard = (props: Props) => {
const { t } = useTranslation();
@@ -63,9 +66,8 @@ const NodeCard = (props: Props) => {
isError = false,
debugResult,
isFolded,
- ...customStyle
+ customStyle
} = props;
-
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
@@ -102,14 +104,6 @@ const NodeCard = (props: Props) => {
if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId });
- // Focus update plugin latest inputExplanationUrl
- onChangeNode({
- nodeId,
- type: 'attr',
- key: 'inputExplanationUrl',
- value: template.inputExplanationUrl
- });
-
return template;
} else {
const template = moduleTemplatesFlat.find(
@@ -275,6 +269,24 @@ const NodeCard = (props: Props) => {
)}
+ {!!nodeTemplate?.diagram && node?.courseUrl && (
+
+ )}
+ {node?.courseUrl && !hasNewVersion && (
+
+ window.open(getDocPath(node.courseUrl || ''), '_blank')}
+ />
+
+ )}
@@ -295,6 +307,7 @@ const NodeCard = (props: Props) => {
onOpenConfirmSync,
onClickSyncVersion,
nodeTemplate?.diagram,
+ node?.courseUrl,
intro,
menuForbid,
nodeList,
diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
index d2c1f6a60..5f2c96735 100644
--- a/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
+++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/context.tsx
@@ -148,11 +148,13 @@ type WorkflowContextType = {
onStartNodeDebug: ({
entryNodeId,
runtimeNodes,
- runtimeEdges
+ runtimeEdges,
+ variables
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
+ variables: Record;
}) => Promise;
onStopNodeDebug: () => void;
@@ -749,17 +751,19 @@ const WorkflowContextProvider = ({
async ({
entryNodeId,
runtimeNodes,
- runtimeEdges
+ runtimeEdges,
+ variables
}: {
entryNodeId: string;
runtimeNodes: RuntimeNodeItemType[];
runtimeEdges: RuntimeEdgeItemType[];
+ variables: Record;
}) => {
const data = {
runtimeNodes,
runtimeEdges,
nextRunNodes: runtimeNodes.filter((node) => node.nodeId === entryNodeId),
- variables: {}
+ variables
};
onStopNodeDebug();
setWorkflowDebugData(data);