### What problem does this PR solve? Feat: Add canvas node toolbar #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.19.1
| @@ -0,0 +1,22 @@ | |||
| import { forwardRef, HTMLAttributes } from 'react'; | |||
| import { cn } from '@/lib/utils'; | |||
| export const BaseNode = forwardRef< | |||
| HTMLDivElement, | |||
| HTMLAttributes<HTMLDivElement> & { selected?: boolean } | |||
| >(({ className, selected, ...props }, ref) => ( | |||
| <div | |||
| ref={ref} | |||
| className={cn( | |||
| 'relative rounded-md border bg-card text-card-foreground', | |||
| className, | |||
| selected ? 'border-muted-foreground shadow-lg' : '', | |||
| 'hover:ring-1', | |||
| )} | |||
| tabIndex={0} | |||
| {...props} | |||
| /> | |||
| )); | |||
| BaseNode.displayName = 'BaseNode'; | |||
| @@ -0,0 +1,101 @@ | |||
| import { NodeProps, NodeToolbar, NodeToolbarProps } from '@xyflow/react'; | |||
| import { | |||
| HTMLAttributes, | |||
| ReactNode, | |||
| createContext, | |||
| forwardRef, | |||
| useCallback, | |||
| useContext, | |||
| useState, | |||
| } from 'react'; | |||
| import { BaseNode } from './base-node'; | |||
| /* TOOLTIP CONTEXT ---------------------------------------------------------- */ | |||
| const TooltipContext = createContext(false); | |||
| /* TOOLTIP NODE ------------------------------------------------------------- */ | |||
| export type TooltipNodeProps = Partial<NodeProps> & { | |||
| children?: ReactNode; | |||
| }; | |||
| /** | |||
| * A component that wraps a node and provides tooltip visibility context. | |||
| */ | |||
| export const TooltipNode = forwardRef<HTMLDivElement, TooltipNodeProps>( | |||
| ({ selected, children }, ref) => { | |||
| const [isTooltipVisible, setTooltipVisible] = useState(false); | |||
| const showTooltip = useCallback(() => setTooltipVisible(true), []); | |||
| const hideTooltip = useCallback(() => setTooltipVisible(false), []); | |||
| return ( | |||
| <TooltipContext.Provider value={isTooltipVisible}> | |||
| <BaseNode | |||
| ref={ref} | |||
| onMouseEnter={showTooltip} | |||
| onMouseLeave={hideTooltip} | |||
| onFocus={showTooltip} | |||
| onBlur={hideTooltip} | |||
| tabIndex={0} | |||
| selected={selected} | |||
| > | |||
| {children} | |||
| </BaseNode> | |||
| </TooltipContext.Provider> | |||
| ); | |||
| }, | |||
| ); | |||
| TooltipNode.displayName = 'TooltipNode'; | |||
| /* TOOLTIP CONTENT ---------------------------------------------------------- */ | |||
| export type TooltipContentProps = NodeToolbarProps; | |||
| /** | |||
| * A component that displays the tooltip content based on visibility context. | |||
| */ | |||
| export const TooltipContent = forwardRef<HTMLDivElement, TooltipContentProps>( | |||
| ({ position, children }, ref) => { | |||
| const isTooltipVisible = useContext(TooltipContext); | |||
| return ( | |||
| <div ref={ref}> | |||
| <NodeToolbar | |||
| isVisible={isTooltipVisible} | |||
| className=" bg-transparent text-primary-foreground " | |||
| tabIndex={1} | |||
| position={position} | |||
| offset={0} | |||
| align={'end'} | |||
| > | |||
| {children} | |||
| </NodeToolbar> | |||
| </div> | |||
| ); | |||
| }, | |||
| ); | |||
| TooltipContent.displayName = 'TooltipContent'; | |||
| /* TOOLTIP TRIGGER ---------------------------------------------------------- */ | |||
| export type TooltipTriggerProps = HTMLAttributes<HTMLParagraphElement>; | |||
| /** | |||
| * A component that triggers the tooltip visibility. | |||
| */ | |||
| export const TooltipTrigger = forwardRef< | |||
| HTMLParagraphElement, | |||
| TooltipTriggerProps | |||
| >(({ children, ...props }, ref) => { | |||
| return ( | |||
| <div ref={ref} {...props}> | |||
| {children} | |||
| </div> | |||
| ); | |||
| }); | |||
| TooltipTrigger.displayName = 'TooltipTrigger'; | |||
| @@ -7,7 +7,7 @@ import { Operator } from '../../constant'; | |||
| import useGraphStore from '../../store'; | |||
| import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | |||
| import styles from './index.less'; | |||
| import NodeHeader from './node-header'; | |||
| import NodeHeader, { ToolBar } from './node-header'; | |||
| function InnerAgentNode({ | |||
| id, | |||
| @@ -26,50 +26,52 @@ function InnerAgentNode({ | |||
| }, [edges, getNode, id]); | |||
| return ( | |||
| <section | |||
| className={classNames( | |||
| styles.ragNode, | |||
| theme === 'dark' ? styles.dark : '', | |||
| { | |||
| [styles.selectedNode]: selected, | |||
| }, | |||
| )} | |||
| > | |||
| {isNotParentAgent && ( | |||
| <> | |||
| <Handle | |||
| id="c" | |||
| type="source" | |||
| position={Position.Left} | |||
| isConnectable={isConnectable} | |||
| className={styles.handle} | |||
| style={LeftHandleStyle} | |||
| ></Handle> | |||
| <Handle | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable={isConnectable} | |||
| className={styles.handle} | |||
| id="b" | |||
| style={RightHandleStyle} | |||
| ></Handle> | |||
| </> | |||
| )} | |||
| <Handle | |||
| type="target" | |||
| position={Position.Top} | |||
| isConnectable={false} | |||
| id="f" | |||
| ></Handle> | |||
| <Handle | |||
| type="source" | |||
| position={Position.Bottom} | |||
| isConnectable={false} | |||
| id="e" | |||
| style={{ left: 180 }} | |||
| ></Handle> | |||
| <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> | |||
| </section> | |||
| <ToolBar selected={selected}> | |||
| <section | |||
| className={classNames( | |||
| styles.ragNode, | |||
| theme === 'dark' ? styles.dark : '', | |||
| { | |||
| [styles.selectedNode]: selected, | |||
| }, | |||
| )} | |||
| > | |||
| {isNotParentAgent && ( | |||
| <> | |||
| <Handle | |||
| id="c" | |||
| type="source" | |||
| position={Position.Left} | |||
| isConnectable={isConnectable} | |||
| className={styles.handle} | |||
| style={LeftHandleStyle} | |||
| ></Handle> | |||
| <Handle | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable={isConnectable} | |||
| className={styles.handle} | |||
| id="b" | |||
| style={RightHandleStyle} | |||
| ></Handle> | |||
| </> | |||
| )} | |||
| <Handle | |||
| type="target" | |||
| position={Position.Top} | |||
| isConnectable={false} | |||
| id="f" | |||
| ></Handle> | |||
| <Handle | |||
| type="source" | |||
| position={Position.Bottom} | |||
| isConnectable={false} | |||
| id="e" | |||
| style={{ left: 180 }} | |||
| ></Handle> | |||
| <NodeHeader id={id} name={data.name} label={data.label}></NodeHeader> | |||
| </section> | |||
| </ToolBar> | |||
| ); | |||
| } | |||
| @@ -72,13 +72,13 @@ export const useBuildSwitchHandlePositions = ({ | |||
| }> = []; | |||
| [...conditions, ''].forEach((x, idx) => { | |||
| let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 32; // case number (Case 1) height + flex gap | |||
| let top = idx === 0 ? 58 + 20 : list[idx - 1].top + 10; // case number (Case 1) height + flex gap | |||
| if (idx - 1 >= 0) { | |||
| const previousItems = conditions[idx - 1]?.items ?? []; | |||
| if (previousItems.length > 0) { | |||
| top += 12; // ConditionBlock padding | |||
| // top += 12; // ConditionBlock padding | |||
| top += previousItems.length * 22; // condition variable height | |||
| top += (previousItems.length - 1) * 25; // operator height | |||
| // top += (previousItems.length - 1) * 25; // operator height | |||
| } | |||
| } | |||
| @@ -1,13 +1,19 @@ | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { Flex } from 'antd'; | |||
| import { Play } from 'lucide-react'; | |||
| import { Copy, Play, Trash2 } from 'lucide-react'; | |||
| import { Operator, operatorMap } from '../../constant'; | |||
| import OperatorIcon from '../../operator-icon'; | |||
| import { needsSingleStepDebugging } from '../../utils'; | |||
| import NodeDropdown from './dropdown'; | |||
| import { NextNodePopover } from './popover'; | |||
| import { memo } from 'react'; | |||
| import { | |||
| TooltipContent, | |||
| TooltipNode, | |||
| TooltipTrigger, | |||
| } from '@/components/xyflow/tooltip-node'; | |||
| import { Position } from '@xyflow/react'; | |||
| import { PropsWithChildren, memo } from 'react'; | |||
| import { RunTooltip } from '../../flow-tooltip'; | |||
| interface IProps { | |||
| id: string; | |||
| @@ -74,3 +80,37 @@ const InnerNodeHeader = ({ | |||
| const NodeHeader = memo(InnerNodeHeader); | |||
| export default NodeHeader; | |||
| function IconWrapper({ children }: PropsWithChildren) { | |||
| return ( | |||
| <div className="p-1.5 bg-text-title rounded-sm cursor-pointer"> | |||
| {children} | |||
| </div> | |||
| ); | |||
| } | |||
| type ToolBarProps = { | |||
| selected?: boolean | undefined; | |||
| } & PropsWithChildren; | |||
| export function ToolBar({ selected, children }: ToolBarProps) { | |||
| return ( | |||
| <TooltipNode selected={selected}> | |||
| <TooltipTrigger>{children}</TooltipTrigger> | |||
| <TooltipContent position={Position.Top}> | |||
| <section className="flex gap-2 items-center"> | |||
| <IconWrapper> | |||
| <Play className="size-3.5" /> | |||
| </IconWrapper> | |||
| <IconWrapper> | |||
| <Copy className="size-3.5" /> | |||
| </IconWrapper> | |||
| <IconWrapper> | |||
| <Trash2 className="size-3.5" /> | |||
| </IconWrapper> | |||
| </section> | |||
| </TooltipContent> | |||
| </TooltipNode> | |||
| ); | |||
| } | |||
| @@ -3,7 +3,6 @@ import { useTheme } from '@/components/theme-provider'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow'; | |||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | |||
| import { Flex } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { memo, useCallback } from 'react'; | |||
| import { SwitchOperatorOptions } from '../../constant'; | |||
| @@ -11,7 +10,7 @@ import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query'; | |||
| import { RightHandleStyle } from './handle-icon'; | |||
| import { useBuildSwitchHandlePositions } from './hooks'; | |||
| import styles from './index.less'; | |||
| import NodeHeader from './node-header'; | |||
| import NodeHeader, { ToolBar } from './node-header'; | |||
| const getConditionKey = (idx: number, length: number) => { | |||
| if (idx === 0 && length !== 1) { | |||
| @@ -40,10 +39,10 @@ const ConditionBlock = ({ | |||
| return ( | |||
| <Card> | |||
| <CardContent className="space-y-1 p-1"> | |||
| <CardContent className="p-0 divide-y divide-background-card"> | |||
| {items.map((x, idx) => ( | |||
| <div key={idx}> | |||
| <section className="flex justify-between gap-2 items-center text-xs"> | |||
| <section className="flex justify-between gap-2 items-center text-xs p-1"> | |||
| <div className="flex-1 truncate text-background-checked"> | |||
| {getLabel(x?.cpn_id)} | |||
| </div> | |||
| @@ -61,61 +60,64 @@ function InnerSwitchNode({ id, data, selected }: NodeProps<ISwitchNode>) { | |||
| const { positions } = useBuildSwitchHandlePositions({ data, id }); | |||
| const { theme } = useTheme(); | |||
| return ( | |||
| <section | |||
| className={classNames( | |||
| styles.logicNode, | |||
| theme === 'dark' ? styles.dark : '', | |||
| { | |||
| [styles.selectedNode]: selected, | |||
| }, | |||
| )} | |||
| > | |||
| <Handle | |||
| type="target" | |||
| position={Position.Left} | |||
| isConnectable | |||
| className={styles.handle} | |||
| id={'a'} | |||
| ></Handle> | |||
| <NodeHeader | |||
| id={id} | |||
| name={data.name} | |||
| label={data.label} | |||
| className={styles.nodeHeader} | |||
| ></NodeHeader> | |||
| <Flex vertical gap={10}> | |||
| {positions.map((position, idx) => { | |||
| return ( | |||
| <div key={idx}> | |||
| <Flex vertical> | |||
| <Flex justify={'space-between'}> | |||
| <span className="text-text-sub-title text-xs translate-y-2"> | |||
| {idx < positions.length - 1 && | |||
| position.condition?.logical_operator?.toUpperCase()} | |||
| </span> | |||
| <span>{getConditionKey(idx, positions.length)}</span> | |||
| </Flex> | |||
| {position.condition && ( | |||
| <ConditionBlock | |||
| nodeId={id} | |||
| condition={position.condition} | |||
| ></ConditionBlock> | |||
| )} | |||
| </Flex> | |||
| <Handle | |||
| key={position.text} | |||
| id={position.text} | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable | |||
| className={styles.handle} | |||
| style={{ ...RightHandleStyle, top: position.top }} | |||
| ></Handle> | |||
| </div> | |||
| ); | |||
| })} | |||
| </Flex> | |||
| </section> | |||
| <ToolBar selected={selected}> | |||
| <section | |||
| className={classNames( | |||
| styles.logicNode, | |||
| theme === 'dark' ? styles.dark : '', | |||
| { | |||
| [styles.selectedNode]: selected, | |||
| }, | |||
| 'group/operator hover:bg-slate-100', | |||
| )} | |||
| > | |||
| <Handle | |||
| type="target" | |||
| position={Position.Left} | |||
| isConnectable | |||
| className={styles.handle} | |||
| id={'a'} | |||
| ></Handle> | |||
| <NodeHeader | |||
| id={id} | |||
| name={data.name} | |||
| label={data.label} | |||
| className={styles.nodeHeader} | |||
| ></NodeHeader> | |||
| <section className="gap-2.5 flex flex-col"> | |||
| {positions.map((position, idx) => { | |||
| return ( | |||
| <div key={idx}> | |||
| <section className="flex flex-col"> | |||
| <div className="flex justify-between"> | |||
| <span className="text-text-sub-title text-xs translate-y-2"> | |||
| {idx < positions.length - 1 && | |||
| position.condition?.logical_operator?.toUpperCase()} | |||
| </span> | |||
| <span>{getConditionKey(idx, positions.length)}</span> | |||
| </div> | |||
| {position.condition && ( | |||
| <ConditionBlock | |||
| nodeId={id} | |||
| condition={position.condition} | |||
| ></ConditionBlock> | |||
| )} | |||
| </section> | |||
| <Handle | |||
| key={position.text} | |||
| id={position.text} | |||
| type="source" | |||
| position={Position.Right} | |||
| isConnectable | |||
| className={styles.handle} | |||
| style={{ ...RightHandleStyle, top: position.top }} | |||
| ></Handle> | |||
| </div> | |||
| ); | |||
| })} | |||
| </section> | |||
| </section> | |||
| </ToolBar> | |||
| ); | |||
| } | |||
| @@ -1,32 +1,3 @@ | |||
| import { | |||
| GitHubIcon, | |||
| KeywordIcon, | |||
| QWeatherIcon, | |||
| WikipediaIcon, | |||
| } from '@/assets/icon/Icon'; | |||
| import { ReactComponent as AkShareIcon } from '@/assets/svg/akshare.svg'; | |||
| import { ReactComponent as ArXivIcon } from '@/assets/svg/arxiv.svg'; | |||
| import { ReactComponent as baiduFanyiIcon } from '@/assets/svg/baidu-fanyi.svg'; | |||
| import { ReactComponent as BaiduIcon } from '@/assets/svg/baidu.svg'; | |||
| import { ReactComponent as BeginIcon } from '@/assets/svg/begin.svg'; | |||
| import { ReactComponent as BingIcon } from '@/assets/svg/bing.svg'; | |||
| import { ReactComponent as ConcentratorIcon } from '@/assets/svg/concentrator.svg'; | |||
| import { ReactComponent as CrawlerIcon } from '@/assets/svg/crawler.svg'; | |||
| import { ReactComponent as DeepLIcon } from '@/assets/svg/deepl.svg'; | |||
| import { ReactComponent as DuckIcon } from '@/assets/svg/duck.svg'; | |||
| import { ReactComponent as EmailIcon } from '@/assets/svg/email.svg'; | |||
| import { ReactComponent as ExeSqlIcon } from '@/assets/svg/exesql.svg'; | |||
| import { ReactComponent as GoogleScholarIcon } from '@/assets/svg/google-scholar.svg'; | |||
| import { ReactComponent as GoogleIcon } from '@/assets/svg/google.svg'; | |||
| import { ReactComponent as InvokeIcon } from '@/assets/svg/invoke-ai.svg'; | |||
| import { ReactComponent as Jin10Icon } from '@/assets/svg/jin10.svg'; | |||
| import { ReactComponent as NoteIcon } from '@/assets/svg/note.svg'; | |||
| import { ReactComponent as PubMedIcon } from '@/assets/svg/pubmed.svg'; | |||
| import { ReactComponent as SwitchIcon } from '@/assets/svg/switch.svg'; | |||
| import { ReactComponent as TemplateIcon } from '@/assets/svg/template.svg'; | |||
| import { ReactComponent as TuShareIcon } from '@/assets/svg/tushare.svg'; | |||
| import { ReactComponent as WenCaiIcon } from '@/assets/svg/wencai.svg'; | |||
| import { ReactComponent as YahooFinanceIcon } from '@/assets/svg/yahoo-finance.svg'; | |||
| import { | |||
| initialKeywordsSimilarityWeightValue, | |||
| initialSimilarityThresholdValue, | |||
| @@ -61,24 +32,10 @@ export enum PromptRole { | |||
| Assistant = 'assistant', | |||
| } | |||
| import { | |||
| BranchesOutlined, | |||
| DatabaseOutlined, | |||
| FormOutlined, | |||
| MergeCellsOutlined, | |||
| MessageOutlined, | |||
| RocketOutlined, | |||
| SendOutlined, | |||
| } from '@ant-design/icons'; | |||
| import upperFirst from 'lodash/upperFirst'; | |||
| import { | |||
| Box, | |||
| CirclePower, | |||
| CloudUpload, | |||
| CodeXml, | |||
| IterationCcw, | |||
| ListOrdered, | |||
| MessageSquareMore, | |||
| OptionIcon, | |||
| TextCursorInput, | |||
| ToggleLeft, | |||
| @@ -152,48 +109,6 @@ export const AgentOperatorList = [ | |||
| Operator.Agent, | |||
| ]; | |||
| export const operatorIconMap = { | |||
| [Operator.Retrieval]: RocketOutlined, | |||
| [Operator.Generate]: MergeCellsOutlined, | |||
| [Operator.Answer]: SendOutlined, | |||
| [Operator.Begin]: BeginIcon, | |||
| [Operator.Categorize]: DatabaseOutlined, | |||
| [Operator.Message]: MessageOutlined, | |||
| [Operator.Relevant]: BranchesOutlined, | |||
| [Operator.RewriteQuestion]: FormOutlined, | |||
| [Operator.KeywordExtract]: KeywordIcon, | |||
| [Operator.DuckDuckGo]: DuckIcon, | |||
| [Operator.Baidu]: BaiduIcon, | |||
| [Operator.Wikipedia]: WikipediaIcon, | |||
| [Operator.PubMed]: PubMedIcon, | |||
| [Operator.ArXiv]: ArXivIcon, | |||
| [Operator.Google]: GoogleIcon, | |||
| [Operator.Bing]: BingIcon, | |||
| [Operator.GoogleScholar]: GoogleScholarIcon, | |||
| [Operator.DeepL]: DeepLIcon, | |||
| [Operator.GitHub]: GitHubIcon, | |||
| [Operator.BaiduFanyi]: baiduFanyiIcon, | |||
| [Operator.QWeather]: QWeatherIcon, | |||
| [Operator.ExeSQL]: ExeSqlIcon, | |||
| [Operator.Switch]: SwitchIcon, | |||
| [Operator.WenCai]: WenCaiIcon, | |||
| [Operator.AkShare]: AkShareIcon, | |||
| [Operator.YahooFinance]: YahooFinanceIcon, | |||
| [Operator.Jin10]: Jin10Icon, | |||
| [Operator.Concentrator]: ConcentratorIcon, | |||
| [Operator.TuShare]: TuShareIcon, | |||
| [Operator.Note]: NoteIcon, | |||
| [Operator.Crawler]: CrawlerIcon, | |||
| [Operator.Invoke]: InvokeIcon, | |||
| [Operator.Template]: TemplateIcon, | |||
| [Operator.Email]: EmailIcon, | |||
| [Operator.Iteration]: IterationCcw, | |||
| [Operator.IterationStart]: CirclePower, | |||
| [Operator.Code]: CodeXml, | |||
| [Operator.WaitingDialogue]: MessageSquareMore, | |||
| [Operator.Agent]: Box, | |||
| }; | |||
| export const operatorMap: Record< | |||
| Operator, | |||
| { | |||
| @@ -296,6 +296,7 @@ const SwitchForm = ({ node }: IOperatorForm) => { | |||
| operator: switchOperatorOptions[0].value, | |||
| }, | |||
| ], | |||
| to: [], | |||
| }) | |||
| } | |||
| > | |||
| @@ -1,24 +1,66 @@ | |||
| import { Operator, operatorIconMap } from './constant'; | |||
| import { IconFont } from '@/components/icon-font'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { CirclePlay } from 'lucide-react'; | |||
| import { Operator } from './constant'; | |||
| interface IProps { | |||
| name: Operator; | |||
| fontSize?: number; | |||
| width?: number; | |||
| color?: string; | |||
| className?: string; | |||
| } | |||
| export const OperatorIconMap = { | |||
| [Operator.Retrieval]: 'retrival-0', | |||
| // [Operator.Generate]: MergeCellsOutlined, | |||
| // [Operator.Answer]: SendOutlined, | |||
| [Operator.Begin]: CirclePlay, | |||
| [Operator.Categorize]: 'a-QuestionClassification', | |||
| [Operator.Message]: 'reply', | |||
| [Operator.Iteration]: 'loop', | |||
| [Operator.Switch]: 'condition', | |||
| [Operator.Code]: 'code-set', | |||
| [Operator.Agent]: 'agent-ai', | |||
| // [Operator.Relevant]: BranchesOutlined, | |||
| // [Operator.RewriteQuestion]: FormOutlined, | |||
| // [Operator.KeywordExtract]: KeywordIcon, | |||
| // [Operator.DuckDuckGo]: DuckIcon, | |||
| // [Operator.Baidu]: BaiduIcon, | |||
| // [Operator.Wikipedia]: WikipediaIcon, | |||
| // [Operator.PubMed]: PubMedIcon, | |||
| // [Operator.ArXiv]: ArXivIcon, | |||
| // [Operator.Google]: GoogleIcon, | |||
| // [Operator.Bing]: BingIcon, | |||
| // [Operator.GoogleScholar]: GoogleScholarIcon, | |||
| // [Operator.DeepL]: DeepLIcon, | |||
| // [Operator.GitHub]: GitHubIcon, | |||
| // [Operator.BaiduFanyi]: baiduFanyiIcon, | |||
| // [Operator.QWeather]: QWeatherIcon, | |||
| // [Operator.ExeSQL]: ExeSqlIcon, | |||
| // [Operator.WenCai]: WenCaiIcon, | |||
| // [Operator.AkShare]: AkShareIcon, | |||
| // [Operator.YahooFinance]: YahooFinanceIcon, | |||
| // [Operator.Jin10]: Jin10Icon, | |||
| // [Operator.Concentrator]: ConcentratorIcon, | |||
| // [Operator.TuShare]: TuShareIcon, | |||
| // [Operator.Note]: NoteIcon, | |||
| // [Operator.Crawler]: CrawlerIcon, | |||
| // [Operator.Invoke]: InvokeIcon, | |||
| // [Operator.Template]: TemplateIcon, | |||
| // [Operator.Email]: EmailIcon, | |||
| // [Operator.IterationStart]: CirclePower, | |||
| // [Operator.WaitingDialogue]: MessageSquareMore, | |||
| }; | |||
| const Empty = () => { | |||
| return <div className="hidden"></div>; | |||
| }; | |||
| const OperatorIcon = ({ name, fontSize, width, color }: IProps) => { | |||
| const Icon = operatorIconMap[name] || Empty; | |||
| return ( | |||
| <Icon | |||
| className={'text-2xl max-h-6 max-w-6 text-[rgb(59, 118, 244)]'} | |||
| style={{ fontSize, color }} | |||
| width={width} | |||
| ></Icon> | |||
| const OperatorIcon = ({ name, className }: IProps) => { | |||
| const Icon = OperatorIconMap[name as keyof typeof OperatorIconMap] || Empty; | |||
| return typeof Icon === 'string' ? ( | |||
| <IconFont name={Icon} className={cn('size-5', className)}></IconFont> | |||
| ) : ( | |||
| <Icon className={cn('size-5', className)}> </Icon> | |||
| ); | |||
| }; | |||