'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