index.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useMemo, useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import { useContext } from 'use-context-selector'
  6. import produce from 'immer'
  7. import cn from 'classnames'
  8. import { useMount } from 'ahooks'
  9. import type { Collection, CustomCollectionBackend, Tool } from '../types'
  10. import Type from './type'
  11. import Category from './category'
  12. import Tools from './tools'
  13. import I18n from '@/context/i18n'
  14. import { getLanguage } from '@/i18n/language'
  15. import Drawer from '@/app/components/base/drawer'
  16. import Button from '@/app/components/base/button'
  17. import Loading from '@/app/components/base/loading'
  18. import SearchInput from '@/app/components/base/search-input'
  19. import { Plus, XClose } from '@/app/components/base/icons/src/vender/line/general'
  20. import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
  21. import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
  22. import {
  23. createCustomCollection,
  24. fetchAllBuiltInTools,
  25. fetchAllCustomTools,
  26. fetchAllWorkflowTools,
  27. removeBuiltInToolCredential,
  28. updateBuiltInToolCredential,
  29. } from '@/service/tools'
  30. import type { ToolWithProvider } from '@/app/components/workflow/types'
  31. import Toast from '@/app/components/base/toast'
  32. import ConfigContext from '@/context/debug-configuration'
  33. import type { ModelConfig } from '@/models/debug'
  34. type Props = {
  35. onHide: () => void
  36. }
  37. // Add and Edit
  38. const AddToolModal: FC<Props> = ({
  39. onHide,
  40. }) => {
  41. const { t } = useTranslation()
  42. const { locale } = useContext(I18n)
  43. const language = getLanguage(locale)
  44. const [currentType, setCurrentType] = useState('builtin')
  45. const [currentCategory, setCurrentCategory] = useState('')
  46. const [keywords, setKeywords] = useState<string>('')
  47. const handleKeywordsChange = (value: string) => {
  48. setKeywords(value)
  49. }
  50. const [toolList, setToolList] = useState<ToolWithProvider[]>([])
  51. const [listLoading, setListLoading] = useState(true)
  52. const getAllTools = async () => {
  53. setListLoading(true)
  54. const buildInTools = await fetchAllBuiltInTools()
  55. const customTools = await fetchAllCustomTools()
  56. const workflowTools = await fetchAllWorkflowTools()
  57. const mergedToolList = [
  58. ...buildInTools,
  59. ...customTools,
  60. ...workflowTools.filter((toolWithProvider) => {
  61. return !toolWithProvider.tools.some((tool) => {
  62. return !!tool.parameters.find(item => item.name === '__image')
  63. })
  64. }),
  65. ]
  66. setToolList(mergedToolList)
  67. setListLoading(false)
  68. }
  69. const filteredList = useMemo(() => {
  70. return toolList.filter((toolWithProvider) => {
  71. if (currentType === 'all')
  72. return true
  73. else
  74. return toolWithProvider.type === currentType
  75. }).filter((toolWithProvider) => {
  76. if (!currentCategory)
  77. return true
  78. else
  79. return toolWithProvider.labels.includes(currentCategory)
  80. }).filter((toolWithProvider) => {
  81. return toolWithProvider.tools.some((tool) => {
  82. return tool.label[language].toLowerCase().includes(keywords.toLowerCase())
  83. })
  84. })
  85. }, [currentType, currentCategory, toolList, keywords, language])
  86. const {
  87. modelConfig,
  88. setModelConfig,
  89. } = useContext(ConfigContext)
  90. const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false)
  91. const doCreateCustomToolCollection = async (data: CustomCollectionBackend) => {
  92. await createCustomCollection(data)
  93. Toast.notify({
  94. type: 'success',
  95. message: t('common.api.actionSuccess'),
  96. })
  97. setIsShowEditCustomCollectionModal(false)
  98. getAllTools()
  99. }
  100. const [showSettingAuth, setShowSettingAuth] = useState(false)
  101. const [collection, setCollection] = useState<Collection>()
  102. const toolSelectHandle = (collection: Collection, tool: Tool) => {
  103. const parameters: Record<string, string> = {}
  104. if (tool.parameters) {
  105. tool.parameters.forEach((item) => {
  106. parameters[item.name] = ''
  107. })
  108. }
  109. const nexModelConfig = produce(modelConfig, (draft: ModelConfig) => {
  110. draft.agentConfig.tools.push({
  111. provider_id: collection.id || collection.name,
  112. provider_type: collection.type,
  113. provider_name: collection.name,
  114. tool_name: tool.name,
  115. tool_label: tool.label[locale] || tool.label[locale.replaceAll('-', '_')],
  116. tool_parameters: parameters,
  117. enabled: true,
  118. })
  119. })
  120. setModelConfig(nexModelConfig)
  121. }
  122. const authSelectHandle = (provider: Collection) => {
  123. setCollection(provider)
  124. setShowSettingAuth(true)
  125. }
  126. const updateBuiltinAuth = async (value: Record<string, any>) => {
  127. if (!collection)
  128. return
  129. await updateBuiltInToolCredential(collection.name, value)
  130. Toast.notify({
  131. type: 'success',
  132. message: t('common.api.actionSuccess'),
  133. })
  134. await getAllTools()
  135. setShowSettingAuth(false)
  136. }
  137. const removeBuiltinAuth = async () => {
  138. if (!collection)
  139. return
  140. await removeBuiltInToolCredential(collection.name)
  141. Toast.notify({
  142. type: 'success',
  143. message: t('common.api.actionSuccess'),
  144. })
  145. await getAllTools()
  146. setShowSettingAuth(false)
  147. }
  148. useMount(() => {
  149. getAllTools()
  150. })
  151. return (
  152. <>
  153. <Drawer
  154. isOpen
  155. mask
  156. clickOutsideNotOpen
  157. onClose={onHide}
  158. footer={null}
  159. panelClassname={cn('mt-16 mx-2 sm:mr-2 mb-3 !p-0 rounded-xl', 'mt-2 !w-[640px]', '!max-w-[640px]')}
  160. >
  161. <div
  162. className='w-full flex bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl'
  163. style={{
  164. height: 'calc(100vh - 16px)',
  165. }}
  166. >
  167. <div className='relative shrink-0 w-[200px] pb-3 bg-gray-100 rounded-l-xl border-r-[0.5px] border-black/2 overflow-y-auto'>
  168. <div className='sticky top-0 left-0 right-0'>
  169. <div className='sticky top-0 left-0 right-0 px-5 py-3 text-md font-semibold text-gray-900'>{t('tools.addTool')}</div>
  170. <div className='px-3 pt-2 pb-4'>
  171. <Button variant='primary' className='w-[176px] text-[13px] leading-[18px] font-medium' onClick={() => setIsShowEditCustomCollectionModal(true)}>
  172. <Plus className='w-4 h-4 mr-1' />
  173. {t('tools.createCustomTool')}
  174. </Button>
  175. </div>
  176. </div>
  177. <div className='px-2 py-1'>
  178. <Type value={currentType} onSelect={setCurrentType} />
  179. <Category value={currentCategory} onSelect={setCurrentCategory} />
  180. </div>
  181. </div>
  182. <div className='relative grow bg-white rounded-r-xl overflow-y-auto'>
  183. <div className='z-10 sticky top-0 left-0 right-0 p-2 flex items-center gap-1 bg-white'>
  184. <div className='grow'>
  185. <SearchInput className='w-full' value={keywords} onChange={handleKeywordsChange} />
  186. </div>
  187. <div className='ml-2 mr-1 w-[1px] h-4 bg-gray-200'></div>
  188. <div className='p-2 cursor-pointer' onClick={onHide}>
  189. <XClose className='w-4 h-4 text-gray-500' />
  190. </div>
  191. </div>
  192. {listLoading && (
  193. <div className='flex h-[200px] items-center justify-center bg-white'>
  194. <Loading />
  195. </div>
  196. )}
  197. {!listLoading && (
  198. <Tools
  199. showWorkflowEmpty={currentType === 'workflow'}
  200. tools={filteredList}
  201. addedTools={(modelConfig?.agentConfig?.tools as any) || []}
  202. onSelect={toolSelectHandle}
  203. onAuthSetup={authSelectHandle}
  204. />
  205. )}
  206. </div>
  207. </div>
  208. </Drawer>
  209. {isShowEditCollectionToolModal && (
  210. <EditCustomToolModal
  211. positionLeft
  212. payload={null}
  213. onHide={() => setIsShowEditCustomCollectionModal(false)}
  214. onAdd={doCreateCustomToolCollection}
  215. />
  216. )}
  217. {showSettingAuth && collection && (
  218. <ConfigCredential
  219. collection={collection}
  220. onCancel={() => setShowSettingAuth(false)}
  221. onSaved={updateBuiltinAuth}
  222. onRemove={removeBuiltinAuth}
  223. />
  224. )}
  225. </>
  226. )
  227. }
  228. export default React.memo(AddToolModal)