index.tsx 8.3 KB

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