Merge branch 'feat/plugins' of github.com:langgenius/dify into feat/plugins

This commit is contained in:
Yi 2024-10-11 16:28:04 +08:00
commit 3cb0a5bd68
13 changed files with 366 additions and 142 deletions

View File

@ -43,7 +43,7 @@ const PluginList = async () => {
<h3 className='my-1'>Install model provide</h3> <h3 className='my-1'>Install model provide</h3>
<div className='grid grid-cols-2 gap-3'> <div className='grid grid-cols-2 gap-3'>
{pluginList.map((plugin, index) => ( {pluginList.map((plugin, index) => (
<InstallModelItem key={index} payload={plugin as any} /> <InstallModelItem key={index} locale={locale} payload={plugin as any} />
))} ))}
</div> </div>

View File

@ -102,7 +102,7 @@ export default function AppSelector({ isMobile }: IAppSelector) {
</Menu.Item> </Menu.Item>
<div className="px-1 py-1"> <div className="px-1 py-1">
<Menu.Item> <Menu.Item>
<div className={itemClassName} onClick={() => setShowAccountSettingModal({ payload: 'account' })}> <div className={itemClassName} onClick={() => setShowAccountSettingModal({ payload: 'provider' })}>
<div>{t('common.userProfile.settings')}</div> <div>{t('common.userProfile.settings')}</div>
</div> </div>
</Menu.Item> </Menu.Item>

View File

@ -6,9 +6,8 @@ import {
RiAccountCircleLine, RiAccountCircleLine,
RiApps2AddFill, RiApps2AddFill,
RiApps2AddLine, RiApps2AddLine,
RiBox3Fill, RiBrainFill,
RiBox3Line, RiBrainLine,
RiCloseLine,
RiColorFilterFill, RiColorFilterFill,
RiColorFilterLine, RiColorFilterLine,
RiDatabase2Fill, RiDatabase2Fill,
@ -31,17 +30,14 @@ import ModelProviderPage from './model-provider-page'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import BillingPage from '@/app/components/billing/billing-page' import BillingPage from '@/app/components/billing/billing-page'
import CustomPage from '@/app/components/custom/custom-page' import CustomPage from '@/app/components/custom/custom-page'
import Modal from '@/app/components/base/modal'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { useAppContext } from '@/context/app-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 = ` const iconClassName = `
w-4 h-4 ml-3 mr-2 w-5 h-5 mr-2
`
const scrolledClassName = `
border-b shadow-xs bg-white/[.98]
` `
type IAccountSettingProps = { type IAccountSettingProps = {
@ -59,7 +55,7 @@ type GroupItem = {
export default function AccountSetting({ export default function AccountSetting({
onCancel, onCancel,
activeTab = 'account', activeTab = 'provider',
}: IAccountSettingProps) { }: IAccountSettingProps) {
const [activeMenu, setActiveMenu] = useState(activeTab) const [activeMenu, setActiveMenu] = useState(activeTab)
const { t } = useTranslation() const { t } = useTranslation()
@ -73,8 +69,8 @@ export default function AccountSetting({
{ {
key: 'provider', key: 'provider',
name: t('common.settings.provider'), name: t('common.settings.provider'),
icon: <RiBox3Line className={iconClassName} />, icon: <RiBrainLine className={iconClassName} />,
activeIcon: <RiBox3Fill className={iconClassName} />, activeIcon: <RiBrainFill className={iconClassName} />,
}, },
{ {
key: 'members', key: 'members',
@ -122,7 +118,7 @@ export default function AccountSetting({
}, },
{ {
key: 'account-group', key: 'account-group',
name: t('common.settings.accountGroup'), name: t('common.settings.generalGroup'),
items: [ items: [
{ {
key: 'account', key: 'account',
@ -161,32 +157,31 @@ export default function AccountSetting({
const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu) const activeItem = [...menuItems[0].items, ...menuItems[1].items].find(item => item.key === activeMenu)
const [searchValue, setSearchValue] = useState<string>('')
return ( return (
<Modal <MenuDialog
isShow show
onClose={() => { }} onClose={onCancel}
className='my-[60px] p-0 max-w-[1024px] rounded-xl overflow-y-auto'
wrapperClassName='pt-[60px]'
> >
<div className='flex'> <div className='mx-auto max-w-[1048px] h-[100vh] 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='w-[44px] sm:w-[224px] pl-4 pr-6 border-r border-divider-burn flex flex-col'>
<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='mt-6 mb-8 px-3 py-2 text-text-primary title-2xl-semi-bold'>{t('common.userProfile.settings')}</div>
<div className='w-full'> <div className='w-full'>
{ {
menuItems.map(menuItem => ( menuItems.map(menuItem => (
<div key={menuItem.key} className='mb-4'> <div key={menuItem.key} className='mb-2'>
{!isCurrentWorkspaceDatasetOperator && ( {!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> <div>
{ {
menuItem.items.map(item => ( menuItem.items.map(item => (
<div <div
key={item.key} key={item.key}
className={` className={cn(
flex items-center h-[37px] mb-[2px] text-sm cursor-pointer rounded-lg 'flex items-center mb-0.5 p-1 pl-3 h-[37px] text-sm cursor-pointer rounded-lg',
${activeMenu === item.key ? 'font-semibold text-primary-600 bg-primary-50' : 'font-light text-gray-700'} 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} title={item.name}
onClick={() => setActiveMenu(item.key)} onClick={() => setActiveMenu(item.key)}
> >
@ -201,18 +196,22 @@ export default function AccountSetting({
} }
</div> </div>
</div> </div>
<div ref={scrollRef} className='relative w-[824px] h-[720px] pb-4 overflow-y-auto'> <div ref={scrollRef} className='relative w-[824px] pb-4 bg-components-panel-bg 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={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'>{activeItem?.name}</div> <div className='shrink-0 text-text-primary title-2xl-semi-bold'>{activeItem?.name}</div>
{ {
activeItem?.description && ( activeItem?.description && (
<div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div> <div className='shrink-0 ml-2 text-xs text-gray-600'>{activeItem?.description}</div>
) )
} }
<div className='grow flex justify-end'> <div className='grow flex justify-end'>
<div className='flex items-center justify-center -mr-4 w-6 h-6 cursor-pointer' onClick={onCancel}> <Input
<RiCloseLine className='w-4 h-4 text-gray-400' /> showLeftIcon
</div> wrapperClassName='!w-[200px]'
className='!h-8 !text-[13px]'
onChange={e => setSearchValue(e.target.value)}
value={searchValue}
/>
</div> </div>
</div> </div>
<div className='px-4 sm:px-8 pt-2'> <div className='px-4 sm:px-8 pt-2'>
@ -228,6 +227,6 @@ export default function AccountSetting({
</div> </div>
</div> </div>
</div> </div>
</Modal> </MenuDialog>
) )
} }

View 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

View File

@ -1,8 +1,16 @@
import { useMemo } from 'react' import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next' 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 SystemModelSelector from './system-model-selector'
import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card' import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
import ProviderCard from './provider-card' // import ProviderCard from './provider-card'
import type { import type {
CustomConfigurationModelFixedFields, CustomConfigurationModelFixedFields,
ModelProvider, ModelProvider,
@ -17,10 +25,15 @@ import {
useUpdateModelList, useUpdateModelList,
useUpdateModelProviders, useUpdateModelProviders,
} from './hooks' } 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 { useProviderContext } from '@/context/provider-context'
import { useModalContextSelector } from '@/context/modal-context' import { useModalContextSelector } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter' 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 ModelProviderPage = () => {
const { t } = useTranslation() const { t } = useTranslation()
@ -57,25 +70,25 @@ const ModelProviderPage = () => {
const handleOpenModal = ( const handleOpenModal = (
provider: ModelProvider, provider: ModelProvider,
configurateMethod: ConfigurationMethodEnum, configurationMethod: ConfigurationMethodEnum,
CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields, CustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields,
) => { ) => {
setShowModelModal({ setShowModelModal({
payload: { payload: {
currentProvider: provider, currentProvider: provider,
currentConfigurationMethod: configurateMethod, currentConfigurationMethod: configurationMethod,
currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields, currentCustomConfigurationModelFixedFields: CustomConfigurationModelFixedFields,
}, },
onSaveCallback: () => { onSaveCallback: () => {
updateModelProviders() updateModelProviders()
if (configurateMethod === ConfigurationMethodEnum.predefinedModel) { if (configurationMethod === ConfigurationMethodEnum.predefinedModel) {
provider.supported_model_types.forEach((type) => { provider.supported_model_types.forEach((type) => {
updateModelList(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({ eventEmitter?.emit({
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST, type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
payload: provider.provider, 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 ( return (
<div className='relative pt-1 -mt-2'> <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]'}`}> <div className={cn('flex items-center mb-2')}>
{ <div className='grow text-text-primary system-md-semibold'>{t('common.modelProvider.models')}</div>
defaultModelNotConfigured <div className={cn(
? ( 'shrink-0 relative flex items-center justify-end gap-2 p-0.5 rounded-lg border border-transparent',
<div className='flex items-center text-xs font-medium text-gray-700'> defaultModelNotConfigured && 'pl-2 bg-components-panel-bg-blur border-components-panel-border shadow-xs',
<AlertTriangle className='mr-1 w-3 h-3 text-[#F79009]' /> )}>
{t('common.modelProvider.notConfigured')} {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%)' }}/>}
</div> {defaultModelNotConfigured && (
) <div className='flex items-center gap-1 text-text-primary system-xs-medium'>
: <div className='text-sm font-medium text-gray-800'>{t('common.modelProvider.models')}</div> <RiAlertFill className='w-4 h-4 text-text-warning-secondary' />
} {t('common.modelProvider.notConfigured')}
<SystemModelSelector </div>
textGenerationDefaultModel={textGenerationDefaultModel} )}
embeddingsDefaultModel={embeddingsDefaultModel} <SystemModelSelector
rerankDefaultModel={rerankDefaultModel} notConfigured={defaultModelNotConfigured}
speech2textDefaultModel={speech2textDefaultModel} textGenerationDefaultModel={textGenerationDefaultModel}
ttsDefaultModel={ttsDefaultModel} embeddingsDefaultModel={embeddingsDefaultModel}
/> rerankDefaultModel={rerankDefaultModel}
speech2textDefaultModel={speech2textDefaultModel}
ttsDefaultModel={ttsDefaultModel}
/>
</div>
</div> </div>
{ {!configuredProviders?.length && (
!!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='pb-3'> <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' />
configuredProviders?.map(provider => (
<ProviderAddedCard
key={provider.provider}
provider={provider}
onOpenModal={(configurateMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurateMethod, currentCustomConfigurationModelFixedFields)}
/>
))
}
</div> </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>
!!notConfiguredProviders?.length && ( )}
<> {!!configuredProviders?.length && (
<div className='flex items-center mb-2 text-xs font-semibold text-gray-500'> <div className='relative'>
+ {t('common.modelProvider.addMoreModelProvider')} {configuredProviders?.map(provider => (
<span className='grow ml-3 h-[1px] bg-gradient-to-r from-[#f3f4f6]' /> <ProviderAddedCard
</div> key={provider.provider}
<div className='grid grid-cols-3 gap-2'> provider={provider}
{ onOpenModal={(configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => handleOpenModal(provider, configurationMethod, currentCustomConfigurationModelFixedFields)}
notConfiguredProviders?.map(provider => ( />
<ProviderCard ))}
key={provider.provider} </div>
provider={provider} )}
onOpenModal={(configurateMethod: ConfigurationMethodEnum) => handleOpenModal(provider, configurateMethod)} {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'>
</div> {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> </div>
) )
} }

View File

@ -1,5 +1,6 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiEqualizer2Line } from '@remixicon/react'
import type { ModelProvider } from '../declarations' import type { ModelProvider } from '../declarations'
import { import {
ConfigurationMethodEnum, ConfigurationMethodEnum,
@ -14,7 +15,6 @@ import PrioritySelector from './priority-selector'
import PriorityUseTip from './priority-use-tip' import PriorityUseTip from './priority-use-tip'
import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index' import { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './index'
import Indicator from '@/app/components/header/indicator' 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 Button from '@/app/components/base/button'
import { changeModelProviderPriority } from '@/service/common' import { changeModelProviderPriority } from '@/service/common'
import { useToastContext } from '@/app/components/base/toast' 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='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'> <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 API-KEY
<Indicator color={isCustomConfigured ? 'green' : 'gray'} /> <Indicator color={isCustomConfigured ? 'green' : 'red'} />
</div> </div>
<div className='flex items-center gap-0.5'> <div className='flex items-center gap-0.5'>
<Button <Button
@ -77,7 +77,7 @@ const CredentialPanel: FC<CredentialPanelProps> = ({
size='small' size='small'
onClick={onSetup} onClick={onSetup}
> >
<Settings01 className='mr-1 w-3 h-3' /> <RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
{t('common.operation.setup')} {t('common.operation.setup')}
</Button> </Button>
{ {

View File

@ -2,6 +2,8 @@ import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
RiArrowRightSLine,
RiInformation2Fill,
RiLoader2Line, RiLoader2Line,
} from '@remixicon/react' } from '@remixicon/react'
import type { import type {
@ -21,7 +23,6 @@ import CredentialPanel from './credential-panel'
import QuotaPanel from './quota-panel' import QuotaPanel from './quota-panel'
import ModelList from './model-list' import ModelList from './model-list'
import AddModelButton from './add-model-button' import AddModelButton from './add-model-button'
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
import { fetchModelProviderModelList } from '@/service/common' import { fetchModelProviderModelList } from '@/service/common'
import { useEventEmitterContextContext } from '@/context/event-emitter' import { useEventEmitterContextContext } from '@/context/event-emitter'
import { IS_CE_EDITION } from '@/config' 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' export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
type ProviderAddedCardProps = { type ProviderAddedCardProps = {
notConfigured?: boolean
provider: ModelProvider provider: ModelProvider
onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void onOpenModal: (configurationMethod: ConfigurationMethodEnum, currentCustomConfigurationModelFixedFields?: CustomConfigurationModelFixedFields) => void
} }
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
notConfigured,
provider, provider,
onOpenModal, onOpenModal,
}) => { }) => {
@ -47,6 +50,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
const hasModelList = fetched && !!modelList.length const hasModelList = fetched && !!modelList.length
const { isCurrentWorkspaceManager } = useAppContext() const { isCurrentWorkspaceManager } = useAppContext()
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION 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) => { const getModelList = async (providerName: string) => {
if (loading) if (loading)
@ -105,7 +109,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
) )
} }
{ {
configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager && ( showCredential && (
<CredentialPanel <CredentialPanel
onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)} onSetup={() => onOpenModal(ConfigurationMethodEnum.predefinedModel)}
provider={provider} provider={provider}
@ -116,34 +120,45 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
{ {
collapsed && ( 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 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'> {(showQuota || !notConfigured) && (
{ <>
hasModelList <div className='group-hover:hidden flex items-center pl-1 pr-1.5 h-6 leading-6'>
? t('common.modelProvider.modelsNum', { num: modelList.length }) {
: t('common.modelProvider.showModels') hasModelList
} ? t('common.modelProvider.modelsNum', { num: modelList.length })
</div> : t('common.modelProvider.showModels')
<div }
className='hidden group-hover:flex items-center pl-1 pr-1.5 h-6 rounded-lg hover:bg-white cursor-pointer' {!loading && <RiArrowRightSLine className='w-4 h-4' />}
onClick={handleOpenModelList} </div>
> <div
<ChevronDownDouble className='mr-0.5 w-3 h-3' /> 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') hasModelList
} ? t('common.modelProvider.showModelsNum', { num: modelList.length })
{ : t('common.modelProvider.showModels')
loading && ( }
<RiLoader2Line className='ml-0.5 animate-spin w-3 h-3' /> {!loading && <RiArrowRightSLine className='w-4 h-4' />}
) {
} loading && (
</div> <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 && ( configurationMethods.includes(ConfigurationMethodEnum.customizableModel) && isCurrentWorkspaceManager && (
<AddModelButton <AddModelButton
onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)} onClick={() => onOpenModal(ConfigurationMethodEnum.customizableModel)}
className='hidden group-hover:flex group-hover:text-primary-600' className='flex'
/> />
) )
} }

View File

@ -1,6 +1,9 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import {
RiArrowRightSLine,
} from '@remixicon/react'
import type { import type {
CustomConfigurationModelFixedFields, CustomConfigurationModelFixedFields,
ModelItem, ModelItem,
@ -12,7 +15,6 @@ import {
// import Tab from './tab' // import Tab from './tab'
import AddModelButton from './add-model-button' import AddModelButton from './add-model-button'
import ModelListItem from './model-list-item' import ModelListItem from './model-list-item'
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
import { useModalContextSelector } from '@/context/modal-context' import { useModalContextSelector } from '@/context/modal-context'
import { useAppContext } from '@/context/app-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='py-1 bg-white rounded-lg'>
<div className='flex items-center pl-1 pr-[3px]'> <div className='flex items-center pl-1 pr-[3px]'>
<span className='group shrink-0 flex items-center mr-2'> <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 })} {t('common.modelProvider.modelsNum', { num: models.length })}
<RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
</span> </span>
<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' 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()} onClick={() => onCollapse()}
> >
<ChevronDownDouble className='mr-0.5 w-3 h-3 rotate-180' /> {t('common.modelProvider.modelsNum', { num: models.length })}
{t('common.modelProvider.collapse')} <RiArrowRightSLine className='mr-0.5 w-4 h-4 rotate-90' />
</span> </span>
</span> </span>
{/* { {/* {

View File

@ -1,6 +1,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { RiEqualizer2Line } from '@remixicon/react'
import ModelSelector from '../model-selector' import ModelSelector from '../model-selector'
import { import {
useModelList, useModelList,
@ -13,7 +14,6 @@ import type {
} from '../declarations' } from '../declarations'
import { ModelTypeEnum } from '../declarations' import { ModelTypeEnum } from '../declarations'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
import { import {
PortalToFollowElem, PortalToFollowElem,
PortalToFollowElemContent, PortalToFollowElemContent,
@ -31,6 +31,7 @@ type SystemModelSelectorProps = {
rerankDefaultModel: DefaultModelResponse | undefined rerankDefaultModel: DefaultModelResponse | undefined
speech2textDefaultModel: DefaultModelResponse | undefined speech2textDefaultModel: DefaultModelResponse | undefined
ttsDefaultModel: DefaultModelResponse | undefined ttsDefaultModel: DefaultModelResponse | undefined
notConfigured: boolean
} }
const SystemModel: FC<SystemModelSelectorProps> = ({ const SystemModel: FC<SystemModelSelectorProps> = ({
textGenerationDefaultModel, textGenerationDefaultModel,
@ -38,6 +39,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
rerankDefaultModel, rerankDefaultModel,
speech2textDefaultModel, speech2textDefaultModel,
ttsDefaultModel, ttsDefaultModel,
notConfigured,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { notify } = useToastContext() const { notify } = useToastContext()
@ -128,14 +130,13 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
}} }}
> >
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}> <PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<div className={` <Button
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 variant={notConfigured ? 'primary' : 'secondary'}
hover:bg-gray-100 hover:shadow-none size='small'
${open && 'bg-gray-100 shadow-none'} >
`}> <RiEqualizer2Line className='mr-1 w-3.5 h-3.5' />
<Settings01 className='mr-1 w-3 h-3 text-gray-500' />
{t('common.modelProvider.systemModelSettings')} {t('common.modelProvider.systemModelSettings')}
</div> </Button>
</PortalToFollowElemTrigger> </PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-50'> <PortalToFollowElemContent className='z-50'>
<div className='pt-4 w-[360px] rounded-xl border-[0.5px] border-black/5 bg-white shadow-xl'> <div className='pt-4 w-[360px] rounded-xl border-[0.5px] border-black/5 bg-white shadow-xl'>

View File

@ -1,5 +1,5 @@
import type { FC } from 'react'
import React from 'react' import React from 'react'
import type { FC } from 'react'
import { RiVerifiedBadgeLine } from '@remixicon/react' import { RiVerifiedBadgeLine } from '@remixicon/react'
import Badge from '../base/badge' import Badge from '../base/badge'
import type { Plugin } from './types' import type { Plugin } from './types'
@ -7,19 +7,20 @@ import Description from './card/base/description'
import Icon from './card/base/card-icon' import Icon from './card/base/card-icon'
import Title from './card/base/title' import Title from './card/base/title'
import DownloadCount from './card/base/download-count' import DownloadCount from './card/base/download-count'
import { getLocaleOnServer } from '@/i18n/server' import type { Locale } from '@/i18n'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
className?: string 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 payload: Plugin
} }
const PluginItem: FC<Props> = async ({ const PluginItem: FC<Props> = async ({
className, className,
locale,
payload, payload,
}) => { }) => {
const locale = getLocaleOnServer()
const { org, label } = payload const { org, label } = payload
return ( return (

View 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

View File

@ -142,6 +142,7 @@ const translation = {
settings: { settings: {
accountGroup: 'ACCOUNT', accountGroup: 'ACCOUNT',
workplaceGroup: 'WORKSPACE', workplaceGroup: 'WORKSPACE',
generalGroup: 'GENERAL',
account: 'My account', account: 'My account',
members: 'Members', members: 'Members',
billing: 'Billing', billing: 'Billing',
@ -275,7 +276,7 @@ const translation = {
}, },
}, },
modelProvider: { 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', systemModelSettings: 'System Model Settings',
systemModelSettingsLink: 'Why is it necessary to set up a system model?', systemModelSettingsLink: 'Why is it necessary to set up a system model?',
selectModel: 'Select your model', selectModel: 'Select your model',
@ -373,6 +374,12 @@ const translation = {
loadBalancingLeastKeyWarning: 'To enable load balancing at least 2 keys must be enabled.', 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.', 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.', 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: { dataSource: {
add: 'Add a data source', add: 'Add a data source',

View File

@ -142,6 +142,7 @@ const translation = {
settings: { settings: {
accountGroup: '账户', accountGroup: '账户',
workplaceGroup: '工作空间', workplaceGroup: '工作空间',
generalGroup: '通用',
account: '我的账户', account: '我的账户',
members: '成员', members: '成员',
billing: '账单', billing: '账单',
@ -275,7 +276,7 @@ const translation = {
}, },
}, },
modelProvider: { modelProvider: {
notConfigured: '系统模型尚未完全配置,部分功能可能无法使用。', notConfigured: '系统模型尚未完全配置',
systemModelSettings: '系统模型设置', systemModelSettings: '系统模型设置',
systemModelSettingsLink: '为什么需要设置系统模型?', systemModelSettingsLink: '为什么需要设置系统模型?',
selectModel: '选择您的模型', selectModel: '选择您的模型',
@ -373,6 +374,12 @@ const translation = {
loadBalancingInfo: '默认情况下,负载平衡使用 Round-robin 策略。如果触发速率限制,将应用 1 分钟的冷却时间', loadBalancingInfo: '默认情况下,负载平衡使用 Round-robin 策略。如果触发速率限制,将应用 1 分钟的冷却时间',
upgradeForLoadBalancing: '升级以解锁负载均衡功能', upgradeForLoadBalancing: '升级以解锁负载均衡功能',
apiKey: 'API 密钥', apiKey: 'API 密钥',
configureRequired: '尚未配置',
configureTip: '请配置 API 密钥,添加模型。',
installProvider: '安装模型供应商',
discoverMore: '发现更多就在',
emptyProviderTitle: '尚未安装模型供应商',
emptyProviderTip: '请安装模型供应商。',
}, },
dataSource: { dataSource: {
add: '添加数据源', add: '添加数据源',