| '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 | |