| 'use client' |
| import React, { useCallback, useEffect, useState } from 'react' |
| import { useTranslation } from 'react-i18next' |
| import { useContext } from 'use-context-selector' |
| import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' |
| import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types' |
| import ToolItem from './tool-item' |
| import cn from '@/utils/classnames' |
| import I18n from '@/context/i18n' |
| import { getLanguage } from '@/i18n/language' |
| import Confirm from '@/app/components/base/confirm' |
| import AppIcon from '@/app/components/base/app-icon' |
| import Button from '@/app/components/base/button' |
| import Indicator from '@/app/components/header/indicator' |
| import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' |
| import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' |
| import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal' |
| import WorkflowToolModal from '@/app/components/tools/workflow-tool' |
| import Toast from '@/app/components/base/toast' |
| import { |
| deleteWorkflowTool, |
| fetchBuiltInToolList, |
| fetchCustomCollection, |
| fetchCustomToolList, |
| fetchModelToolList, |
| fetchWorkflowToolDetail, |
| removeBuiltInToolCredential, |
| removeCustomCollection, |
| saveWorkflowToolProvider, |
| updateBuiltInToolCredential, |
| updateCustomCollection, |
| } from '@/service/tools' |
| import { useModalContext } from '@/context/modal-context' |
| import { useProviderContext } from '@/context/provider-context' |
| import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' |
| import Loading from '@/app/components/base/loading' |
| import { useAppContext } from '@/context/app-context' |
|
|
| type Props = { |
| collection: Collection |
| onRefreshData: () => void |
| } |
|
|
| const ProviderDetail = ({ |
| collection, |
| onRefreshData, |
| }: Props) => { |
| const { t } = useTranslation() |
| const { locale } = useContext(I18n) |
| const language = getLanguage(locale) |
|
|
| const needAuth = collection.allow_delete || collection.type === CollectionType.model |
| const isAuthed = collection.is_team_authorization |
| const isBuiltIn = collection.type === CollectionType.builtIn |
| const isModel = collection.type === CollectionType.model |
| const { isCurrentWorkspaceManager } = useAppContext() |
|
|
| const [isDetailLoading, setIsDetailLoading] = useState(false) |
|
|
| |
| const [showSettingAuth, setShowSettingAuth] = useState(false) |
| const { setShowModelModal } = useModalContext() |
| const { modelProviders: providers } = useProviderContext() |
| const showSettingAuthModal = () => { |
| if (isModel) { |
| const provider = providers.find(item => item.provider === collection?.id) |
| if (provider) { |
| setShowModelModal({ |
| payload: { |
| currentProvider: provider, |
| currentConfigurationMethod: ConfigurationMethodEnum.predefinedModel, |
| currentCustomConfigurationModelFixedFields: undefined, |
| }, |
| onSaveCallback: () => { |
| onRefreshData() |
| }, |
| }) |
| } |
| } |
| else { |
| setShowSettingAuth(true) |
| } |
| } |
| |
| const [customCollection, setCustomCollection] = useState<CustomCollectionBackend | WorkflowToolProviderResponse | null>(null) |
| const [isShowEditCollectionToolModal, setIsShowEditCustomCollectionModal] = useState(false) |
| const [showConfirmDelete, setShowConfirmDelete] = useState(false) |
| const [deleteAction, setDeleteAction] = useState('') |
| const doUpdateCustomToolCollection = async (data: CustomCollectionBackend) => { |
| await updateCustomCollection(data) |
| onRefreshData() |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setIsShowEditCustomCollectionModal(false) |
| } |
| const doRemoveCustomToolCollection = async () => { |
| await removeCustomCollection(collection?.name as string) |
| onRefreshData() |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setIsShowEditCustomCollectionModal(false) |
| } |
| const getCustomProvider = useCallback(async () => { |
| setIsDetailLoading(true) |
| const res = await fetchCustomCollection(collection.name) |
| if (res.credentials.auth_type === AuthType.apiKey && !res.credentials.api_key_header_prefix) { |
| if (res.credentials.api_key_value) |
| res.credentials.api_key_header_prefix = AuthHeaderPrefix.custom |
| } |
| setCustomCollection({ |
| ...res, |
| labels: collection.labels, |
| provider: collection.name, |
| }) |
| setIsDetailLoading(false) |
| }, [collection.labels, collection.name]) |
| |
| const [isShowEditWorkflowToolModal, setIsShowEditWorkflowToolModal] = useState(false) |
| const getWorkflowToolProvider = useCallback(async () => { |
| setIsDetailLoading(true) |
| const res = await fetchWorkflowToolDetail(collection.id) |
| const payload = { |
| ...res, |
| parameters: res.tool?.parameters.map((item) => { |
| return { |
| name: item.name, |
| description: item.llm_description, |
| form: item.form, |
| required: item.required, |
| type: item.type, |
| } |
| }) || [], |
| labels: res.tool?.labels || [], |
| } |
| setCustomCollection(payload) |
| setIsDetailLoading(false) |
| }, [collection.id]) |
| const removeWorkflowToolProvider = async () => { |
| await deleteWorkflowTool(collection.id) |
| onRefreshData() |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setIsShowEditWorkflowToolModal(false) |
| } |
| const updateWorkflowToolProvider = async (data: WorkflowToolProviderRequest & Partial<{ |
| workflow_app_id: string |
| workflow_tool_id: string |
| }>) => { |
| await saveWorkflowToolProvider(data) |
| onRefreshData() |
| getWorkflowToolProvider() |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| setIsShowEditWorkflowToolModal(false) |
| } |
| const onClickCustomToolDelete = () => { |
| setDeleteAction('customTool') |
| setShowConfirmDelete(true) |
| } |
| const onClickWorkflowToolDelete = () => { |
| setDeleteAction('workflowTool') |
| setShowConfirmDelete(true) |
| } |
| const handleConfirmDelete = () => { |
| if (deleteAction === 'customTool') |
| doRemoveCustomToolCollection() |
|
|
| else if (deleteAction === 'workflowTool') |
| removeWorkflowToolProvider() |
|
|
| setShowConfirmDelete(false) |
| } |
|
|
| |
| const [toolList, setToolList] = useState<Tool[]>([]) |
| const getProviderToolList = useCallback(async () => { |
| setIsDetailLoading(true) |
| try { |
| if (collection.type === CollectionType.builtIn) { |
| const list = await fetchBuiltInToolList(collection.name) |
| setToolList(list) |
| } |
| else if (collection.type === CollectionType.model) { |
| const list = await fetchModelToolList(collection.name) |
| setToolList(list) |
| } |
| else if (collection.type === CollectionType.workflow) { |
| setToolList([]) |
| } |
| else { |
| const list = await fetchCustomToolList(collection.name) |
| setToolList(list) |
| } |
| } |
| catch (e) { } |
| setIsDetailLoading(false) |
| }, [collection.name, collection.type]) |
|
|
| useEffect(() => { |
| if (collection.type === CollectionType.custom) |
| getCustomProvider() |
| if (collection.type === CollectionType.workflow) |
| getWorkflowToolProvider() |
| getProviderToolList() |
| }, [collection.name, collection.type, getCustomProvider, getProviderToolList, getWorkflowToolProvider]) |
|
|
| return ( |
| <div className='px-6 py-3'> |
| <div className='flex items-center py-1 gap-2'> |
| <div className='relative shrink-0'> |
| {typeof collection.icon === 'string' && ( |
| <div className='w-8 h-8 bg-center bg-cover bg-no-repeat rounded-md' style={{ backgroundImage: `url(${collection.icon})` }} /> |
| )} |
| {typeof collection.icon !== 'string' && ( |
| <AppIcon |
| size='small' |
| icon={collection.icon.content} |
| background={collection.icon.background} |
| /> |
| )} |
| </div> |
| <div className='grow w-0 py-[1px]'> |
| <div className='flex items-center text-md leading-6 font-semibold text-gray-900'> |
| <div className='truncate' title={collection.label[language]}>{collection.label[language]}</div> |
| </div> |
| </div> |
| </div> |
| <div className='mt-2 min-h-[36px] text-gray-500 text-sm leading-[18px]'>{collection.description[language]}</div> |
| <div className='flex gap-1 border-b-[0.5px] border-black/5'> |
| {(collection.type === CollectionType.builtIn) && needAuth && ( |
| <Button |
| variant={isAuthed ? 'secondary' : 'primary'} |
| className={cn('shrink-0 my-3 w-full', isAuthed && 'bg-white')} |
| onClick={() => { |
| if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) |
| showSettingAuthModal() |
| }} |
| disabled={!isCurrentWorkspaceManager} |
| > |
| {isAuthed && <Indicator className='mr-2' color={'green'} />} |
| <div className={cn('text-white leading-[18px] text-[13px] font-medium', isAuthed && '!text-gray-700')}> |
| {isAuthed ? t('tools.auth.authorized') : t('tools.auth.unauthorized')} |
| </div> |
| </Button> |
| )} |
| {collection.type === CollectionType.custom && !isDetailLoading && ( |
| <Button |
| className={cn('shrink-0 my-3 w-full')} |
| onClick={() => setIsShowEditCustomCollectionModal(true)} |
| > |
| <Settings01 className='mr-1 w-4 h-4 text-gray-500' /> |
| <div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div> |
| </Button> |
| )} |
| {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( |
| <> |
| <Button |
| variant='primary' |
| className={cn('shrink-0 my-3 w-[183px]')} |
| > |
| <a className='flex items-center text-white' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'> |
| <div className='leading-5 text-sm font-medium'>{t('tools.openInStudio')}</div> |
| <LinkExternal02 className='ml-1 w-4 h-4' /> |
| </a> |
| </Button> |
| <Button |
| className={cn('shrink-0 my-3 w-[183px]')} |
| onClick={() => setIsShowEditWorkflowToolModal(true)} |
| disabled={!isCurrentWorkspaceManager} |
| > |
| <div className='leading-5 text-sm font-medium text-gray-700'>{t('tools.createTool.editAction')}</div> |
| </Button> |
| </> |
| )} |
| </div> |
| {} |
| <div className='pt-3'> |
| {isDetailLoading && <div className='flex h-[200px]'><Loading type='app' /></div>} |
| {!isDetailLoading && ( |
| <div className='text-xs font-medium leading-6 text-gray-500'> |
| {collection.type === CollectionType.workflow && <span className=''>{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span>} |
| {collection.type !== CollectionType.workflow && <span className=''>{t('tools.includeToolNum', { num: toolList.length }).toLocaleUpperCase()}</span>} |
| {needAuth && (isBuiltIn || isModel) && !isAuthed && ( |
| <> |
| <span className='px-1'>·</span> |
| <span className='text-[#DC6803]'>{t('tools.auth.setup').toLocaleUpperCase()}</span> |
| </> |
| )} |
| </div> |
| )} |
| {!isDetailLoading && ( |
| <div className='mt-1'> |
| {collection.type !== CollectionType.workflow && toolList.map(tool => ( |
| <ToolItem |
| key={tool.name} |
| disabled={needAuth && (isBuiltIn || isModel) && !isAuthed} |
| collection={collection} |
| tool={tool} |
| isBuiltIn={isBuiltIn} |
| isModel={isModel} |
| /> |
| ))} |
| {collection.type === CollectionType.workflow && (customCollection as WorkflowToolProviderResponse)?.tool?.parameters.map(item => ( |
| <div key={item.name} className='mb-2 px-4 py-3 rounded-xl bg-gray-25 border-[0.5px] border-gray-200'> |
| <div className='flex items-center gap-2'> |
| <span className='font-medium text-sm text-gray-900'>{item.name}</span> |
| <span className='text-xs leading-[18px] text-gray-500'>{item.type}</span> |
| <span className='font-medium text-xs leading-[18px] text-[#ec4a0a]'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span> |
| </div> |
| <div className='h-[18px] leading-[18px] text-gray-500 text-xs'>{item.llm_description}</div> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| {showSettingAuth && ( |
| <ConfigCredential |
| collection={collection} |
| onCancel={() => setShowSettingAuth(false)} |
| onSaved={async (value) => { |
| await updateBuiltInToolCredential(collection.name, value) |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| await onRefreshData() |
| setShowSettingAuth(false) |
| }} |
| onRemove={async () => { |
| await removeBuiltInToolCredential(collection.name) |
| Toast.notify({ |
| type: 'success', |
| message: t('common.api.actionSuccess'), |
| }) |
| await onRefreshData() |
| setShowSettingAuth(false) |
| }} |
| /> |
| )} |
| {isShowEditCollectionToolModal && ( |
| <EditCustomToolModal |
| payload={customCollection} |
| onHide={() => setIsShowEditCustomCollectionModal(false)} |
| onEdit={doUpdateCustomToolCollection} |
| onRemove={onClickCustomToolDelete} |
| /> |
| )} |
| {isShowEditWorkflowToolModal && ( |
| <WorkflowToolModal |
| payload={customCollection} |
| onHide={() => setIsShowEditWorkflowToolModal(false)} |
| onRemove={onClickWorkflowToolDelete} |
| onSave={updateWorkflowToolProvider} |
| /> |
| )} |
| {showConfirmDelete && ( |
| <Confirm |
| title={t('tools.createTool.deleteToolConfirmTitle')} |
| content={t('tools.createTool.deleteToolConfirmContent')} |
| isShow={showConfirmDelete} |
| onConfirm={handleConfirmDelete} |
| onCancel={() => setShowConfirmDelete(false)} |
| /> |
| )} |
| </div> |
| ) |
| } |
| export default ProviderDetail |
|
|