index.tsx 7.7 KB

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