wwf
3 天以前 a430284aa21e3ae1f0d5654e55b2ad2852519cc2
app/components/app/configuration/config/agent/agent-tools/index.tsx
@@ -1,24 +1,21 @@
'use client'
import type { FC } from 'react'
import React, { useMemo, useState } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import copy from 'copy-to-clipboard'
import produce from 'immer'
import {
  RiDeleteBinLine,
  RiEqualizer2Line,
  RiInformation2Line,
  RiHammerFill,
} from '@remixicon/react'
import { useFormattingChangedDispatcher } from '../../../debug/hooks'
import SettingBuiltInTool from './setting-built-in-tool'
import cn from '@/utils/classnames'
import Panel from '@/app/components/app/configuration/base/feature-panel'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
import OperationBtn from '@/app/components/app/configuration/base/operation-btn'
import AppIcon from '@/app/components/base/app-icon'
import Button from '@/app/components/base/button'
import Indicator from '@/app/components/header/indicator'
import Switch from '@/app/components/base/switch'
import Toast from '@/app/components/base/toast'
import ConfigContext from '@/context/debug-configuration'
import type { AgentTool } from '@/types/app'
import { type Collection, CollectionType } from '@/app/components/tools/types'
@@ -26,12 +23,7 @@
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import Tooltip from '@/app/components/base/tooltip'
import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other'
import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials'
import { updateBuiltInToolCredential } from '@/service/tools'
import cn from '@/utils/classnames'
import ToolPicker from '@/app/components/workflow/block-selector/tool-picker'
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
import { canFindTool } from '@/utils'
import AddToolModal from '@/app/components/tools/add-tool-modal'
type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null
const AgentTools: FC = () => {
@@ -41,19 +33,9 @@
  const formattingChangedDispatcher = useFormattingChangedDispatcher()
  const [currentTool, setCurrentTool] = useState<AgentToolWithMoreInfo>(null)
  const currentCollection = useMemo(() => {
    if (!currentTool) return null
    const collection = collectionList.find(collection => canFindTool(collection.id, currentTool?.provider_id) && collection.type === currentTool?.provider_type)
    return collection
  }, [currentTool, collectionList])
  const [isShowSettingTool, setIsShowSettingTool] = useState(false)
  const [isShowSettingAuth, setShowSettingAuth] = useState(false)
  const tools = (modelConfig?.agentConfig?.tools as AgentTool[] || []).map((item) => {
    const collection = collectionList.find(
      collection =>
        canFindTool(collection.id, item.provider_id)
        && collection.type === item.provider_type,
    )
    const collection = collectionList.find(collection => collection.id === item.provider_id && collection.type === item.provider_type)
    const icon = collection?.icon
    return {
      ...item,
@@ -73,40 +55,14 @@
    formattingChangedDispatcher()
  }
  const handleToolAuthSetting = (value: AgentToolWithMoreInfo) => {
    const newModelConfig = produce(modelConfig, (draft) => {
      const tool = (draft.agentConfig.tools).find((item: any) => item.provider_id === value?.collection?.id && item.tool_name === value?.tool_name)
      if (tool)
        (tool as AgentTool).notAuthor = false
    })
    setModelConfig(newModelConfig)
    setIsShowSettingTool(false)
    formattingChangedDispatcher()
  }
  const [isDeleting, setIsDeleting] = useState<number>(-1)
  const handleSelectTool = (tool: ToolDefaultValue) => {
    const newModelConfig = produce(modelConfig, (draft) => {
      draft.agentConfig.tools.push({
        provider_id: tool.provider_id,
        provider_type: tool.provider_type as CollectionType,
        provider_name: tool.provider_name,
        tool_name: tool.tool_name,
        tool_label: tool.tool_label,
        tool_parameters: tool.params,
        notAuthor: !tool.is_team_authorization,
        enabled: true,
      })
    })
    setModelConfig(newModelConfig)
  }
  return (
    <>
      <Panel
        className={cn('mt-2', tools.length === 0 && 'pb-2')}
        className="mt-2"
        noBodySpacing={tools.length === 0}
        headerIcon={
          <RiHammerFill className='w-4 h-4 text-primary-500' />
        }
        title={
          <div className='flex items-center'>
            <div className='mr-1'>{t('appDebug.agent.tools.name')}</div>
@@ -121,181 +77,142 @@
        }
        headerRight={
          <div className='flex items-center'>
            <div className='text-xs font-normal leading-[18px] text-text-tertiary'>{tools.filter(item => !!item.enabled).length}/{tools.length}&nbsp;{t('appDebug.agent.tools.enabled')}</div>
            <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>
            {tools.length < MAX_TOOLS_NUM && (
              <>
                <div className='ml-3 mr-1 h-3.5 w-px bg-divider-regular'></div>
                <ToolPicker
                  trigger={<OperationBtn type="add" />}
                  isShow={isShowChooseTool}
                  onShowChange={setIsShowChooseTool}
                  disabled={false}
                  supportAddCustomTool
                  onSelect={handleSelectTool}
                  selectedTools={tools}
                />
                <div className='ml-3 mr-1 h-3.5 w-px bg-gray-200'></div>
                <OperationBtn type="add" onClick={() => setIsShowChooseTool(true)} />
              </>
            )}
          </div>
        }
      >
        <div className='grid grid-cols-1 flex-wrap items-center justify-between gap-1 2xl:grid-cols-2'>
        <div className='grid gap-1 grid-cols-1 2xl:grid-cols-2 items-center flex-wrap justify-between'>
          {tools.map((item: AgentTool & { icon: any; collection?: Collection }, index) => (
            <div key={index}
              className={cn(
                'cursor group relative flex w-full items-center justify-between rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg p-1.5 pr-2 shadow-xs last-of-type:mb-0 hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-sm',
                isDeleting === index && 'border-state-destructive-border hover:bg-state-destructive-hover',
              )}
              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 ')}
            >
              <div className='flex w-0 grow items-center'>
                {item.isDeleted && <DefaultToolIcon className='h-5 w-5' />}
                {!item.isDeleted && (
                  <div className={cn((item.notAuthor || !item.enabled) && 'opacity-50')}>
                    {typeof item.icon === 'string' && <div className='h-5 w-5 rounded-md bg-cover bg-center' style={{ backgroundImage: `url(${item.icon})` }} />}
                    {typeof item.icon !== 'string' && <AppIcon className='rounded-md' size='xs' icon={item.icon?.content} background={item.icon?.background} />}
                  </div>
                )}
              <div className='grow w-0 flex items-center'>
                {(item.isDeleted || item.notAuthor)
                  ? (
                    <DefaultToolIcon className='w-6 h-6' />
                  )
                  : (
                    typeof item.icon === 'string'
                      ? (
                        <div
                          className='w-6 h-6 bg-cover bg-center rounded-md'
                          style={{
                            backgroundImage: `url(${item.icon})`,
                          }}
                        ></div>
                      )
                      : (
                        <AppIcon
                          className='rounded-md'
                          size='tiny'
                          icon={item.icon?.content}
                          background={item.icon?.background}
                        />
                      ))}
                <div
                  className={cn(
                    'system-xs-regular ml-1.5 flex w-0 grow items-center truncate',
                    (item.isDeleted || item.notAuthor || !item.enabled) ? 'opacity-50' : '',
                  )}
                  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')}
                >
                  <span className='system-xs-medium pr-1.5 text-text-secondary'>{item.provider_type === CollectionType.builtIn ? item.provider_name.split('/').pop() : item.tool_label}</span>
                  <span className='text-text-tertiary'>{item.tool_label}</span>
                  {!item.isDeleted && (
                    <Tooltip
                      needsDelay
                      popupContent={
                        <div className='w-[180px]'>
                          <div className='mb-1.5 text-text-secondary'>{item.tool_name}</div>
                          <div className='mb-1.5 text-text-tertiary'>{t('tools.toolNameUsageTip')}</div>
                          <div className='cursor-pointer text-text-accent' onClick={() => copy(item.tool_name)}>{t('tools.copyToolName')}</div>
                        </div>
                      }
                    >
                      <div className='h-4 w-4'>
                        <div className='ml-0.5 hidden group-hover:inline-block'>
                          <RiInformation2Line className='h-4 w-4 text-text-tertiary' />
                        </div>
                      </div>
                    </Tooltip>
                  )}
                  <span className='text-gray-800 pr-2'>{item.provider_type === CollectionType.builtIn ? item.provider_name : item.tool_label}</span>
                  <Tooltip
                    popupContent={t('tools.toolNameUsageTip')}
                  >
                    <span className='text-gray-500'>{item.tool_name}</span>
                  </Tooltip>
                </div>
              </div>
              <div className='ml-1 flex shrink-0 items-center'>
                {item.isDeleted && (
                  <div className='mr-2 flex items-center'>
                    <Tooltip
                      popupContent={t('tools.toolRemoved')}
                      needsDelay
                    >
                      <div className='mr-1 cursor-pointer rounded-md p-1 hover:bg-black/5'>
                        <AlertTriangle className='h-4 w-4 text-[#F79009]' />
                      </div>
                    </Tooltip>
                    <div
                      className='cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive'
                      onClick={() => {
              <div className='shrink-0 ml-1 flex items-center'>
                {(item.isDeleted || item.notAuthor)
                  ? (
                    <div className='flex items-center'>
                      <Tooltip
                        popupContent={t(`tools.${item.isDeleted ? 'toolRemoved' : 'notAuthorized'}`)}
                        needsDelay
                      >
                        <div className='mr-1 p-1 rounded-md hover:bg-black/5  cursor-pointer' onClick={() => {
                          if (item.notAuthor)
                            setIsShowChooseTool(true)
                        }}>
                          <AlertTriangle className='w-4 h-4 text-[#F79009]' />
                        </div>
                      </Tooltip>
                      <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
                        const newModelConfig = produce(modelConfig, (draft) => {
                          draft.agentConfig.tools.splice(index, 1)
                        })
                        setModelConfig(newModelConfig)
                        formattingChangedDispatcher()
                      }}
                      onMouseOver={() => setIsDeleting(index)}
                      onMouseLeave={() => setIsDeleting(-1)}
                    >
                      <RiDeleteBinLine className='h-4 w-4' />
                      }}>
                        <RiDeleteBinLine className='w-4 h-4 text-gray-500' />
                      </div>
                      <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
                    </div>
                  </div>
                )}
                {!item.isDeleted && (
                  <div className='mr-2 hidden items-center gap-1 group-hover:flex'>
                    {!item.notAuthor && (
                  )
                  : (
                    <div className='hidden group-hover:flex items-center'>
                      <Tooltip
                        popupContent={t('tools.setBuiltInTools.infoAndSetting')}
                        needsDelay
                      >
                        <div className='cursor-pointer rounded-md p-1  hover:bg-black/5' onClick={() => {
                        <div className='p-1 rounded-md hover:bg-black/5  cursor-pointer' onClick={() => {
                          setCurrentTool(item)
                          setIsShowSettingTool(true)
                        }}>
                          <RiEqualizer2Line className='h-4 w-4 text-text-tertiary' />
                          <InfoCircle className='w-4 h-4 text-gray-500' />
                        </div>
                      </Tooltip>
                    )}
                    <div
                      className='cursor-pointer rounded-md p-1 text-text-tertiary hover:text-text-destructive'
                      onClick={() => {
                      <div className='p-1 rounded-md hover:bg-black/5 cursor-pointer' onClick={() => {
                        const newModelConfig = produce(modelConfig, (draft) => {
                          draft.agentConfig.tools.splice(index, 1)
                        })
                        setModelConfig(newModelConfig)
                        formattingChangedDispatcher()
                      }}
                      onMouseOver={() => setIsDeleting(index)}
                      onMouseLeave={() => setIsDeleting(-1)}
                    >
                      <RiDeleteBinLine className='h-4 w-4' />
                      }}>
                        <RiDeleteBinLine className='w-4 h-4 text-gray-500' />
                      </div>
                      <div className='ml-2 mr-3 w-px h-3.5 bg-gray-200'></div>
                    </div>
                  </div>
                )}
                <div className={cn(item.isDeleted && 'opacity-50')}>
                  {!item.notAuthor && (
                    <Switch
                      defaultValue={item.isDeleted ? false : item.enabled}
                      disabled={item.isDeleted}
                      size='md'
                      onChange={(enabled) => {
                        const newModelConfig = produce(modelConfig, (draft) => {
                          (draft.agentConfig.tools[index] as any).enabled = enabled
                        })
                        setModelConfig(newModelConfig)
                        formattingChangedDispatcher()
                      }} />
                  )}
                  {item.notAuthor && (
                    <Button variant='secondary' size='small' onClick={() => {
                      setCurrentTool(item)
                      setShowSettingAuth(true)
                    }}>
                      {t('tools.notAuthorized')}
                      <Indicator className='ml-2' color='orange' />
                    </Button>
                  )}
                <div className={cn((item.isDeleted || item.notAuthor) && 'opacity-50')}>
                  <Switch
                    defaultValue={(item.isDeleted || item.notAuthor) ? false : item.enabled}
                    disabled={(item.isDeleted || item.notAuthor)}
                    size='md'
                    onChange={(enabled) => {
                      const newModelConfig = produce(modelConfig, (draft) => {
                        (draft.agentConfig.tools[index] as any).enabled = enabled
                      })
                      setModelConfig(newModelConfig)
                      formattingChangedDispatcher()
                    }} />
                </div>
              </div>
            </div>
          ))}
        </div >
      </Panel >
      {isShowSettingTool && (
        <SettingBuiltInTool
          toolName={currentTool?.tool_name as string}
          setting={currentTool?.tool_parameters}
          collection={currentTool?.collection as Collection}
          isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn}
          isModel={currentTool?.collection?.type === CollectionType.model}
          onSave={handleToolSettingChange}
          onHide={() => setIsShowSettingTool(false)}
        />
      {isShowChooseTool && (
        <AddToolModal onHide={() => setIsShowChooseTool(false)} />
      )}
      {isShowSettingAuth && (
        <ConfigCredential
          collection={currentCollection as any}
          onCancel={() => setShowSettingAuth(false)}
          onSaved={async (value) => {
            await updateBuiltInToolCredential((currentCollection as any).name, value)
            Toast.notify({
              type: 'success',
              message: t('common.api.actionSuccess'),
            })
            handleToolAuthSetting(currentTool)
            setShowSettingAuth(false)
          }}
        />
      )}
      {
        isShowSettingTool && (
          <SettingBuiltInTool
            toolName={currentTool?.tool_name as string}
            setting={currentTool?.tool_parameters as any}
            collection={currentTool?.collection as Collection}
            isBuiltIn={currentTool?.collection?.type === CollectionType.builtIn}
            isModel={currentTool?.collection?.type === CollectionType.model}
            onSave={handleToolSettingChange}
            onHide={() => setIsShowSettingTool(false)}
          />)
      }
    </>
  )
}