tool-call.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. 'use client'
  2. import type { FC } from 'react'
  3. import { useState } from 'react'
  4. import {
  5. RiCheckboxCircleLine,
  6. RiErrorWarningLine,
  7. } from '@remixicon/react'
  8. import { useContext } from 'use-context-selector'
  9. import cn from '@/utils/classnames'
  10. import BlockIcon from '@/app/components/workflow/block-icon'
  11. import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
  12. import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
  13. import { ChevronRight } from '@/app/components/base/icons/src/vender/line/arrows'
  14. import type { ToolCall } from '@/models/log'
  15. import { BlockEnum } from '@/app/components/workflow/types'
  16. import I18n from '@/context/i18n'
  17. type Props = {
  18. toolCall: ToolCall
  19. isLLM: boolean
  20. isFinal?: boolean
  21. tokens?: number
  22. observation?: any
  23. finalAnswer?: any
  24. }
  25. const ToolCallItem: FC<Props> = ({ toolCall, isLLM = false, isFinal, tokens, observation, finalAnswer }) => {
  26. const [collapseState, setCollapseState] = useState<boolean>(true)
  27. const { locale } = useContext(I18n)
  28. const toolName = isLLM ? 'LLM' : (toolCall.tool_label[locale] || toolCall.tool_label[locale.replaceAll('-', '_')])
  29. const getTime = (time: number) => {
  30. if (time < 1)
  31. return `${(time * 1000).toFixed(3)} ms`
  32. if (time > 60)
  33. return `${parseInt(Math.round(time / 60).toString())} m ${(time % 60).toFixed(3)} s`
  34. return `${time.toFixed(3)} s`
  35. }
  36. const getTokenCount = (tokens: number) => {
  37. if (tokens < 1000)
  38. return tokens
  39. if (tokens >= 1000 && tokens < 1000000)
  40. return `${parseFloat((tokens / 1000).toFixed(3))}K`
  41. if (tokens >= 1000000)
  42. return `${parseFloat((tokens / 1000000).toFixed(3))}M`
  43. }
  44. return (
  45. <div className={cn('py-1')}>
  46. <div className={cn('group transition-all bg-white border border-gray-100 rounded-2xl shadow-xs hover:shadow-md')}>
  47. <div
  48. className={cn(
  49. 'flex items-center py-3 pl-[6px] pr-3 cursor-pointer',
  50. !collapseState && '!pb-2',
  51. )}
  52. onClick={() => setCollapseState(!collapseState)}
  53. >
  54. <ChevronRight
  55. className={cn(
  56. 'shrink-0 w-3 h-3 mr-1 text-gray-400 transition-all group-hover:text-gray-500',
  57. !collapseState && 'rotate-90',
  58. )}
  59. />
  60. <BlockIcon className={cn('shrink-0 mr-2')} type={isLLM ? BlockEnum.LLM : BlockEnum.Tool} toolIcon={toolCall.tool_icon} />
  61. <div className={cn(
  62. 'grow text-gray-700 text-[13px] leading-[16px] font-semibold truncate',
  63. )} title={toolName}>{toolName}</div>
  64. <div className='shrink-0 text-gray-500 text-xs leading-[18px]'>
  65. {toolCall.time_cost && (
  66. <span>{getTime(toolCall.time_cost || 0)}</span>
  67. )}
  68. {isLLM && (
  69. <span>{`${getTokenCount(tokens || 0)} tokens`}</span>
  70. )}
  71. </div>
  72. {toolCall.status === 'success' && (
  73. <RiCheckboxCircleLine className='shrink-0 ml-2 w-3.5 h-3.5 text-[#12B76A]' />
  74. )}
  75. {toolCall.status === 'error' && (
  76. <RiErrorWarningLine className='shrink-0 ml-2 w-3.5 h-3.5 text-[#F04438]' />
  77. )}
  78. </div>
  79. {!collapseState && (
  80. <div className='pb-2'>
  81. <div className={cn('px-[10px] py-1')}>
  82. {toolCall.status === 'error' && (
  83. <div className='px-3 py-[10px] bg-[#fef3f2] rounded-lg border-[0.5px] border-[rbga(0,0,0,0.05)] text-xs leading-[18px] text-[#d92d20] shadow-xs'>{toolCall.error}</div>
  84. )}
  85. </div>
  86. {toolCall.tool_input && (
  87. <div className={cn('px-[10px] py-1')}>
  88. <CodeEditor
  89. readOnly
  90. title={<div>INPUT</div>}
  91. language={CodeLanguage.json}
  92. value={toolCall.tool_input}
  93. isJSONStringifyBeauty
  94. />
  95. </div>
  96. )}
  97. {toolCall.tool_output && (
  98. <div className={cn('px-[10px] py-1')}>
  99. <CodeEditor
  100. readOnly
  101. title={<div>OUTPUT</div>}
  102. language={CodeLanguage.json}
  103. value={toolCall.tool_output}
  104. isJSONStringifyBeauty
  105. />
  106. </div>
  107. )}
  108. {isLLM && (
  109. <div className={cn('px-[10px] py-1')}>
  110. <CodeEditor
  111. readOnly
  112. title={<div>OBSERVATION</div>}
  113. language={CodeLanguage.json}
  114. value={observation}
  115. isJSONStringifyBeauty
  116. />
  117. </div>
  118. )}
  119. {isLLM && (
  120. <div className={cn('px-[10px] py-1')}>
  121. <CodeEditor
  122. readOnly
  123. title={<div>{isFinal ? 'FINAL ANSWER' : 'THOUGHT'}</div>}
  124. language={CodeLanguage.json}
  125. value={finalAnswer}
  126. isJSONStringifyBeauty
  127. />
  128. </div>
  129. )}
  130. </div>
  131. )}
  132. </div>
  133. </div>
  134. )
  135. }
  136. export default ToolCallItem