index.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useEffect, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import cn from 'classnames'
  6. import Button from '../base/button'
  7. import { Plus } from '../base/icons/src/vender/line/general'
  8. import Toast from '../base/toast'
  9. import type { Collection, CustomCollectionBackend, Tool } from './types'
  10. import { CollectionType, LOC } from './types'
  11. import ToolNavList from './tool-nav-list'
  12. import Search from './search'
  13. import Contribute from './contribute'
  14. import ToolList from './tool-list'
  15. import EditCustomToolModal from './edit-custom-collection-modal'
  16. import NoCustomTool from './info/no-custom-tool'
  17. import NoSearchRes from './info/no-search-res'
  18. import NoCustomToolPlaceholder from './no-custom-tool-placeholder'
  19. import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
  20. import TabSlider from '@/app/components/base/tab-slider'
  21. import { createCustomCollection, fetchCollectionList as doFetchCollectionList, fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList } from '@/service/tools'
  22. import type { AgentTool } from '@/types/app'
  23. type Props = {
  24. loc: LOC
  25. addedTools?: AgentTool[]
  26. onAddTool?: (collection: Collection, payload: Tool) => void
  27. selectedProviderId?: string
  28. }
  29. const Tools: FC<Props> = ({
  30. loc,
  31. addedTools,
  32. onAddTool,
  33. selectedProviderId,
  34. }) => {
  35. const { t } = useTranslation()
  36. const isInToolsPage = loc === LOC.tools
  37. const isInDebugPage = !isInToolsPage
  38. const [collectionList, setCollectionList] = useState<Collection[]>([])
  39. const [currCollectionIndex, setCurrCollectionIndex] = useState<number | null>(null)
  40. const [isDetailLoading, setIsDetailLoading] = useState(false)
  41. const fetchCollectionList = async () => {
  42. const list = await doFetchCollectionList()
  43. setCollectionList(list)
  44. if (list.length > 0 && currCollectionIndex === null) {
  45. let index = 0
  46. if (selectedProviderId)
  47. index = list.findIndex(item => item.id === selectedProviderId)
  48. setCurrCollectionIndex(index || 0)
  49. }
  50. }
  51. useEffect(() => {
  52. fetchCollectionList()
  53. }, [])
  54. const collectionTypeOptions = (() => {
  55. const res = [
  56. { value: CollectionType.builtIn, text: t('tools.type.builtIn') },
  57. { value: CollectionType.custom, text: t('tools.type.custom') },
  58. ]
  59. if (!isInToolsPage)
  60. res.unshift({ value: CollectionType.all, text: t('tools.type.all') })
  61. return res
  62. })()
  63. const [query, setQuery] = useState('')
  64. const [toolPageCollectionType, setToolPageCollectionType] = useTabSearchParams({
  65. defaultTab: collectionTypeOptions[0].value,
  66. })
  67. const [appPageCollectionType, setAppPageCollectionType] = useState(collectionTypeOptions[0].value)
  68. const { collectionType, setCollectionType } = (() => {
  69. if (isInToolsPage) {
  70. return {
  71. collectionType: toolPageCollectionType,
  72. setCollectionType: setToolPageCollectionType,
  73. }
  74. }
  75. return {
  76. collectionType: appPageCollectionType,
  77. setCollectionType: setAppPageCollectionType,
  78. }
  79. })()
  80. const showCollectionList = (() => {
  81. let typeFilteredList: Collection[] = []
  82. if (collectionType === CollectionType.all)
  83. typeFilteredList = collectionList.filter(item => item.type !== CollectionType.model)
  84. else if (collectionType === CollectionType.builtIn)
  85. typeFilteredList = collectionList.filter(item => item.type === CollectionType.builtIn)
  86. else if (collectionType === CollectionType.custom)
  87. typeFilteredList = collectionList.filter(item => item.type === CollectionType.custom)
  88. if (query)
  89. return typeFilteredList.filter(item => item.name.includes(query))
  90. return typeFilteredList
  91. })()
  92. const hasNoCustomCollection = !collectionList.find(item => item.type === CollectionType.custom)
  93. useEffect(() => {
  94. setCurrCollectionIndex(0)
  95. }, [collectionType])
  96. const currCollection = (() => {
  97. if (currCollectionIndex === null)
  98. return null
  99. return showCollectionList[currCollectionIndex]
  100. })()
  101. const [currTools, setCurrentTools] = useState<Tool[]>([])
  102. useEffect(() => {
  103. if (!currCollection)
  104. return
  105. (async () => {
  106. setIsDetailLoading(true)
  107. try {
  108. if (currCollection.type === CollectionType.builtIn) {
  109. const list = await fetchBuiltInToolList(currCollection.name)
  110. setCurrentTools(list)
  111. }
  112. else if (currCollection.type === CollectionType.model) {
  113. const list = await fetchModelToolList(currCollection.name)
  114. setCurrentTools(list)
  115. }
  116. else {
  117. const list = await fetchCustomToolList(currCollection.name)
  118. setCurrentTools(list)
  119. }
  120. }
  121. catch (e) { }
  122. setIsDetailLoading(false)
  123. })()
  124. }, [currCollection?.name, currCollection?.type])
  125. const [isShowEditCollectionToolModal, setIsShowEditCollectionToolModal] = useState(false)
  126. const handleCreateToolCollection = () => {
  127. setIsShowEditCollectionToolModal(true)
  128. }
  129. const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
  130. await createCustomCollection(data)
  131. Toast.notify({
  132. type: 'success',
  133. message: t('common.api.actionSuccess'),
  134. })
  135. await fetchCollectionList()
  136. setIsShowEditCollectionToolModal(false)
  137. }
  138. return (
  139. <>
  140. <div className='flex h-full'>
  141. {/* sidebar */}
  142. <div className={cn(isInToolsPage ? 'sm:w-[216px] px-4' : 'sm:w-[256px] px-3', 'flex flex-col w-16 shrink-0 pb-2')}>
  143. {isInToolsPage && (
  144. <Button className='mt-6 flex items-center !h-8 pl-4' type='primary' onClick={handleCreateToolCollection}>
  145. <Plus className='w-4 h-4 mr-1' />
  146. <div className='leading-[18px] text-[13px] font-medium truncate'>{t('tools.createCustomTool')}</div>
  147. </Button>
  148. )}
  149. {isInDebugPage && (
  150. <div className='mt-6 flex space-x-1 items-center'>
  151. <Search
  152. className='grow'
  153. value={query}
  154. onChange={setQuery}
  155. />
  156. <Button className='flex items-center justify-center !w-8 !h-8 !p-0' type='primary'>
  157. <Plus className='w-4 h-4' onClick={handleCreateToolCollection} />
  158. </Button>
  159. </div>
  160. )}
  161. <TabSlider
  162. className='mt-3'
  163. itemWidth={isInToolsPage ? 89 : 75}
  164. value={collectionType}
  165. onChange={v => setCollectionType(v as CollectionType)}
  166. options={collectionTypeOptions}
  167. />
  168. {isInToolsPage && (
  169. <Search
  170. className='mt-5'
  171. value={query}
  172. onChange={setQuery}
  173. />
  174. )}
  175. {(collectionType === CollectionType.custom && hasNoCustomCollection)
  176. ? (
  177. <div className='grow h-0 p-2 pt-8'>
  178. <NoCustomTool onCreateTool={handleCreateToolCollection} />
  179. </div>
  180. )
  181. : (
  182. (showCollectionList.length > 0 || !query)
  183. ? <ToolNavList
  184. className='mt-2 grow height-0 overflow-y-auto'
  185. currentIndex={currCollectionIndex || 0}
  186. list={showCollectionList}
  187. onChosen={setCurrCollectionIndex}
  188. />
  189. : (
  190. <div className='grow h-0 p-2 pt-8'>
  191. <NoSearchRes
  192. onReset={() => { setQuery('') }}
  193. />
  194. </div>
  195. )
  196. )}
  197. {loc === LOC.tools && (
  198. <Contribute />
  199. )}
  200. </div>
  201. {/* tools */}
  202. <div className={cn('grow h-full overflow-hidden p-2')}>
  203. <div className='h-full bg-white rounded-2xl'>
  204. {!(collectionType === CollectionType.custom && hasNoCustomCollection) && showCollectionList.length > 0 && (
  205. <ToolList
  206. collection={currCollection}
  207. list={currTools}
  208. loc={loc}
  209. addedTools={addedTools}
  210. onAddTool={onAddTool}
  211. onRefreshData={fetchCollectionList}
  212. onCollectionRemoved={() => {
  213. setCurrCollectionIndex(0)
  214. fetchCollectionList()
  215. }}
  216. isLoading={isDetailLoading}
  217. />
  218. )}
  219. {collectionType === CollectionType.custom && hasNoCustomCollection && (
  220. <NoCustomToolPlaceholder />
  221. )}
  222. </div>
  223. </div>
  224. </div>
  225. {isShowEditCollectionToolModal && (
  226. <EditCustomToolModal
  227. payload={null}
  228. onHide={() => setIsShowEditCollectionToolModal(false)}
  229. onAdd={doCreateCustomToolCollection}
  230. />
  231. )}
  232. </>
  233. )
  234. }
  235. export default React.memo(Tools)