all-tools.tsx 5.2 KB

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