configure-button.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. 'use client'
  2. import React, { useCallback, useEffect, useMemo, useState } from 'react'
  3. import { useTranslation } from 'react-i18next'
  4. import { useRouter } from 'next/navigation'
  5. import cn from '@/utils/classnames'
  6. import Button from '@/app/components/base/button'
  7. import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows'
  8. import { Tools } from '@/app/components/base/icons/src/vender/line/others'
  9. import Indicator from '@/app/components/header/indicator'
  10. import WorkflowToolModal from '@/app/components/tools/workflow-tool'
  11. import Loading from '@/app/components/base/loading'
  12. import Toast from '@/app/components/base/toast'
  13. import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools'
  14. import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types'
  15. import type { InputVar } from '@/app/components/workflow/types'
  16. import { useAppContext } from '@/context/app-context'
  17. import { useInvalidateAllWorkflowTools } from '@/service/use-tools'
  18. type Props = {
  19. disabled: boolean
  20. published: boolean
  21. detailNeedUpdate: boolean
  22. workflowAppId: string
  23. icon: Emoji
  24. name: string
  25. description: string
  26. inputs?: InputVar[]
  27. handlePublish: () => void
  28. onRefreshData?: () => void
  29. }
  30. const WorkflowToolConfigureButton = ({
  31. disabled,
  32. published,
  33. detailNeedUpdate,
  34. workflowAppId,
  35. icon,
  36. name,
  37. description,
  38. inputs,
  39. handlePublish,
  40. onRefreshData,
  41. }: Props) => {
  42. const { t } = useTranslation()
  43. const router = useRouter()
  44. const [showModal, setShowModal] = useState(false)
  45. const [isLoading, setIsLoading] = useState(false)
  46. const [detail, setDetail] = useState<WorkflowToolProviderResponse>()
  47. const { isCurrentWorkspaceManager } = useAppContext()
  48. const invalidateAllWorkflowTools = useInvalidateAllWorkflowTools()
  49. const outdated = useMemo(() => {
  50. if (!detail)
  51. return false
  52. if (detail.tool.parameters.length !== inputs?.length) {
  53. return true
  54. }
  55. else {
  56. for (const item of inputs || []) {
  57. const param = detail.tool.parameters.find(toolParam => toolParam.name === item.variable)
  58. if (!param) {
  59. return true
  60. }
  61. else if (param.required !== item.required) {
  62. return true
  63. }
  64. else {
  65. if (item.type === 'paragraph' && param.type !== 'string')
  66. return true
  67. if (item.type === 'text-input' && param.type !== 'string')
  68. return true
  69. }
  70. }
  71. }
  72. return false
  73. }, [detail, inputs])
  74. const payload = useMemo(() => {
  75. let parameters: WorkflowToolProviderParameter[] = []
  76. if (!published) {
  77. parameters = (inputs || []).map((item) => {
  78. return {
  79. name: item.variable,
  80. description: '',
  81. form: 'llm',
  82. required: item.required,
  83. type: item.type,
  84. }
  85. })
  86. }
  87. else if (detail && detail.tool) {
  88. parameters = (inputs || []).map((item) => {
  89. return {
  90. name: item.variable,
  91. required: item.required,
  92. type: item.type === 'paragraph' ? 'string' : item.type,
  93. description: detail.tool.parameters.find(param => param.name === item.variable)?.llm_description || '',
  94. form: detail.tool.parameters.find(param => param.name === item.variable)?.form || 'llm',
  95. }
  96. })
  97. }
  98. return {
  99. icon: detail?.icon || icon,
  100. label: detail?.label || name,
  101. name: detail?.name || '',
  102. description: detail?.description || description,
  103. parameters,
  104. labels: detail?.tool?.labels || [],
  105. privacy_policy: detail?.privacy_policy || '',
  106. ...(published
  107. ? {
  108. workflow_tool_id: detail?.workflow_tool_id,
  109. }
  110. : {
  111. workflow_app_id: workflowAppId,
  112. }),
  113. }
  114. }, [detail, published, workflowAppId, icon, name, description, inputs])
  115. const getDetail = useCallback(async (workflowAppId: string) => {
  116. setIsLoading(true)
  117. const res = await fetchWorkflowToolDetailByAppID(workflowAppId)
  118. setDetail(res)
  119. setIsLoading(false)
  120. }, [])
  121. useEffect(() => {
  122. if (published)
  123. getDetail(workflowAppId)
  124. }, [getDetail, published, workflowAppId])
  125. useEffect(() => {
  126. if (detailNeedUpdate)
  127. getDetail(workflowAppId)
  128. }, [detailNeedUpdate, getDetail, workflowAppId])
  129. const createHandle = async (data: WorkflowToolProviderRequest & { workflow_app_id: string }) => {
  130. try {
  131. await createWorkflowToolProvider(data)
  132. invalidateAllWorkflowTools()
  133. onRefreshData?.()
  134. getDetail(workflowAppId)
  135. Toast.notify({
  136. type: 'success',
  137. message: t('common.api.actionSuccess'),
  138. })
  139. setShowModal(false)
  140. }
  141. catch (e) {
  142. Toast.notify({ type: 'error', message: (e as Error).message })
  143. }
  144. }
  145. const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{
  146. workflow_app_id: string
  147. workflow_tool_id: string
  148. }>) => {
  149. try {
  150. await handlePublish()
  151. await saveWorkflowToolProvider(data)
  152. onRefreshData?.()
  153. invalidateAllWorkflowTools()
  154. getDetail(workflowAppId)
  155. Toast.notify({
  156. type: 'success',
  157. message: t('common.api.actionSuccess'),
  158. })
  159. setShowModal(false)
  160. }
  161. catch (e) {
  162. Toast.notify({ type: 'error', message: (e as Error).message })
  163. }
  164. }
  165. return (
  166. <>
  167. <div className='mt-2 pt-2 border-t-[0.5px] border-divider-regular'>
  168. {(!published || !isLoading) && (
  169. <div className={cn(
  170. 'group bg-background-section-burn rounded-lg transition-colors',
  171. disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'cursor-pointer',
  172. !disabled && !published && 'hover:bg-primary-50',
  173. )}>
  174. {isCurrentWorkspaceManager
  175. ? (
  176. <div
  177. className='flex justify-start items-center text-text-primary gap-2 px-2.5 py-2'
  178. onClick={() => !disabled && !published && setShowModal(true)}
  179. >
  180. <Tools className={cn('relative w-4 h-4', !disabled && !published && 'group-hover:text-primary-600')} />
  181. <div title={t('workflow.common.workflowAsTool') || ''} className={cn('grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate', !disabled && !published && 'group-hover:text-primary-600')}>{t('workflow.common.workflowAsTool')}</div>
  182. {!published && (
  183. <span className='shrink-0 px-1 border border-divider-regular rounded-[5px] bg-background-default-subtle text-[10px] font-medium leading-[18px] text-text-tertiary'>{t('workflow.common.configureRequired').toLocaleUpperCase()}</span>
  184. )}
  185. </div>)
  186. : (
  187. <div
  188. className='flex justify-start items-center gap-2 px-2.5 py-2'
  189. >
  190. <Tools className='w-4 h-4 text-text-tertiary' />
  191. <div title={t('workflow.common.workflowAsTool') || ''} className='grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate text-text-tertiary'>{t('workflow.common.workflowAsTool')}</div>
  192. </div>
  193. )}
  194. {published && (
  195. <div className='px-2.5 py-2 border-t-[0.5px] border-divider-regular'>
  196. <div className='flex justify-between'>
  197. <Button
  198. size='small'
  199. className='w-[140px]'
  200. onClick={() => setShowModal(true)}
  201. disabled={!isCurrentWorkspaceManager}
  202. >
  203. {t('workflow.common.configure')}
  204. {outdated && <Indicator className='ml-1' color={'yellow'} />}
  205. </Button>
  206. <Button
  207. size='small'
  208. className='w-[140px]'
  209. onClick={() => router.push('/tools?category=workflow')}
  210. >
  211. {t('workflow.common.manageInTools')}
  212. <ArrowUpRight className='ml-1' />
  213. </Button>
  214. </div>
  215. {outdated && <div className='mt-1 text-xs leading-[18px] text-[#dc6803]'>{t('workflow.common.workflowAsToolTip')}</div>}
  216. </div>
  217. )}
  218. </div>
  219. )}
  220. {published && isLoading && <div className='pt-2'><Loading type='app' /></div>}
  221. </div>
  222. {showModal && (
  223. <WorkflowToolModal
  224. isAdd={!published}
  225. payload={payload}
  226. onHide={() => setShowModal(false)}
  227. onCreate={createHandle}
  228. onSave={updateWorkflowToolProvider}
  229. />
  230. )}
  231. </>
  232. )
  233. }
  234. export default WorkflowToolConfigureButton