| | |
| | | import React, { useEffect, useState } from 'react' |
| | | import { useTranslation } from 'react-i18next' |
| | | import { useContext } from 'use-context-selector' |
| | | import { |
| | | RiArrowLeftLine, |
| | | RiCloseLine, |
| | | } from '@remixicon/react' |
| | | import Drawer from '@/app/components/base/drawer' |
| | | import Loading from '@/app/components/base/loading' |
| | | import ActionButton from '@/app/components/base/action-button' |
| | | import Icon from '@/app/components/plugins/card/base/card-icon' |
| | | import OrgInfo from '@/app/components/plugins/card/base/org-info' |
| | | import Description from '@/app/components/plugins/card/base/description' |
| | | import TabSlider from '@/app/components/base/tab-slider-plain' |
| | | |
| | | import Button from '@/app/components/base/button' |
| | | import cn from '@/utils/classnames' |
| | | import Drawer from '@/app/components/base/drawer-plus' |
| | | import Form from '@/app/components/header/account-setting/model-provider-page/model-modal/Form' |
| | | import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' |
| | | import type { Collection, Tool } from '@/app/components/tools/types' |
| | | import { CollectionType } from '@/app/components/tools/types' |
| | | import { fetchBuiltInToolList, fetchCustomToolList, fetchModelToolList, fetchWorkflowToolList } from '@/service/tools' |
| | | import I18n from '@/context/i18n' |
| | | import Button from '@/app/components/base/button' |
| | | import Loading from '@/app/components/base/loading' |
| | | import { DiagonalDividingLine } from '@/app/components/base/icons/src/public/common' |
| | | import { getLanguage } from '@/i18n/language' |
| | | import cn from '@/utils/classnames' |
| | | import AppIcon from '@/app/components/base/app-icon' |
| | | |
| | | type Props = { |
| | | showBackButton?: boolean |
| | | collection: Collection |
| | | isBuiltIn?: boolean |
| | | isModel?: boolean |
| | |
| | | } |
| | | |
| | | const SettingBuiltInTool: FC<Props> = ({ |
| | | showBackButton = false, |
| | | collection, |
| | | isBuiltIn = true, |
| | | isModel = true, |
| | |
| | | const [tools, setTools] = useState<Tool[]>([]) |
| | | const currTool = tools.find(tool => tool.name === toolName) |
| | | const formSchemas = currTool ? toolParametersToFormSchemas(currTool.parameters) : [] |
| | | const infoSchemas = formSchemas.filter(item => item.form === 'llm') |
| | | const settingSchemas = formSchemas.filter(item => item.form !== 'llm') |
| | | const infoSchemas = formSchemas.filter((item: any) => item.form === 'llm') |
| | | const settingSchemas = formSchemas.filter((item: any) => item.form !== 'llm') |
| | | const hasSetting = settingSchemas.length > 0 |
| | | const [tempSetting, setTempSetting] = useState(setting) |
| | | const [currType, setCurrType] = useState('info') |
| | |
| | | setTempSetting(addDefaultValue(setting, formSchemas)) |
| | | } |
| | | } |
| | | catch { } |
| | | catch (e) { } |
| | | setIsLoading(false) |
| | | })() |
| | | }, [collection?.name, collection?.id, collection?.type]) |
| | |
| | | |
| | | const isValid = (() => { |
| | | let valid = true |
| | | settingSchemas.forEach((item) => { |
| | | settingSchemas.forEach((item: any) => { |
| | | if (item.required && !tempSetting[item.name]) |
| | | valid = false |
| | | }) |
| | | return valid |
| | | })() |
| | | |
| | | const getType = (type: string) => { |
| | | if (type === 'number-input') |
| | | return t('tools.setBuiltInTools.number') |
| | | if (type === 'text-input') |
| | | return t('tools.setBuiltInTools.string') |
| | | if (type === 'file') |
| | | return t('tools.setBuiltInTools.file') |
| | | return type |
| | | } |
| | | |
| | | const infoUI = ( |
| | | <div className=''> |
| | | <div className='pt-2'> |
| | | <div className='leading-5 text-sm font-medium text-gray-900'> |
| | | {t('tools.setBuiltInTools.toolDescription')} |
| | | </div> |
| | | <div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'> |
| | | {currTool?.description[language]} |
| | | </div> |
| | | |
| | | {infoSchemas.length > 0 && ( |
| | | <div className='space-y-1 py-2'> |
| | | {infoSchemas.map((item, index) => ( |
| | | <div key={index} className='py-1'> |
| | | <div className='flex items-center gap-2'> |
| | | <div className='code-sm-semibold text-text-secondary'>{item.label[language]}</div> |
| | | <div className='system-xs-regular text-text-tertiary'> |
| | | {getType(item.type)} |
| | | <div className='mt-6'> |
| | | <div className='flex items-center mb-4 leading-[18px] text-xs font-semibold text-gray-500 uppercase'> |
| | | <div className='mr-3'>{t('tools.setBuiltInTools.parameters')}</div> |
| | | <div className='grow w-0 h-px bg-[#f3f4f6]'></div> |
| | | </div> |
| | | <div className='space-y-4'> |
| | | {infoSchemas.map((item: any, index) => ( |
| | | <div key={index}> |
| | | <div className='flex items-center space-x-2 leading-[18px]'> |
| | | <div className='text-[13px] font-semibold text-gray-900'>{item.label[language]}</div> |
| | | <div className='text-xs font-medium text-gray-500'>{item.type === 'number-input' ? t('tools.setBuiltInTools.number') : t('tools.setBuiltInTools.string')}</div> |
| | | {item.required && ( |
| | | <div className='text-xs font-medium text-[#EC4A0A]'>{t('tools.setBuiltInTools.required')}</div> |
| | | )} |
| | | </div> |
| | | {item.required && ( |
| | | <div className='system-xs-medium text-text-warning-secondary'>{t('tools.setBuiltInTools.required')}</div> |
| | | {item.human_description && ( |
| | | <div className='mt-1 leading-[18px] text-xs font-normal text-gray-600'> |
| | | {item.human_description?.[language]} |
| | | </div> |
| | | )} |
| | | </div> |
| | | {item.human_description && ( |
| | | <div className='system-xs-regular mt-0.5 text-text-tertiary'> |
| | | {item.human_description?.[language]} |
| | | </div> |
| | | )} |
| | | </div> |
| | | ))} |
| | | ))} |
| | | </div> |
| | | </div> |
| | | )} |
| | | </div> |
| | |
| | | <Form |
| | | value={tempSetting} |
| | | onChange={setTempSetting} |
| | | formSchemas={settingSchemas} |
| | | formSchemas={settingSchemas as any} |
| | | isEditMode={false} |
| | | showOnVariableMap={{}} |
| | | validating={false} |
| | | inputClassName='!bg-gray-50' |
| | | readonly={readonly} |
| | | /> |
| | | ) |
| | | |
| | | return ( |
| | | <Drawer |
| | | isOpen |
| | | isShow |
| | | onHide={onHide} |
| | | title={( |
| | | <div className='flex items-center'> |
| | | {typeof collection.icon === 'string' |
| | | ? ( |
| | | <div |
| | | className='w-6 h-6 bg-cover bg-center rounded-md flex-shrink-0' |
| | | style={{ |
| | | backgroundImage: `url(${collection.icon})`, |
| | | }} |
| | | ></div> |
| | | ) |
| | | : ( |
| | | <AppIcon |
| | | className='rounded-md' |
| | | size='tiny' |
| | | icon={(collection.icon as any)?.content} |
| | | background={(collection.icon as any)?.background} |
| | | /> |
| | | )} |
| | | |
| | | <div className='ml-2 leading-6 text-base font-semibold text-gray-900'>{currTool?.label[language]}</div> |
| | | {(hasSetting && !readonly) && (<> |
| | | <DiagonalDividingLine className='mx-4' /> |
| | | <div className='flex space-x-6'> |
| | | <div |
| | | className={cn(isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base')} |
| | | onClick={() => setCurrType('info')} |
| | | > |
| | | {t('tools.setBuiltInTools.info')} |
| | | {isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>} |
| | | </div> |
| | | <div className={cn(!isInfoActive ? 'text-gray-900 font-semibold' : 'font-normal text-gray-600 cursor-pointer', 'relative text-base ')} |
| | | onClick={() => setCurrType('setting')} |
| | | > |
| | | {t('tools.setBuiltInTools.setting')} |
| | | {!isInfoActive && <div className='absolute left-0 bottom-[-16px] w-full h-0.5 bg-primary-600'></div>} |
| | | </div> |
| | | </div> |
| | | </>)} |
| | | </div> |
| | | )} |
| | | panelClassName='mt-[65px] !w-[405px]' |
| | | maxWidthClassName='!max-w-[405px]' |
| | | height='calc(100vh - 65px)' |
| | | headerClassName='!border-b-black/5' |
| | | body={ |
| | | <div className='h-full pt-3'> |
| | | {isLoading |
| | | ? <div className='flex h-full items-center'> |
| | | <Loading type='app' /> |
| | | </div> |
| | | : (<div className='flex flex-col h-full'> |
| | | <div className='grow h-0 overflow-y-auto px-6'> |
| | | {isInfoActive ? infoUI : settingUI} |
| | | </div> |
| | | {!readonly && !isInfoActive && ( |
| | | <div className='mt-2 shrink-0 flex justify-end py-4 px-6 space-x-2 rounded-b-[10px] bg-gray-50 border-t border-black/5'> |
| | | <Button className='flex items-center h-8 !px-3 !text-[13px] font-medium !text-gray-700' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button className='flex items-center h-8 !px-3 !text-[13px] font-medium' variant='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | )} |
| | | </div>)} |
| | | </div> |
| | | } |
| | | isShowMask={false} |
| | | 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')} |
| | | > |
| | | <> |
| | | {isLoading && <Loading type='app' />} |
| | | {!isLoading && ( |
| | | <> |
| | | {/* header */} |
| | | <div className='relative border-b border-divider-subtle p-4 pb-3'> |
| | | <div className='absolute right-3 top-3'> |
| | | <ActionButton onClick={onHide}> |
| | | <RiCloseLine className='h-4 w-4' /> |
| | | </ActionButton> |
| | | </div> |
| | | {showBackButton && ( |
| | | <div |
| | | className='system-xs-semibold-uppercase mb-2 flex cursor-pointer items-center gap-1 text-text-accent-secondary' |
| | | onClick={onHide} |
| | | > |
| | | <RiArrowLeftLine className='h-4 w-4' /> |
| | | BACK |
| | | </div> |
| | | )} |
| | | <div className='flex items-center gap-1'> |
| | | <Icon size='tiny' className='h-6 w-6' src={collection.icon} /> |
| | | <OrgInfo |
| | | packageNameClassName='w-auto' |
| | | orgName={collection.author} |
| | | packageName={collection.name.split('/').pop() || ''} |
| | | /> |
| | | </div> |
| | | <div className='system-md-semibold mt-1 text-text-primary'>{currTool?.label[language]}</div> |
| | | {!!currTool?.description[language] && ( |
| | | <Description className='mt-3' text={currTool.description[language]} descriptionLineRows={2}></Description> |
| | | )} |
| | | </div> |
| | | {/* form */} |
| | | <div className='h-full'> |
| | | <div className='flex h-full flex-col'> |
| | | {(hasSetting && !readonly) ? ( |
| | | <TabSlider |
| | | className='mt-1 shrink-0 px-4' |
| | | itemClassName='py-3' |
| | | noBorderBottom |
| | | value={currType} |
| | | onChange={(value) => { |
| | | setCurrType(value) |
| | | }} |
| | | options={[ |
| | | { value: 'info', text: t('tools.setBuiltInTools.parameters')! }, |
| | | { value: 'setting', text: t('tools.setBuiltInTools.setting')! }, |
| | | ]} |
| | | /> |
| | | ) : ( |
| | | <div className='system-sm-semibold-uppercase p-4 pb-1 text-text-primary'>{t('tools.setBuiltInTools.parameters')}</div> |
| | | )} |
| | | <div className='h-0 grow overflow-y-auto px-4'> |
| | | {isInfoActive ? infoUI : settingUI} |
| | | </div> |
| | | {!readonly && !isInfoActive && ( |
| | | <div className='mt-2 flex shrink-0 justify-end space-x-2 rounded-b-[10px] border-t border-divider-regular bg-components-panel-bg px-6 py-4'> |
| | | <Button className='flex h-8 items-center !px-3 !text-[13px] font-medium ' onClick={onHide}>{t('common.operation.cancel')}</Button> |
| | | <Button className='flex h-8 items-center !px-3 !text-[13px] font-medium' variant='primary' disabled={!isValid} onClick={() => onSave?.(addDefaultValue(tempSetting, formSchemas))}>{t('common.operation.save')}</Button> |
| | | </div> |
| | | )} |
| | | </div> |
| | | </div> |
| | | </> |
| | | )} |
| | | </> |
| | | </Drawer> |
| | | /> |
| | | ) |
| | | } |
| | | export default React.memo(SettingBuiltInTool) |