From 2b52b32b96f2ee55f20c992978d52941442a38ec Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 13 Apr 2026 11:36:14 +0800 Subject: [PATCH 1/2] fix(web): variable ui update --- web/src/i18n/en.ts | 3 + web/src/i18n/zh.ts | 3 + .../components/Editor/nodes/VariableNode.tsx | 16 +-- .../Editor/plugin/AutocompletePlugin.tsx | 83 ++++++------ .../components/Properties/VariableSelect.tsx | 124 +++++++++--------- .../Properties/hooks/useVariableList.ts | 4 +- .../Workflow/components/Properties/index.tsx | 8 +- 7 files changed, 121 insertions(+), 120 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index e78964d5..59a303f1 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -2552,6 +2552,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re 'list-operator.input_list': 'Input list', }, checkListHasErrors: 'Please resolve all issues in the checklist before publishing', + variableSelect: { + empty: 'No variables available', + }, }, emotionEngine: { emotionEngineConfig: 'Emotion Engine Configuration', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index d206a1c6..1c3791d4 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -2516,6 +2516,9 @@ export const zh = { 'list-operator.input_list': '输入变量', }, checkListHasErrors: '发布前确认检查清单中所有问题均已解决', + variableSelect: { + empty: '暂无变量', + }, }, emotionEngine: { emotionEngineConfig: '情感引擎配置', diff --git a/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx b/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx index 5688342c..72e73220 100644 --- a/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx +++ b/web/src/views/Workflow/components/Editor/nodes/VariableNode.tsx @@ -48,17 +48,13 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({ return ( - {data.isContext ? ( - 📄 - ) : data.group !== 'CONVERSATION' && !data.value.includes('conv') ? ( - - ) : } + {!data.isContext && data.group !== 'CONVERSATION' && !data.value.includes('conv') + ?
+ : null + } {!data.isContext && data.group !== 'CONVERSATION' && ( <> {!data.value.includes('conv') && <> @@ -73,7 +69,7 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({ )} )} - {data.label} + {data.label} ); }; diff --git a/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx b/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx index f9537032..9f718826 100644 --- a/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx +++ b/web/src/views/Workflow/components/Editor/plugin/AutocompletePlugin.tsx @@ -2,12 +2,13 @@ * @Author: ZhaoYing * @Date: 2025-12-23 16:22:51 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-07 16:51:04 + * @Last Modified time: 2026-04-13 11:12:18 */ import { useEffect, useLayoutEffect, useState, useRef, type FC } from 'react'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, KEY_ENTER_COMMAND, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ESCAPE_COMMAND } from 'lexical'; import { Space, Flex } from 'antd'; +import clsx from 'clsx'; import { INSERT_VARIABLE_COMMAND, CLOSE_AUTOCOMPLETE_COMMAND } from '../commands'; import type { NodeProperties } from '../../../types' @@ -284,23 +285,23 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => { ref={popupRef} data-autocomplete-popup="true" onMouseDown={(e) => e.preventDefault()} - className="rb:fixed rb:z-1000 rb:bg-white rb:rounded-xl rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)]" + className="rb:min-w-70 rb:max-h-57.5 rb:overflow-y-auto rb:fixed rb:z-1000 rb:bg-white rb:rounded-lg rb:border-[0.5px] rb:border-[#EBEBEB] rb:shadow-[0px_2px_6px_0px_rgba(0,0,0,0.1)] rb:py-3 rb:px-2" style={{ top: popupPosition.top, left: popupPosition.left, }} > -
- - {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => { - const nodeName = nodeOptions[0]?.nodeData?.name || nodeId; - const nodeIcon = nodeOptions[0]?.nodeData?.icon; - return ( -
- {nodeName !== 'undefined' && - {nodeIcon &&
} + + {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => { + const nodeName = nodeOptions[0]?.nodeData?.name || nodeId; + return ( +
+ {nodeName !== 'undefined' && +
{nodeName} - } +
+ } + {nodeOptions.map((option) => { const globalIndex = flatOptions.indexOf(option); const isExpanded = expandedParent?.key === option.key; @@ -310,14 +311,13 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => { key={option.key} ref={(el) => { if (el) itemRefs.current.set(option.key, el); }} data-selected={selectedIndex === globalIndex} - className="rb:pl-6! rb:pr-3! rb:py-2!" + className={clsx("rb:px-2! rb:py-0.75! rb:rounded-sm rb:leading-4.5 rb:text-[#5B6167] rb:hover:bg-[#F6F6F6]", { + 'rb:bg-[#F6F6F6]': selectedIndex === globalIndex || isExpanded, + 'rb:cursor-not-allowed rb:opacity-65': option.disabled, + 'rb:cursor-pointer': !option.disabled, + })} align="center" justify="space-between" - style={{ - cursor: option.disabled ? 'not-allowed' : 'pointer', - background: (selectedIndex === globalIndex || isExpanded) ? '#f0f8ff' : 'white', - opacity: option.disabled ? 0.5 : 1, - }} onClick={() => { if (option.disabled) return; insertMention(option); @@ -337,26 +337,27 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => { } }} > - {option.label && - {option.isContext ? '📄' : `{x}`} - {option.label} - } - - {option.dataType && {option.dataType}} - {hasChildren && } + {option.label && +
+ {`{x}`} {option.label} +
+ } + + {option.dataType && {option.dataType}} + {hasChildren &&
}
); })} -
- ); - })} -
-
+
+
+ ); + })} +
{/* Child variables panel - floats to the left */} {expandedParent?.children?.length && (
= ({ options }) => { }} onMouseEnter={() => setExpandedParent(expandedParent)} > - {/* Header */} -
- +
+ {expandedParent.nodeData.name}.{expandedParent.label} {expandedParent.dataType} @@ -377,19 +377,18 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => { !child.disabled && insertMention(child)} onMouseEnter={() => setSelectedIndex(childIndex)} > - {child.label} - {child.dataType && {child.dataType}} + {child.label} + {child.dataType && {child.dataType}} ); })} diff --git a/web/src/views/Workflow/components/Properties/VariableSelect.tsx b/web/src/views/Workflow/components/Properties/VariableSelect.tsx index b28d7b4f..c0207cb5 100644 --- a/web/src/views/Workflow/components/Properties/VariableSelect.tsx +++ b/web/src/views/Workflow/components/Properties/VariableSelect.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 15:40:13 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-08 10:48:21 + * @Last Modified time: 2026-04-13 11:25:40 */ import { useState, useRef, useEffect, useLayoutEffect, type FC } from 'react' import { createPortal } from 'react-dom' @@ -190,20 +190,30 @@ const VariableSelect: FC = ({ {/* Trigger */}
setOpen(o => !o)} > {multiple ? ( selectedValues.length > 0 ? ( - + {selectedValues.map(v => { const s = suggestionMap.get(v); if (!s) return null; @@ -214,11 +224,11 @@ const VariableSelect: FC = ({ return ( - {!isConv && nd?.icon &&
} + {!isConv && nd?.icon &&
} {!isConv && nd?.name && {nd.name}{sep}} - + {parent ? <>{parent.label}{sep}{s.label} : s.label} = ({ ); })} - + ) : ( - {placeholder} + {placeholder} ) ) : selectedSuggestion ? (
- - {!isConversation && nodeData?.icon &&
} - {!isConversation && nodeData?.name && {nodeData.name}} - {!isConversation && nodeData?.name && {sep}} - + + {!isConversation && nodeData?.icon &&
} + {!isConversation && nodeData?.name && {nodeData.name}} + {!isConversation && nodeData?.name && {sep}} + {parentOfSelected ? <>{parentOfSelected.label}{sep}{selectedSuggestion.label} : selectedSuggestion.label} @@ -266,18 +278,19 @@ const VariableSelect: FC = ({ {open && createPortal(
-
- {Object.entries(filteredGroups).map(([nodeId, suggestions]) => { +
+ {Object.entries(filteredGroups).map(([nodeId, suggestions], index) => { const nd = suggestions[0].nodeData; return ( -
- - {nd.icon &&
} +
+
{nd.name} - +
{suggestions.map(s => { const isSelected = multiple ? selectedValues.includes(`{{${s.value}}}`) @@ -288,11 +301,9 @@ const VariableSelect: FC = ({ { if (el) itemRefs.current.set(s.key, el); }} - className={clsx("rb:pl-6! rb:pr-3! rb:py-1.25! rb:rounded-lg!", { - 'rb:bg-[#e6f4ff]': isSelected || isExpanded, - 'rb:bg-white rb:hover:bg-[#F6F6F6]!': !(isSelected || isExpanded), - 'rb:opacity-60': s.disabled, - 'rb:cursor-not-allowed': s.disabled, + className={clsx("rb:px-2! rb:py-0.75! rb:rounded-sm rb:leading-4.5 rb:text-[#5B6167] rb:hover:bg-[#F6F6F6]", { + 'rb:bg-[#F6F6F6]': isSelected || isExpanded, + 'rb:cursor-not-allowed rb:opacity-65': s.disabled, 'rb:cursor-pointer': !s.disabled, })} align="center" @@ -314,17 +325,16 @@ const VariableSelect: FC = ({ } }} > - +
{multiple && ( - + )} - {`{x}`} - {s.label} - - - {s.dataType && {s.dataType}} + {`{x}`} {s.label} +
- {hasChildren &&
} + + {s.dataType && {s.dataType}} + {hasChildren &&
}
); @@ -334,7 +344,7 @@ const VariableSelect: FC = ({ })} {Object.keys(filteredGroups).length === 0 && (
- {t('workflow.variableSelect.empty', '暂无变量')} + {t('workflow.variableSelect.empty')}
)}
@@ -346,18 +356,13 @@ const VariableSelect: FC = ({ {open && expandedParent?.children?.length && createPortal(
setExpandedParent(expandedParent)} > -
!expandedParent.disabled && handleSelect(expandedParent)} - > +
- - {expandedParent.nodeData.name}.{expandedParent.label} - + {expandedParent.nodeData.name}.{expandedParent.label} {expandedParent.dataType}
@@ -365,32 +370,27 @@ const VariableSelect: FC = ({ const isSelected = multiple ? selectedValues.includes(`{{${child.value}}}`) : `{{${child.value}}}` === value; - const hasGrandChildren = !!child.children?.length; return ( !child.disabled && handleSelect(child)} > - + {multiple && ( )} - {child.label} - - - {child.dataType && {child.dataType}} - {hasGrandChildren && } + {child.label} + + {child.dataType && {child.dataType}} + ); })} diff --git a/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts b/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts index 3c4ea6f7..14dcced2 100644 --- a/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts +++ b/web/src/views/Workflow/components/Properties/hooks/useVariableList.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-01-19 17:00:26 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-08 10:12:27 + * @Last Modified time: 2026-04-13 10:44:17 */ /** * useVariableList Hook @@ -414,7 +414,7 @@ export const useVariableList = ( const pd = parentLoop.getData(); const pid = pd.id; if (pd.type === 'loop') { - (pd.cycle_vars || []).forEach((cv: any) => addVariable(list, keys, `${pid}_cycle_${cv.name}`, cv.name, cv.type || 'String', `${pid}.${cv.name}`, pd)); + (pd.cycle_vars || []).forEach((cv: any) => addVariable(list, keys, `${pid}_cycle_${cv.name}`, cv.name, cv.type || 'string', `${pid}.${cv.name}`, pd)); } else if (pd.type === 'iteration' && pd.config.input.defaultValue) { let itemType = 'object'; const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue); diff --git a/web/src/views/Workflow/components/Properties/index.tsx b/web/src/views/Workflow/components/Properties/index.tsx index b5bc2d2e..f826edd9 100644 --- a/web/src/views/Workflow/components/Properties/index.tsx +++ b/web/src/views/Workflow/components/Properties/index.tsx @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 15:39:59 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-10 17:24:19 + * @Last Modified time: 2026-04-13 10:44:19 */ import { type FC, useEffect, useState, useMemo } from "react"; import clsx from 'clsx' @@ -266,7 +266,7 @@ const Properties: FC = ({ key, label: cycleVar.name, type: 'variable', - dataType: cycleVar.type || 'String', + dataType: cycleVar.type || 'string', value: `${parentNodeId}.${cycleVar.name}`, nodeData: parentData, }); @@ -643,7 +643,7 @@ const Properties: FC = ({ key: contextKey, label: 'context', type: 'variable', - dataType: 'String', + dataType: 'string', value: `context`, nodeData: selectedNode.getData(), isContext: true, @@ -791,7 +791,7 @@ const Properties: FC = ({ key: `${selectedNode.id}_cycle_${cycleVar.name}`, label: cycleVar.name, type: 'variable', - dataType: cycleVar.type || 'String', + dataType: cycleVar.type || 'string', value: `${selectedNode.getData().id}.${cycleVar.name}`, nodeData: selectedNode.getData(), })); From 520ee7c132b3ed4bad8bd3c619e8a4ffb7f12097 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Mon, 13 Apr 2026 12:01:37 +0800 Subject: [PATCH 2/2] fix(web): sub node connected --- .../views/Workflow/hooks/useWorkflowGraph.ts | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index f385acf3..5d0bb9c6 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -2,7 +2,7 @@ * @Author: ZhaoYing * @Date: 2026-02-03 15:17:48 * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-04-07 23:17:50 + * @Last Modified time: 2026-04-13 12:00:09 */ import { Clipboard, Graph, Keyboard, MiniMap, Node, Snapline, type Edge } from '@antv/x6'; import { register } from '@antv/x6-react-shape'; @@ -1022,24 +1022,39 @@ export const useWorkflowGraph = ({ graphRef.current.on('node:removed', blankClick) // When edge connected, bring connected nodes' ports to front - graphRef.current.on('edge:connected', ({ isNew }) => { - // Bring edge to front first, then bring child nodes above edges - // Parent (loop/iteration) nodes stay behind to avoid covering edges - // Reset any port hover state left from dragging + graphRef.current.on('edge:connected', ({ isNew, edge }) => { if (isNew) { - graphRef.current?.getNodes().forEach(node => { - if (!node.getData()?.cycle) node.toFront(); - }); - graphRef.current?.getEdges().forEach(edge => { - const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId()); - const targetCell = graphRef.current?.getCellById(edge.getTargetCellId()); - if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) { - edge.toFront(); - } - }); - graphRef.current?.getNodes().forEach(node => { - if (node.getData()?.cycle) node.toFront(); - }); + const sourceCellId = edge.getSourceCellId() + const targetCellId = edge.getTargetCellId() + const sourceCell = graphRef.current?.getCellById(sourceCellId); + const targetCell = graphRef.current?.getCellById(targetCellId); + + sourceCell?.toFront(); + targetCell?.toFront() + if (['loop', 'iteration'].includes(sourceCell?.getData()?.type)) { + graphRef.current?.getEdges().forEach(edge => { + const edgeSourceCell = graphRef.current?.getCellById(edge.getSourceCellId()); + const edgeTargetCell = graphRef.current?.getCellById(edge.getTargetCellId()); + if (edgeSourceCell?.getData()?.cycle === sourceCellId || edgeTargetCell?.getData()?.cycle === sourceCellId) { + edge.toFront(); + } + }); + graphRef.current?.getNodes().forEach(node => { + if (node.getData()?.cycle === sourceCellId) node.toFront(); + }); + } + if (['loop', 'iteration'].includes(targetCell?.getData()?.type)) { + graphRef.current?.getEdges().forEach(edge => { + const edgeSourceCell = graphRef.current?.getCellById(edge.getSourceCellId()); + const edgeTargetCell = graphRef.current?.getCellById(edge.getTargetCellId()); + if (edgeSourceCell?.getData()?.cycle === targetCellId || edgeTargetCell?.getData()?.cycle === targetCellId) { + edge.toFront(); + } + }); + graphRef.current?.getNodes().forEach(node => { + if (node.getData()?.cycle === targetCellId) node.toFront(); + }); + } } });