Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

add-block.tsx 3.3KB

6 meses atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import {
  2. memo,
  3. useCallback,
  4. useState,
  5. } from 'react'
  6. import { RiAddCircleFill } from '@remixicon/react'
  7. import { useStoreApi } from 'reactflow'
  8. import { useTranslation } from 'react-i18next'
  9. import type { OffsetOptions } from '@floating-ui/react'
  10. import {
  11. generateNewNode,
  12. getNodeCustomTypeByNodeDataType,
  13. } from '../utils'
  14. import {
  15. useAvailableBlocks,
  16. useNodesMetaData,
  17. useNodesReadOnly,
  18. usePanelInteractions,
  19. } from '../hooks'
  20. import { useWorkflowStore } from '../store'
  21. import TipPopup from './tip-popup'
  22. import cn from '@/utils/classnames'
  23. import BlockSelector from '@/app/components/workflow/block-selector'
  24. import type {
  25. OnSelectBlock,
  26. } from '@/app/components/workflow/types'
  27. import {
  28. BlockEnum,
  29. } from '@/app/components/workflow/types'
  30. type AddBlockProps = {
  31. renderTrigger?: (open: boolean) => React.ReactNode
  32. offset?: OffsetOptions
  33. }
  34. const AddBlock = ({
  35. renderTrigger,
  36. offset,
  37. }: AddBlockProps) => {
  38. const { t } = useTranslation()
  39. const store = useStoreApi()
  40. const workflowStore = useWorkflowStore()
  41. const { nodesReadOnly } = useNodesReadOnly()
  42. const { handlePaneContextmenuCancel } = usePanelInteractions()
  43. const [open, setOpen] = useState(false)
  44. const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
  45. const { nodesMap: nodesMetaDataMap } = useNodesMetaData()
  46. const handleOpenChange = useCallback((open: boolean) => {
  47. setOpen(open)
  48. if (!open)
  49. handlePaneContextmenuCancel()
  50. }, [handlePaneContextmenuCancel])
  51. const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
  52. const {
  53. getNodes,
  54. } = store.getState()
  55. const nodes = getNodes()
  56. const nodesWithSameType = nodes.filter(node => node.data.type === type)
  57. const {
  58. defaultValue,
  59. } = nodesMetaDataMap![type]
  60. const { newNode } = generateNewNode({
  61. type: getNodeCustomTypeByNodeDataType(type),
  62. data: {
  63. ...(defaultValue as any),
  64. title: nodesWithSameType.length > 0 ? `${defaultValue.title} ${nodesWithSameType.length + 1}` : defaultValue.title,
  65. ...(toolDefaultValue || {}),
  66. _isCandidate: true,
  67. },
  68. position: {
  69. x: 0,
  70. y: 0,
  71. },
  72. })
  73. workflowStore.setState({
  74. candidateNode: newNode,
  75. })
  76. }, [store, workflowStore, nodesMetaDataMap])
  77. const renderTriggerElement = useCallback((open: boolean) => {
  78. return (
  79. <TipPopup
  80. title={t('workflow.common.addBlock')}
  81. >
  82. <div className={cn(
  83. 'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary',
  84. `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
  85. open && 'bg-state-accent-active text-text-accent',
  86. )}>
  87. <RiAddCircleFill className='h-4 w-4' />
  88. </div>
  89. </TipPopup>
  90. )
  91. }, [nodesReadOnly, t])
  92. return (
  93. <BlockSelector
  94. open={open}
  95. onOpenChange={handleOpenChange}
  96. disabled={nodesReadOnly}
  97. onSelect={handleSelect}
  98. placement='right-start'
  99. offset={offset ?? {
  100. mainAxis: 4,
  101. crossAxis: -8,
  102. }}
  103. trigger={renderTrigger || renderTriggerElement}
  104. popupClassName='!min-w-[256px]'
  105. availableBlocksTypes={availableNextBlocks}
  106. />
  107. )
  108. }
  109. export default memo(AddBlock)