| 'use client' |
| import React, { useCallback, useEffect, useMemo, useState } from 'react' |
| import { useTranslation } from 'react-i18next' |
| import { useRouter } from 'next/navigation' |
| import cn from '@/utils/classnames' |
| import Button from '@/app/components/base/button' |
| import { ArrowUpRight } from '@/app/components/base/icons/src/vender/line/arrows' |
| import { Tools } from '@/app/components/base/icons/src/vender/line/others' |
| import Indicator from '@/app/components/header/indicator' |
| import WorkflowToolModal from '@/app/components/tools/workflow-tool' |
| import Loading from '@/app/components/base/loading' |
| import Toast from '@/app/components/base/toast' |
| import { createWorkflowToolProvider, fetchWorkflowToolDetailByAppID, saveWorkflowToolProvider } from '@/service/tools' |
| import type { Emoji, WorkflowToolProviderParameter, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '@/app/components/tools/types' |
| import type { InputVar } from '@/app/components/workflow/types' |
| import { useAppContext } from '@/context/app-context' |
|
|
| type Props = { |
| disabled: boolean |
| published: boolean |
| detailNeedUpdate: boolean |
| workflowAppId: string |
| icon: Emoji |
| name: string |
| description: string |
| inputs?: InputVar[] |
| handlePublish: () => void |
| onRefreshData?: () => void |
| } |
|
|
| const WorkflowToolConfigureButton = ({ |
| disabled, |
| published, |
| detailNeedUpdate, |
| workflowAppId, |
| icon, |
| name, |
| description, |
| inputs, |
| handlePublish, |
| onRefreshData, |
| }: Props) => { |
| const { t } = useTranslation() |
| const router = useRouter() |
| const [showModal, setShowModal] = useState(false) |
| const [isLoading, setIsLoading] = useState(false) |
| const [detail, setDetail] = useState<WorkflowToolProviderResponse>() |
| const { isCurrentWorkspaceManager } = useAppContext() |
|
|
| const outdated = useMemo(() => { |
| if (!detail) |
| return false |
| if (detail.tool.parameters.length !== inputs?.length) { |
| return true |
| } |
| else { |
| for (const item of inputs || []) { |
| const param = detail.tool.parameters.find(toolParam => toolParam.name === item.variable) |
| if (!param) { |
| return true |
| } |
| else if (param.required !== item.required) { |
| return true |
| } |
| else { |
| if (item.type === 'paragraph' && param.type !== 'string') |
| return true |
| if (item.type === 'text-input' && param.type !== 'string') |
| return true |
| } |
| } |
| } |
| return false |
| }, [detail, inputs]) |
|
|
| const payload = useMemo(() => { |
| let parameters: WorkflowToolProviderParameter[] = [] |
| if (!published) { |
| parameters = (inputs || []).map((item) => { |
| return { |
| name: item.variable, |
| description: '', |
| form: 'llm', |
| required: item.required, |
| type: item.type, |
| } |
| }) |
| } |
| else if (detail && detail.tool) { |
| parameters = (inputs || []).map((item) => { |
| return { |
| name: item.variable, |
| required: item.required, |
| type: item.type === 'paragraph' ? 'string' : item.type, |
| description: detail.tool.parameters.find(param => param.name === item.variable)?.llm_description || '', |
| form: detail.tool.parameters.find(param => param.name === item.variable)?.form || 'llm', |
| } |
| }) |
| } |
| return { |
| icon: detail?.icon || icon, |
| label: detail?.label || name, |
| name: detail?.name || '', |
| description: detail?.description || description, |
| parameters, |
| labels: detail?.tool?.labels || [], |
| privacy_policy: detail?.privacy_policy || '', |
| ...(published |
| ? { |
| workflow_tool_id: detail?.workflow_tool_id, |
| } |
| : { |
| workflow_app_id: workflowAppId, |
| }), |
| } |
| }, [detail, published, workflowAppId, icon, name, description, inputs]) |
|
|
| const getDetail = useCallback(async (workflowAppId: string) => { |
| setIsLoading(true) |
| const res = await fetchWorkflowToolDetailByAppID(workflowAppId) |
| setDetail(res) |
| setIsLoading(false) |
| }, []) |
|
|
| useEffect(() => { |
| if (published) |
| getDetail(workflowAppId) |
| }, [getDetail, published, workflowAppId]) |
|
|
| useEffect(() => { |
| if (detailNeedUpdate) |
| getDetail(workflowAppId) |
| }, [detailNeedUpdate, getDetail, workflowAppId]) |
|
|
| const createHandle = async (data: WorkflowToolProviderRequest & { workflow_app_id: string }) => { |
| try { |
| await createWorkflowToolProvider(data) |
| onRefreshData?.() |
| getDetail(workflowAppId) |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setShowModal(false) |
| } |
| catch (e) { |
| Toast.notify({ type: 'error', message: (e as Error).message }) |
| } |
| } |
|
|
| const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{ |
| workflow_app_id: string |
| workflow_tool_id: string |
| }>) => { |
| try { |
| await handlePublish() |
| await saveWorkflowToolProvider(data) |
| onRefreshData?.() |
| getDetail(workflowAppId) |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setShowModal(false) |
| } |
| catch (e) { |
| Toast.notify({ type: 'error', message: (e as Error).message }) |
| } |
| } |
|
|
| return ( |
| <> |
| <div className='mt-2 pt-2 border-t-[0.5px] border-t-black/5'> |
| {(!published || !isLoading) && ( |
| <div className={cn( |
| 'group bg-gray-100 rounded-lg transition-colors', |
| disabled ? 'shadow-xs opacity-30 cursor-not-allowed' : 'cursor-pointer', |
| !published && 'hover:bg-primary-50', |
| )}> |
| {isCurrentWorkspaceManager |
| ? ( |
| <div |
| className='flex justify-start items-center gap-2 px-2.5 py-2' |
| onClick={() => !published && setShowModal(true)} |
| > |
| <Tools className={cn('relative w-4 h-4', !published && 'group-hover:text-primary-600')} /> |
| <div title={t('workflow.common.workflowAsTool') || ''} className={cn('grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate', !published && 'group-hover:text-primary-600')}>{t('workflow.common.workflowAsTool')}</div> |
| {!published && ( |
| <span className='shrink-0 px-1 border border-black/8 rounded-[5px] bg-white text-[10px] font-medium leading-[18px] text-gray-500'>{t('workflow.common.configureRequired').toLocaleUpperCase()}</span> |
| )} |
| </div>) |
| : ( |
| <div |
| className='flex justify-start items-center gap-2 px-2.5 py-2' |
| > |
| <Tools className='w-4 h-4 text-gray-500' /> |
| <div title={t('workflow.common.workflowAsTool') || ''} className='grow shrink basis-0 text-[13px] font-medium leading-[18px] truncate text-gray-500'>{t('workflow.common.workflowAsTool')}</div> |
| </div> |
| )} |
| {published && ( |
| <div className='px-2.5 py-2 border-t-[0.5px] border-black/5'> |
| <div className='flex justify-between'> |
| <Button |
| size='small' |
| className='w-[140px]' |
| onClick={() => setShowModal(true)} |
| disabled={!isCurrentWorkspaceManager} |
| > |
| {t('workflow.common.configure')} |
| {outdated && <Indicator className='ml-1' color={'yellow'} />} |
| </Button> |
| <Button |
| size='small' |
| className='w-[140px]' |
| onClick={() => router.push('/tools?category=workflow')} |
| > |
| {t('workflow.common.manageInTools')} |
| <ArrowUpRight className='ml-1' /> |
| </Button> |
| </div> |
| {outdated && <div className='mt-1 text-xs leading-[18px] text-[#dc6803]'>{t('workflow.common.workflowAsToolTip')}</div>} |
| </div> |
| )} |
| </div> |
| )} |
| {published && isLoading && <div className='pt-2'><Loading type='app' /></div>} |
| </div> |
| {showModal && ( |
| <WorkflowToolModal |
| isAdd={!published} |
| payload={payload} |
| onHide={() => setShowModal(false)} |
| onCreate={createHandle} |
| onSave={updateWorkflowToolProvider} |
| /> |
| )} |
| </> |
| ) |
| } |
| export default WorkflowToolConfigureButton |
|
|