tool-icon.tsx 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import Tooltip from '@/app/components/base/tooltip'
  2. import Indicator from '@/app/components/header/indicator'
  3. import classNames from '@/utils/classnames'
  4. import { memo, useMemo, useRef, useState } from 'react'
  5. import { useAllBuiltInTools, useAllCustomTools, useAllWorkflowTools } from '@/service/use-tools'
  6. import { getIconFromMarketPlace } from '@/utils/get-icon'
  7. import { useTranslation } from 'react-i18next'
  8. import { Group } from '@/app/components/base/icons/src/vender/other'
  9. type Status = 'not-installed' | 'not-authorized' | undefined
  10. export type ToolIconProps = {
  11. providerName: string
  12. }
  13. export const ToolIcon = memo(({ providerName }: ToolIconProps) => {
  14. const containerRef = useRef<HTMLDivElement>(null)
  15. const { data: buildInTools } = useAllBuiltInTools()
  16. const { data: customTools } = useAllCustomTools()
  17. const { data: workflowTools } = useAllWorkflowTools()
  18. const isDataReady = !!buildInTools && !!customTools && !!workflowTools
  19. const currentProvider = useMemo(() => {
  20. const mergedTools = [...(buildInTools || []), ...(customTools || []), ...(workflowTools || [])]
  21. return mergedTools.find((toolWithProvider) => {
  22. return toolWithProvider.name === providerName
  23. })
  24. }, [buildInTools, customTools, providerName, workflowTools])
  25. const providerNameParts = providerName.split('/')
  26. const author = providerNameParts[0]
  27. const name = providerNameParts[1]
  28. const icon = useMemo(() => {
  29. if (currentProvider) return currentProvider.icon as string
  30. const iconFromMarketPlace = getIconFromMarketPlace(`${author}/${name}`)
  31. return iconFromMarketPlace
  32. }, [author, currentProvider, name])
  33. const status: Status = useMemo(() => {
  34. if (!isDataReady) return undefined
  35. if (!currentProvider) return 'not-installed'
  36. if (currentProvider.is_team_authorization === false) return 'not-authorized'
  37. return undefined
  38. }, [currentProvider, isDataReady])
  39. const indicator = status === 'not-installed' ? 'red' : status === 'not-authorized' ? 'yellow' : undefined
  40. const notSuccess = (['not-installed', 'not-authorized'] as Array<Status>).includes(status)
  41. const { t } = useTranslation()
  42. const tooltip = useMemo(() => {
  43. if (!notSuccess) return undefined
  44. if (status === 'not-installed') return t('workflow.nodes.agent.toolNotInstallTooltip', { tool: name })
  45. if (status === 'not-authorized') return t('workflow.nodes.agent.toolNotAuthorizedTooltip', { tool: name })
  46. throw new Error('Unknown status')
  47. }, [name, notSuccess, status, t])
  48. const [iconFetchError, setIconFetchError] = useState(false)
  49. return <Tooltip
  50. triggerMethod='hover'
  51. popupContent={tooltip}
  52. disabled={!notSuccess}
  53. >
  54. <div
  55. className={classNames(
  56. 'size-5 border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge relative flex items-center justify-center rounded-[6px]',
  57. )}
  58. ref={containerRef}
  59. >
  60. {!iconFetchError
  61. ? <img
  62. src={icon}
  63. alt='tool icon'
  64. className={classNames(
  65. 'w-full h-full size-3.5 object-cover',
  66. notSuccess && 'opacity-50',
  67. )}
  68. onError={() => setIconFetchError(true)}
  69. />
  70. : <Group className="h-3 w-3 opacity-35" />
  71. }
  72. {indicator && <Indicator color={indicator} className="absolute right-[-1px] top-[-1px]" />}
  73. </div>
  74. </Tooltip>
  75. })
  76. ToolIcon.displayName = 'ToolIcon'