index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import cn from 'classnames'
  6. import { useContext } from 'use-context-selector'
  7. import produce from 'immer'
  8. import { useFormattingChangedDispatcher } from '../../../debug/hooks'
  9. import ChooseTool from './choose-tool'
  10. import SettingBuiltInTool from './setting-built-in-tool'
  11. import Panel from '@/app/components/app/configuration/base/feature-panel'
  12. import Tooltip from '@/app/components/base/tooltip'
  13. import { HelpCircle, InfoCircle, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
  14. import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
  15. import { ToolsActive } from '@/app/components/base/icons/src/public/header-nav/tools'
  16. import AppIcon from '@/app/components/base/app-icon'
  17. import Switch from '@/app/components/base/switch'
  18. import ConfigContext from '@/context/debug-configuration'
  19. import type { AgentTool } from '@/types/app'
  20. import { type Collection, CollectionType } from '@/app/components/tools/types'
  21. import { MAX_TOOLS_NUM } from '@/config'
  22. import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
  23. import TooltipPlus from '@/app/components/base/tooltip-plus'
  24. import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
  25. type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
  26. const AgentTools: FC = () => {
  27. const { t } = useTranslation()
  28. const [isShowChooseTool, setIsShowChooseTool] = useState(false)
  29. const { modelConfig, setModelConfig, collectionList } = useContext(ConfigContext)
  30. const formattingChangedDispatcher = useFormattingChangedDispatcher()
  31. const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
  32. const [selectedProviderId, setSelectedProviderId] = useState<string | undefined>(undefined)
  33. const [isShowSettingTool, setIsShowSettingTool] = useState(false)
  34. const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
  35. const collection = collectionList.find(collection => collection.id === item.provider_id && collection.type === item.provider_type)
  36. const icon = collection?.icon
  37. return {
  38. ...item,
  39. icon,
  40. collection,
  41. }
  42. })
  43. const handleToolSettingChange = (value: Record<string, any>) => {
  44. const newModelConfig = produce(modelConfig, (draft) => {
  45. const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === currentTool?.collection?.id && item.tool_name === currentTool?.tool_name)
  46. if (tool)
  47. (tool as AgentTool).tool_parameters = value
  48. })
  49. setModelConfig(newModelConfig)
  50. setIsShowSettingTool(false)
  51. formattingChangedDispatcher()
  52. }
  53. return (
  54. <>
  55. <Panel
  56. className="mt-4"
  57. noBodySpacing={tools.length === 0}
  58. headerIcon={
  59. <ToolsActive className='w-4 h-4 text-primary-500' />
  60. }
  61. title={
  62. <div className='flex items-center'>
  63. <div className='mr-1'>{t('appDebug.agent.tools.name')}</div>
  64. <Tooltip htmlContent={<div className='w-[180px]'>
  65. {t('appDebug.agent.tools.description')}
  66. </div>} selector='config-tools-tooltip'>
  67. <HelpCircle className='w-[14px] h-[14px] text-gray-400' />
  68. </Tooltip>
  69. </div>
  70. }
  71. headerRight={
  72. <div className='flex items-center'>
  73. <div className='leading-[18px] text-xs font-normal text-gray-500'>{tools.filter((item: any) => !!item.enabled).length}/{tools.length}&nbsp;{t('appDebug.agent.tools.enabled')}</div>
  74. {tools.length < MAX_TOOLS_NUM && (
  75. <>
  76. <div className='ml-3 mr-1 h-3.5 w-px bg-gray-200'></div>
  77. <OperationBtn type="add" onClick={() => {
  78. setSelectedProviderId(undefined)
  79. setIsShowChooseTool(true)
  80. }} />
  81. </>
  82. )}
  83. </div>
  84. }
  85. >
  86. <div className='grid gap-1 grid-cols-1 2xl:grid-cols-2 items-center flex-wrap justify-between'>
  87. {tools.map((item: AgentTool & { icon: any; collection?: Collection }, index) => (
  88. <div key={index}
  89. className={cn((item.isDeleted || item.notAuthor) ? 'bg-white/50' : 'bg-white', (item.enabled && !item.isDeleted && !item.notAuthor) && 'shadow-xs', index > 1 && 'mt-1', 'group relative flex justify-between items-center last-of-type:mb-0 pl-2.5 py-2 pr-3 w-full rounded-lg border-[0.5px] border-gray-200 ')}
  90. >
  91. <div className='grow w-0 flex items-center'>
  92. {(item.isDeleted || item.notAuthor)
  93. ? (
  94. <DefaultToolIcon className='w-6 h-6' />
  95. )
  96. : (
  97. typeof item.icon === 'string'
  98. ? (
  99. <div
  100. className='w-6 h-6 bg-cover bg-center rounded-md'
  101. style={{
  102. backgroundImage: `url(${item.icon})`,
  103. }}
  104. ></div>
  105. )
  106. : (
  107. <AppIcon
  108. className='rounded-md'
  109. size='tiny'
  110. icon={item.icon?.content}
  111. background={item.icon?.background}
  112. />
  113. ))}
  114. <div
  115. title={item.tool_name}
  116. className={cn((item.isDeleted || item.notAuthor) ? 'line-through opacity-50' : '', 'grow w-0 ml-2 leading-[18px] text-[13px] font-medium text-gray-800 truncate')}
  117. >
  118. {item.tool_label || item.tool_name}
  119. </div>
  120. </div>
  121. <div className='shrink-0 ml-1 flex items-center'>
  122. {(item.isDeleted || item.notAuthor)
  123. ? (
  124. <div className='flex items-center'>
  125. <TooltipPlus
  126. popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)}
  127. >
  128. <div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
  129. if (item.notAuthor) {
  130. setSelectedProviderId(item.provider_id)
  131. setIsShowChooseTool(true)
  132. }
  133. }}>
  134. <AlertTriangle className='w-4 h-4 text-[#F79009]' />
  135. </div>
  136. </TooltipPlus>
  137. <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
  138. const newModelConfig = produce(modelConfig, (draft) => {
  139. draft.agentConfig.tools.splice(index, 1)
  140. })
  141. setModelConfig(newModelConfig)
  142. formattingChangedDispatcher()
  143. }}>
  144. <Trash03 className='w-4 h-4 text-gray-500' />
  145. </div>
  146. <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
  147. </div>
  148. )
  149. : (
  150. <div className='hidden group-hover:flex items-center'>
  151. {/* {item.provider_type === CollectionType.builtIn && ( */}
  152. <TooltipPlus
  153. popupContent={t('tools.setBuiltInTools.infoAndSetting')}
  154. >
  155. <div className='mr-1 p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
  156. setCurrentTool(item)
  157. setIsShowSettingTool(true)
  158. }}>
  159. <InfoCircle className='w-4 h-4 text-gray-500' />
  160. </div>
  161. </TooltipPlus>
  162. {/* )} */}
  163. <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
  164. const newModelConfig = produce(modelConfig, (draft) => {
  165. draft.agentConfig.tools.splice(index, 1)
  166. })
  167. setModelConfig(newModelConfig)
  168. formattingChangedDispatcher()
  169. }}>
  170. <Trash03 className='w-4 h-4 text-gray-500' />
  171. </div>
  172. <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
  173. </div>
  174. )}
  175. <div className={cn((item.isDeleted || item.notAuthor) && 'opacity-50')}>
  176. <Switch
  177. defaultValue={(item.isDeleted || item.notAuthor) ? false : item.enabled}
  178. disabled={(item.isDeleted || item.notAuthor)}
  179. size='md'
  180. onChange={(enabled) => {
  181. const newModelConfig = produce(modelConfig, (draft) => {
  182. (draft.agentConfig.tools[index] as any).enabled = enabled
  183. })
  184. setModelConfig(newModelConfig)
  185. formattingChangedDispatcher()
  186. }} />
  187. </div>
  188. </div>
  189. </div>
  190. ))}
  191. </div >
  192. </Panel >
  193. {isShowChooseTool && (
  194. <ChooseTool
  195. show
  196. onHide={() => setIsShowChooseTool(false)}
  197. selectedProviderId={selectedProviderId}
  198. />
  199. )}
  200. {
  201. isShowSettingTool && (
  202. <SettingBuiltInTool
  203. toolName={currentTool?.tool_name as string}
  204. setting={currentTool?.tool_parameters as any}
  205. collection={currentTool?.collection as Collection}
  206. isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn}
  207. isModel={currentTool?.collection?.type === CollectionType.model}
  208. onSave={handleToolSettingChange}
  209. onHide={() => setIsShowSettingTool(false)}
  210. />)
  211. }
  212. </>
  213. )
  214. }
  215. export default React.memo(AgentTools)