From fbc4ca980c644209691f4266ba4e61dffa8310cd Mon Sep 17 00:00:00 2001 From: zkyTech Date: Wed, 11 Dec 2024 10:10:53 +0800 Subject: [PATCH 01/10] fix: Remove duplicate 'response_format' parameter from model YAML files (#11531) Co-authored-by: zhangkunyuan --- .../model_providers/tongyi/llm/qwen-vl-max-0809.yaml | 2 -- .../model_runtime/model_providers/tongyi/llm/qwen-vl-max.yaml | 2 -- .../model_providers/tongyi/llm/qwen-vl-plus-0201.yaml | 2 -- .../model_providers/tongyi/llm/qwen-vl-plus-0809.yaml | 2 -- .../model_runtime/model_providers/tongyi/llm/qwen-vl-plus.yaml | 2 -- 5 files changed, 10 deletions(-) diff --git a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max-0809.yaml b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max-0809.yaml index 94b6666d0569fe..5970fec5e6d4fa 100644 --- a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max-0809.yaml +++ b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max-0809.yaml @@ -59,8 +59,6 @@ parameter_rules: help: zh_Hans: 生成时使用的随机数种子,用户控制模型生成内容的随机性。支持无符号64位整数,默认值为 1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 en_US: The random number seed used when generating, the user controls the randomness of the content generated by the model. Supports unsigned 64-bit integers, default value is 1234. When using seed, the model will try its best to generate the same or similar results, but there is currently no guarantee that the results will be exactly the same every time. - - name: response_format - use_template: response_format - name: repetition_penalty required: false type: float diff --git a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max.yaml b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max.yaml index b6172c1cbc3d06..c5c8dc5f7c4963 100644 --- a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max.yaml +++ b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-max.yaml @@ -59,8 +59,6 @@ parameter_rules: help: zh_Hans: 生成时使用的随机数种子,用户控制模型生成内容的随机性。支持无符号64位整数,默认值为 1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 en_US: The random number seed used when generating, the user controls the randomness of the content generated by the model. Supports unsigned 64-bit integers, default value is 1234. When using seed, the model will try its best to generate the same or similar results, but there is currently no guarantee that the results will be exactly the same every time. - - name: response_format - use_template: response_format - name: repetition_penalty required: false type: float diff --git a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0201.yaml b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0201.yaml index 03cb039d15a7dd..9d9d6c6d11141f 100644 --- a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0201.yaml +++ b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0201.yaml @@ -58,8 +58,6 @@ parameter_rules: help: zh_Hans: 生成时使用的随机数种子,用户控制模型生成内容的随机性。支持无符号64位整数,默认值为 1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 en_US: The random number seed used when generating, the user controls the randomness of the content generated by the model. Supports unsigned 64-bit integers, default value is 1234. When using seed, the model will try its best to generate the same or similar results, but there is currently no guarantee that the results will be exactly the same every time. - - name: response_format - use_template: response_format - name: repetition_penalty required: false type: float diff --git a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0809.yaml b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0809.yaml index 0be4b68f4f93ad..2fab6db648e722 100644 --- a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0809.yaml +++ b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus-0809.yaml @@ -59,8 +59,6 @@ parameter_rules: help: zh_Hans: 生成时使用的随机数种子,用户控制模型生成内容的随机性。支持无符号64位整数,默认值为 1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 en_US: The random number seed used when generating, the user controls the randomness of the content generated by the model. Supports unsigned 64-bit integers, default value is 1234. When using seed, the model will try its best to generate the same or similar results, but there is currently no guarantee that the results will be exactly the same every time. - - name: response_format - use_template: response_format - name: repetition_penalty required: false type: float diff --git a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus.yaml b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus.yaml index 6c8a8121c6d243..61820ca8538d29 100644 --- a/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus.yaml +++ b/api/core/model_runtime/model_providers/tongyi/llm/qwen-vl-plus.yaml @@ -59,8 +59,6 @@ parameter_rules: help: zh_Hans: 生成时使用的随机数种子,用户控制模型生成内容的随机性。支持无符号64位整数,默认值为 1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。 en_US: The random number seed used when generating, the user controls the randomness of the content generated by the model. Supports unsigned 64-bit integers, default value is 1234. When using seed, the model will try its best to generate the same or similar results, but there is currently no guarantee that the results will be exactly the same every time. - - name: response_format - use_template: response_format - name: repetition_penalty required: false type: float From 42d986b96dee14de8d5fbad51c3c5c4a4eab71d3 Mon Sep 17 00:00:00 2001 From: Tommy <34446820+Asterovim@users.noreply.github.com> Date: Wed, 11 Dec 2024 03:14:16 +0100 Subject: [PATCH 02/10] [Pixtral] Add new model ; add vision (#11231) --- .../mistralai/llm/_position.yaml | 2 + .../mistralai/llm/pixtral-12b-2409.yaml | 3 +- .../mistralai/llm/pixtral-large-2411.yaml | 52 +++++++++++++++++++ .../mistralai/llm/pixtral-large-latest.yaml | 52 +++++++++++++++++++ 4 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-2411.yaml create mode 100644 api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-latest.yaml diff --git a/api/core/model_runtime/model_providers/mistralai/llm/_position.yaml b/api/core/model_runtime/model_providers/mistralai/llm/_position.yaml index bdb06b7fff6376..5702797ac447c8 100644 --- a/api/core/model_runtime/model_providers/mistralai/llm/_position.yaml +++ b/api/core/model_runtime/model_providers/mistralai/llm/_position.yaml @@ -1,3 +1,5 @@ +- pixtral-large-latest +- pixtral-large-2411 - pixtral-12b-2409 - codestral-latest - mistral-embed diff --git a/api/core/model_runtime/model_providers/mistralai/llm/pixtral-12b-2409.yaml b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-12b-2409.yaml index 0b002b49cac8e0..9eb663bc31350b 100644 --- a/api/core/model_runtime/model_providers/mistralai/llm/pixtral-12b-2409.yaml +++ b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-12b-2409.yaml @@ -5,6 +5,7 @@ label: model_type: llm features: - agent-thought + - vision model_properties: mode: chat context_size: 128000 @@ -21,7 +22,7 @@ parameter_rules: max: 1 - name: max_tokens use_template: max_tokens - default: 1024 + default: 8192 min: 1 max: 8192 - name: safe_prompt diff --git a/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-2411.yaml b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-2411.yaml new file mode 100644 index 00000000000000..606c9aa3319a8b --- /dev/null +++ b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-2411.yaml @@ -0,0 +1,52 @@ +model: pixtral-large-2411 +label: + zh_Hans: pixtral-large-2411 + en_US: pixtral-large-2411 +model_type: llm +features: + - agent-thought + - vision +model_properties: + mode: chat + context_size: 128000 +parameter_rules: + - name: temperature + use_template: temperature + default: 0.7 + min: 0 + max: 1 + - name: top_p + use_template: top_p + default: 1 + min: 0 + max: 1 + - name: max_tokens + use_template: max_tokens + default: 8192 + min: 1 + max: 8192 + - name: safe_prompt + default: false + type: boolean + help: + en_US: Whether to inject a safety prompt before all conversations. + zh_Hans: 是否开启提示词审查 + label: + en_US: SafePrompt + zh_Hans: 提示词审查 + - name: random_seed + type: int + help: + en_US: The seed to use for random sampling. If set, different calls will generate deterministic results. + zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定 + label: + en_US: RandomSeed + zh_Hans: 随机数种子 + default: 0 + min: 0 + max: 2147483647 +pricing: + input: '0.008' + output: '0.024' + unit: '0.001' + currency: USD diff --git a/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-latest.yaml b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-latest.yaml new file mode 100644 index 00000000000000..4f0ed5ae5d77f2 --- /dev/null +++ b/api/core/model_runtime/model_providers/mistralai/llm/pixtral-large-latest.yaml @@ -0,0 +1,52 @@ +model: pixtral-large-latest +label: + zh_Hans: pixtral-large-latest + en_US: pixtral-large-latest +model_type: llm +features: + - agent-thought + - vision +model_properties: + mode: chat + context_size: 128000 +parameter_rules: + - name: temperature + use_template: temperature + default: 0.7 + min: 0 + max: 1 + - name: top_p + use_template: top_p + default: 1 + min: 0 + max: 1 + - name: max_tokens + use_template: max_tokens + default: 8192 + min: 1 + max: 8192 + - name: safe_prompt + default: false + type: boolean + help: + en_US: Whether to inject a safety prompt before all conversations. + zh_Hans: 是否开启提示词审查 + label: + en_US: SafePrompt + zh_Hans: 提示词审查 + - name: random_seed + type: int + help: + en_US: The seed to use for random sampling. If set, different calls will generate deterministic results. + zh_Hans: 当开启随机数种子以后,你可以通过指定一个固定的种子来使得回答结果更加稳定 + label: + en_US: RandomSeed + zh_Hans: 随机数种子 + default: 0 + min: 0 + max: 2147483647 +pricing: + input: '0.008' + output: '0.024' + unit: '0.001' + currency: USD From 86dfdcb8ecb193437887541f16aea7bf1ea70502 Mon Sep 17 00:00:00 2001 From: Yi Xiao <54782454+YIXIAO0@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:08:09 +0800 Subject: [PATCH 03/10] chore: update thai lang in app page (#11541) --- web/i18n/th-TH/app.ts | 104 +++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/web/i18n/th-TH/app.ts b/web/i18n/th-TH/app.ts index 6a55e0f51b04b6..230f1508076825 100644 --- a/web/i18n/th-TH/app.ts +++ b/web/i18n/th-TH/app.ts @@ -1,14 +1,14 @@ const translation = { - createApp: 'สร้างแอพ', + createApp: 'สร้างโปรเจกต์ใหม่', types: { all: 'ทั้งหมด', chatbot: 'แชทบอท', agent: 'ตัวแทน', - workflow: 'เวิร์กโฟลว์', - completion: 'เสร็จ สมบูรณ์', + workflow: 'กระบวนการทำงาน', + completion: 'เสร็จสมบูรณ์', }, duplicate: 'สำเนา', - duplicateTitle: 'แอปที่ซ้ํากัน', + duplicateTitle: 'ชื่อซ้ำ', export: 'ส่งออก DSL', exportFailed: 'ส่งออก DSL ล้มเหลว', importDSL: 'นําเข้าไฟล์ DSL', @@ -17,42 +17,42 @@ const translation = { importFromDSLFile: 'จากไฟล์ DSL', importFromDSLUrl: 'จาก URL', importFromDSLUrlPlaceholder: 'วางลิงค์ DSL ที่นี่', - deleteAppConfirmTitle: 'ลบแอพนี้?', - deleteAppConfirmContent: 'การลบแอปนั้นไม่สามารถย้อนกลับได้ ผู้ใช้จะไม่สามารถเข้าถึงแอปของคุณอีกต่อไป และการกําหนดค่าพร้อมท์และบันทึกทั้งหมดจะถูกลบอย่างถาวร', - appDeleted: 'แอพถูกลบ', - appDeleteFailed: 'ลบแอปไม่สําเร็จ', - join: 'เข้าร่วมชุมชน', + deleteAppConfirmTitle: 'ลบโปรเจกต์นี้?', + deleteAppConfirmContent: 'การลบโปรเจกนั้นไม่สามารถย้อนกลับได้ ผู้ใช้จะไม่สามารถเข้าถึงโปรเจกต์ของคุณอีกต่อไป และการกําหนดค่าต่างๆและบันทึกทั้งหมดจะถูกลบอย่างถาวร', + appDeleted: 'โปรเจกต์ถูกลบ', + appDeleteFailed: 'ลบโปรเจกต์ไม่สําเร็จ', + join: 'เข้าร่วมชุมชนนักพัฒนา', communityIntro: 'พูดคุยกับสมาชิกในทีม ผู้ร่วมให้ข้อมูล และนักพัฒนาในช่องทางต่างๆ', roadmap: 'ดูแผนงานของเรา', newApp: { - startFromBlank: 'สร้างจากช่องว่าง', + startFromBlank: 'สร้างโปรเจกต์ปล่าว', startFromTemplate: 'สร้างจากเทมเพลต', - captionAppType: 'คุณต้องการสร้างแอปประเภทใด', - chatbotDescription: 'สร้างแอปพลิเคชันที่ใช้การแชท แอพนี้ใช้รูปแบบคําถามและคําตอบ ทําให้สามารถสนทนาต่อเนื่องได้หลายรอบ', - completionDescription: 'สร้างแอปพลิเคชันที่สร้างข้อความคุณภาพสูงตามข้อความแจ้ง เช่น การสร้างบทความ สรุป การแปล และอื่นๆ', - completionWarning: 'แอปประเภทนี้จะไม่รองรับอีกต่อไป', - agentDescription: 'สร้างตัวแทนอัจฉริยะที่สามารถเลือกเครื่องมือเพื่อทํางานให้เสร็จได้โดยอัตโนมัติ', - workflowDescription: 'สร้างแอปพลิเคชันที่สร้างข้อความคุณภาพสูงตามการประสานเวิร์กโฟลว์ที่มีการปรับแต่งในระดับสูง เหมาะสําหรับผู้ใช้ที่มีประสบการณ์', + captionAppType: 'คุณต้องการสร้างโปรเจกต์ประเภทใด', + chatbotDescription: 'สร้างโปรเจกต์เป็นแอปพลิเคชันที่ใช้การแชท โปรเจกต์นี้ใช้รูปแบบคําถามและคําตอบ ทําให้สามารถสนทนาต่อเนื่องได้หลายรอบ(Multi-turn)', + completionDescription: 'สร้างโปรเจกต์เป็นแอปพลิเคชันที่สร้างข้อความคุณภาพสูงตามข้อความแจ้ง เช่น การสร้างบทความ สรุป การแปล และอื่นๆ', + completionWarning: 'โปรเจกต์ประเภทนี้จะไม่รองรับอีกต่อไป', + agentDescription: 'สร้างตัวแทน(Agent)อัจฉริยะที่สามารถเลือกเครื่องมือเพื่อทํางานให้เสร็จได้โดยอัตโนมัติ', + workflowDescription: 'สร้างโปรเจกต์ เป็นแอปพลิเคชันที่สร้างข้อความคุณภาพสูงตามการประสานกระบวนการทำงาน(Workflow) ที่มีการปรับแต่งในระดับสูง เหมาะสําหรับผู้ใช้ที่มีประสบการณ์', workflowWarning: 'ขณะนี้อยู่ในช่วงเบต้า', - chatbotType: 'วิธีการประสานแชทบอท', + chatbotType: 'รูปแบบแชทบอท', basic: 'พื้นฐาน', basicTip: 'สําหรับผู้เริ่มต้นสามารถเปลี่ยนไปใช้ Chatflow ได้ในภายหลัง', basicFor: 'สําหรับผู้เริ่มต้น', - basicDescription: 'Basic Orchestrate ช่วยให้สามารถประสานแอปแชทบอทโดยใช้การตั้งค่าง่ายๆ โดยไม่สามารถแก้ไขข้อความแจ้งในตัวได้ เหมาะสําหรับผู้เริ่มต้น', + basicDescription: 'Basic Orchestrate ช่วยให้สามารถประสานงานกันของ โปรเจกต์แชทบอทโดยใช้การตั้งค่าง่ายๆ โดยไม่สามารถแก้ไขข้อความแจ้งในตัวได้ เหมาะสําหรับผู้เริ่มต้น', advanced: 'แชทโฟลว์', - advancedFor: 'สําหรับผู้ใช้ขั้นสูง', + advancedFor: 'สําหรับผู้ใช้ขั้นสูง ที่สามารถปรับแต่งขั้นตอนและตัวเลือกต่างๆได้อย่างอิสระ', advancedDescription: 'Workflow Orchestrate ประสานงาน Chatbots ในรูปแบบของเวิร์กโฟลว์ โดยนําเสนอการปรับแต่งในระดับสูง รวมถึงความสามารถในการแก้ไขข้อความแจ้งในตัว เหมาะสําหรับผู้ใช้ที่มีประสบการณ์', - captionName: 'ไอคอนและชื่อแอป', - appNamePlaceholder: 'ตั้งชื่อแอปของคุณ', + captionName: 'ไอคอนและชื่อโปรเจกต์', + appNamePlaceholder: 'ตั้งชื่อโปรเจกต์ของคุณ', captionDescription: 'คำอธิบาย', - appDescriptionPlaceholder: 'ป้อนคําอธิบายของแอป', + appDescriptionPlaceholder: 'ป้อนคําอธิบายของโปรเจกต์', useTemplate: 'ใช้เทมเพลตนี้', - previewDemo: 'ตัวอย่างการสาธิต', + previewDemo: 'ตัวอย่างการใช้งาน', chatApp: 'ผู้ช่วย', - chatAppIntro: 'ฉันต้องการสร้างแอปพลิเคชันที่ใช้การแชท แอพนี้ใช้รูปแบบคําถามและคําตอบ ทําให้สามารถสนทนาต่อเนื่องได้หลายรอบ', - agentAssistant: 'ผู้ช่วยตัวแทนใหม่', - completeApp: 'เครื่องกําเนิดข้อความ', - completeAppIntro: 'ฉันต้องการสร้างแอปพลิเคชันที่สร้างข้อความคุณภาพสูงตามข้อความแจ้ง เช่น การสร้างบทความ สรุป การแปล และอื่นๆ', + chatAppIntro: 'ฉันต้องการสร้างโปรเจกต์ ที่เป็นแอปพลิเคชันที่ใช้การแชท โปรเจกต์นี้ใช้รูปแบบคําถามและคําตอบ ทําให้สามารถสนทนาต่อเนื่องได้หลายรอบ(Multi-turn)', + agentAssistant: 'ผู้ช่วยใหม่', + completeApp: 'เครื่องมือสร้างข้อความ', + completeAppIntro: 'ฉันต้องการสร้างโปรเจกต์ที่ ที่สามารถสร้างข้อความคุณภาพสูงตามข้อความแจ้ง เช่น การสร้างบทความ สรุป การแปล และอื่นๆ', showTemplates: 'ฉันต้องการเลือกจากเทมเพลต', hideTemplates: 'กลับไปที่การเลือกโหมด', Create: 'สร้าง', @@ -60,21 +60,21 @@ const translation = { Confirm: 'ยืนยัน', nameNotEmpty: 'ชื่อต้องไม่ว่างเปล่า', appTemplateNotSelected: 'โปรดเลือกเทมเพลต', - appTypeRequired: 'โปรดเลือกประเภทแอป', - appCreated: 'สร้างแอป', - caution: 'ความระมัดระวัง', + appTypeRequired: 'โปรดเลือกประเภทโปรเจกต์', + appCreated: 'สร้างโปรเจกต์', + caution: 'ข้อควรระวัง', appCreateDSLWarning: 'ข้อควรระวัง: ความแตกต่างของเวอร์ชัน DSL อาจส่งผลต่อคุณสมบัติบางอย่าง', - appCreateDSLErrorTitle: 'ความเข้ากันไม่ได้ของเวอร์ชัน', - appCreateDSLErrorPart1: 'ตรวจพบความแตกต่างอย่างมีนัยสําคัญในเวอร์ชัน DSL การบังคับนําเข้าอาจทําให้แอปพลิเคชันทํางานผิดปกติ', + appCreateDSLErrorTitle: 'ความเข้ากันไม่ได้ของ DSL เวอร์ชัน', + appCreateDSLErrorPart1: 'ตรวจพบความแตกต่างอย่างมีนัยสําคัญในเวอร์ชัน DSL การบังคับนําเข้าอาจทําให้โปรเจกต์ทํางานผิดปกติ', appCreateDSLErrorPart2: 'คุณต้องการดําเนินการต่อหรือไม่?', - appCreateDSLErrorPart3: 'เวอร์ชัน DSL ของแอปพลิเคชันปัจจุบัน:', + appCreateDSLErrorPart3: 'เวอร์ชัน DSL ของโปรเจกต์ปัจจุบัน:', appCreateDSLErrorPart4: 'เวอร์ชัน DSL ที่ระบบรองรับ:', - appCreateFailed: 'สร้างแอปไม่สําเร็จ', + appCreateFailed: 'สร้างโปรเจกต์ไม่สําเร็จ', }, editApp: 'แก้ไขข้อมูล', - editAppTitle: 'แก้ไขข้อมูลแอป', - editDone: 'อัปเดตข้อมูลแอป', - editFailed: 'อัปเดตข้อมูลแอปไม่สําเร็จ', + editAppTitle: 'แก้ไขข้อมูลโปรเจกต์', + editDone: 'อัปเดตข้อมูลโปรเจกต์', + editFailed: 'อัปเดตข้อมูลโปรเจกต์ไม่สําเร็จ', iconPicker: { ok: 'ตกลง, ได้', cancel: 'ยกเลิก', @@ -83,47 +83,47 @@ const translation = { }, answerIcon: { title: 'ใช้ไอคอน WebApp เพื่อแทนที่ 🤖', - description: 'จะใช้ไอคอน WebApp เพื่อแทนที่🤖ในแอปพลิเคชันที่ใช้ร่วมกันหรือไม่', + description: 'จะใช้ไอคอน WebApp เพื่อแทนที่🤖ในโปรเจกต์ที่ใช้ร่วมกันหรือไม่', descriptionInExplore: 'จะใช้ไอคอน WebApp เพื่อแทนที่🤖ใน Explore หรือไม่', }, switch: 'เปลี่ยนไปใช้ Workflow Orchestrate', - switchTipStart: 'สําเนาแอปใหม่จะถูกสร้างขึ้นสําหรับคุณ และสําเนาใหม่จะเปลี่ยนเป็น Workflow Orchestration สําเนาใหม่จะ', + switchTipStart: 'สําเนาโปรเจกต์ใหม่จะถูกสร้างขึ้นสําหรับคุณ และสําเนาใหม่จะเปลี่ยนเป็น Workflow Orchestration', switchTip: 'ไม่อนุญาต', switchTipEnd: 'เปลี่ยนกลับเป็น Basic Orchestrate', - switchLabel: 'สําเนาแอปที่จะสร้าง', - removeOriginal: 'ลบแอปเดิม', + switchLabel: 'สําเนาโปรเจกต์ที่จะสร้าง', + removeOriginal: 'ลบโปรเจกต์เดิม', switchStart: 'สวิตช์สตาร์ท', typeSelector: { all: 'ทุกประเภท', chatbot: 'แชทบอท', agent: 'ตัวแทน', workflow: 'เวิร์กโฟลว์', - completion: 'เสร็จ สมบูรณ์', + completion: 'เสร็จ', }, tracing: { - title: 'การติดตามประสิทธิภาพของแอป', - description: 'การกําหนดค่าผู้ให้บริการ LLMOps บุคคลที่สามและประสิทธิภาพของแอปติดตาม', + title: 'การติดตามประสิทธิภาพของโปรเจกต์', + description: 'การกําหนดค่าผู้ให้บริการ LLMOps บุคคลที่สามและประสิทธิภาพของโปรเจกต์ที่นำไปใช้', config: 'กําหนดค่า', - view: 'ทิวทัศน์', - collapse: 'ทรุด', + view: 'มุมมอง', + collapse: 'ยุบ', expand: 'ขยาย', tracing: 'ติดตาม', - disabled: 'พิการ', + disabled: 'ปิดการใช้งาน', disabledTip: 'โปรดกําหนดค่าผู้ให้บริการก่อน', enabled: 'ให้บริการ', - tracingDescription: 'บันทึกบริบททั้งหมดของการดําเนินการแอป รวมถึงการเรียก LLM บริบท พรอมต์ คําขอ HTTP และอื่นๆ ไปยังแพลตฟอร์มการติดตามของบุคคลที่สาม', + tracingDescription: 'บันทึกบริบททั้งหมดของการดําเนินการของโปรเจกต์ รวมถึงการเรียก LLM, Prompt คําขอ HTTP และอื่นๆไปยังแพลตฟอร์มของของบุคคลที่สาม', configProviderTitle: { configured: 'กําหนดค่าแล้ว', notConfigured: 'ผู้ให้บริการกําหนดค่าเพื่อเปิดใช้งานการติดตาม', moreProvider: 'ผู้ให้บริการเพิ่มเติม', }, langsmith: { - title: 'แลงสมิธ', - description: 'แพลตฟอร์มนักพัฒนาแบบครบวงจรสําหรับทุกขั้นตอนของวงจรชีวิตแอปพลิเคชันที่ขับเคลื่อนด้วย LLM', + title: 'Langsmith', + description: 'แพลตฟอร์มนักพัฒนาแบบครบวงจรสําหรับทุกขั้นตอนของ การพัฒนาโปรเจกต์ที่ขับเคลื่อนด้วย LLM', }, langfuse: { - title: 'แลงฟิวส์', - description: 'การติดตาม การประเมิน การจัดการพร้อมท์ และเมตริกเพื่อแก้ไขข้อบกพร่องและปรับปรุงแอปพลิเคชัน LLM ของคุณ', + title: 'Langfuse', + description: 'การติดตาม การประเมินการจัดการพร้อมท์ และเมตริกเพื่อแก้ไขข้อบกพร่องและปรับปรุงโปรเจกต์ LLM ของคุณ', }, inUse: 'ใช้งาน', configProvider: { From bec5451f12b247d17e67110e57136d35cf3373df Mon Sep 17 00:00:00 2001 From: zxhlyh Date: Wed, 11 Dec 2024 14:21:38 +0800 Subject: [PATCH 04/10] feat: workflow continue on error (#11474) --- .../workflow-variable-block/component.tsx | 6 +- .../custom-edge-linear-gradient-render.tsx | 53 ++++++++ web/app/components/workflow/custom-edge.tsx | 57 +++++++- .../workflow/hooks/use-edges-interactions.ts | 34 +++-- .../workflow/hooks/use-nodes-interactions.ts | 1 + .../workflow/hooks/use-workflow-run.ts | 78 +++++++++-- .../components/before-run-form/index.tsx | 2 +- .../components/collapse/field-collapse.tsx | 26 ++++ .../nodes/_base/components/collapse/index.tsx | 56 ++++++++ .../nodes/_base/components/editor/base.tsx | 3 + .../components/editor/code-editor/index.tsx | 3 + .../components/error-handle/default-value.tsx | 89 +++++++++++++ .../error-handle/error-handle-on-node.tsx | 67 ++++++++++ .../error-handle/error-handle-on-panel.tsx | 90 +++++++++++++ .../error-handle/error-handle-tip.tsx | 43 ++++++ .../error-handle-type-selector.tsx | 95 ++++++++++++++ .../error-handle/fail-branch-card.tsx | 32 +++++ .../_base/components/error-handle/hooks.ts | 123 ++++++++++++++++++ .../_base/components/error-handle/types.ts | 13 ++ .../_base/components/error-handle/utils.ts | 83 ++++++++++++ .../nodes/_base/components/next-step/add.tsx | 20 ++- .../_base/components/next-step/container.tsx | 14 +- .../_base/components/next-step/index.tsx | 90 ++++++++----- .../nodes/_base/components/node-handle.tsx | 44 ++++--- .../nodes/_base/components/output-vars.tsx | 26 +--- .../nodes/_base/components/variable-tag.tsx | 6 +- .../nodes/_base/components/variable/utils.ts | 18 +++ .../variable/var-reference-picker.tsx | 9 +- .../variable/var-reference-vars.tsx | 6 +- .../nodes/_base/hooks/use-output-var-list.ts | 25 +++- .../components/workflow/nodes/_base/node.tsx | 38 ++++-- .../components/workflow/nodes/_base/panel.tsx | 16 ++- .../nodes/document-extractor/panel.tsx | 2 +- .../nodes/http/components/timeout/index.tsx | 87 +++++-------- .../components/workflow/nodes/http/panel.tsx | 18 ++- .../if-else/components/condition-value.tsx | 12 +- .../workflow/nodes/iteration/panel.tsx | 7 +- .../nodes/knowledge-retrieval/panel.tsx | 4 +- .../workflow/nodes/list-operator/panel.tsx | 6 +- .../components/workflow/nodes/llm/panel.tsx | 20 ++- .../nodes/parameter-extractor/panel.tsx | 58 ++++----- .../nodes/question-classifier/default.ts | 10 ++ .../nodes/question-classifier/panel.tsx | 43 +++--- .../nodes/template-transform/panel.tsx | 2 +- .../components/workflow/nodes/tool/panel.tsx | 8 +- .../components/node-group-item.tsx | 3 + .../components/node-variable-item.tsx | 8 +- .../workflow/panel/workflow-preview.tsx | 1 + web/app/components/workflow/run/index.tsx | 1 + web/app/components/workflow/run/meta.tsx | 6 + web/app/components/workflow/run/node.tsx | 21 ++- .../components/workflow/run/result-panel.tsx | 7 + .../workflow/run/status-container.tsx | 4 +- web/app/components/workflow/run/status.tsx | 57 +++++++- web/app/components/workflow/types.ts | 15 ++- web/app/components/workflow/utils.ts | 37 +++++- web/i18n/en-US/workflow.ts | 27 ++++ web/i18n/zh-Hans/workflow.ts | 27 ++++ web/models/log.ts | 1 + web/types/workflow.ts | 5 +- 60 files changed, 1481 insertions(+), 282 deletions(-) create mode 100644 web/app/components/workflow/custom-edge-linear-gradient-render.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/collapse/index.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/types.ts create mode 100644 web/app/components/workflow/nodes/_base/components/error-handle/utils.ts diff --git a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx index 65f3dad3a21f6e..0073ac300b7a90 100644 --- a/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx +++ b/web/app/components/base/prompt-editor/plugins/workflow-variable-block/component.tsx @@ -26,6 +26,7 @@ import { VarBlockIcon } from '@/app/components/workflow/block-icon' import { Line3 } from '@/app/components/base/icons/src/public/common' import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import Tooltip from '@/app/components/base/tooltip' +import { isExceptionVariable } from '@/app/components/workflow/utils' type WorkflowVariableBlockComponentProps = { nodeKey: string @@ -53,6 +54,7 @@ const WorkflowVariableBlockComponent = ({ const node = localWorkflowNodesMap![variables[0]] const isEnv = isENV(variables) const isChatVar = isConversationVar(variables) + const isException = isExceptionVariable(varName, node?.type) useEffect(() => { if (!editor.hasNodes([WorkflowVariableBlockNode])) @@ -98,10 +100,10 @@ const WorkflowVariableBlockComponent = ({ )}
- {!isEnv && !isChatVar && } + {!isEnv && !isChatVar && } {isEnv && } {isChatVar && } -
{varName}
+
{varName}
{ !node && !isEnv && !isChatVar && ( diff --git a/web/app/components/workflow/custom-edge-linear-gradient-render.tsx b/web/app/components/workflow/custom-edge-linear-gradient-render.tsx new file mode 100644 index 00000000000000..b799bb36b298d7 --- /dev/null +++ b/web/app/components/workflow/custom-edge-linear-gradient-render.tsx @@ -0,0 +1,53 @@ +type CustomEdgeLinearGradientRenderProps = { + id: string + startColor: string + stopColor: string + position: { + x1: number + x2: number + y1: number + y2: number + } +} +const CustomEdgeLinearGradientRender = ({ + id, + startColor, + stopColor, + position, +}: CustomEdgeLinearGradientRenderProps) => { + const { + x1, + x2, + y1, + y2, + } = position + return ( + + + + + + + ) +} + +export default CustomEdgeLinearGradientRender diff --git a/web/app/components/workflow/custom-edge.tsx b/web/app/components/workflow/custom-edge.tsx index 68e2ef945e8b57..ce95549055540d 100644 --- a/web/app/components/workflow/custom-edge.tsx +++ b/web/app/components/workflow/custom-edge.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, + useMemo, useState, } from 'react' import { intersection } from 'lodash-es' @@ -20,8 +21,12 @@ import type { Edge, OnSelectBlock, } from './types' +import { NodeRunningStatus } from './types' +import { getEdgeColor } from './utils' import { ITERATION_CHILDREN_Z_INDEX } from './constants' +import CustomEdgeLinearGradientRender from './custom-edge-linear-gradient-render' import cn from '@/utils/classnames' +import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' const CustomEdge = ({ id, @@ -53,6 +58,26 @@ const CustomEdge = ({ const { handleNodeAdd } = useNodesInteractions() const { availablePrevBlocks } = useAvailableBlocks((data as Edge['data'])!.targetType, (data as Edge['data'])?.isInIteration) const { availableNextBlocks } = useAvailableBlocks((data as Edge['data'])!.sourceType, (data as Edge['data'])?.isInIteration) + const { + _sourceRunningStatus, + _targetRunningStatus, + } = data + + const linearGradientId = useMemo(() => { + if ( + ( + _sourceRunningStatus === NodeRunningStatus.Succeeded + || _sourceRunningStatus === NodeRunningStatus.Failed + || _sourceRunningStatus === NodeRunningStatus.Exception + ) && ( + _targetRunningStatus === NodeRunningStatus.Succeeded + || _targetRunningStatus === NodeRunningStatus.Failed + || _targetRunningStatus === NodeRunningStatus.Exception + || _targetRunningStatus === NodeRunningStatus.Running + ) + ) + return id + }, [_sourceRunningStatus, _targetRunningStatus, id]) const handleOpenChange = useCallback((v: boolean) => { setOpen(v) @@ -73,14 +98,43 @@ const CustomEdge = ({ ) }, [handleNodeAdd, source, sourceHandleId, target, targetHandleId]) + const stroke = useMemo(() => { + if (selected) + return getEdgeColor(NodeRunningStatus.Running) + + if (linearGradientId) + return `url(#${linearGradientId})` + + if (data?._connectedNodeIsHovering) + return getEdgeColor(NodeRunningStatus.Running, sourceHandleId === ErrorHandleTypeEnum.failBranch) + + return getEdgeColor() + }, [data._connectedNodeIsHovering, linearGradientId, selected, sourceHandleId]) + return ( <> + { + linearGradientId && ( + + ) + } @@ -95,6 +149,7 @@ const CustomEdge = ({ position: 'absolute', transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`, pointerEvents: 'all', + opacity: data._waitingRun ? 0.7 : 1, }} > { edges, setEdges, } = store.getState() - const currentEdgeIndex = edges.findIndex(edge => edge.source === nodeId && edge.sourceHandle === branchId) + const edgeWillBeDeleted = edges.filter(edge => edge.source === nodeId && edge.sourceHandle === branchId) - if (currentEdgeIndex < 0) + if (!edgeWillBeDeleted.length) return - const currentEdge = edges[currentEdgeIndex] - const newNodes = produce(getNodes(), (draft: Node[]) => { - const sourceNode = draft.find(node => node.id === currentEdge.source) - const targetNode = draft.find(node => node.id === currentEdge.target) - - if (sourceNode) - sourceNode.data._connectedSourceHandleIds = sourceNode.data._connectedSourceHandleIds?.filter(handleId => handleId !== currentEdge.sourceHandle) - - if (targetNode) - targetNode.data._connectedTargetHandleIds = targetNode.data._connectedTargetHandleIds?.filter(handleId => handleId !== currentEdge.targetHandle) + const nodes = getNodes() + const nodesConnectedSourceOrTargetHandleIdsMap = getNodesConnectedSourceOrTargetHandleIdsMap( + edgeWillBeDeleted.map(edge => ({ type: 'remove', edge })), + nodes, + ) + const newNodes = produce(nodes, (draft: Node[]) => { + draft.forEach((node) => { + if (nodesConnectedSourceOrTargetHandleIdsMap[node.id]) { + node.data = { + ...node.data, + ...nodesConnectedSourceOrTargetHandleIdsMap[node.id], + } + } + }) }) setNodes(newNodes) const newEdges = produce(edges, (draft) => { - draft.splice(currentEdgeIndex, 1) + return draft.filter(edge => !edgeWillBeDeleted.find(e => e.id === edge.id)) }) setEdges(newEdges) handleSyncWorkflowDraft() @@ -155,7 +159,9 @@ export const useEdgesInteractions = () => { const newEdges = produce(edges, (draft) => { draft.forEach((edge) => { - edge.data._run = false + edge.data._sourceRunningStatus = undefined + edge.data._targetRunningStatus = undefined + edge.data._waitingRun = false }) }) setEdges(newEdges) diff --git a/web/app/components/workflow/hooks/use-nodes-interactions.ts b/web/app/components/workflow/hooks/use-nodes-interactions.ts index 375a269377166a..8962333311d151 100644 --- a/web/app/components/workflow/hooks/use-nodes-interactions.ts +++ b/web/app/components/workflow/hooks/use-nodes-interactions.ts @@ -1033,6 +1033,7 @@ export const useNodesInteractions = () => { const newNodes = produce(nodes, (draft) => { draft.forEach((node) => { node.data._runningStatus = undefined + node.data._waitingRun = false }) }) setNodes(newNodes) diff --git a/web/app/components/workflow/hooks/use-workflow-run.ts b/web/app/components/workflow/hooks/use-workflow-run.ts index 5fbca27791397e..f6a9d24cd336ee 100644 --- a/web/app/components/workflow/hooks/use-workflow-run.ts +++ b/web/app/components/workflow/hooks/use-workflow-run.ts @@ -1,6 +1,5 @@ import { useCallback } from 'react' import { - getIncomers, useReactFlow, useStoreApi, } from 'reactflow' @@ -9,8 +8,8 @@ import { v4 as uuidV4 } from 'uuid' import { usePathname } from 'next/navigation' import { useWorkflowStore } from '../store' import { useNodesSyncDraft } from '../hooks' -import type { Node } from '../types' import { + BlockEnum, NodeRunningStatus, WorkflowRunningStatus, } from '../types' @@ -28,6 +27,7 @@ import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player import { getFilesInLogs, } from '@/app/components/base/file-uploader/utils' +import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' export const useWorkflowRun = () => { const store = useStoreApi() @@ -174,6 +174,8 @@ export const useWorkflowRun = () => { setIterParallelLogMap, } = workflowStore.getState() const { + getNodes, + setNodes, edges, setEdges, } = store.getState() @@ -186,12 +188,20 @@ export const useWorkflowRun = () => { status: WorkflowRunningStatus.Running, } })) - + const nodes = getNodes() + const newNodes = produce(nodes, (draft) => { + draft.forEach((node) => { + node.data._waitingRun = true + }) + }) + setNodes(newNodes) const newEdges = produce(edges, (draft) => { draft.forEach((edge) => { edge.data = { ...edge.data, - _run: false, + _sourceRunningStatus: undefined, + _targetRunningStatus: undefined, + _waitingRun: true, } }) }) @@ -311,13 +321,27 @@ export const useWorkflowRun = () => { } const newNodes = produce(nodes, (draft) => { draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running + draft[currentNodeIndex].data._waitingRun = false }) setNodes(newNodes) - const incomeNodesId = getIncomers({ id: data.node_id } as Node, newNodes, edges).filter(node => node.data._runningStatus === NodeRunningStatus.Succeeded).map(node => node.id) const newEdges = produce(edges, (draft) => { - draft.forEach((edge) => { - if (edge.target === data.node_id && incomeNodesId.includes(edge.source)) - edge.data = { ...edge.data, _run: true } as any + const incomeEdges = draft.filter((edge) => { + return edge.target === data.node_id + }) + + incomeEdges.forEach((edge) => { + const incomeNode = nodes.find(node => node.id === edge.source)! + if ( + (!incomeNode.data._runningBranchId && edge.sourceHandle === 'source') + || (incomeNode.data._runningBranchId && edge.sourceHandle === incomeNode.data._runningBranchId) + ) { + edge.data = { + ...edge.data, + _sourceRunningStatus: incomeNode.data._runningStatus, + _targetRunningStatus: NodeRunningStatus.Running, + _waitingRun: false, + } + } }) }) setEdges(newEdges) @@ -336,6 +360,8 @@ export const useWorkflowRun = () => { const { getNodes, setNodes, + edges, + setEdges, } = store.getState() const nodes = getNodes() const nodeParentId = nodes.find(node => node.id === data.node_id)!.parentId @@ -423,8 +449,31 @@ export const useWorkflowRun = () => { const newNodes = produce(nodes, (draft) => { const currentNode = draft.find(node => node.id === data.node_id)! currentNode.data._runningStatus = data.status as any + if (data.status === NodeRunningStatus.Exception) { + if (data.execution_metadata.error_strategy === ErrorHandleTypeEnum.failBranch) + currentNode.data._runningBranchId = ErrorHandleTypeEnum.failBranch + } + else { + if (data.node_type === BlockEnum.IfElse) + currentNode.data._runningBranchId = data?.outputs?.selected_case_id + + if (data.node_type === BlockEnum.QuestionClassifier) + currentNode.data._runningBranchId = data?.outputs?.class_id + } }) setNodes(newNodes) + const newEdges = produce(edges, (draft) => { + const incomeEdges = draft.filter((edge) => { + return edge.target === data.node_id + }) + incomeEdges.forEach((edge) => { + edge.data = { + ...edge.data, + _targetRunningStatus: data.status as any, + } + }) + }) + setEdges(newEdges) prevNodeId = data.node_id } @@ -474,13 +523,20 @@ export const useWorkflowRun = () => { const newNodes = produce(nodes, (draft) => { draft[currentNodeIndex].data._runningStatus = NodeRunningStatus.Running draft[currentNodeIndex].data._iterationLength = data.metadata.iterator_length + draft[currentNodeIndex].data._waitingRun = false }) setNodes(newNodes) const newEdges = produce(edges, (draft) => { - const edge = draft.find(edge => edge.target === data.node_id && edge.source === prevNodeId) + const incomeEdges = draft.filter(edge => edge.target === data.node_id) - if (edge) - edge.data = { ...edge.data, _run: true } as any + incomeEdges.forEach((edge) => { + edge.data = { + ...edge.data, + _sourceRunningStatus: nodes.find(node => node.id === edge.source)!.data._runningStatus, + _targetRunningStatus: NodeRunningStatus.Running, + _waitingRun: false, + } + }) }) setEdges(newEdges) diff --git a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx index 79d9c5b4dd8217..92a4deb51381b6 100644 --- a/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx +++ b/web/app/components/workflow/nodes/_base/components/before-run-form/index.tsx @@ -59,7 +59,7 @@ const BeforeRunForm: FC = ({ }) => { const { t } = useTranslation() - const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed + const isFinished = runningStatus === NodeRunningStatus.Succeeded || runningStatus === NodeRunningStatus.Failed || runningStatus === NodeRunningStatus.Exception const isRunning = runningStatus === NodeRunningStatus.Running const isFileLoaded = (() => { // system files diff --git a/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx b/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx new file mode 100644 index 00000000000000..7d2698a6d0956d --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/collapse/field-collapse.tsx @@ -0,0 +1,26 @@ +import Collapse from '.' + +type FieldCollapseProps = { + title: string + children: JSX.Element +} +const FieldCollapse = ({ + title, + children, +}: FieldCollapseProps) => { + return ( +
+ {title}
+ } + > +
+ {children} +
+ +
+ ) +} + +export default FieldCollapse diff --git a/web/app/components/workflow/nodes/_base/components/collapse/index.tsx b/web/app/components/workflow/nodes/_base/components/collapse/index.tsx new file mode 100644 index 00000000000000..a798ff0a9e8570 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/collapse/index.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react' +import { RiArrowDropRightLine } from '@remixicon/react' +import cn from '@/utils/classnames' + +export { default as FieldCollapse } from './field-collapse' + +type CollapseProps = { + disabled?: boolean + trigger: JSX.Element + children: JSX.Element + collapsed?: boolean + onCollapse?: (collapsed: boolean) => void +} +const Collapse = ({ + disabled, + trigger, + children, + collapsed, + onCollapse, +}: CollapseProps) => { + const [collapsedLocal, setCollapsedLocal] = useState(true) + const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal + + return ( + <> +
{ + if (!disabled) { + setCollapsedLocal(!collapsedMerged) + onCollapse?.(!collapsedMerged) + } + }} + > +
+ { + !disabled && ( + + ) + } +
+ {trigger} +
+ { + !collapsedMerged && children + } + + ) +} + +export default Collapse diff --git a/web/app/components/workflow/nodes/_base/components/editor/base.tsx b/web/app/components/workflow/nodes/_base/components/editor/base.tsx index 18ec5ea4a354e3..37bae03c996ab5 100644 --- a/web/app/components/workflow/nodes/_base/components/editor/base.tsx +++ b/web/app/components/workflow/nodes/_base/components/editor/base.tsx @@ -33,6 +33,7 @@ type Props = { }[] showFileList?: boolean showCodeGenerator?: boolean + tip?: JSX.Element } const Base: FC = ({ @@ -49,6 +50,7 @@ const Base: FC = ({ fileList = [], showFileList, showCodeGenerator = false, + tip, }) => { const ref = useRef(null) const { @@ -100,6 +102,7 @@ const Base: FC = ({ + {tip &&
{tip}
} void showCodeGenerator?: boolean className?: string + tip?: JSX.Element } export const languageMap = { @@ -69,6 +70,7 @@ const CodeEditor: FC = ({ onGenerated, showCodeGenerator = false, className, + tip, }) => { const [isFocus, setIsFocus] = React.useState(false) const [isMounted, setIsMounted] = React.useState(false) @@ -211,6 +213,7 @@ const CodeEditor: FC = ({ fileList={fileList as any} showFileList={showFileList} showCodeGenerator={showCodeGenerator} + tip={tip} > {main} diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx new file mode 100644 index 00000000000000..45c23fcc183ec6 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/default-value.tsx @@ -0,0 +1,89 @@ +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import type { DefaultValueForm } from './types' +import Input from '@/app/components/base/input' +import { VarType } from '@/app/components/workflow/types' +import { CodeLanguage } from '@/app/components/workflow/nodes/code/types' +import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' + +type DefaultValueProps = { + forms: DefaultValueForm[] + onFormChange: (form: DefaultValueForm) => void +} +const DefaultValue = ({ + forms, + onFormChange, +}: DefaultValueProps) => { + const { t } = useTranslation() + const getFormChangeHandler = useCallback(({ key, type }: DefaultValueForm) => { + return (payload: any) => { + let value + if (type === VarType.string || type === VarType.number) + value = payload.target.value + + if (type === VarType.array || type === VarType.arrayNumber || type === VarType.arrayString || type === VarType.arrayObject || type === VarType.arrayFile || type === VarType.object) + value = payload + + onFormChange({ key, type, value }) + } + }, [onFormChange]) + + return ( +
+
+ {t('workflow.nodes.common.errorHandle.defaultValue.desc')} +   + + {t('workflow.common.learnMore')} + +
+
+ { + forms.map((form, index) => { + return ( +
+
+
{form.key}
+
{form.type}
+
+ { + (form.type === VarType.string || form.type === VarType.number) && ( + + ) + } + { + ( + form.type === VarType.array + || form.type === VarType.arrayNumber + || form.type === VarType.arrayString + || form.type === VarType.arrayObject + || form.type === VarType.object + ) && ( + + ) + } +
+ ) + }) + } +
+
+ ) +} + +export default DefaultValue diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx new file mode 100644 index 00000000000000..64ce9ec226bc7c --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-node.tsx @@ -0,0 +1,67 @@ +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useUpdateNodeInternals } from 'reactflow' +import { NodeSourceHandle } from '../node-handle' +import { ErrorHandleTypeEnum } from './types' +import type { Node } from '@/app/components/workflow/types' +import { NodeRunningStatus } from '@/app/components/workflow/types' +import cn from '@/utils/classnames' + +type ErrorHandleOnNodeProps = Pick +const ErrorHandleOnNode = ({ + id, + data, +}: ErrorHandleOnNodeProps) => { + const { t } = useTranslation() + const { error_strategy } = data + const updateNodeInternals = useUpdateNodeInternals() + + useEffect(() => { + if (error_strategy === ErrorHandleTypeEnum.failBranch) + updateNodeInternals(id) + }, [error_strategy, id, updateNodeInternals]) + + if (!error_strategy) + return null + + return ( +
+
+
+ {t('workflow.common.onFailure')} +
+
+ { + error_strategy === ErrorHandleTypeEnum.defaultValue && ( + t('workflow.nodes.common.errorHandle.defaultValue.output') + ) + } + { + error_strategy === ErrorHandleTypeEnum.failBranch && ( + t('workflow.nodes.common.errorHandle.failBranch.title') + ) + } +
+ { + error_strategy === ErrorHandleTypeEnum.failBranch && ( + + ) + } +
+
+ ) +} + +export default ErrorHandleOnNode diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx new file mode 100644 index 00000000000000..f11f8bd5fb0784 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-on-panel.tsx @@ -0,0 +1,90 @@ +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import Collapse from '../collapse' +import { ErrorHandleTypeEnum } from './types' +import ErrorHandleTypeSelector from './error-handle-type-selector' +import FailBranchCard from './fail-branch-card' +import DefaultValue from './default-value' +import { + useDefaultValue, + useErrorHandle, +} from './hooks' +import type { DefaultValueForm } from './types' +import type { + CommonNodeType, + Node, +} from '@/app/components/workflow/types' +import Split from '@/app/components/workflow/nodes/_base/components/split' +import Tooltip from '@/app/components/base/tooltip' + +type ErrorHandleProps = Pick +const ErrorHandle = ({ + id, + data, +}: ErrorHandleProps) => { + const { t } = useTranslation() + const { error_strategy, default_value } = data + const { + collapsed, + setCollapsed, + handleErrorHandleTypeChange, + } = useErrorHandle(id, data) + const { handleFormChange } = useDefaultValue(id) + + const getHandleErrorHandleTypeChange = useCallback((data: CommonNodeType) => { + return (value: ErrorHandleTypeEnum) => { + handleErrorHandleTypeChange(value, data) + } + }, [handleErrorHandleTypeChange]) + + const getHandleFormChange = useCallback((data: CommonNodeType) => { + return (v: DefaultValueForm) => { + handleFormChange(v, data) + } + }, [handleFormChange]) + + return ( + <> + +
+ +
+
+ {t('workflow.nodes.common.errorHandle.title')} +
+ +
+ +
+ } + > + <> + { + error_strategy === ErrorHandleTypeEnum.failBranch && !collapsed && ( + + ) + } + { + error_strategy === ErrorHandleTypeEnum.defaultValue && !collapsed && !!default_value?.length && ( + + ) + } + + + + + ) +} + +export default ErrorHandle diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx new file mode 100644 index 00000000000000..3e60308ea77da4 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-tip.tsx @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { RiAlertFill } from '@remixicon/react' +import { ErrorHandleTypeEnum } from './types' + +type ErrorHandleTipProps = { + type?: ErrorHandleTypeEnum +} +const ErrorHandleTip = ({ + type, +}: ErrorHandleTipProps) => { + const { t } = useTranslation() + + const text = useMemo(() => { + if (type === ErrorHandleTypeEnum.failBranch) + return t('workflow.nodes.common.errorHandle.failBranch.inLog') + + if (type === ErrorHandleTypeEnum.defaultValue) + return t('workflow.nodes.common.errorHandle.defaultValue.inLog') + }, []) + + if (!type) + return null + + return ( +
+
+ +
+ {text} +
+
+ ) +} + +export default ErrorHandleTip diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx new file mode 100644 index 00000000000000..dadfa8d0b0d317 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/error-handle-type-selector.tsx @@ -0,0 +1,95 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + RiArrowDownSLine, + RiCheckLine, +} from '@remixicon/react' +import { ErrorHandleTypeEnum } from './types' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Button from '@/app/components/base/button' + +type ErrorHandleTypeSelectorProps = { + value: ErrorHandleTypeEnum + onSelected: (value: ErrorHandleTypeEnum) => void +} +const ErrorHandleTypeSelector = ({ + value, + onSelected, +}: ErrorHandleTypeSelectorProps) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const options = [ + { + value: ErrorHandleTypeEnum.none, + label: t('workflow.nodes.common.errorHandle.none.title'), + description: t('workflow.nodes.common.errorHandle.none.desc'), + }, + { + value: ErrorHandleTypeEnum.defaultValue, + label: t('workflow.nodes.common.errorHandle.defaultValue.title'), + description: t('workflow.nodes.common.errorHandle.defaultValue.desc'), + }, + { + value: ErrorHandleTypeEnum.failBranch, + label: t('workflow.nodes.common.errorHandle.failBranch.title'), + description: t('workflow.nodes.common.errorHandle.failBranch.desc'), + }, + ] + const selectedOption = options.find(option => option.value === value) + + return ( + + { + e.stopPropagation() + setOpen(v => !v) + }}> + + + +
+ { + options.map(option => ( +
{ + e.stopPropagation() + onSelected(option.value) + setOpen(false) + }} + > +
+ { + value === option.value && ( + + ) + } +
+
+
{option.label}
+
{option.description}
+
+
+ )) + } +
+
+
+ ) +} + +export default ErrorHandleTypeSelector diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx new file mode 100644 index 00000000000000..5dbba10f6c4f38 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/fail-branch-card.tsx @@ -0,0 +1,32 @@ +import { RiMindMap } from '@remixicon/react' +import { useTranslation } from 'react-i18next' + +const FailBranchCard = () => { + const { t } = useTranslation() + + return ( +
+
+
+ +
+
+ {t('workflow.nodes.common.errorHandle.failBranch.customize')} +
+
+ {t('workflow.nodes.common.errorHandle.failBranch.customizeTip')} +   + + {t('workflow.common.learnMore')} + +
+
+
+ ) +} + +export default FailBranchCard diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts b/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts new file mode 100644 index 00000000000000..06eb4fc48fbf65 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/hooks.ts @@ -0,0 +1,123 @@ +import { + useCallback, + useMemo, + useState, +} from 'react' +import { ErrorHandleTypeEnum } from './types' +import type { DefaultValueForm } from './types' +import { getDefaultValue } from './utils' +import type { + CommonNodeType, +} from '@/app/components/workflow/types' +import { + useEdgesInteractions, + useNodeDataUpdate, +} from '@/app/components/workflow/hooks' + +export const useDefaultValue = ( + id: string, +) => { + const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() + const handleFormChange = useCallback(( + { + key, + value, + type, + }: DefaultValueForm, + data: CommonNodeType, + ) => { + const default_value = data.default_value || [] + const index = default_value.findIndex(form => form.key === key) + + if (index > -1) { + const newDefaultValue = [...default_value] + newDefaultValue[index].value = value + handleNodeDataUpdateWithSyncDraft({ + id, + data: { + default_value: newDefaultValue, + }, + }) + return + } + + handleNodeDataUpdateWithSyncDraft({ + id, + data: { + default_value: [ + ...default_value, + { + key, + value, + type, + }, + ], + }, + }) + }, [handleNodeDataUpdateWithSyncDraft, id]) + + return { + handleFormChange, + } +} + +export const useErrorHandle = ( + id: string, + data: CommonNodeType, +) => { + const initCollapsed = useMemo(() => { + if (data.error_strategy === ErrorHandleTypeEnum.none) + return true + + return false + }, [data.error_strategy]) + const [collapsed, setCollapsed] = useState(initCollapsed) + const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate() + const { handleEdgeDeleteByDeleteBranch } = useEdgesInteractions() + + const handleErrorHandleTypeChange = useCallback((value: ErrorHandleTypeEnum, data: CommonNodeType) => { + if (data.error_strategy === value) + return + + if (value === ErrorHandleTypeEnum.none) { + handleNodeDataUpdateWithSyncDraft({ + id, + data: { + error_strategy: undefined, + default_value: undefined, + }, + }) + setCollapsed(true) + handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch) + } + + if (value === ErrorHandleTypeEnum.failBranch) { + handleNodeDataUpdateWithSyncDraft({ + id, + data: { + error_strategy: value, + default_value: undefined, + }, + }) + setCollapsed(false) + } + + if (value === ErrorHandleTypeEnum.defaultValue) { + handleNodeDataUpdateWithSyncDraft({ + id, + data: { + error_strategy: value, + default_value: getDefaultValue(data), + }, + }) + setCollapsed(false) + handleEdgeDeleteByDeleteBranch(id, ErrorHandleTypeEnum.failBranch) + } + }, [id, handleNodeDataUpdateWithSyncDraft, handleEdgeDeleteByDeleteBranch]) + + return { + collapsed, + setCollapsed, + handleErrorHandleTypeChange, + } +} diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/types.ts b/web/app/components/workflow/nodes/_base/components/error-handle/types.ts new file mode 100644 index 00000000000000..29493641b0be01 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/types.ts @@ -0,0 +1,13 @@ +import type { VarType } from '@/app/components/workflow/types' + +export enum ErrorHandleTypeEnum { + none = 'none', + failBranch = 'fail-branch', + defaultValue = 'default-value', +} + +export type DefaultValueForm = { + key: string + type: VarType + value?: any +} diff --git a/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts b/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts new file mode 100644 index 00000000000000..eef9677c48e5d4 --- /dev/null +++ b/web/app/components/workflow/nodes/_base/components/error-handle/utils.ts @@ -0,0 +1,83 @@ +import type { CommonNodeType } from '@/app/components/workflow/types' +import { + BlockEnum, + VarType, +} from '@/app/components/workflow/types' +import type { CodeNodeType } from '@/app/components/workflow/nodes/code/types' + +const getDefaultValueByType = (type: VarType) => { + if (type === VarType.string) + return '' + + if (type === VarType.number) + return 0 + + if (type === VarType.object) + return '{}' + + if (type === VarType.arrayObject || type === VarType.arrayString || type === VarType.arrayNumber || type === VarType.arrayFile) + return '[]' + + return '' +} + +export const getDefaultValue = (data: CommonNodeType) => { + const { type } = data + + if (type === BlockEnum.LLM) { + return [{ + key: 'text', + type: VarType.string, + value: getDefaultValueByType(VarType.string), + }] + } + + if (type === BlockEnum.HttpRequest) { + return [ + { + key: 'body', + type: VarType.string, + value: getDefaultValueByType(VarType.string), + }, + { + key: 'status_code', + type: VarType.number, + value: getDefaultValueByType(VarType.number), + }, + { + key: 'headers', + type: VarType.object, + value: getDefaultValueByType(VarType.object), + }, + ] + } + + if (type === BlockEnum.Tool) { + return [ + { + key: 'text', + type: VarType.string, + value: getDefaultValueByType(VarType.string), + }, + { + key: 'json', + type: VarType.arrayObject, + value: getDefaultValueByType(VarType.arrayObject), + }, + ] + } + + if (type === BlockEnum.Code) { + const { outputs } = data as CodeNodeType + + return Object.keys(outputs).map((key) => { + return { + key, + type: outputs[key].type, + value: getDefaultValueByType(outputs[key].type), + } + }) + } + + return [] +} diff --git a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx index 75694983cdcbd8..54ab4b327f0626 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step/add.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step/add.tsx @@ -1,6 +1,7 @@ import { memo, useCallback, + useMemo, useState, } from 'react' import { useTranslation } from 'react-i18next' @@ -24,12 +25,14 @@ type AddProps = { nodeData: CommonNodeType sourceHandle: string isParallel?: boolean + isFailBranch?: boolean } const Add = ({ nodeId, nodeData, sourceHandle, isParallel, + isFailBranch, }: AddProps) => { const { t } = useTranslation() const [open, setOpen] = useState(false) @@ -58,6 +61,15 @@ const Add = ({ setOpen(newOpen) }, [checkParallelLimit, nodeId, sourceHandle]) + const tip = useMemo(() => { + if (isFailBranch) + return t('workflow.common.addFailureBranch') + + if (isParallel) + return t('workflow.common.addParallelNode') + + return t('workflow.panel.selectNextStep') + }, [isFailBranch, isParallel, t]) const renderTrigger = useCallback((open: boolean) => { return (
- { - isParallel - ? t('workflow.common.addParallelNode') - : t('workflow.panel.selectNextStep') - } + {tip}
) - }, [t, nodesReadOnly, isParallel]) + }, [nodesReadOnly, tip]) return ( { return ( -
+
{ branchName && (
{branchName} @@ -44,6 +53,7 @@ const Container = ({ } { return data._targetBranches || [] }, [data]) - const nodeWithBranches = data.type === BlockEnum.IfElse || data.type === BlockEnum.QuestionClassifier const edges = useEdges() const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges) const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id) - const branchesOutgoers = useMemo(() => { - if (!branches?.length) - return [] + const list = useMemo(() => { + let items = [] + if (branches?.length) { + items = branches.map((branch, index) => { + const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id) + const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!) - return branches.map((branch) => { - const connected = connectedEdges.filter(edge => edge.sourceHandle === branch.id) + return { + branch: { + ...branch, + name: data.type === BlockEnum.QuestionClassifier ? `${t('workflow.nodes.questionClassifiers.class')} ${index + 1}` : branch.name, + }, + nextNodes, + } + }) + } + else { + const connected = connectedEdges.filter(edge => edge.sourceHandle === 'source') const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!) - return { - branch, + items = [{ + branch: { + id: '', + name: '', + }, nextNodes, + }] + + if (data.error_strategy === ErrorHandleTypeEnum.failBranch && hasErrorHandleNode(data.type)) { + const connected = connectedEdges.filter(edge => edge.sourceHandle === ErrorHandleTypeEnum.failBranch) + const nextNodes = connected.map(edge => outgoers.find(outgoer => outgoer.id === edge.target)!) + + items.push({ + branch: { + id: ErrorHandleTypeEnum.failBranch, + name: t('workflow.common.onFailure'), + }, + nextNodes, + }) } - }) - }, [branches, connectedEdges, outgoers]) + } + + return items + }, [branches, connectedEdges, data.error_strategy, data.type, outgoers, t]) return (
@@ -57,34 +88,23 @@ const NextStep = ({ />
item.nextNodes.length + 1) : [1]} + list={list.length ? list.map(item => item.nextNodes.length + 1) : [1]} />
{ - !nodeWithBranches && ( - - ) - } - { - nodeWithBranches && ( - branchesOutgoers.map((item, index) => { - return ( - - ) - }) - ) + list.map((item, index) => { + return ( + + ) + }) }
diff --git a/web/app/components/workflow/nodes/_base/components/node-handle.tsx b/web/app/components/workflow/nodes/_base/components/node-handle.tsx index 17dca45ebcc696..65798e46107856 100644 --- a/web/app/components/workflow/nodes/_base/components/node-handle.tsx +++ b/web/app/components/workflow/nodes/_base/components/node-handle.tsx @@ -10,7 +10,10 @@ import { Position, } from 'reactflow' import { useTranslation } from 'react-i18next' -import { BlockEnum } from '../../../types' +import { + BlockEnum, + NodeRunningStatus, +} from '../../../types' import type { Node } from '../../../types' import BlockSelector from '../../../block-selector' import type { ToolDefaultValue } from '../../../block-selector/types' @@ -24,11 +27,13 @@ import { import { useStore, } from '../../../store' +import cn from '@/utils/classnames' type NodeHandleProps = { handleId: string handleClassName?: string nodeSelectorClassName?: string + showExceptionStatus?: boolean } & Pick export const NodeTargetHandle = memo(({ @@ -72,14 +77,17 @@ export const NodeTargetHandle = memo(({ id={handleId} type='target' position={Position.Left} - className={` - !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1] - after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-primary-500 - hover:scale-125 transition-all - ${!connected && 'after:opacity-0'} - ${data.type === BlockEnum.Start && 'opacity-0'} - ${handleClassName} - `} + className={cn( + '!w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]', + 'after:absolute after:w-0.5 after:h-2 after:left-1.5 after:top-1 after:bg-workflow-link-line-handle', + 'hover:scale-125 transition-all', + data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle', + data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle', + data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle', + !connected && 'after:opacity-0', + data.type === BlockEnum.Start && 'opacity-0', + handleClassName, + )} isConnectable={isConnectable} onClick={handleHandleClick} > @@ -114,6 +122,7 @@ export const NodeSourceHandle = memo(({ handleId, handleClassName, nodeSelectorClassName, + showExceptionStatus, }: NodeHandleProps) => { const { t } = useTranslation() const notInitialWorkflow = useStore(s => s.notInitialWorkflow) @@ -157,13 +166,16 @@ export const NodeSourceHandle = memo(({ id={handleId} type='source' position={Position.Right} - className={` - group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1] - after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-primary-500 - hover:scale-125 transition-all - ${!connected && 'after:opacity-0'} - ${handleClassName} - `} + className={cn( + 'group/handle !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none z-[1]', + 'after:absolute after:w-0.5 after:h-2 after:right-1.5 after:top-1 after:bg-workflow-link-line-handle', + 'hover:scale-125 transition-all', + data._runningStatus === NodeRunningStatus.Succeeded && 'after:bg-workflow-link-line-success-handle', + data._runningStatus === NodeRunningStatus.Failed && 'after:bg-workflow-link-line-error-handle', + showExceptionStatus && data._runningStatus === NodeRunningStatus.Exception && 'after:bg-workflow-link-line-failure-handle', + !connected && 'after:opacity-0', + handleClassName, + )} isConnectable={isConnectable} onClick={handleHandleClick} > diff --git a/web/app/components/workflow/nodes/_base/components/output-vars.tsx b/web/app/components/workflow/nodes/_base/components/output-vars.tsx index 4b7f9fc12e1a51..a0d7a25c07d831 100644 --- a/web/app/components/workflow/nodes/_base/components/output-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/output-vars.tsx @@ -2,11 +2,7 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import { - RiArrowDownSLine, -} from '@remixicon/react' -import cn from '@/utils/classnames' +import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' type Props = { className?: string @@ -15,28 +11,14 @@ type Props = { } const OutputVars: FC = ({ - className, title, children, }) => { const { t } = useTranslation() - const [isFold, { - toggle: toggleFold, - }] = useBoolean(true) return ( -
-
-
{title || t('workflow.nodes.common.outputVars')}
- -
- {!isFold && ( -
- {children} -
- )} -
+ + {children} + ) } type VarItemProps = { diff --git a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx index fc8c1ce9c9a1c6..0c5c3bde4bf171 100644 --- a/web/app/components/workflow/nodes/_base/components/variable-tag.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable-tag.tsx @@ -17,6 +17,7 @@ import { BubbleX, Env } from '@/app/components/base/icons/src/vender/line/others import { getNodeInfoById, isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils' import Tooltip from '@/app/components/base/tooltip' import cn from '@/utils/classnames' +import { isExceptionVariable } from '@/app/components/workflow/utils' type VariableTagProps = { valueSelector: ValueSelector @@ -45,6 +46,7 @@ const VariableTag = ({ const isValid = Boolean(node) || isEnv || isChatVar const variableName = isSystemVar(valueSelector) ? valueSelector.slice(0).join('.') : valueSelector.slice(1).join('.') + const isException = isExceptionVariable(variableName, node?.data.type) const { t } = useTranslation() return ( @@ -67,12 +69,12 @@ const VariableTag = ({ )} - + )} {isEnv && } {isChatVar && }
{variableName} diff --git a/web/app/components/workflow/nodes/_base/components/variable/utils.ts b/web/app/components/workflow/nodes/_base/components/variable/utils.ts index 982f4f750030ea..715ad1c7b175ad 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/utils.ts +++ b/web/app/components/workflow/nodes/_base/components/variable/utils.ts @@ -315,6 +315,24 @@ const formatItem = ( } } + const { error_strategy } = data + + if (error_strategy) { + res.vars = [ + ...res.vars, + { + variable: 'error_message', + type: VarType.string, + isException: true, + }, + { + variable: 'error_type', + type: VarType.string, + isException: true, + }, + ] + } + const selector = [id] res.vars = res.vars.filter((v) => { const isCurrentMatched = filterVar(v, (() => { diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx index e4d354a615fe9a..3a4cece35c9863 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-picker.tsx @@ -36,6 +36,7 @@ import TypeSelector from '@/app/components/workflow/nodes/_base/components/selec import AddButton from '@/app/components/base/button/add-button' import Badge from '@/app/components/base/badge' import Tooltip from '@/app/components/base/tooltip' +import { isExceptionVariable } from '@/app/components/workflow/utils' const TRIGGER_DEFAULT_WIDTH = 227 @@ -224,16 +225,18 @@ const VarReferencePicker: FC = ({ isConstant: !!isConstant, }) - const { isEnv, isChatVar, isValidVar } = useMemo(() => { + const { isEnv, isChatVar, isValidVar, isException } = useMemo(() => { const isEnv = isENV(value as ValueSelector) const isChatVar = isConversationVar(value as ValueSelector) const isValidVar = Boolean(outputVarNode) || isEnv || isChatVar + const isException = isExceptionVariable(varName, outputVarNode?.type) return { isEnv, isChatVar, isValidVar, + isException, } - }, [value, outputVarNode]) + }, [value, outputVarNode, varName]) // 8(left/right-padding) + 14(icon) + 4 + 14 + 2 = 42 + 17 buff const availableWidth = triggerWidth - 56 @@ -335,7 +338,7 @@ const VarReferencePicker: FC = ({ {!hasValue && } {isEnv && } {isChatVar && } -
{varName}
diff --git a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx index eb28279c0c1cf8..9ac5e4a4e4b6ae 100644 --- a/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx +++ b/web/app/components/workflow/nodes/_base/components/variable/var-reference-vars.tsx @@ -37,6 +37,7 @@ type ItemProps = { onHovering?: (value: boolean) => void itemWidth?: number isSupportFileVar?: boolean + isException?: boolean } const Item: FC = ({ @@ -48,6 +49,7 @@ const Item: FC = ({ onHovering, itemWidth, isSupportFileVar, + isException, }) => { const isFile = itemData.type === VarType.file const isObj = ([VarType.object, VarType.file].includes(itemData.type) && itemData.children && itemData.children.length > 0) @@ -109,7 +111,7 @@ const Item: FC = ({ onClick={handleChosen} >
- {!isEnv && !isChatVar && } + {!isEnv && !isChatVar && } {isEnv && } {isChatVar && } {!isEnv && !isChatVar && ( @@ -216,6 +218,7 @@ const ObjectChildren: FC = ({ onChange={onChange} onHovering={setIsChildrenHovering} isSupportFileVar={isSupportFileVar} + isException={v.isException} /> )) } @@ -312,6 +315,7 @@ const VarReferenceVars: FC = ({ onChange={onChange} itemWidth={itemWidth} isSupportFileVar={isSupportFileVar} + isException={v.isException} /> ))}
)) diff --git a/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts b/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts index c7bce2ef07a9c0..839cd1402677f5 100644 --- a/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts +++ b/web/app/components/workflow/nodes/_base/hooks/use-output-var-list.ts @@ -1,12 +1,22 @@ import { useCallback, useState } from 'react' import produce from 'immer' import { useBoolean } from 'ahooks' -import { type OutputVar } from '../../code/types' -import type { ValueSelector } from '@/app/components/workflow/types' -import { VarType } from '@/app/components/workflow/types' +import type { + CodeNodeType, + OutputVar, +} from '../../code/types' +import type { + ValueSelector, +} from '@/app/components/workflow/types' +import { + BlockEnum, + VarType, +} from '@/app/components/workflow/types' import { useWorkflow, } from '@/app/components/workflow/hooks' +import { ErrorHandleTypeEnum } from '@/app/components/workflow/nodes/_base/components/error-handle/types' +import { getDefaultValue } from '@/app/components/workflow/nodes/_base/components/error-handle/utils' type Params = { id: string @@ -29,6 +39,9 @@ function useOutputVarList({ const handleVarsChange = useCallback((newVars: OutputVar, changedIndex?: number, newKey?: string) => { const newInputs = produce(inputs, (draft: any) => { draft[varKey] = newVars + + if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs') + draft.default_value = getDefaultValue(draft as any) }) setInputs(newInputs) @@ -59,6 +72,9 @@ function useOutputVarList({ children: null, }, } + + if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs') + draft.default_value = getDefaultValue(draft as any) }) setInputs(newInputs) onOutputKeyOrdersChange([...outputKeyOrders, newKey]) @@ -84,6 +100,9 @@ function useOutputVarList({ const newInputs = produce(inputs, (draft: any) => { delete draft[varKey][key] + + if ((inputs as CodeNodeType).type === BlockEnum.Code && (inputs as CodeNodeType).error_strategy === ErrorHandleTypeEnum.defaultValue && varKey === 'outputs') + draft.default_value = getDefaultValue(draft as any) }) setInputs(newInputs) onOutputKeyOrdersChange(outputKeyOrders.filter((_, i) => i !== index)) diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index c5b78c5c2140f9..f2da2da35a4fe9 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -10,8 +10,9 @@ import { useRef, } from 'react' import { - RiCheckboxCircleLine, - RiErrorWarningLine, + RiAlertFill, + RiCheckboxCircleFill, + RiErrorWarningFill, RiLoader2Line, } from '@remixicon/react' import { useTranslation } from 'react-i18next' @@ -24,6 +25,7 @@ import { useNodesReadOnly, useToolIcon, } from '../../hooks' +import { hasErrorHandleNode } from '../../utils' import { useNodeIterationInteractions } from '../iteration/use-interactions' import type { IterationNodeType } from '../iteration/types' import { @@ -32,6 +34,7 @@ import { } from './components/node-handle' import NodeResizer from './components/node-resizer' import NodeControl from './components/node-control' +import ErrorHandleOnNode from './components/error-handle/error-handle-on-node' import AddVariablePopupWithPosition from './components/add-variable-popup-with-position' import cn from '@/utils/classnames' import BlockIcon from '@/app/components/workflow/block-icon' @@ -71,11 +74,13 @@ const BaseNode: FC = ({ showRunningBorder, showSuccessBorder, showFailedBorder, + showExceptionBorder, } = useMemo(() => { return { showRunningBorder: data._runningStatus === NodeRunningStatus.Running && !showSelectedBorder, showSuccessBorder: data._runningStatus === NodeRunningStatus.Succeeded && !showSelectedBorder, showFailedBorder: data._runningStatus === NodeRunningStatus.Failed && !showSelectedBorder, + showExceptionBorder: data._runningStatus === NodeRunningStatus.Exception && !showSelectedBorder, } }, [data._runningStatus, showSelectedBorder]) @@ -85,6 +90,7 @@ const BaseNode: FC = ({ 'flex border-[2px] rounded-2xl', showSelectedBorder ? 'border-components-option-card-option-selected-border' : 'border-transparent', !showSelectedBorder && data._inParallelHovering && 'border-workflow-block-border-highlight', + data._waitingRun && 'opacity-70', )} ref={nodeRef} style={{ @@ -99,9 +105,10 @@ const BaseNode: FC = ({ data.type !== BlockEnum.Iteration && 'w-[240px] bg-workflow-block-bg', data.type === BlockEnum.Iteration && 'flex flex-col w-full h-full bg-[#fcfdff]/80', !data._runningStatus && 'hover:shadow-lg', - showRunningBorder && '!border-primary-500', - showSuccessBorder && '!border-[#12B76A]', - showFailedBorder && '!border-[#F04438]', + showRunningBorder && '!border-state-accent-solid', + showSuccessBorder && '!border-state-success-solid', + showFailedBorder && '!border-state-destructive-solid', + showExceptionBorder && '!border-state-warning-solid', data._isBundled && '!shadow-lg', )} > @@ -192,24 +199,29 @@ const BaseNode: FC = ({
{ data._iterationLength && data._iterationIndex && data._runningStatus === NodeRunningStatus.Running && ( -
+
{data._iterationIndex > data._iterationLength ? data._iterationLength : data._iterationIndex}/{data._iterationLength}
) } { (data._runningStatus === NodeRunningStatus.Running || data._singleRunningStatus === NodeRunningStatus.Running) && ( - + ) } { data._runningStatus === NodeRunningStatus.Succeeded && ( - + ) } { data._runningStatus === NodeRunningStatus.Failed && ( - + + ) + } + { + data._runningStatus === NodeRunningStatus.Exception && ( + ) }
@@ -225,6 +237,14 @@ const BaseNode: FC = ({
) } + { + hasErrorHandleNode(data.type) && ( + + ) + } { data.desc && data.type !== BlockEnum.Iteration && (
diff --git a/web/app/components/workflow/nodes/_base/panel.tsx b/web/app/components/workflow/nodes/_base/panel.tsx index 83387621fcedfa..e5cb0862266926 100644 --- a/web/app/components/workflow/nodes/_base/panel.tsx +++ b/web/app/components/workflow/nodes/_base/panel.tsx @@ -20,6 +20,7 @@ import { DescriptionInput, TitleInput, } from './components/title-description-input' +import ErrorHandleOnPanel from './components/error-handle/error-handle-on-panel' import { useResizePanel } from './hooks/use-resize-panel' import cn from '@/utils/classnames' import BlockIcon from '@/app/components/workflow/block-icon' @@ -34,7 +35,10 @@ import { useWorkflow, useWorkflowHistory, } from '@/app/components/workflow/hooks' -import { canRunBySingle } from '@/app/components/workflow/utils' +import { + canRunBySingle, + hasErrorHandleNode, +} from '@/app/components/workflow/utils' import Tooltip from '@/app/components/base/tooltip' import type { Node } from '@/app/components/workflow/types' import { useStore as useAppStore } from '@/app/components/app/store' @@ -161,9 +165,17 @@ const BasePanel: FC = ({ />
-
+
{cloneElement(children, { id, data })}
+ { + hasErrorHandleNode(data.type) && ( + + ) + } { !!availableNextBlocks.length && (
diff --git a/web/app/components/workflow/nodes/document-extractor/panel.tsx b/web/app/components/workflow/nodes/document-extractor/panel.tsx index 52491875cd98bd..1e26fe4c337ee2 100644 --- a/web/app/components/workflow/nodes/document-extractor/panel.tsx +++ b/web/app/components/workflow/nodes/document-extractor/panel.tsx @@ -72,7 +72,7 @@ const Panel: FC> = ({
-
+
= ({ readonly, payload, onChange }) => { const { t } = useTranslation() const { connect, read, write, max_connect_timeout, max_read_timeout, max_write_timeout } = payload ?? {} - const [isFold, { - toggle: toggleFold, - }] = useBoolean(true) - return ( - <> -
-
-
{t(`${i18nPrefix}.timeout.title`)}
- + +
+
+ onChange?.({ ...payload, connect: v })} + min={1} + max={max_connect_timeout || 300} + /> + onChange?.({ ...payload, read: v })} + min={1} + max={max_read_timeout || 600} + /> + onChange?.({ ...payload, write: v })} + min={1} + max={max_write_timeout || 600} + />
- {!isFold && ( -
-
- onChange?.({ ...payload, connect: v })} - min={1} - max={max_connect_timeout || 300} - /> - onChange?.({ ...payload, read: v })} - min={1} - max={max_read_timeout || 600} - /> - onChange?.({ ...payload, write: v })} - min={1} - max={max_write_timeout || 600} - /> -
-
- )}
- - +
) } export default React.memo(Timeout) diff --git a/web/app/components/workflow/nodes/http/panel.tsx b/web/app/components/workflow/nodes/http/panel.tsx index eb9a15e5b4a31e..5c613aa0f35a41 100644 --- a/web/app/components/workflow/nodes/http/panel.tsx +++ b/web/app/components/workflow/nodes/http/panel.tsx @@ -65,7 +65,7 @@ const Panel: FC> = ({ return null return ( -
+
> = ({
-
- -
+ {(isShowAuthorization && !readOnly) && ( > = ({ /> )} -
+
<> { const { t } = useTranslation() + const nodes = useNodes() const variableName = labelName || (isSystemVar(variableSelector) ? variableSelector.slice(0).join('.') : variableSelector.slice(1).join('.')) const operatorName = isComparisonOperatorNeedTranslate(operator) ? t(`workflow.nodes.ifElse.comparisonOperator.${operator}`) : operator const notHasValue = comparisonOperatorNotRequireValue(operator) const isEnvVar = isENV(variableSelector) const isChatVar = isConversationVar(variableSelector) + const node: Node | undefined = nodes.find(n => n.id === variableSelector[0]) as Node + const isException = isExceptionVariable(variableName, node?.data.type) const formatValue = useMemo(() => { if (notHasValue) return '' @@ -67,7 +76,7 @@ const ConditionValue = ({ return (
- {!isEnvVar && !isChatVar && } + {!isEnvVar && !isChatVar && } {isEnvVar && } {isChatVar && } @@ -75,6 +84,7 @@ const ConditionValue = ({ className={cn( 'shrink-0 ml-0.5 truncate text-xs font-medium text-text-accent', !notHasValue && 'max-w-[70px]', + isException && 'text-text-warning', )} title={variableName} > diff --git a/web/app/components/workflow/nodes/iteration/panel.tsx b/web/app/components/workflow/nodes/iteration/panel.tsx index 4ba42d488e81b1..9b6b3d37907792 100644 --- a/web/app/components/workflow/nodes/iteration/panel.tsx +++ b/web/app/components/workflow/nodes/iteration/panel.tsx @@ -18,7 +18,6 @@ import Switch from '@/app/components/base/switch' import Select from '@/app/components/base/select' import Slider from '@/app/components/base/slider' import Input from '@/app/components/base/input' -import Divider from '@/app/components/base/divider' const i18nPrefix = 'workflow.nodes.iteration' @@ -72,7 +71,7 @@ const Panel: FC> = ({ } = useConfig(id, data) return ( -
+
> = ({
) } -
- -
+
diff --git a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx index 3bfc7c56ed4d2a..bae4217d11fa84 100644 --- a/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx +++ b/web/app/components/workflow/nodes/knowledge-retrieval/panel.tsx @@ -53,7 +53,7 @@ const Panel: FC> = ({ }, [setRerankModelOpen]) return ( -
+
{/* {JSON.stringify(inputs, null, 2)} */} > = ({
-
+
<> > = ({ } = useConfig(id, data) return ( -
-
+
+
@@ -157,7 +157,7 @@ const Panel: FC> = ({
-
+
<> > = ({ />
-
- - <> - - - -
+ + <> + + + {isShowSingleRun && ( > = ({ const model = inputs.model return ( -
-
+
+
@@ -157,38 +158,33 @@ const Panel: FC> = ({ nodesOutputVars={availableVars} availableNodes={availableNodesWithParent} /> - - <> - - {/* Memory */} - {isChatMode && ( -
- -
- )} - {isSupportFunctionCall && ( -
- -
- )} - -
-
+ + <> + {/* Memory */} + {isChatMode && ( +
+ +
+ )} + {isSupportFunctionCall && ( +
+ +
+ )} + +
{inputs.parameters?.length > 0 && (<> -
+
<> {inputs.parameters.map((param, index) => ( diff --git a/web/app/components/workflow/nodes/question-classifier/default.ts b/web/app/components/workflow/nodes/question-classifier/default.ts index a0936b66e3cdc3..b01db041dab16c 100644 --- a/web/app/components/workflow/nodes/question-classifier/default.ts +++ b/web/app/components/workflow/nodes/question-classifier/default.ts @@ -26,6 +26,16 @@ const nodeDefault: NodeDefault = { name: '', }, ], + _targetBranches: [ + { + id: '1', + name: '', + }, + { + id: '2', + name: '', + }, + ], vision: { enabled: false, }, diff --git a/web/app/components/workflow/nodes/question-classifier/panel.tsx b/web/app/components/workflow/nodes/question-classifier/panel.tsx index 523ec5001996ec..7d27a89d29ddda 100644 --- a/web/app/components/workflow/nodes/question-classifier/panel.tsx +++ b/web/app/components/workflow/nodes/question-classifier/panel.tsx @@ -14,6 +14,7 @@ import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/befo import ResultPanel from '@/app/components/workflow/run/result-panel' import Split from '@/app/components/workflow/nodes/_base/components/split' import OutputVars, { VarItem } from '@/app/components/workflow/nodes/_base/components/output-vars' +import { FieldCollapse } from '@/app/components/workflow/nodes/_base/components/collapse' const i18nPrefix = 'workflow.nodes.questionClassifiers' @@ -55,8 +56,8 @@ const Panel: FC> = ({ const model = inputs.model return ( -
-
+
+
@@ -107,27 +108,27 @@ const Panel: FC> = ({ readonly={readOnly} /> - - - +
+ + + -
+
<> > = ({ />
-
+
<> > = ({ } return ( -
+
{!readOnly && isShowAuthBtn && ( <> -
+