tool-picker.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React from 'react'
  4. import { useMemo, useState } from 'react'
  5. import {
  6. PortalToFollowElem,
  7. PortalToFollowElemContent,
  8. PortalToFollowElemTrigger,
  9. } from '@/app/components/base/portal-to-follow-elem'
  10. import type {
  11. OffsetOptions,
  12. Placement,
  13. } from '@floating-ui/react'
  14. import AllTools from '@/app/components/workflow/block-selector/all-tools'
  15. import type { ToolDefaultValue, ToolValue } from './types'
  16. import type { BlockEnum } from '@/app/components/workflow/types'
  17. import SearchBox from '@/app/components/plugins/marketplace/search-box'
  18. import { useTranslation } from 'react-i18next'
  19. import { useBoolean } from 'ahooks'
  20. import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal/modal'
  21. import {
  22. createCustomCollection,
  23. } from '@/service/tools'
  24. import type { CustomCollectionBackend } from '@/app/components/tools/types'
  25. import Toast from '@/app/components/base/toast'
  26. import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools, useInvalidateAllCustomTools } from '@/service/use-tools'
  27. import cn from '@/utils/classnames'
  28. type Props = {
  29. panelClassName?: string
  30. disabled: boolean
  31. trigger: React.ReactNode
  32. placement?: Placement
  33. offset?: OffsetOptions
  34. isShow: boolean
  35. onShowChange: (isShow: boolean) => void
  36. onSelect: (tool: ToolDefaultValue) => void
  37. supportAddCustomTool?: boolean
  38. scope?: string
  39. selectedTools?: ToolValue[]
  40. }
  41. const ToolPicker: FC<Props> = ({
  42. disabled,
  43. trigger,
  44. placement = 'right-start',
  45. offset = 0,
  46. isShow,
  47. onShowChange,
  48. onSelect,
  49. supportAddCustomTool,
  50. scope = 'all',
  51. selectedTools,
  52. panelClassName,
  53. }) => {
  54. const { t } = useTranslation()
  55. const [searchText, setSearchText] = useState('')
  56. const [tags, setTags] = useState<string[]>([])
  57. const { data: buildInTools } = useAllBuiltInTools()
  58. const { data: customTools } = useAllCustomTools()
  59. const invalidateCustomTools = useInvalidateAllCustomTools()
  60. const { data: workflowTools } = useAllWorkflowTools()
  61. const { builtinToolList, customToolList, workflowToolList } = useMemo(() => {
  62. if (scope === 'plugins') {
  63. return {
  64. builtinToolList: buildInTools,
  65. customToolList: [],
  66. workflowToolList: [],
  67. }
  68. }
  69. if (scope === 'custom') {
  70. return {
  71. builtinToolList: [],
  72. customToolList: customTools,
  73. workflowToolList: [],
  74. }
  75. }
  76. if (scope === 'workflow') {
  77. return {
  78. builtinToolList: [],
  79. customToolList: [],
  80. workflowToolList: workflowTools,
  81. }
  82. }
  83. return {
  84. builtinToolList: buildInTools,
  85. customToolList: customTools,
  86. workflowToolList: workflowTools,
  87. }
  88. }, [scope, buildInTools, customTools, workflowTools])
  89. const handleAddedCustomTool = invalidateCustomTools
  90. const handleTriggerClick = () => {
  91. if (disabled) return
  92. onShowChange(true)
  93. }
  94. const handleSelect = (_type: BlockEnum, tool?: ToolDefaultValue) => {
  95. onSelect(tool!)
  96. }
  97. const [isShowEditCollectionToolModal, {
  98. setFalse: hideEditCustomCollectionModal,
  99. setTrue: showEditCustomCollectionModal,
  100. }] = useBoolean(false)
  101. const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
  102. await createCustomCollection(data)
  103. Toast.notify({
  104. type: 'success',
  105. message: t('common.api.actionSuccess'),
  106. })
  107. hideEditCustomCollectionModal()
  108. handleAddedCustomTool()
  109. }
  110. if (isShowEditCollectionToolModal) {
  111. return (
  112. <EditCustomToolModal
  113. positionLeft
  114. payload={null}
  115. onHide={hideEditCustomCollectionModal}
  116. onAdd={doCreateCustomToolCollection}
  117. />
  118. )
  119. }
  120. return (
  121. <PortalToFollowElem
  122. placement={placement}
  123. offset={offset}
  124. open={isShow}
  125. onOpenChange={onShowChange}
  126. >
  127. <PortalToFollowElemTrigger
  128. onClick={handleTriggerClick}
  129. >
  130. {trigger}
  131. </PortalToFollowElemTrigger>
  132. <PortalToFollowElemContent className='z-[1000]'>
  133. <div className={cn('relative w-[356px] min-h-20 rounded-xl backdrop-blur-sm bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg', panelClassName)}>
  134. <div className='p-2 pb-1'>
  135. <SearchBox
  136. search={searchText}
  137. onSearchChange={setSearchText}
  138. tags={tags}
  139. onTagsChange={setTags}
  140. size='small'
  141. placeholder={t('plugin.searchTools')!}
  142. />
  143. </div>
  144. <AllTools
  145. className='mt-1'
  146. toolContentClassName='max-w-[360px]'
  147. tags={tags}
  148. searchText={searchText}
  149. onSelect={handleSelect}
  150. buildInTools={builtinToolList || []}
  151. customTools={customToolList || []}
  152. workflowTools={workflowToolList || []}
  153. supportAddCustomTool={supportAddCustomTool}
  154. onAddedCustomTool={handleAddedCustomTool}
  155. onShowAddCustomCollectionModal={showEditCustomCollectionModal}
  156. selectedTools={selectedTools}
  157. />
  158. </div>
  159. </PortalToFollowElemContent>
  160. </PortalToFollowElem>
  161. )
  162. }
  163. export default React.memo(ToolPicker)