| | |
| | | import React, { useCallback, useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import { |
| | | RiCloseLine, |
| | | } from '@remixicon/react' |
| | | import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' |
| | | import Link from 'next/link' |
| | | 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 Icon from '@/app/components/plugins/card/base/card-icon' |
| | | import Title from '@/app/components/plugins/card/base/title' |
| | | import OrgInfo from '@/app/components/plugins/card/base/org-info' |
| | | import Description from '@/app/components/plugins/card/base/description' |
| | | 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 Drawer from '@/app/components/base/drawer' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | |
| | | import { |
| | | deleteWorkflowTool, |
| | | fetchBuiltInToolList, |
| | |
| | | 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' |
| | | import { useInvalidateAllWorkflowTools } from '@/service/use-tools' |
| | | |
| | | type Props = { |
| | | collection: Collection |
| | | onHide: () => void |
| | | onRefreshData: () => void |
| | | } |
| | | |
| | | const ProviderDetail = ({ |
| | | collection, |
| | | onHide, |
| | | onRefreshData, |
| | | }: Props) => { |
| | | const { t } = useTranslation() |
| | |
| | | const isBuiltIn = collection.type === CollectionType.builtIn |
| | | const isModel = collection.type === CollectionType.model |
| | | const { isCurrentWorkspaceManager } = useAppContext() |
| | | const invalidateAllWorkflowTools = useInvalidateAllWorkflowTools() |
| | | |
| | | const [isDetailLoading, setIsDetailLoading] = useState(false) |
| | | |
| | | // built in provider |
| | |
| | | workflow_tool_id: string |
| | | }>) => { |
| | | await saveWorkflowToolProvider(data) |
| | | invalidateAllWorkflowTools() |
| | | onRefreshData() |
| | | getWorkflowToolProvider() |
| | | Toast.notify({ |
| | |
| | | setToolList(list) |
| | | } |
| | | } |
| | | catch { } |
| | | catch (e) { } |
| | | setIsDetailLoading(false) |
| | | }, [collection.name, collection.type]) |
| | | |
| | |
| | | }, [collection.name, collection.type, getCustomProvider, getProviderToolList, getWorkflowToolProvider]) |
| | | |
| | | return ( |
| | | <Drawer |
| | | isOpen={!!collection} |
| | | clickOutsideNotOpen={false} |
| | | onClose={onHide} |
| | | footer={null} |
| | | mask={false} |
| | | positionCenter={false} |
| | | panelClassName={cn('mb-2 mr-2 mt-[64px] !w-[420px] !max-w-[420px] justify-start rounded-2xl border-[0.5px] border-components-panel-border !bg-components-panel-bg !p-0 shadow-xl')} |
| | | > |
| | | <div className='flex h-full flex-col p-4'> |
| | | <div className="shrink-0"> |
| | | <div className='mb-3 flex'> |
| | | <Icon src={collection.icon} /> |
| | | <div className="ml-3 w-0 grow"> |
| | | <div className="flex h-5 items-center"> |
| | | <Title title={collection.label[language]} /> |
| | | </div> |
| | | <div className='mb-1 flex h-4 items-center justify-between'> |
| | | <OrgInfo |
| | | className="mt-0.5" |
| | | packageNameClassName='w-auto' |
| | | orgName={collection.author} |
| | | packageName={collection.name} |
| | | /> |
| | | </div> |
| | | </div> |
| | | <div className='flex gap-1'> |
| | | <ActionButton onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | <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> |
| | | {!!collection.description[language] && ( |
| | | <Description text={collection.description[language]} descriptionLineRows={2}></Description> |
| | | </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> |
| | | )} |
| | | <div className='flex gap-1 border-b-[0.5px] border-divider-subtle'> |
| | | {collection.type === CollectionType.custom && !isDetailLoading && ( |
| | | {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 |
| | | className={cn('my-3 w-full shrink-0')} |
| | | onClick={() => setIsShowEditCustomCollectionModal(true)} |
| | | variant='primary' |
| | | className={cn('shrink-0 my-3 w-[183px]')} |
| | | > |
| | | <Settings01 className='mr-1 h-4 w-4 text-text-tertiary' /> |
| | | <div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div> |
| | | <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> |
| | | )} |
| | | {collection.type === CollectionType.workflow && !isDetailLoading && customCollection && ( |
| | | <> |
| | | <Button |
| | | variant='primary' |
| | | className={cn('my-3 w-[183px] shrink-0')} |
| | | > |
| | | <Link className='flex items-center' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'> |
| | | <div className='system-sm-medium'>{t('tools.openInStudio')}</div> |
| | | <LinkExternal02 className='ml-1 h-4 w-4' /> |
| | | </Link> |
| | | </Button> |
| | | <Button |
| | | className={cn('my-3 w-[183px] shrink-0')} |
| | | onClick={() => setIsShowEditWorkflowToolModal(true)} |
| | | disabled={!isCurrentWorkspaceManager} |
| | | > |
| | | <div className='system-sm-medium text-text-secondary'>{t('tools.createTool.editAction')}</div> |
| | | </Button> |
| | | </> |
| | | )} |
| | | </div> |
| | | <div className='flex min-h-0 flex-1 flex-col pt-3'> |
| | | {isDetailLoading && <div className='flex h-[200px]'><Loading type='app' /></div>} |
| | | {!isDetailLoading && ( |
| | | <> |
| | | <div className="shrink-0"> |
| | | {(collection.type === CollectionType.builtIn || collection.type === CollectionType.model) && isAuthed && ( |
| | | <div className='system-sm-semibold-uppercase mb-1 flex h-6 items-center justify-between text-text-secondary'> |
| | | {t('plugin.detailPanel.actionNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' })} |
| | | {needAuth && ( |
| | | <Button |
| | | variant='secondary' |
| | | size='small' |
| | | onClick={() => { |
| | | if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) |
| | | showSettingAuthModal() |
| | | }} |
| | | disabled={!isCurrentWorkspaceManager} |
| | | > |
| | | <Indicator className='mr-2' color={'green'} /> |
| | | {t('tools.auth.authorized')} |
| | | </Button> |
| | | )} |
| | | </div> |
| | | )} |
| | | {(collection.type === CollectionType.builtIn || collection.type === CollectionType.model) && needAuth && !isAuthed && ( |
| | | <> |
| | | <div className='system-sm-semibold-uppercase text-text-secondary'> |
| | | <span className=''>{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> |
| | | <span className='px-1'>·</span> |
| | | <span className='text-util-colors-orange-orange-600'>{t('tools.auth.setup').toLocaleUpperCase()}</span> |
| | | </div> |
| | | <Button |
| | | variant='primary' |
| | | className={cn('my-3 w-full shrink-0')} |
| | | onClick={() => { |
| | | if (collection.type === CollectionType.builtIn || collection.type === CollectionType.model) |
| | | showSettingAuthModal() |
| | | }} |
| | | disabled={!isCurrentWorkspaceManager} |
| | | > |
| | | {t('tools.auth.unauthorized')} |
| | | </Button> |
| | | </> |
| | | )} |
| | | {(collection.type === CollectionType.custom) && ( |
| | | <div className='system-sm-semibold-uppercase text-text-secondary'> |
| | | <span className=''>{t('tools.includeToolNum', { num: toolList.length, action: toolList.length > 1 ? 'actions' : 'action' }).toLocaleUpperCase()}</span> |
| | | </div> |
| | | )} |
| | | {(collection.type === CollectionType.workflow) && ( |
| | | <div className='system-sm-semibold-uppercase text-text-secondary'> |
| | | <span className=''>{t('tools.createTool.toolInput.title').toLocaleUpperCase()}</span> |
| | | </div> |
| | | )} |
| | | </div> |
| | | <div className='mt-1 flex-1 overflow-y-auto py-2'> |
| | | {collection.type !== CollectionType.workflow && toolList.map(tool => ( |
| | | <ToolItem |
| | | key={tool.name} |
| | | disabled={false} |
| | | 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-1 py-1'> |
| | | <div className='mb-1 flex items-center gap-2'> |
| | | <span className='code-sm-semibold text-text-secondary'>{item.name}</span> |
| | | <span className='system-xs-regular text-text-tertiary'>{item.type}</span> |
| | | <span className='system-xs-medium text-text-warning-secondary'>{item.required ? t('tools.createTool.toolInput.required') : ''}</span> |
| | | </div> |
| | | <div className='system-xs-regular text-text-tertiary'>{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)} |
| | | /> |
| | | <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> |
| | | </Drawer> |
| | | {/* Tools */} |
| | | <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 |