configure-button.tsx 8.8 KB

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