panel.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import type {
  2. FC,
  3. ReactElement,
  4. } from 'react'
  5. import {
  6. cloneElement,
  7. memo,
  8. useCallback,
  9. } from 'react'
  10. import {
  11. RiCloseLine,
  12. RiPlayLargeLine,
  13. } from '@remixicon/react'
  14. import { useShallow } from 'zustand/react/shallow'
  15. import { useTranslation } from 'react-i18next'
  16. import NextStep from './components/next-step'
  17. import PanelOperator from './components/panel-operator'
  18. import HelpLink from './components/help-link'
  19. import {
  20. DescriptionInput,
  21. TitleInput,
  22. } from './components/title-description-input'
  23. import { useResizePanel } from './hooks/use-resize-panel'
  24. import cn from '@/utils/classnames'
  25. import BlockIcon from '@/app/components/workflow/block-icon'
  26. import {
  27. WorkflowHistoryEvent,
  28. useAvailableBlocks,
  29. useNodeDataUpdate,
  30. useNodesInteractions,
  31. useNodesReadOnly,
  32. useNodesSyncDraft,
  33. useToolIcon,
  34. useWorkflow,
  35. useWorkflowHistory,
  36. } from '@/app/components/workflow/hooks'
  37. import { canRunBySingle } from '@/app/components/workflow/utils'
  38. import Tooltip from '@/app/components/base/tooltip'
  39. import type { Node } from '@/app/components/workflow/types'
  40. import { useStore as useAppStore } from '@/app/components/app/store'
  41. import { useStore } from '@/app/components/workflow/store'
  42. type BasePanelProps = {
  43. children: ReactElement
  44. } & Node
  45. const BasePanel: FC<BasePanelProps> = ({
  46. id,
  47. data,
  48. children,
  49. }) => {
  50. const { t } = useTranslation()
  51. const { showMessageLogModal } = useAppStore(useShallow(state => ({
  52. showMessageLogModal: state.showMessageLogModal,
  53. })))
  54. const showSingleRunPanel = useStore(s => s.showSingleRunPanel)
  55. const panelWidth = localStorage.getItem('workflow-node-panel-width') ? parseFloat(localStorage.getItem('workflow-node-panel-width')!) : 420
  56. const {
  57. setPanelWidth,
  58. } = useWorkflow()
  59. const { handleNodeSelect } = useNodesInteractions()
  60. const { handleSyncWorkflowDraft } = useNodesSyncDraft()
  61. const { nodesReadOnly } = useNodesReadOnly()
  62. const { availableNextBlocks } = useAvailableBlocks(data.type, data.isInIteration)
  63. const toolIcon = useToolIcon(data)
  64. const handleResize = useCallback((width: number) => {
  65. setPanelWidth(width)
  66. }, [setPanelWidth])
  67. const {
  68. triggerRef,
  69. containerRef,
  70. } = useResizePanel({
  71. direction: 'horizontal',
  72. triggerDirection: 'left',
  73. minWidth: 420,
  74. maxWidth: 720,
  75. onResize: handleResize,
  76. })
  77. const { saveStateToHistory } = useWorkflowHistory()
  78. const {
  79. handleNodeDataUpdate,
  80. handleNodeDataUpdateWithSyncDraft,
  81. } = useNodeDataUpdate()
  82. const handleTitleBlur = useCallback((title: string) => {
  83. handleNodeDataUpdateWithSyncDraft({ id, data: { title } })
  84. saveStateToHistory(WorkflowHistoryEvent.NodeTitleChange)
  85. }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
  86. const handleDescriptionChange = useCallback((desc: string) => {
  87. handleNodeDataUpdateWithSyncDraft({ id, data: { desc } })
  88. saveStateToHistory(WorkflowHistoryEvent.NodeDescriptionChange)
  89. }, [handleNodeDataUpdateWithSyncDraft, id, saveStateToHistory])
  90. return (
  91. <div className={cn(
  92. 'relative mr-2 h-full',
  93. showMessageLogModal && '!absolute !mr-0 w-[384px] overflow-hidden -top-[5px] right-[416px] z-0 shadow-lg border-[0.5px] border-components-panel-border rounded-2xl transition-all',
  94. )}>
  95. <div
  96. ref={triggerRef}
  97. className='absolute top-1/2 -translate-y-1/2 -left-2 w-3 h-6 cursor-col-resize resize-x'>
  98. <div className='w-1 h-6 bg-divider-regular rounded-sm'></div>
  99. </div>
  100. <div
  101. ref={containerRef}
  102. className={cn('h-full bg-components-panel-bg shadow-lg border-[0.5px] border-components-panel-border rounded-2xl', showSingleRunPanel ? 'overflow-hidden' : 'overflow-y-auto')}
  103. style={{
  104. width: `${panelWidth}px`,
  105. }}
  106. >
  107. <div className='sticky top-0 bg-components-panel-bg border-b-[0.5px] border-black/5 z-10'>
  108. <div className='flex items-center px-4 pt-4 pb-1'>
  109. <BlockIcon
  110. className='shrink-0 mr-1'
  111. type={data.type}
  112. toolIcon={toolIcon}
  113. size='md'
  114. />
  115. <TitleInput
  116. value={data.title || ''}
  117. onBlur={handleTitleBlur}
  118. />
  119. <div className='shrink-0 flex items-center text-gray-500'>
  120. {
  121. canRunBySingle(data.type) && !nodesReadOnly && (
  122. <Tooltip
  123. popupContent={t('workflow.panel.runThisStep')}
  124. popupClassName='mr-1'
  125. >
  126. <div
  127. className='flex items-center justify-center mr-1 w-6 h-6 rounded-md hover:bg-black/5 cursor-pointer'
  128. onClick={() => {
  129. handleNodeDataUpdate({ id, data: { _isSingleRun: true } })
  130. handleSyncWorkflowDraft(true)
  131. }}
  132. >
  133. <RiPlayLargeLine className='w-4 h-4 text-text-tertiary' />
  134. </div>
  135. </Tooltip>
  136. )
  137. }
  138. <HelpLink nodeType={data.type} />
  139. <PanelOperator id={id} data={data} showHelpLink={false} />
  140. <div className='mx-3 w-[1px] h-3.5 bg-divider-regular' />
  141. <div
  142. className='flex items-center justify-center w-6 h-6 cursor-pointer'
  143. onClick={() => handleNodeSelect(id, true)}
  144. >
  145. <RiCloseLine className='w-4 h-4 text-text-tertiary' />
  146. </div>
  147. </div>
  148. </div>
  149. <div className='p-2'>
  150. <DescriptionInput
  151. value={data.desc || ''}
  152. onChange={handleDescriptionChange}
  153. />
  154. </div>
  155. </div>
  156. <div className='py-2'>
  157. {cloneElement(children, { id, data })}
  158. </div>
  159. {
  160. !!availableNextBlocks.length && (
  161. <div className='p-4 border-t-[0.5px] border-t-black/5'>
  162. <div className='flex items-center mb-1 system-sm-semibold-uppercase text-text-secondary'>
  163. {t('workflow.panel.nextStep').toLocaleUpperCase()}
  164. </div>
  165. <div className='mb-2 system-xs-regular text-text-tertiary'>
  166. {t('workflow.panel.addNextStep')}
  167. </div>
  168. <NextStep selectedNode={{ id, data } as Node} />
  169. </div>
  170. )
  171. }
  172. </div>
  173. </div>
  174. )
  175. }
  176. export default memo(BasePanel)