wwf
2025-05-20 938c3e5a587ce950a94964ea509b9e7f8834dfae
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import {
  useCallback,
} from 'react'
import { useTranslation } from 'react-i18next'
import { RiMoreFill } from '@remixicon/react'
import { intersection } from 'lodash-es'
import {
  PortalToFollowElem,
  PortalToFollowElemContent,
  PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Button from '@/app/components/base/button'
import BlockSelector from '@/app/components/workflow/block-selector'
import {
  useAvailableBlocks,
  useNodesInteractions,
} from '@/app/components/workflow/hooks'
import type {
  CommonNodeType,
  OnSelectBlock,
} from '@/app/components/workflow/types'
 
type ChangeItemProps = {
  data: CommonNodeType
  nodeId: string
  sourceHandle: string
}
const ChangeItem = ({
  data,
  nodeId,
  sourceHandle,
}: ChangeItemProps) => {
  const { t } = useTranslation()
 
  const { handleNodeChange } = useNodesInteractions()
  const {
    availablePrevBlocks,
    availableNextBlocks,
  } = useAvailableBlocks(data.type, data.isInIteration, data.isInLoop)
 
  const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
    handleNodeChange(nodeId, type, sourceHandle, toolDefaultValue)
  }, [nodeId, sourceHandle, handleNodeChange])
 
  const renderTrigger = useCallback(() => {
    return (
      <div className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'>
        {t('workflow.panel.change')}
      </div>
    )
  }, [t])
 
  return (
    <BlockSelector
      onSelect={handleSelect}
      placement='top-end'
      offset={{
        mainAxis: 6,
        crossAxis: 8,
      }}
      trigger={renderTrigger}
      popupClassName='!w-[328px]'
      availableBlocksTypes={intersection(availablePrevBlocks, availableNextBlocks).filter(item => item !== data.type)}
    />
  )
}
 
type OperatorProps = {
  open: boolean
  onOpenChange: (v: boolean) => void
  data: CommonNodeType
  nodeId: string
  sourceHandle: string
}
const Operator = ({
  open,
  onOpenChange,
  data,
  nodeId,
  sourceHandle,
}: OperatorProps) => {
  const { t } = useTranslation()
  const {
    handleNodeDelete,
    handleNodeDisconnect,
  } = useNodesInteractions()
 
  return (
    <PortalToFollowElem
      placement='bottom-end'
      offset={{ mainAxis: 4, crossAxis: -4 }}
      open={open}
      onOpenChange={onOpenChange}
    >
      <PortalToFollowElemTrigger onClick={() => onOpenChange(!open)}>
        <Button className='h-6 w-6 p-0'>
          <RiMoreFill className='h-4 w-4' />
        </Button>
      </PortalToFollowElemTrigger>
      <PortalToFollowElemContent className='z-10'>
        <div className='system-md-regular min-w-[120px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur text-text-secondary shadow-lg'>
          <div className='p-1'>
            <ChangeItem
              data={data}
              nodeId={nodeId}
              sourceHandle={sourceHandle}
            />
            <div
              className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
              onClick={() => handleNodeDisconnect(nodeId)}
            >
              {t('workflow.common.disconnect')}
            </div>
          </div>
          <div className='p-1'>
            <div
              className='flex h-8 cursor-pointer items-center rounded-lg px-2 hover:bg-state-base-hover'
              onClick={() => handleNodeDelete(nodeId)}
            >
              {t('common.operation.delete')}
            </div>
          </div>
        </div>
      </PortalToFollowElemContent>
    </PortalToFollowElem>
  )
}
 
export default Operator