mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 09:25:56 +08:00
Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins
This commit is contained in:
commit
3cb0a5bd68
@ -43,7 +43,7 @@ const PluginList = async () => {
|
||||
<h3 className='my-1'>Install model provide</h3>
|
||||
<div className='grid grid-cols-2 gap-3'>
|
||||
{pluginList.map((plugin, index) => (
|
||||
<InstallModelItem key={index} payload={plugin as any} />
|
||||
<InstallModelItem key={index} locale={locale} payload={plugin as any} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
@ -102,7 +102,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
|
||||
</Menu.Item>
|
||||
<div className="px-1 py-1">
|
||||
<Menu.Item>
|
||||
<div className={itemClassName} onClick={() => setShowAccountSettingModal({ payload: 'account' })}>
|
||||
<div className={itemClassName} onClick={() => setShowAccountSettingModal({ payload: 'provider' })}>
|
||||
<div>{t('common.userProfile.settings')}</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
|
@ -6,9 +6,8 @@ import {
|
||||
RiAccountCircleLine,
|
||||
RiApps2AddFill,
|
||||
RiApps2AddLine,
|
||||
RiBox3Fill,
|
||||
RiBox3Line,
|
||||
RiCloseLine,
|
||||
RiBrainFill,
|
||||
RiBrainLine,
|
||||
RiColorFilterFill,
|
||||
RiColorFilterLine,
|
||||
RiDatabase2Fill,
|
||||
@ -31,17 +30,14 @@ import ModelProviderPage from './model-provider-page'
|
||||
import cn from '@/utils/classnames'
|
||||
import BillingPage from '@/app/components/billing/billing-page'
|
||||
import CustomPage from '@/app/components/custom/custom-page'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import MenuDialog from '@/app/components/header/account-setting/menu-dialog'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
const iconClassName = `
|
||||
w-4 h-4 ml-3 mr-2
|
||||
`
|
||||
|
||||
const scrolledClassName = `
|
||||
border-b shadow-xs bg-white/[.98]
|
||||
w-5 h-5 mr-2
|
||||
`
|
||||
|
||||
type IAccountSettingProps = {
|
||||
@ -59,7 +55,7 @@ type GroupItem = {
|
||||
|
||||
export default function AccountSetting({
|
||||
onCancel,
|
||||
activeTab = 'account',
|
||||
activeTab = 'provider',
|
||||
}: IAccountSettingProps) {
|
||||
const [activeMenu, setActiveMenu] = useState(activeTab)
|
||||
const { t } = useTranslation()
|
||||
@ -73,8 +69,8 @@ export default function AccountSetting({
|
||||
{
|
||||
key: 'provider',
|
||||
name: t('common.settings.provider'),
|
||||
icon: <RiBox3Line className={iconClassName} />,
|
||||
activeIcon: <RiBox3Fill className={iconClassName} />,
|
||||
icon: <RiBrainLine className={iconClassName} />,
|
||||
activeIcon: <RiBrainFill className={iconClassName} />,
|
||||
},
|
||||
{
|
||||
key: 'members',
|
||||
@ -122,7 +118,7 @@ export default function AccountSetting({
|
||||
},
|
||||
{
|
||||
key: 'account-group',
|
||||
name: t('common.settings.accountGroup'),
|
||||
name: t('common.settings.generalGroup'),
|
||||
items: [
|
||||
{
|
||||
key: 'account',
|
||||
@ -161,32 +157,31 @@ export default function AccountSetting({
|
||||
|
||||
const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)
|
||||
|
||||
const [searchValue, setSearchValue] = useState<string>('')
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={() => { }}
|
||||
className='my-[60px] p-0 max-w-[1024px] rounded-xl overflow-y-auto'
|
||||
wrapperClassName='pt-[60px]'
|
||||
<MenuDialog
|
||||
show
|
||||
onClose={onCancel}
|
||||
>
|
||||
<div className='flex'>
|
||||
<div className='w-[44px] sm:w-[200px] px-[1px] py-4 sm:p-4 border border-gray-100 shrink-0 sm:shrink-1 flex flex-col items-center sm:items-start'>
|
||||
<div className='mb-8 ml-0 sm:ml-2 text-sm sm:text-base font-medium leading-6 text-gray-900'>{t('common.userProfile.settings')}</div>
|
||||
<div className='mx-auto max-w-[1048px] h-[100vh] flex'>
|
||||
<div className='w-[44px] sm:w-[224px] pl-4 pr-6 border-r border-divider-burn flex flex-col'>
|
||||
<div className='mt-6 mb-8 px-3 py-2 text-text-primary title-2xl-semi-bold'>{t('common.userProfile.settings')}</div>
|
||||
<div className='w-full'>
|
||||
{
|
||||
menuItems.map(menuItem => (
|
||||
<div key={menuItem.key} className='mb-4'>
|
||||
<div key={menuItem.key} className='mb-2'>
|
||||
{!isCurrentWorkspaceDatasetOperator && (
|
||||
<div className='px-2 mb-[6px] text-[10px] sm:text-xs font-medium text-gray-500'>{menuItem.name}</div>
|
||||
<div className='py-2 pl-3 pb-1 mb-0.5 text-text-tertiary system-xs-medium-uppercase'>{menuItem.name}</div>
|
||||
)}
|
||||
<div>
|
||||
{
|
||||
menuItem.items.map(item => (
|
||||
<div
|
||||
key={item.key}
|
||||
className={`
|
||||
flex items-center h-[37px] mb-[2px] text-sm cursor-pointer rounded-lg
|
||||
${activeMenu === item.key ? 'font-semibold text-primary-600 bg-primary-50' : 'font-light text-gray-700'}
|
||||
`}
|
||||
className={cn(
|
||||
'flex items-center mb-0.5 p-1 pl-3 h-[37px] text-sm cursor-pointer rounded-lg',
|
||||
activeMenu === item.key ? 'bg-state-base-active text-components-menu-item-text-active system-sm-semibold' : 'text-components-menu-item-text system-sm-medium')}
|
||||
title={item.name}
|
||||
onClick={() => setActiveMenu(item.key)}
|
||||
>
|
||||
@ -201,18 +196,22 @@ export default function AccountSetting({
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div ref={scrollRef} className='relative w-[824px] h-[720px] pb-4 overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 px-6 py-4 flex items-center h-14 mb-4 bg-white text-base font-medium text-gray-900 z-20', scrolled && scrolledClassName)}>
|
||||
<div className='shrink-0'>{activeItem?.name}</div>
|
||||
<div ref={scrollRef} className='relative w-[824px] pb-4 bg-components-panel-bg overflow-y-auto'>
|
||||
<div className={cn('sticky top-0 mx-8 pt-[27px] pb-2 mb-[18px] flex items-center bg-components-panel-bg z-20', scrolled && 'border-b')}>
|
||||
<div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
|
||||
{
|
||||
activeItem?.description && (
|
||||
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
|
||||
)
|
||||
}
|
||||
<div className='grow flex justify-end'>
|
||||
<div className='flex items-center justify-center -mr-4 w-6 h-6 cursor-pointer' onClick={onCancel}>
|
||||
<RiCloseLine className='w-4 h-4 text-gray-400' />
|
||||
</div>
|
||||
<Input
|
||||
showLeftIcon
|
||||
wrapperClassName='!w-[200px]'
|
||||
className='!h-8 !text-[13px]'
|
||||
onChange={e => setSearchValue(e.target.value)}
|
||||
value={searchValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-4 sm:px-8 pt-2'>
|
||||
@ -228,6 +227,6 @@ export default function AccountSetting({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</MenuDialog>
|
||||
)
|
||||
}
|
||||
|
72
web/app/components/header/account-setting/menu-dialog.tsx
Normal file
72
web/app/components/header/account-setting/menu-dialog.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { Fragment, useCallback, useEffect } from 'react'
|
||||
import type { ReactNode } from 'react'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type DialogProps = {
|
||||
className?: string
|
||||
children: ReactNode
|
||||
show: boolean
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const MenuDialog = ({
|
||||
className,
|
||||
children,
|
||||
show,
|
||||
onClose,
|
||||
}: DialogProps) => {
|
||||
const close = useCallback(() => onClose?.(), [onClose])
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape')
|
||||
close()
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [close])
|
||||
|
||||
return (
|
||||
<Transition appear show={show} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-40" onClose={() => {}}>
|
||||
<div className="fixed inset-0">
|
||||
<div className="flex flex-col items-center justify-center min-h-full">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Dialog.Panel className={cn('grow relative w-full h-full p-0 overflow-hidden text-left align-middle transition-all transform bg-background-sidenav-bg backdrop-blur-md', className)}>
|
||||
<div className='absolute right-0 top-0 h-full w-1/2 bg-components-panel-bg'/>
|
||||
<div className='absolute top-6 right-6 flex flex-col items-center'>
|
||||
<Button
|
||||
variant='tertiary'
|
||||
size='large'
|
||||
className='px-2'
|
||||
onClick={close}
|
||||
>
|
||||
<RiCloseLine className='w-5 h-5' />
|
||||
</Button>
|
||||
<div className='mt-1 text-text-tertiary system-2xs-medium-uppercase'>ESC</div>
|
||||
</div>
|
||||
{children}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition >
|
||||
)
|
||||
}
|
||||
|
||||
export default MenuDialog
|
@ -1,8 +1,16 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
RiAlertFill,
|
||||
RiArrowDownSLine,
|
||||
RiArrowRightUpLine,
|
||||
RiBrainLine,
|
||||
} from '@remixicon/react'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import SystemModelSelector from './system-model-selector'
|
||||
import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
|
||||
import ProviderCard from './provider-card'
|
||||
// import ProviderCard from './provider-card'
|
||||
import type {
|
||||
CustomConfigurationModelFixedFields,
|
||||
ModelProvider,
|
||||
@ -17,10 +25,15 @@ import {
|
||||
useUpdateModelList,
|
||||
useUpdateModelProviders,
|
||||
} from './hooks'
|
||||
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import I18n from '@/context/i18n'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
import { extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock'
|
||||
|
||||
const ModelProviderPage = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -57,25 +70,25 @@ const ModelProviderPage = () => {
|
||||
|
||||
const handleOpenModal = (
|
||||
provider: ModelProvider,
|
||||
configurateMethod: ConfigurationMethodEnum,
|
||||
configurationMethod: ConfigurationMethodEnum,
|
||||
CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
|
||||
) => {
|
||||
setShowModelModal({
|
||||
payload: {
|
||||
currentProvider: provider,
|
||||
currentConfigurationMethod: configurateMethod,
|
||||
currentConfigurationMethod: configurationMethod,
|
||||
currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields,
|
||||
},
|
||||
onSaveCallback: () => {
|
||||
updateModelProviders()
|
||||
|
||||
if (configurateMethod === ConfigurationMethodEnum.predefinedModel) {
|
||||
if (configurationMethod === ConfigurationMethodEnum.predefinedModel) {
|
||||
provider.supported_model_types.forEach((type) => {
|
||||
updateModelList(type)
|
||||
})
|
||||
}
|
||||
|
||||
if (configurateMethod === ConfigurationMethodEnum.customizableModel && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
|
||||
if (configurationMethod === ConfigurationMethodEnum.customizableModel && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
|
||||
payload: provider.provider,
|
||||
@ -88,63 +101,95 @@ const ModelProviderPage = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const { locale } = useContext(I18n)
|
||||
|
||||
// TODO #Plugin list API#
|
||||
const pluginList = [toolNotion, extensionDallE, modelGPT4]
|
||||
|
||||
return (
|
||||
<div className='relative pt-1 -mt-2'>
|
||||
<div className={`flex items-center justify-between mb-2 h-8 ${defaultModelNotConfigured && 'px-3 bg-[#FFFAEB] rounded-lg border border-[#FEF0C7]'}`}>
|
||||
{
|
||||
defaultModelNotConfigured
|
||||
? (
|
||||
<div className='flex items-center text-xs font-medium text-gray-700'>
|
||||
<AlertTriangle className='mr-1 w-3 h-3 text-[#F79009]' />
|
||||
{t('common.modelProvider.notConfigured')}
|
||||
</div>
|
||||
)
|
||||
: <div className='text-sm font-medium text-gray-800'>{t('common.modelProvider.models')}</div>
|
||||
}
|
||||
<SystemModelSelector
|
||||
textGenerationDefaultModel={textGenerationDefaultModel}
|
||||
embeddingsDefaultModel={embeddingsDefaultModel}
|
||||
rerankDefaultModel={rerankDefaultModel}
|
||||
speech2textDefaultModel={speech2textDefaultModel}
|
||||
ttsDefaultModel={ttsDefaultModel}
|
||||
/>
|
||||
<div className={cn('flex items-center mb-2')}>
|
||||
<div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
|
||||
<div className={cn(
|
||||
'shrink-0 relative flex items-center justify-end gap-2 p-0.5 rounded-lg border border-transparent',
|
||||
defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
|
||||
)}>
|
||||
{defaultModelNotConfigured && <div className='absolute top-0 bottom-0 right-0 left-0 opacity-40' style={{ background: 'linear-gradient(92deg, rgba(247, 144, 9, 0.25) 0%, rgba(255, 255, 255, 0.00) 100%)' }}/>}
|
||||
{defaultModelNotConfigured && (
|
||||
<div className='flex items-center gap-1 text-text-primary system-xs-medium'>
|
||||
<RiAlertFill className='w-4 h-4 text-text-warning-secondary' />
|
||||
{t('common.modelProvider.notConfigured')}
|
||||
</div>
|
||||
)}
|
||||
<SystemModelSelector
|
||||
notConfigured={defaultModelNotConfigured}
|
||||
textGenerationDefaultModel={textGenerationDefaultModel}
|
||||
embeddingsDefaultModel={embeddingsDefaultModel}
|
||||
rerankDefaultModel={rerankDefaultModel}
|
||||
speech2textDefaultModel={speech2textDefaultModel}
|
||||
ttsDefaultModel={ttsDefaultModel}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!!configuredProviders?.length && (
|
||||
<div className='pb-3'>
|
||||
{
|
||||
configuredProviders?.map(provider => (
|
||||
<ProviderAddedCard
|
||||
key={provider.provider}
|
||||
provider={provider}
|
||||
onOpenModal={(configurateMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurateMethod, currentCustomConfigurationModelFixedFields)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
{!configuredProviders?.length && (
|
||||
<div className='mb-2 p-4 rounded-[10px]' style={{ background: 'linear-gradient(90deg, rgba(200, 206, 218, 0.20) 0%, rgba(200, 206, 218, 0.04) 100%)' }}>
|
||||
<div className='w-10 h-10 flex items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur'>
|
||||
<RiBrainLine className='w-5 h-5 text-text-primary' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!notConfiguredProviders?.length && (
|
||||
<>
|
||||
<div className='flex items-center mb-2 text-xs font-semibold text-gray-500'>
|
||||
+ {t('common.modelProvider.addMoreModelProvider')}
|
||||
<span className='grow ml-3 h-[1px] bg-gradient-to-r from-[#f3f4f6]' />
|
||||
</div>
|
||||
<div className='grid grid-cols-3 gap-2'>
|
||||
{
|
||||
notConfiguredProviders?.map(provider => (
|
||||
<ProviderCard
|
||||
key={provider.provider}
|
||||
provider={provider}
|
||||
onOpenModal={(configurateMethod: ConfigurationMethodEnum) => handleOpenModal(provider, configurateMethod)}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
<div className='mt-2 text-text-secondary system-sm-medium'>{t('common.modelProvider.emptyProviderTitle')}</div>
|
||||
<div className='mt-1 text-text-tertiary system-xs-regular'>{t('common.modelProvider.emptyProviderTip')}</div>
|
||||
</div>
|
||||
)}
|
||||
{!!configuredProviders?.length && (
|
||||
<div className='relative'>
|
||||
{configuredProviders?.map(provider => (
|
||||
<ProviderAddedCard
|
||||
key={provider.provider}
|
||||
provider={provider}
|
||||
onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{false && !!notConfiguredProviders?.length && (
|
||||
<>
|
||||
<div className='flex items-center mb-2 pt-2 text-text-primary system-md-semibold'>{t('common.modelProvider.configureRequired')}</div>
|
||||
<div className='relative'>
|
||||
{notConfiguredProviders?.map(provider => (
|
||||
<ProviderAddedCard
|
||||
notConfigured
|
||||
key={provider.provider}
|
||||
provider={provider}
|
||||
onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className='mb-2'>
|
||||
<Divider className='!mt-4 h-px' />
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex items-center gap-1 text-text-primary system-md-semibold cursor-pointer' onClick={() => setCollapse(!collapse)}>
|
||||
<RiArrowDownSLine className={cn('w-4 h-4', collapse && '-rotate-90')} />
|
||||
{t('common.modelProvider.installProvider')}
|
||||
</div>
|
||||
<div className='flex items-center mb-2 pt-2'>
|
||||
<span className='pr-1 text-text-tertiary system-sm-regular'>{t('common.modelProvider.discoverMore')}</span>
|
||||
<Link target="_blank" href="/plugins" className='inline-flex items-center system-sm-medium text-text-accent'>
|
||||
Dify Marketplace
|
||||
<RiArrowRightUpLine className='w-4 h-4' />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
{!collapse && (
|
||||
<div className='grid grid-cols-2 gap-2'>
|
||||
{pluginList.map((plugin, index) => (
|
||||
<ProviderCard key={index} locale={locale} payload={plugin as any} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import {
|
||||
ConfigurationMethodEnum,
|
||||
@ -14,7 +15,6 @@ import PrioritySelector from './priority-selector'
|
||||
import PriorityUseTip from './priority-use-tip'
|
||||
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { changeModelProviderPriority } from '@/service/common'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
@ -69,7 +69,7 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
||||
<div className='shrink-0 relative ml-1 p-1 w-[112px] rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
||||
<div className='flex items-center justify-between mb-1 pt-1 pl-2 pr-[7px] h-5 text-xs font-medium text-gray-500'>
|
||||
API-KEY
|
||||
<Indicator color={isCustomConfigured ? 'green' : 'gray'} />
|
||||
<Indicator color={isCustomConfigured ? 'green' : 'red'} />
|
||||
</div>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<Button
|
||||
@ -77,7 +77,7 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
|
||||
size='small'
|
||||
onClick={onSetup}
|
||||
>
|
||||
<Settings01 className='mr-1 w-3 h-3' />
|
||||
<RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.operation.setup')}
|
||||
</Button>
|
||||
{
|
||||
|
@ -2,6 +2,8 @@ import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiInformation2Fill,
|
||||
RiLoader2Line,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
@ -21,7 +23,6 @@ import CredentialPanel from './credential-panel'
|
||||
import QuotaPanel from './quota-panel'
|
||||
import ModelList from './model-list'
|
||||
import AddModelButton from './add-model-button'
|
||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { IS_CE_EDITION } from '@/config'
|
||||
@ -29,10 +30,12 @@ import { useAppContext } from '@/context/app-context'
|
||||
|
||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||
type ProviderAddedCardProps = {
|
||||
notConfigured?: boolean
|
||||
provider: ModelProvider
|
||||
onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
|
||||
}
|
||||
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
notConfigured,
|
||||
provider,
|
||||
onOpenModal,
|
||||
}) => {
|
||||
@ -47,6 +50,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
const hasModelList = fetched && !!modelList.length
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION
|
||||
const showCredential = configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager
|
||||
|
||||
const getModelList = async (providerName: string) => {
|
||||
if (loading)
|
||||
@ -105,7 +109,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager && (
|
||||
showCredential && (
|
||||
<CredentialPanel
|
||||
onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)}
|
||||
provider={provider}
|
||||
@ -116,34 +120,45 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
{
|
||||
collapsed && (
|
||||
<div className='group flex items-center justify-between pl-2 py-1.5 pr-[11px] border-t border-t-black/5 bg-white/30 text-xs font-medium text-gray-500'>
|
||||
<div className='group-hover:hidden pl-1 pr-1.5 h-6 leading-6'>
|
||||
{
|
||||
hasModelList
|
||||
? t('common.modelProvider.modelsNum', { num: modelList.length })
|
||||
: t('common.modelProvider.showModels')
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer'
|
||||
onClick={handleOpenModelList}
|
||||
>
|
||||
<ChevronDownDouble className='mr-0.5 w-3 h-3' />
|
||||
{
|
||||
hasModelList
|
||||
? t('common.modelProvider.showModelsNum', { num: modelList.length })
|
||||
: t('common.modelProvider.showModels')
|
||||
}
|
||||
{
|
||||
loading && (
|
||||
<RiLoader2Line className='ml-0.5 animate-spin w-3 h-3' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{(showQuota || !notConfigured) && (
|
||||
<>
|
||||
<div className='group-hover:hidden flex items-center pl-1 pr-1.5 h-6 leading-6'>
|
||||
{
|
||||
hasModelList
|
||||
? t('common.modelProvider.modelsNum', { num: modelList.length })
|
||||
: t('common.modelProvider.showModels')
|
||||
}
|
||||
{!loading && <RiArrowRightSLine className='w-4 h-4' />}
|
||||
</div>
|
||||
<div
|
||||
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer'
|
||||
onClick={handleOpenModelList}
|
||||
>
|
||||
{
|
||||
hasModelList
|
||||
? t('common.modelProvider.showModelsNum', { num: modelList.length })
|
||||
: t('common.modelProvider.showModels')
|
||||
}
|
||||
{!loading && <RiArrowRightSLine className='w-4 h-4' />}
|
||||
{
|
||||
loading && (
|
||||
<RiLoader2Line className='ml-0.5 animate-spin w-3 h-3' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showQuota && notConfigured && (
|
||||
<div className='flex items-center pl-1 pr-1.5 h-6'>
|
||||
<RiInformation2Fill className='mr-1 w-4 h-4 text-text-accent' />
|
||||
<span>{t('common.modelProvider.configureTip')}</span>
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && (
|
||||
<AddModelButton
|
||||
onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)}
|
||||
className='hidden group-hover:flex group-hover:text-primary-600'
|
||||
className='flex'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import type { FC } from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
} from '@remixicon/react'
|
||||
import type {
|
||||
CustomConfigurationModelFixedFields,
|
||||
ModelItem,
|
||||
@ -12,7 +15,6 @@ import {
|
||||
// import Tab from './tab'
|
||||
import AddModelButton from './add-model-button'
|
||||
import ModelListItem from './model-list-item'
|
||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import { useModalContextSelector } from '@/context/modal-context'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
@ -51,15 +53,16 @@ const ModelList: FC<ModelListProps> = ({
|
||||
<div className='py-1 bg-white rounded-lg'>
|
||||
<div className='flex items-center pl-1 pr-[3px]'>
|
||||
<span className='group shrink-0 flex items-center mr-2'>
|
||||
<span className='group-hover:hidden pl-1 pr-1.5 h-6 leading-6 text-xs font-medium text-gray-500'>
|
||||
<span className='group-hover:hidden inline-flex pl-1 pr-1.5 h-6 leading-6 text-xs font-medium text-gray-500'>
|
||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||
</span>
|
||||
<span
|
||||
className='hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 text-xs font-medium text-gray-500 bg-gray-50 cursor-pointer rounded-lg'
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
<ChevronDownDouble className='mr-0.5 w-3 h-3 rotate-180' />
|
||||
{t('common.modelProvider.collapse')}
|
||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
|
||||
</span>
|
||||
</span>
|
||||
{/* {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import ModelSelector from '../model-selector'
|
||||
import {
|
||||
useModelList,
|
||||
@ -13,7 +14,6 @@ import type {
|
||||
} from '../declarations'
|
||||
import { ModelTypeEnum } from '../declarations'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
@ -31,6 +31,7 @@ type SystemModelSelectorProps = {
|
||||
rerankDefaultModel: DefaultModelResponse | undefined
|
||||
speech2textDefaultModel: DefaultModelResponse | undefined
|
||||
ttsDefaultModel: DefaultModelResponse | undefined
|
||||
notConfigured: boolean
|
||||
}
|
||||
const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
textGenerationDefaultModel,
|
||||
@ -38,6 +39,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
rerankDefaultModel,
|
||||
speech2textDefaultModel,
|
||||
ttsDefaultModel,
|
||||
notConfigured,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
@ -128,14 +130,13 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<div className={`
|
||||
flex items-center px-2 h-6 text-xs text-gray-700 cursor-pointer bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs
|
||||
hover:bg-gray-100 hover:shadow-none
|
||||
${open && 'bg-gray-100 shadow-none'}
|
||||
`}>
|
||||
<Settings01 className='mr-1 w-3 h-3 text-gray-500' />
|
||||
<Button
|
||||
variant={notConfigured ? 'primary' : 'secondary'}
|
||||
size='small'
|
||||
>
|
||||
<RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
|
||||
{t('common.modelProvider.systemModelSettings')}
|
||||
</div>
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='pt-4 w-[360px] rounded-xl border-[0.5px] border-black/5 bg-white shadow-xl'>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import Badge from '../base/badge'
|
||||
import type { Plugin } from './types'
|
||||
@ -7,19 +7,20 @@ import Description from './card/base/description'
|
||||
import Icon from './card/base/card-icon'
|
||||
import Title from './card/base/title'
|
||||
import DownloadCount from './card/base/download-count'
|
||||
import { getLocaleOnServer } from '@/i18n/server'
|
||||
import type { Locale } from '@/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
locale: Locale // The component is used in both client and server side, so we can't get the locale from both side(getLocaleOnServer and useContext)
|
||||
payload: Plugin
|
||||
}
|
||||
|
||||
const PluginItem: FC<Props> = async ({
|
||||
className,
|
||||
locale,
|
||||
payload,
|
||||
}) => {
|
||||
const locale = getLocaleOnServer()
|
||||
const { org, label } = payload
|
||||
|
||||
return (
|
||||
|
74
web/app/components/plugins/provider-card.tsx
Normal file
74
web/app/components/plugins/provider-card.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { RiArrowRightUpLine, RiVerifiedBadgeLine } from '@remixicon/react'
|
||||
import Badge from '../base/badge'
|
||||
import type { Plugin } from './types'
|
||||
import Description from './card/base/description'
|
||||
import Icon from './card/base/card-icon'
|
||||
import Title from './card/base/title'
|
||||
import DownloadCount from './card/base/download-count'
|
||||
import Button from '@/app/components/base/button'
|
||||
import type { Locale } from '@/i18n'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
locale: Locale // The component is used in both client and server side, so we can't get the locale from both side(getLocaleOnServer and useContext)
|
||||
payload: Plugin
|
||||
}
|
||||
|
||||
const ProviderCard: FC<Props> = ({
|
||||
className,
|
||||
locale,
|
||||
payload,
|
||||
}) => {
|
||||
const { org, label } = payload
|
||||
|
||||
return (
|
||||
<div className={cn('group relative p-4 pb-3 border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg hover-bg-components-panel-on-panel-item-bg rounded-xl shadow-xs', className)}>
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<Icon src={payload.icon} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex items-center h-5">
|
||||
<Title title={label[locale]} />
|
||||
<RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />
|
||||
</div>
|
||||
<div className='mb-1 flex justify-between items-center h-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='text-text-tertiary system-xs-regular'>{org}</div>
|
||||
<div className='mx-2 text-text-quaternary system-xs-regular'>·</div>
|
||||
<DownloadCount downloadCount={payload.install_count || 0} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Description className='mt-3' text={payload.brief[locale]} descriptionLineRows={2}></Description>
|
||||
<div className='mt-3 flex space-x-0.5'>
|
||||
{['LLM', 'text embedding', 'speech2text'].map(tag => (
|
||||
<Badge key={tag} text={tag} />
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className='hidden group-hover:flex items-center gap-2 absolute bottom-0 left-0 right-0 p-4 pt-8'
|
||||
style={{ background: 'linear-gradient(0deg, #F9FAFB 60.27%, rgba(249, 250, 251, 0.00) 100%)' }}
|
||||
>
|
||||
<Button
|
||||
className='flex-grow'
|
||||
variant='primary'
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
<Button
|
||||
className='flex-grow'
|
||||
variant='secondary'
|
||||
>
|
||||
Details
|
||||
<RiArrowRightUpLine className='w-4 h-4' />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProviderCard
|
@ -142,6 +142,7 @@ const translation = {
|
||||
settings: {
|
||||
accountGroup: 'ACCOUNT',
|
||||
workplaceGroup: 'WORKSPACE',
|
||||
generalGroup: 'GENERAL',
|
||||
account: 'My account',
|
||||
members: 'Members',
|
||||
billing: 'Billing',
|
||||
@ -275,7 +276,7 @@ const translation = {
|
||||
},
|
||||
},
|
||||
modelProvider: {
|
||||
notConfigured: 'The system model has not yet been fully configured, and some functions may be unavailable.',
|
||||
notConfigured: 'The system model has not yet been fully configured',
|
||||
systemModelSettings: 'System Model Settings',
|
||||
systemModelSettingsLink: 'Why is it necessary to set up a system model?',
|
||||
selectModel: 'Select your model',
|
||||
@ -373,6 +374,12 @@ const translation = {
|
||||
loadBalancingLeastKeyWarning: 'To enable load balancing at least 2 keys must be enabled.',
|
||||
loadBalancingInfo: 'By default, load balancing uses the Round-robin strategy. If rate limiting is triggered, a 1-minute cooldown period will be applied.',
|
||||
upgradeForLoadBalancing: 'Upgrade your plan to enable Load Balancing.',
|
||||
configureRequired: 'Configure required',
|
||||
configureTip: 'Set up api-key or add model to use',
|
||||
installProvider: 'Install model providers',
|
||||
discoverMore: 'Discover more in ',
|
||||
emptyProviderTitle: 'Model provider not set up',
|
||||
emptyProviderTip: 'Please install a model provider first.',
|
||||
},
|
||||
dataSource: {
|
||||
add: 'Add a data source',
|
||||
|
@ -142,6 +142,7 @@ const translation = {
|
||||
settings: {
|
||||
accountGroup: '账户',
|
||||
workplaceGroup: '工作空间',
|
||||
generalGroup: '通用',
|
||||
account: '我的账户',
|
||||
members: '成员',
|
||||
billing: '账单',
|
||||
@ -275,7 +276,7 @@ const translation = {
|
||||
},
|
||||
},
|
||||
modelProvider: {
|
||||
notConfigured: '系统模型尚未完全配置,部分功能可能无法使用。',
|
||||
notConfigured: '系统模型尚未完全配置',
|
||||
systemModelSettings: '系统模型设置',
|
||||
systemModelSettingsLink: '为什么需要设置系统模型?',
|
||||
selectModel: '选择您的模型',
|
||||
@ -373,6 +374,12 @@ const translation = {
|
||||
loadBalancingInfo: '默认情况下,负载平衡使用 Round-robin 策略。如果触发速率限制,将应用 1 分钟的冷却时间',
|
||||
upgradeForLoadBalancing: '升级以解锁负载均衡功能',
|
||||
apiKey: 'API 密钥',
|
||||
configureRequired: '尚未配置',
|
||||
configureTip: '请配置 API 密钥,添加模型。',
|
||||
installProvider: '安装模型供应商',
|
||||
discoverMore: '发现更多就在',
|
||||
emptyProviderTitle: '尚未安装模型供应商',
|
||||
emptyProviderTip: '请安装模型供应商。',
|
||||
},
|
||||
dataSource: {
|
||||
add: '添加数据源',
|
||||
|
Loading…
x
Reference in New Issue
Block a user