Merge pull request #865 from SuanmoSuanyangTechnology/fix/v0.3.0_zy

Fix/v0.3.0 zy
This commit is contained in:
yingzhao
2026-04-13 12:02:58 +08:00
committed by GitHub
8 changed files with 154 additions and 138 deletions

View File

@@ -2552,6 +2552,9 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
'list-operator.input_list': 'Input list', 'list-operator.input_list': 'Input list',
}, },
checkListHasErrors: 'Please resolve all issues in the checklist before publishing', checkListHasErrors: 'Please resolve all issues in the checklist before publishing',
variableSelect: {
empty: 'No variables available',
},
}, },
emotionEngine: { emotionEngine: {
emotionEngineConfig: 'Emotion Engine Configuration', emotionEngineConfig: 'Emotion Engine Configuration',

View File

@@ -2516,6 +2516,9 @@ export const zh = {
'list-operator.input_list': '输入变量', 'list-operator.input_list': '输入变量',
}, },
checkListHasErrors: '发布前确认检查清单中所有问题均已解决', checkListHasErrors: '发布前确认检查清单中所有问题均已解决',
variableSelect: {
empty: '暂无变量',
},
}, },
emotionEngine: { emotionEngine: {
emotionEngineConfig: '情感引擎配置', emotionEngineConfig: '情感引擎配置',

View File

@@ -48,17 +48,13 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({
return ( return (
<span <span
onClick={handleClick} onClick={handleClick}
className={clsx('rb:border rb:rounded-md rb:bg-white rb:text-[10px] rb:inline-flex rb:items-center rb:py-0 rb:px-1.5 rb:mx-0.5 rb:cursor-pointer', { className="rb-border rb:rounded-md rb:bg-white rb:text-[10px] rb:text-[#212332] rb:h-5! rb:inline-flex rb:items-center rb:p-1 rb:mx-px rb:cursor-pointer"
'rb:border-[#171719]': isSelected,
'rb:border-[#DFE4ED]': !isSelected
})}
contentEditable={false} contentEditable={false}
> >
{data.isContext ? ( {!data.isContext && data.group !== 'CONVERSATION' && !data.value.includes('conv')
<span style={{ fontSize: '12px', marginRight: '4px' }}>📄</span> ? <div className={`rb:size-3 rb:mr-1 rb:bg-cover ${data.nodeData?.icon}`} />
) : data.group !== 'CONVERSATION' && !data.value.includes('conv') ? ( : null
<span className={`rb:size-4 rb:mr-1 rb:bg-cover rb:inline-block rb:flex-shrink-0 ${data.nodeData?.icon}`} /> }
) : <span className="rb:inline-block rb:h-4"></span>}
{!data.isContext && data.group !== 'CONVERSATION' && ( {!data.isContext && data.group !== 'CONVERSATION' && (
<> <>
{!data.value.includes('conv') && <> {!data.value.includes('conv') && <>
@@ -73,7 +69,7 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({
)} )}
</> </>
)} )}
<span className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1 rb:text-[#171719]">{data.label}</span> <span className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1">{data.label}</span>
</span> </span>
); );
}; };

View File

@@ -2,12 +2,13 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2025-12-23 16:22:51 * @Date: 2025-12-23 16:22:51
* @Last Modified by: ZhaoYing * @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 { useEffect, useLayoutEffect, useState, useRef, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; 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 { $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 { Space, Flex } from 'antd';
import clsx from 'clsx';
import { INSERT_VARIABLE_COMMAND, CLOSE_AUTOCOMPLETE_COMMAND } from '../commands'; import { INSERT_VARIABLE_COMMAND, CLOSE_AUTOCOMPLETE_COMMAND } from '../commands';
import type { NodeProperties } from '../../../types' import type { NodeProperties } from '../../../types'
@@ -284,23 +285,23 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
ref={popupRef} ref={popupRef}
data-autocomplete-popup="true" data-autocomplete-popup="true"
onMouseDown={(e) => e.preventDefault()} 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={{ style={{
top: popupPosition.top, top: popupPosition.top,
left: popupPosition.left, left: popupPosition.left,
}} }}
> >
<div className="rb:py-1 rb:min-w-70 rb:max-h-50 rb:overflow-y-auto"> <Flex vertical gap={12}>
<Flex vertical gap={12}> {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => {
{Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => { const nodeName = nodeOptions[0]?.nodeData?.name || nodeId;
const nodeName = nodeOptions[0]?.nodeData?.name || nodeId; return (
const nodeIcon = nodeOptions[0]?.nodeData?.icon; <div key={nodeId} className="rb:text-[12px]">
return ( {nodeName !== 'undefined' &&
<div key={nodeId}> <div className="rb:px-2 rb:leading-4.25 rb:mb-1.25 rb:font-medium rb:text-[#5B6167]">
{nodeName !== 'undefined' && <Flex align="center" gap={4} className="rb:px-3! rb:text-[12px] rb:py-1.25! rb:font-medium rb:text-[#5B6167]">
{nodeIcon && <div className={`rb:size-3 rb:bg-cover ${nodeIcon}`} />}
{nodeName} {nodeName}
</Flex>} </div>
}
<Flex vertical gap={2}>
{nodeOptions.map((option) => { {nodeOptions.map((option) => {
const globalIndex = flatOptions.indexOf(option); const globalIndex = flatOptions.indexOf(option);
const isExpanded = expandedParent?.key === option.key; const isExpanded = expandedParent?.key === option.key;
@@ -310,14 +311,13 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
key={option.key} key={option.key}
ref={(el) => { if (el) itemRefs.current.set(option.key, el); }} ref={(el) => { if (el) itemRefs.current.set(option.key, el); }}
data-selected={selectedIndex === globalIndex} 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" align="center"
justify="space-between" justify="space-between"
style={{
cursor: option.disabled ? 'not-allowed' : 'pointer',
background: (selectedIndex === globalIndex || isExpanded) ? '#f0f8ff' : 'white',
opacity: option.disabled ? 0.5 : 1,
}}
onClick={() => { onClick={() => {
if (option.disabled) return; if (option.disabled) return;
insertMention(option); insertMention(option);
@@ -337,26 +337,27 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
} }
}} }}
> >
{option.label && <Space size={4}> {option.label &&
<span className="rb:text-[#155EEF]">{option.isContext ? '📄' : `{x}`}</span> <div className="rb:font-medium">
<span>{option.label}</span> <span className="rb:text-[#155EEF]">{`{x}`}</span> {option.label}
</Space>} </div>
<Space size={4}> }
{option.dataType && <span className="rb:text-[#5B6167]">{option.dataType}</span>} <Space size={2}>
{hasChildren && <span className="rb:text-[#5B6167] rb:ml-1"></span>} {option.dataType && <span>{option.dataType}</span>}
{hasChildren && <div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/arrow_up.svg')] rb:rotate-90"></div>}
</Space> </Space>
</Flex> </Flex>
); );
})} })}
</div> </Flex>
); </div>
})} );
</Flex> })}
</div> </Flex>
{/* Child variables panel - floats to the left */} {/* Child variables panel - floats to the left */}
{expandedParent?.children?.length && ( {expandedParent?.children?.length && (
<div <div
className="rb:absolute rb:bg-white rb:rounded-xl rb:py-1 rb:min-w-60 rb:max-h-60 rb:overflow-y-auto 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:text-[12px] 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={{ style={{
top: childPanelTop, top: childPanelTop,
right: 'calc(100% + 8px)', right: 'calc(100% + 8px)',
@@ -364,9 +365,8 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
}} }}
onMouseEnter={() => setExpandedParent(expandedParent)} onMouseEnter={() => setExpandedParent(expandedParent)}
> >
{/* Header */} <div className="rb:pb-2 rb:mb-1 rb:font-medium rb:text-[#5B6167] rb-border-b">
<div className="rb:px-3 rb:py-2 rb:text-[12px] rb:font-medium rb:text-[#5B6167] rb:border-b rb:border-[#F0F0F0]"> <Flex justify="space-between" align="center" gap={8}>
<Flex justify="space-between" align="center">
<span>{expandedParent.nodeData.name}.{expandedParent.label}</span> <span>{expandedParent.nodeData.name}.{expandedParent.label}</span>
<span>{expandedParent.dataType}</span> <span>{expandedParent.dataType}</span>
</Flex> </Flex>
@@ -377,19 +377,18 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
<Flex <Flex
key={child.key} key={child.key}
data-selected={selectedIndex === childIndex} data-selected={selectedIndex === childIndex}
className="rb:px-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 === childIndex,
'rb:cursor-not-allowed rb:opacity-65': child.disabled,
'rb:cursor-pointer': !child.disabled,
})}
align="center" align="center"
justify="space-between" justify="space-between"
style={{
cursor: child.disabled ? 'not-allowed' : 'pointer',
background: selectedIndex === childIndex ? '#f0f8ff' : 'white',
opacity: child.disabled ? 0.5 : 1,
}}
onClick={() => !child.disabled && insertMention(child)} onClick={() => !child.disabled && insertMention(child)}
onMouseEnter={() => setSelectedIndex(childIndex)} onMouseEnter={() => setSelectedIndex(childIndex)}
> >
<span>{child.label}</span> <span className="rb:font-medium">{child.label}</span>
{child.dataType && <span className="rb:text-[#5B6167]">{child.dataType}</span>} {child.dataType && <span>{child.dataType}</span>}
</Flex> </Flex>
); );
})} })}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:40:13 * @Date: 2026-02-03 15:40:13
* @Last Modified by: ZhaoYing * @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 { useState, useRef, useEffect, useLayoutEffect, type FC } from 'react'
import { createPortal } from 'react-dom' import { createPortal } from 'react-dom'
@@ -190,20 +190,30 @@ const VariableSelect: FC<VariableSelectProps> = ({
{/* Trigger */} {/* Trigger */}
<div <div
className={clsx( className={clsx(
'rb:w-full rb:flex rb:items-center rb:justify-between rb:cursor-pointer rb:rounded-md rb:px-2 rb:transition-colors', 'rb:w-full rb:flex rb:items-center rb:justify-between rb:cursor-pointer rb:rounded-lg rb:px-2 rb:transition-colors', {
variant === 'filled' && 'rb:bg-[#F6F6F6] rb:border-none rb:shadow-none', 'rb:bg-[#F6F6F6] rb:border-none rb:shadow-none': variant === 'filled',
variant === 'outlined' && 'rb:border rb:border-[#d9d9d9] hover:rb:border-[#4096ff] rb:bg-white', 'rb:border rb:border-[#d9d9d9] hover:rb:border-[#4096ff] rb:bg-white': variant === 'outlined',
variant === 'outlined' && open && 'rb:border-[#4096ff] rb:shadow-[0_0_0_2px_rgba(5,145,255,0.1)]', 'rb:border-[#4096ff] rb:shadow-[0_0_0_2px_rgba(5,145,255,0.1)]': variant === 'outlined' && open,
variant === 'borderless' && 'rb:border-none rb:shadow-none rb:bg-transparent', 'rb:border-none rb:shadow-none rb:bg-transparent': variant === 'borderless',
multiple && size === 'small' ? 'rb:min-h-7 rb:py-0.75' : multiple ? 'rb:min-h-8 rb:py-1' : size === 'small' ? 'rb:h-7 rb:text-[10px]' : size === 'large' ? 'rb:h-10' : 'rb:h-8 rb:text-[12px]', 'rb:text-[12px]': size === 'small',
!multiple && (size === 'small' ? 'rb:text-[12px]' : 'rb:text-[12px]'), 'rb:text-[14px]': size !== 'small',
},
multiple && size === 'small'
? 'rb:min-h-7 rb:py-0.75'
: multiple
? 'rb:min-h-8 rb:py-1'
: size === 'small'
? 'rb:h-7 rb:text-[10px]'
: size === 'large'
? 'rb:h-10'
: 'rb:h-8 rb:text-[12px]',
className className
)} )}
onClick={() => setOpen(o => !o)} onClick={() => setOpen(o => !o)}
> >
{multiple ? ( {multiple ? (
selectedValues.length > 0 ? ( selectedValues.length > 0 ? (
<span className="rb:flex rb:flex-wrap rb:gap-1 rb:flex-1 rb:min-w-0"> <Flex wrap gap={4} className="rb:flex-1! rb:min-w-0">
{selectedValues.map(v => { {selectedValues.map(v => {
const s = suggestionMap.get(v); const s = suggestionMap.get(v);
if (!s) return null; if (!s) return null;
@@ -214,11 +224,11 @@ const VariableSelect: FC<VariableSelectProps> = ({
return ( return (
<span <span
key={v} key={v}
className="rb:inline-flex rb:items-center rb:gap-0.5 rb:bg-[#f0f8ff] rb:rounded rb:px-1 rb:py-0.5 rb:text-[11px] rb:max-w-full" className="rb-border rb:rounded-md rb:bg-white rb:text-[10px] rb:text-[#212332] rb:h-5! rb:inline-flex rb:items-center rb:p-1 rb:cursor-pointer"
> >
{!isConv && nd?.icon && <div className={`rb:size-3 rb:shrink-0 rb:bg-cover ${nd.icon}`} />} {!isConv && nd?.icon && <div className={`rb:size-3 rb:bg-cover ${nd.icon}`} />}
{!isConv && nd?.name && <span className="rb:text-[#5B6167]">{nd.name}{sep}</span>} {!isConv && nd?.name && <span className="rb:text-[#5B6167]">{nd.name}{sep}</span>}
<span className="rb:text-[#171719]"> <span>
{parent ? <>{parent.label}{sep}{s.label}</> : s.label} {parent ? <>{parent.label}{sep}{s.label}</> : s.label}
</span> </span>
<span <span
@@ -228,17 +238,19 @@ const VariableSelect: FC<VariableSelectProps> = ({
</span> </span>
); );
})} })}
</span> </Flex>
) : ( ) : (
<span className="rb:text-[#bfbfbf] rb:flex-1 rb:text-[12px]">{placeholder}</span> <span className="rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap rb:flex-1">{placeholder}</span>
) )
) : selectedSuggestion ? ( ) : selectedSuggestion ? (
<div className="rb:flex rb:flex-1 rb:min-w-0 rb:max-w-full"> <div className="rb:flex rb:flex-1 rb:min-w-0 rb:max-w-full">
<span className="rb:inline-flex rb:items-center rb:gap-0.5 rb:bg-[#f0f8ff] rb:rounded rb:px-1 rb:py-0.5 rb:text-[11px] rb:max-w-full rb:overflow-hidden"> <span
{!isConversation && nodeData?.icon && <div className={`rb:size-3 rb:shrink-0 rb:bg-cover ${nodeData.icon}`} />} className="rb-border rb:rounded-md rb:bg-white rb:text-[10px] rb:text-[#212332] rb:h-5! rb:inline-flex rb:items-center rb:p-1 rb:cursor-pointer"
{!isConversation && nodeData?.name && <span className="rb:text-[#5B6167] rb:shrink rb:min-w-0 rb:truncate rb:max-w-[40%]">{nodeData.name}</span>} >
{!isConversation && nodeData?.name && <span className="rb:text-[#5B6167]">{sep}</span>} {!isConversation && nodeData?.icon && <div className={`rb:size-3 rb:bg-cover rb:mr-1 ${nodeData.icon}`} />}
<span className="rb:text-[#171719] rb:shrink rb:min-w-0 rb:truncate"> {!isConversation && nodeData?.name && <span className="rb:shrink rb:min-w-0 rb:truncate rb:max-w-[40%]">{nodeData.name}</span>}
{!isConversation && nodeData?.name && <span>{sep}</span>}
<span className="rb:shrink rb:min-w-0 rb:truncate">
{parentOfSelected ? <>{parentOfSelected.label}{sep}{selectedSuggestion.label}</> : selectedSuggestion.label} {parentOfSelected ? <>{parentOfSelected.label}{sep}{selectedSuggestion.label}</> : selectedSuggestion.label}
</span> </span>
</span> </span>
@@ -266,18 +278,19 @@ const VariableSelect: FC<VariableSelectProps> = ({
{open && createPortal( {open && createPortal(
<div <div
ref={dropdownRef} ref={dropdownRef}
className="rb:fixed rb:z-9999 rb:bg-white rb:text-[14px] rb:rounded-lg rb:shadow-[0px_2px_12px_0px_rgba(23,23,25,0.12)] rb:p-1" 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: dropdownPos.top, left: dropdownPos.left, minWidth: dropdownPos.width }} style={{ top: dropdownPos.top, left: dropdownPos.left, minWidth: dropdownPos.width }}
> >
<div className="rb:min-w-70 rb:max-h-60 rb:overflow-y-auto rb:py-1"> <div className="rb:min-w-70 rb:max-h-57.5 rb:overflow-y-auto">
{Object.entries(filteredGroups).map(([nodeId, suggestions]) => { {Object.entries(filteredGroups).map(([nodeId, suggestions], index) => {
const nd = suggestions[0].nodeData; const nd = suggestions[0].nodeData;
return ( return (
<div key={nodeId}> <div key={nodeId} className={clsx("rb:text-[12px]", {
<Flex align="center" gap={4} className="rb:px-3! rb:py-1.25! rb:text-[12px] rb:text-[#5B6167]"> 'rb:mt-3': index !== 0
{nd.icon && <div className={`rb:size-4 rb:bg-cover ${nd.icon}`} />} })}>
<div className="rb:px-2 rb:leading-4.25 rb:mb-1.25 rb:font-medium rb:text-[#5B6167]">
{nd.name} {nd.name}
</Flex> </div>
{suggestions.map(s => { {suggestions.map(s => {
const isSelected = multiple const isSelected = multiple
? selectedValues.includes(`{{${s.value}}}`) ? selectedValues.includes(`{{${s.value}}}`)
@@ -288,11 +301,9 @@ const VariableSelect: FC<VariableSelectProps> = ({
<Flex <Flex
key={s.key} key={s.key}
ref={(el) => { if (el) itemRefs.current.set(s.key, el); }} ref={(el) => { if (el) itemRefs.current.set(s.key, el); }}
className={clsx("rb:pl-6! rb:pr-3! rb:py-1.25! rb:rounded-lg!", { 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-[#e6f4ff]': isSelected || isExpanded, 'rb:bg-[#F6F6F6]': isSelected || isExpanded,
'rb:bg-white rb:hover:bg-[#F6F6F6]!': !(isSelected || isExpanded), 'rb:cursor-not-allowed rb:opacity-65': s.disabled,
'rb:opacity-60': s.disabled,
'rb:cursor-not-allowed': s.disabled,
'rb:cursor-pointer': !s.disabled, 'rb:cursor-pointer': !s.disabled,
})} })}
align="center" align="center"
@@ -314,17 +325,16 @@ const VariableSelect: FC<VariableSelectProps> = ({
} }
}} }}
> >
<Space size={4}> <div className="rb:font-medium">
{multiple && ( {multiple && (
<Checkbox checked={isSelected} /> <Checkbox checked={isSelected} className="rb:mr-2!" />
)} )}
<span className="rb:text-[#155EEF]">{`{x}`}</span> <span className="rb:text-[#155EEF]">{`{x}`}</span> {s.label}
<span>{s.label}</span> </div>
</Space>
<Space size={4} className="rb:text-[#5B6167] rb:text-[12px]">
{s.dataType && <span>{s.dataType}</span>}
{hasChildren && <div className="rb:size-3 rb:bg-cover rb:bg-[url('@/assets/images/common/arrow_up.svg')] rb:rotate-90"></div>} <Space size={2}>
{s.dataType && <span>{s.dataType}</span>}
{hasChildren && <div className="rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/arrow_up.svg')] rb:rotate-90"></div>}
</Space> </Space>
</Flex> </Flex>
); );
@@ -334,7 +344,7 @@ const VariableSelect: FC<VariableSelectProps> = ({
})} })}
{Object.keys(filteredGroups).length === 0 && ( {Object.keys(filteredGroups).length === 0 && (
<div className="rb:px-3 rb:py-4 rb:text-center rb:text-[#bfbfbf] rb:text-[14px]"> <div className="rb:px-3 rb:py-4 rb:text-center rb:text-[#bfbfbf] rb:text-[14px]">
{t('workflow.variableSelect.empty', '暂无变量')} {t('workflow.variableSelect.empty')}
</div> </div>
)} )}
</div> </div>
@@ -346,18 +356,13 @@ const VariableSelect: FC<VariableSelectProps> = ({
{open && expandedParent?.children?.length && createPortal( {open && expandedParent?.children?.length && createPortal(
<div <div
id="variable-select-child-panel" id="variable-select-child-panel"
className="rb:fixed rb:z-9999 rb:bg-white rb:rounded-xl rb:py-1 rb:min-w-60 rb:max-h-60 rb:overflow-y-auto rb:text-[14px] 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:text-[12px] 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: childPanelPos.top, right: childPanelPos.right }} style={{ top: childPanelPos.top, right: childPanelPos.right }}
onMouseEnter={() => setExpandedParent(expandedParent)} onMouseEnter={() => setExpandedParent(expandedParent)}
> >
<div <div className="rb:pb-2 rb:mb-1 rb:font-medium rb:text-[#5B6167] rb-border-b">
className="rb:px-3 rb:py-2 rb:text-[14px] rb:font-medium rb:text-[#5B6167] rb:border-b rb:border-[#F0F0F0] rb:cursor-pointer rb:hover:bg-[#f0f8ff]"
onClick={() => !expandedParent.disabled && handleSelect(expandedParent)}
>
<Flex justify="space-between" align="center" gap={8}> <Flex justify="space-between" align="center" gap={8}>
<Flex align="center" gap={6}> <span>{expandedParent.nodeData.name}.{expandedParent.label}</span>
<span>{expandedParent.nodeData.name}.{expandedParent.label}</span>
</Flex>
<span>{expandedParent.dataType}</span> <span>{expandedParent.dataType}</span>
</Flex> </Flex>
</div> </div>
@@ -365,32 +370,27 @@ const VariableSelect: FC<VariableSelectProps> = ({
const isSelected = multiple const isSelected = multiple
? selectedValues.includes(`{{${child.value}}}`) ? selectedValues.includes(`{{${child.value}}}`)
: `{{${child.value}}}` === value; : `{{${child.value}}}` === value;
const hasGrandChildren = !!child.children?.length;
return ( return (
<Flex <Flex
key={child.key} key={child.key}
className={clsx("rb:px-3! rb:py-2! rb:hover:bg-[#f0f8ff]!", { 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-[#f0f8ff]': isSelected, 'rb:bg-[#F6F6F6]': isSelected,
'rb:white': !isSelected 'rb:cursor-not-allowed rb:opacity-65': child.disabled,
'rb:cursor-pointer': !child.disabled,
})} })}
align="center" align="center"
justify="space-between" justify="space-between"
style={{
cursor: child.disabled ? 'not-allowed' : 'pointer',
opacity: child.disabled ? 0.5 : 1,
}}
onClick={() => !child.disabled && handleSelect(child)} onClick={() => !child.disabled && handleSelect(child)}
> >
<Flex align="center" gap={6}> <Flex align="center" gap={8}>
{multiple && ( {multiple && (
<Checkbox checked={isSelected} /> <Checkbox checked={isSelected} />
)} )}
<span>{child.label}</span> <span className="rb:font-medium">{child.label}</span>
</Flex>
<Flex align="center" gap={4}>
{child.dataType && <span className="rb:text-[#5B6167]">{child.dataType}</span>}
{hasGrandChildren && <span className="rb:text-[#5B6167] rb:ml-1"></span>}
</Flex> </Flex>
<Space size={2}>
{child.dataType && <span>{child.dataType}</span>}
</Space>
</Flex> </Flex>
); );
})} })}

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-01-19 17:00:26 * @Date: 2026-01-19 17:00:26
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-08 10:12:27 * @Last Modified time: 2026-04-13 10:44:17
*/ */
/** /**
* useVariableList Hook * useVariableList Hook
@@ -414,7 +414,7 @@ export const useVariableList = (
const pd = parentLoop.getData(); const pd = parentLoop.getData();
const pid = pd.id; const pid = pd.id;
if (pd.type === 'loop') { 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) { } else if (pd.type === 'iteration' && pd.config.input.defaultValue) {
let itemType = 'object'; let itemType = 'object';
const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue); const iv = list.find(v => `{{${v.value}}}` === pd.config.input.defaultValue);

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:39:59 * @Date: 2026-02-03 15:39:59
* @Last Modified by: ZhaoYing * @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 { type FC, useEffect, useState, useMemo } from "react";
import clsx from 'clsx' import clsx from 'clsx'
@@ -266,7 +266,7 @@ const Properties: FC<PropertiesProps> = ({
key, key,
label: cycleVar.name, label: cycleVar.name,
type: 'variable', type: 'variable',
dataType: cycleVar.type || 'String', dataType: cycleVar.type || 'string',
value: `${parentNodeId}.${cycleVar.name}`, value: `${parentNodeId}.${cycleVar.name}`,
nodeData: parentData, nodeData: parentData,
}); });
@@ -643,7 +643,7 @@ const Properties: FC<PropertiesProps> = ({
key: contextKey, key: contextKey,
label: 'context', label: 'context',
type: 'variable', type: 'variable',
dataType: 'String', dataType: 'string',
value: `context`, value: `context`,
nodeData: selectedNode.getData(), nodeData: selectedNode.getData(),
isContext: true, isContext: true,
@@ -791,7 +791,7 @@ const Properties: FC<PropertiesProps> = ({
key: `${selectedNode.id}_cycle_${cycleVar.name}`, key: `${selectedNode.id}_cycle_${cycleVar.name}`,
label: cycleVar.name, label: cycleVar.name,
type: 'variable', type: 'variable',
dataType: cycleVar.type || 'String', dataType: cycleVar.type || 'string',
value: `${selectedNode.getData().id}.${cycleVar.name}`, value: `${selectedNode.getData().id}.${cycleVar.name}`,
nodeData: selectedNode.getData(), nodeData: selectedNode.getData(),
})); }));

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 15:17:48 * @Date: 2026-02-03 15:17:48
* @Last Modified by: ZhaoYing * @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 { Clipboard, Graph, Keyboard, MiniMap, Node, Snapline, type Edge } from '@antv/x6';
import { register } from '@antv/x6-react-shape'; import { register } from '@antv/x6-react-shape';
@@ -1022,24 +1022,39 @@ export const useWorkflowGraph = ({
graphRef.current.on('node:removed', blankClick) graphRef.current.on('node:removed', blankClick)
// When edge connected, bring connected nodes' ports to front // When edge connected, bring connected nodes' ports to front
graphRef.current.on('edge:connected', ({ isNew }) => { graphRef.current.on('edge:connected', ({ isNew, edge }) => {
// 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
if (isNew) { if (isNew) {
graphRef.current?.getNodes().forEach(node => { const sourceCellId = edge.getSourceCellId()
if (!node.getData()?.cycle) node.toFront(); const targetCellId = edge.getTargetCellId()
}); const sourceCell = graphRef.current?.getCellById(sourceCellId);
graphRef.current?.getEdges().forEach(edge => { const targetCell = graphRef.current?.getCellById(targetCellId);
const sourceCell = graphRef.current?.getCellById(edge.getSourceCellId());
const targetCell = graphRef.current?.getCellById(edge.getTargetCellId()); sourceCell?.toFront();
if (sourceCell?.getData()?.cycle || targetCell?.getData()?.cycle) { targetCell?.toFront()
edge.toFront(); if (['loop', 'iteration'].includes(sourceCell?.getData()?.type)) {
} graphRef.current?.getEdges().forEach(edge => {
}); const edgeSourceCell = graphRef.current?.getCellById(edge.getSourceCellId());
graphRef.current?.getNodes().forEach(node => { const edgeTargetCell = graphRef.current?.getCellById(edge.getTargetCellId());
if (node.getData()?.cycle) node.toFront(); 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();
});
}
} }
}); });