### 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
| 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'; |
| 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'; |
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | import { LeftHandleStyle, RightHandleStyle } from './handle-icon'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import NodeHeader from './node-header'; | |||||
| import NodeHeader, { ToolBar } from './node-header'; | |||||
| function InnerAgentNode({ | function InnerAgentNode({ | ||||
| id, | id, | ||||
| }, [edges, getNode, id]); | }, [edges, getNode, id]); | ||||
| return ( | 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> | |||||
| ); | ); | ||||
| } | } | ||||
| }> = []; | }> = []; | ||||
| [...conditions, ''].forEach((x, idx) => { | [...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) { | if (idx - 1 >= 0) { | ||||
| const previousItems = conditions[idx - 1]?.items ?? []; | const previousItems = conditions[idx - 1]?.items ?? []; | ||||
| if (previousItems.length > 0) { | if (previousItems.length > 0) { | ||||
| top += 12; // ConditionBlock padding | |||||
| // top += 12; // ConditionBlock padding | |||||
| top += previousItems.length * 22; // condition variable height | top += previousItems.length * 22; // condition variable height | ||||
| top += (previousItems.length - 1) * 25; // operator height | |||||
| // top += (previousItems.length - 1) * 25; // operator height | |||||
| } | } | ||||
| } | } | ||||
| import { useTranslate } from '@/hooks/common-hooks'; | import { useTranslate } from '@/hooks/common-hooks'; | ||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import { Play } from 'lucide-react'; | |||||
| import { Copy, Play, Trash2 } from 'lucide-react'; | |||||
| import { Operator, operatorMap } from '../../constant'; | import { Operator, operatorMap } from '../../constant'; | ||||
| import OperatorIcon from '../../operator-icon'; | import OperatorIcon from '../../operator-icon'; | ||||
| import { needsSingleStepDebugging } from '../../utils'; | import { needsSingleStepDebugging } from '../../utils'; | ||||
| import NodeDropdown from './dropdown'; | import NodeDropdown from './dropdown'; | ||||
| import { NextNodePopover } from './popover'; | 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'; | import { RunTooltip } from '../../flow-tooltip'; | ||||
| interface IProps { | interface IProps { | ||||
| id: string; | id: string; | ||||
| const NodeHeader = memo(InnerNodeHeader); | const NodeHeader = memo(InnerNodeHeader); | ||||
| export default NodeHeader; | 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> | |||||
| ); | |||||
| } |
| import { Card, CardContent } from '@/components/ui/card'; | import { Card, CardContent } from '@/components/ui/card'; | ||||
| import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow'; | import { ISwitchCondition, ISwitchNode } from '@/interfaces/database/flow'; | ||||
| import { Handle, NodeProps, Position } from '@xyflow/react'; | import { Handle, NodeProps, Position } from '@xyflow/react'; | ||||
| import { Flex } from 'antd'; | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { memo, useCallback } from 'react'; | import { memo, useCallback } from 'react'; | ||||
| import { SwitchOperatorOptions } from '../../constant'; | import { SwitchOperatorOptions } from '../../constant'; | ||||
| import { RightHandleStyle } from './handle-icon'; | import { RightHandleStyle } from './handle-icon'; | ||||
| import { useBuildSwitchHandlePositions } from './hooks'; | import { useBuildSwitchHandlePositions } from './hooks'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import NodeHeader from './node-header'; | |||||
| import NodeHeader, { ToolBar } from './node-header'; | |||||
| const getConditionKey = (idx: number, length: number) => { | const getConditionKey = (idx: number, length: number) => { | ||||
| if (idx === 0 && length !== 1) { | if (idx === 0 && length !== 1) { | ||||
| return ( | return ( | ||||
| <Card> | <Card> | ||||
| <CardContent className="space-y-1 p-1"> | |||||
| <CardContent className="p-0 divide-y divide-background-card"> | |||||
| {items.map((x, idx) => ( | {items.map((x, idx) => ( | ||||
| <div key={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"> | <div className="flex-1 truncate text-background-checked"> | ||||
| {getLabel(x?.cpn_id)} | {getLabel(x?.cpn_id)} | ||||
| </div> | </div> | ||||
| const { positions } = useBuildSwitchHandlePositions({ data, id }); | const { positions } = useBuildSwitchHandlePositions({ data, id }); | ||||
| const { theme } = useTheme(); | const { theme } = useTheme(); | ||||
| return ( | 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> | |||||
| ); | ); | ||||
| } | } | ||||
| 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 { | import { | ||||
| initialKeywordsSimilarityWeightValue, | initialKeywordsSimilarityWeightValue, | ||||
| initialSimilarityThresholdValue, | initialSimilarityThresholdValue, | ||||
| Assistant = 'assistant', | Assistant = 'assistant', | ||||
| } | } | ||||
| import { | |||||
| BranchesOutlined, | |||||
| DatabaseOutlined, | |||||
| FormOutlined, | |||||
| MergeCellsOutlined, | |||||
| MessageOutlined, | |||||
| RocketOutlined, | |||||
| SendOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import upperFirst from 'lodash/upperFirst'; | import upperFirst from 'lodash/upperFirst'; | ||||
| import { | import { | ||||
| Box, | |||||
| CirclePower, | |||||
| CloudUpload, | CloudUpload, | ||||
| CodeXml, | |||||
| IterationCcw, | |||||
| ListOrdered, | ListOrdered, | ||||
| MessageSquareMore, | |||||
| OptionIcon, | OptionIcon, | ||||
| TextCursorInput, | TextCursorInput, | ||||
| ToggleLeft, | ToggleLeft, | ||||
| Operator.Agent, | 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< | export const operatorMap: Record< | ||||
| Operator, | Operator, | ||||
| { | { |
| operator: switchOperatorOptions[0].value, | operator: switchOperatorOptions[0].value, | ||||
| }, | }, | ||||
| ], | ], | ||||
| to: [], | |||||
| }) | }) | ||||
| } | } | ||||
| > | > |
| 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 { | interface IProps { | ||||
| name: Operator; | 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 = () => { | const Empty = () => { | ||||
| return <div className="hidden"></div>; | 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> | |||||
| ); | ); | ||||
| }; | }; | ||||