You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import {
  2. useEffect,
  3. useMemo,
  4. useRef,
  5. useState,
  6. } from 'react'
  7. import type {
  8. OnSelectBlock,
  9. ToolWithProvider,
  10. } from '../types'
  11. import type { ToolValue } from './types'
  12. import { ToolTypeEnum } from './types'
  13. import Tools from './tools'
  14. import { useToolTabs } from './hooks'
  15. import ViewTypeSelect, { ViewType } from './view-type-select'
  16. import cn from '@/utils/classnames'
  17. import { useGetLanguage } from '@/context/i18n'
  18. import type { ListRef } from '@/app/components/workflow/block-selector/market-place-plugin/list'
  19. import PluginList, { type ListProps } from '@/app/components/workflow/block-selector/market-place-plugin/list'
  20. import ActionButton from '../../base/action-button'
  21. import { RiAddLine } from '@remixicon/react'
  22. import { PluginType } from '../../plugins/types'
  23. import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
  24. import { useGlobalPublicStore } from '@/context/global-public-context'
  25. type AllToolsProps = {
  26. className?: string
  27. toolContentClassName?: string
  28. searchText: string
  29. tags: ListProps['tags']
  30. buildInTools: ToolWithProvider[]
  31. customTools: ToolWithProvider[]
  32. workflowTools: ToolWithProvider[]
  33. onSelect: OnSelectBlock
  34. supportAddCustomTool?: boolean
  35. onAddedCustomTool?: () => void
  36. onShowAddCustomCollectionModal?: () => void
  37. selectedTools?: ToolValue[]
  38. }
  39. const DEFAULT_TAGS: AllToolsProps['tags'] = []
  40. const AllTools = ({
  41. className,
  42. toolContentClassName,
  43. searchText,
  44. tags = DEFAULT_TAGS,
  45. onSelect,
  46. buildInTools,
  47. workflowTools,
  48. customTools,
  49. supportAddCustomTool,
  50. onShowAddCustomCollectionModal,
  51. selectedTools,
  52. }: AllToolsProps) => {
  53. const language = useGetLanguage()
  54. const tabs = useToolTabs()
  55. const [activeTab, setActiveTab] = useState(ToolTypeEnum.All)
  56. const [activeView, setActiveView] = useState<ViewType>(ViewType.flat)
  57. const hasFilter = searchText || tags.length > 0
  58. const isMatchingKeywords = (text: string, keywords: string) => {
  59. return text.toLowerCase().includes(keywords.toLowerCase())
  60. }
  61. const tools = useMemo(() => {
  62. let mergedTools: ToolWithProvider[] = []
  63. if (activeTab === ToolTypeEnum.All)
  64. mergedTools = [...buildInTools, ...customTools, ...workflowTools]
  65. if (activeTab === ToolTypeEnum.BuiltIn)
  66. mergedTools = buildInTools
  67. if (activeTab === ToolTypeEnum.Custom)
  68. mergedTools = customTools
  69. if (activeTab === ToolTypeEnum.Workflow)
  70. mergedTools = workflowTools
  71. if (!hasFilter)
  72. return mergedTools.filter(toolWithProvider => toolWithProvider.tools.length > 0)
  73. return mergedTools.filter((toolWithProvider) => {
  74. return isMatchingKeywords(toolWithProvider.name, searchText) || toolWithProvider.tools.some((tool) => {
  75. return tool.label[language].toLowerCase().includes(searchText.toLowerCase()) || tool.name.toLowerCase().includes(searchText.toLowerCase())
  76. })
  77. })
  78. }, [activeTab, buildInTools, customTools, workflowTools, searchText, language, hasFilter])
  79. const {
  80. queryPluginsWithDebounced: fetchPlugins,
  81. plugins: notInstalledPlugins = [],
  82. } = useMarketplacePlugins()
  83. const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
  84. useEffect(() => {
  85. if (enable_marketplace) return
  86. if (searchText || tags.length > 0) {
  87. fetchPlugins({
  88. query: searchText,
  89. tags,
  90. category: PluginType.tool,
  91. })
  92. }
  93. // eslint-disable-next-line react-hooks/exhaustive-deps
  94. }, [searchText, tags, enable_marketplace])
  95. const pluginRef = useRef<ListRef>(null)
  96. const wrapElemRef = useRef<HTMLDivElement>(null)
  97. return (
  98. <div className={cn(className)}>
  99. <div className='flex items-center justify-between border-b-[0.5px] border-divider-subtle bg-background-default-hover px-3 shadow-xs'>
  100. <div className='flex h-8 items-center space-x-1'>
  101. {
  102. tabs.map(tab => (
  103. <div
  104. className={cn(
  105. 'flex h-6 cursor-pointer items-center rounded-md px-2 hover:bg-state-base-hover',
  106. 'text-xs font-medium text-text-secondary',
  107. activeTab === tab.key && 'bg-state-base-hover-alt',
  108. )}
  109. key={tab.key}
  110. onClick={() => setActiveTab(tab.key)}
  111. >
  112. {tab.name}
  113. </div>
  114. ))
  115. }
  116. </div>
  117. <ViewTypeSelect viewType={activeView} onChange={setActiveView} />
  118. {supportAddCustomTool && (
  119. <div className='flex items-center'>
  120. <div className='mr-1.5 h-3.5 w-px bg-divider-regular'></div>
  121. <ActionButton
  122. className='bg-components-button-primary-bg text-components-button-primary-text hover:bg-components-button-primary-bg hover:text-components-button-primary-text'
  123. onClick={onShowAddCustomCollectionModal}
  124. >
  125. <RiAddLine className='h-4 w-4' />
  126. </ActionButton>
  127. </div>
  128. )}
  129. </div>
  130. <div
  131. ref={wrapElemRef}
  132. className='max-h-[464px] overflow-y-auto'
  133. onScroll={pluginRef.current?.handleScroll}
  134. >
  135. <Tools
  136. className={toolContentClassName}
  137. showWorkflowEmpty={activeTab === ToolTypeEnum.Workflow}
  138. tools={tools}
  139. onSelect={onSelect}
  140. viewType={activeView}
  141. hasSearchText={!!searchText}
  142. selectedTools={selectedTools}
  143. />
  144. {/* Plugins from marketplace */}
  145. {enable_marketplace && <PluginList
  146. ref={pluginRef}
  147. wrapElemRef={wrapElemRef}
  148. list={notInstalledPlugins}
  149. searchText={searchText}
  150. toolContentClassName={toolContentClassName}
  151. tags={tags}
  152. />}
  153. </div>
  154. </div>
  155. )
  156. }
  157. export default AllTools