tool.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useState } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import {
  6. RiArrowDownSLine,
  7. RiLoader2Line,
  8. } from '@remixicon/react'
  9. import type { ToolInfoInThought } from '../type'
  10. import Panel from './panel'
  11. import cn from '@/utils/classnames'
  12. import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
  13. import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
  14. import type { Emoji } from '@/app/components/tools/types'
  15. import AppIcon from '@/app/components/base/app-icon'
  16. type Props = {
  17. payload: ToolInfoInThought
  18. allToolIcons?: Record<string, string | Emoji>
  19. }
  20. const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
  21. if (toolName.startsWith('dataset_'))
  22. return <DataSetIcon className='shrink-0'></DataSetIcon>
  23. const icon = allToolIcons[toolName]
  24. if (!icon)
  25. return null
  26. return (
  27. typeof icon === 'string'
  28. ? (
  29. <div
  30. className='w-3 h-3 bg-cover bg-center rounded-[3px] shrink-0'
  31. style={{
  32. backgroundImage: `url(${icon})`,
  33. }}
  34. ></div>
  35. )
  36. : (
  37. <AppIcon
  38. className='rounded-[3px] shrink-0'
  39. size='xs'
  40. icon={icon?.content}
  41. background={icon?.background}
  42. />
  43. ))
  44. }
  45. const Tool: FC<Props> = ({
  46. payload,
  47. allToolIcons = {},
  48. }) => {
  49. const { t } = useTranslation()
  50. const { name, label, input, isFinished, output } = payload
  51. const toolName = name.startsWith('dataset_') ? t('dataset.knowledge') : name
  52. const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
  53. const [isShowDetail, setIsShowDetail] = useState(false)
  54. const icon = getIcon(name, allToolIcons) as any
  55. return (
  56. <div>
  57. <div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto bg-white rounded-md')}>
  58. <div
  59. className={cn('flex items-center h-7 px-2 cursor-pointer')}
  60. onClick={() => setIsShowDetail(!isShowDetail)}
  61. >
  62. {!isFinished && (
  63. <RiLoader2Line className='w-3 h-3 text-gray-500 animate-spin shrink-0' />
  64. )}
  65. {isFinished && !isShowDetail && (
  66. <CheckCircle className='w-3 h-3 text-[#12B76A] shrink-0' />
  67. )}
  68. {isFinished && isShowDetail && (
  69. icon
  70. )}
  71. <span className='mx-1 text-xs font-medium text-gray-500 shrink-0'>
  72. {t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
  73. </span>
  74. <span
  75. className='text-xs font-medium text-gray-700 truncate'
  76. title={toolLabel}
  77. >
  78. {toolLabel}
  79. </span>
  80. <RiArrowDownSLine
  81. className={cn(isShowDetail && 'rotate-180', 'ml-1 w-3 h-3 text-gray-500 select-none cursor-pointer shrink-0')}
  82. />
  83. </div>
  84. {isShowDetail && (
  85. <div className='border-t border-black/5 p-2 space-y-2 '>
  86. <Panel
  87. isRequest={true}
  88. toolName={toolName}
  89. content={input} />
  90. {output && (
  91. <Panel
  92. isRequest={false}
  93. toolName={toolName}
  94. content={output as string} />
  95. )}
  96. </div>
  97. )}
  98. </div>
  99. </div>
  100. )
  101. }
  102. export default React.memo(Tool)