Model Runtime (#1858)
Co-authored-by: StyleZhang <jasonapring2015@outlook.com> Co-authored-by: Garfield Dai <dai.hai@foxmail.com> Co-authored-by: chenhe <guchenhe@gmail.com> Co-authored-by: jyong <jyong@dify.ai> Co-authored-by: Joel <iamjoel007@gmail.com> Co-authored-by: Yeuoly <admin@srmxy.cn>
This commit is contained in:
parent
e91dd28a76
commit
d069c668f8
807 changed files with 171310 additions and 23806 deletions
|
|
@ -0,0 +1,29 @@
|
|||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PlusCircle } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
|
||||
type AddModelButtonProps = {
|
||||
className?: string
|
||||
onClick: () => void
|
||||
}
|
||||
const AddModelButton: FC<AddModelButtonProps> = ({
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`
|
||||
shrink-0 flex items-center px-1.5 h-6 text-xs font-medium text-gray-500 cursor-pointer
|
||||
hover:bg-primary-50 hover:text-primary-600 rounded-md ${className}
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<PlusCircle className='mr-1 w-3 h-3' />
|
||||
{t('common.modelProvider.addModel')}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddModelButton
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import {
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
} from '../declarations'
|
||||
import { useUpdateModelList } from '../hooks'
|
||||
import PrioritySelector from './priority-selector'
|
||||
import PriorityUseTip from './priority-use-tip'
|
||||
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'
|
||||
|
||||
type CredentialPanelProps = {
|
||||
provider: ModelProvider
|
||||
onSetup: () => void
|
||||
}
|
||||
const CredentialPanel: FC<CredentialPanelProps> = ({
|
||||
provider,
|
||||
onSetup,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
const { mutate } = useSWRConfig()
|
||||
const updateModelList = useUpdateModelList()
|
||||
const customConfig = provider.custom_configuration
|
||||
const systemConfig = provider.system_configuration
|
||||
const priorityUseType = provider.preferred_provider_type
|
||||
const customConfiged = customConfig.status === CustomConfigurationStatusEnum.active
|
||||
|
||||
const handleChangePriority = async (key: PreferredProviderTypeEnum) => {
|
||||
const res = await changeModelProviderPriority({
|
||||
url: `/workspaces/current/model-providers/${provider.provider}/preferred-provider-type`,
|
||||
body: {
|
||||
preferred_provider_type: key,
|
||||
},
|
||||
})
|
||||
if (res.result === 'success') {
|
||||
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
|
||||
mutate('/workspaces/current/model-providers')
|
||||
updateModelList(1)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<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={customConfiged ? 'green' : 'gray'} />
|
||||
</div>
|
||||
<div className='flex items-center gap-0.5'>
|
||||
<Button
|
||||
className='grow px-0 h-6 bg-white text-xs font-medium rounded-md'
|
||||
onClick={onSetup}
|
||||
>
|
||||
<Settings01 className='mr-1 w-3 h-3' />
|
||||
{t('common.operation.setup')}
|
||||
</Button>
|
||||
{
|
||||
systemConfig.enabled && customConfiged && (
|
||||
<PrioritySelector
|
||||
value={priorityUseType}
|
||||
onSelect={handleChangePriority}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
priorityUseType === PreferredProviderTypeEnum.custom && systemConfig.enabled && (
|
||||
<PriorityUseTip />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CredentialPanel
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import type { FC } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
CustomConfigrationModelFixedFields,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import { ConfigurateMethodEnum } from '../declarations'
|
||||
import {
|
||||
DEFAULT_BACKGROUND_COLOR,
|
||||
modelTypeFormat,
|
||||
} from '../utils'
|
||||
import ProviderIcon from '../provider-icon'
|
||||
import ModelBadge from '../model-badge'
|
||||
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 { Loading02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { fetchModelProviderModelList } from '@/service/common'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||
type ProviderAddedCardProps = {
|
||||
provider: ModelProvider
|
||||
onOpenModal: (configurateMethod: ConfigurateMethodEnum, currentCustomConfigrationModelFixedFields?: CustomConfigrationModelFixedFields) => void
|
||||
}
|
||||
const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
provider,
|
||||
onOpenModal,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [fetched, setFetched] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [collapsed, setCollapsed] = useState(true)
|
||||
const [modelList, setModelList] = useState<ModelItem[]>([])
|
||||
const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote)
|
||||
const systemConfig = provider.system_configuration
|
||||
const hasModelList = fetched && !!modelList.length
|
||||
const showQuota = systemConfig.enabled || ['minimax', 'spark', 'zhipuai', 'anthropic'].includes(provider.provider)
|
||||
|
||||
const getModelList = async (providerName: string) => {
|
||||
if (loading)
|
||||
return
|
||||
try {
|
||||
setLoading(true)
|
||||
const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${providerName}/models`)
|
||||
setModelList(modelsData.data)
|
||||
setCollapsed(false)
|
||||
setFetched(true)
|
||||
}
|
||||
finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
const handleOpenModelList = () => {
|
||||
if (fetched) {
|
||||
setCollapsed(false)
|
||||
return
|
||||
}
|
||||
|
||||
getModelList(provider.provider)
|
||||
}
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST && v.payload === provider.provider)
|
||||
getModelList(v.payload)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className='mb-2 rounded-xl border-[0.5px] border-black/5 shadow-xs'
|
||||
style={{ background: provider.background || DEFAULT_BACKGROUND_COLOR }}
|
||||
>
|
||||
<div className='flex pl-3 py-2 pr-2 rounded-t-xl'>
|
||||
<div className='grow px-1 pt-1 pb-0.5'>
|
||||
<ProviderIcon
|
||||
className='mb-2'
|
||||
provider={provider}
|
||||
/>
|
||||
<div className='flex gap-0.5'>
|
||||
{
|
||||
provider.supported_model_types.map(modelType => (
|
||||
<ModelBadge key={modelType}>
|
||||
{modelTypeFormat(modelType)}
|
||||
</ModelBadge>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
showQuota && (
|
||||
<QuotaPanel
|
||||
provider={provider}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
configurateMethods.includes(ConfigurateMethodEnum.predefinedModel) && (
|
||||
<CredentialPanel
|
||||
onSetup={() => onOpenModal(ConfigurateMethodEnum.predefinedModel)}
|
||||
provider={provider}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
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 && (
|
||||
<Loading02 className='ml-0.5 animate-spin w-3 h-3' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
configurateMethods.includes(ConfigurateMethodEnum.customizableModel) && (
|
||||
<AddModelButton
|
||||
onClick={() => onOpenModal(ConfigurateMethodEnum.customizableModel)}
|
||||
className='hidden group-hover:flex group-hover:text-primary-600'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!collapsed && (
|
||||
<ModelList
|
||||
provider={provider}
|
||||
models={modelList}
|
||||
onCollapse={() => setCollapsed(true)}
|
||||
onConfig={currentCustomConfigrationModelFixedFields => onOpenModal(ConfigurateMethodEnum.customizableModel, currentCustomConfigrationModelFixedFields)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProviderAddedCard
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
CustomConfigrationModelFixedFields,
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import {
|
||||
ConfigurateMethodEnum,
|
||||
ModelStatusEnum,
|
||||
} from '../declarations'
|
||||
import { useLanguage } from '../hooks'
|
||||
import ModelIcon from '../model-icon'
|
||||
import ModelName from '../model-name'
|
||||
// import Tab from './tab'
|
||||
import AddModelButton from './add-model-button'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { Settings01 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type ModelListProps = {
|
||||
provider: ModelProvider
|
||||
models: ModelItem[]
|
||||
onCollapse: () => void
|
||||
onConfig: (currentCustomConfigrationModelFixedFields?: CustomConfigrationModelFixedFields) => void
|
||||
}
|
||||
const ModelList: FC<ModelListProps> = ({
|
||||
provider,
|
||||
models,
|
||||
onCollapse,
|
||||
onConfig,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const language = useLanguage()
|
||||
const configurateMethods = provider.configurate_methods.filter(method => method !== ConfigurateMethodEnum.fetchFromRemote)
|
||||
const canCustomConfig = configurateMethods.includes(ConfigurateMethodEnum.customizableModel)
|
||||
// const canSystemConfig = configurateMethods.includes(ConfigurateMethodEnum.predefinedModel)
|
||||
|
||||
return (
|
||||
<div className='px-2 pb-2 rounded-b-xl'>
|
||||
<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'>
|
||||
{t('common.modelProvider.modelsNum', { num: models.length })}
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
hidden group-hover:inline-flex items-center pl-1 pr-1.5 h-6 bg-gray-50
|
||||
text-xs font-medium text-gray-500 cursor-pointer rounded-lg
|
||||
`}
|
||||
onClick={() => onCollapse()}
|
||||
>
|
||||
<ChevronDownDouble className='mr-0.5 w-3 h-3 rotate-180' />
|
||||
{t('common.modelProvider.collapse')}
|
||||
</span>
|
||||
</span>
|
||||
{/* {
|
||||
canCustomConfig && canSystemConfig && (
|
||||
<span className='flex items-center'>
|
||||
<Tab active='all' onSelect={() => {}} />
|
||||
</span>
|
||||
)
|
||||
} */}
|
||||
{
|
||||
canCustomConfig && (
|
||||
<div className='grow flex justify-end'>
|
||||
<AddModelButton onClick={() => onConfig()} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
models.map(model => (
|
||||
<div
|
||||
key={model.model}
|
||||
className={`
|
||||
group flex items-center pl-2 pr-2.5 h-8 rounded-lg
|
||||
${canCustomConfig && 'hover:bg-gray-50'}
|
||||
`}
|
||||
>
|
||||
<div className='shrink-0 mr-2' style={{ background: provider.icon_small[language] }} />
|
||||
<ModelIcon
|
||||
className='shrink-0 mr-2'
|
||||
provider={provider}
|
||||
modelName={model.model}
|
||||
/>
|
||||
<ModelName
|
||||
className='grow text-sm font-normal text-gray-900'
|
||||
modelItem={model}
|
||||
showModelType
|
||||
showMode
|
||||
showContextSize
|
||||
/>
|
||||
<div className='shrink-0 flex items-center'>
|
||||
{
|
||||
model.fetch_from === ConfigurateMethodEnum.customizableModel && (
|
||||
<Button
|
||||
className='hidden group-hover:flex py-0 h-7 text-xs font-medium text-gray-700'
|
||||
onClick={() => onConfig({ __model_name: model.model, __model_type: model.model_type })}
|
||||
>
|
||||
<Settings01 className='mr-[5px] w-3.5 h-3.5' />
|
||||
{t('common.modelProvider.config')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Indicator
|
||||
className='ml-2.5'
|
||||
color={model.status === ModelStatusEnum.active ? 'green' : 'gray'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelList
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import { Fragment } from 'react'
|
||||
import type { FC } from 'react'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { PreferredProviderTypeEnum } from '../declarations'
|
||||
import { Check, DotsHorizontal } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type SelectorProps = {
|
||||
value?: string
|
||||
onSelect: (key: PreferredProviderTypeEnum) => void
|
||||
}
|
||||
const Selector: FC<SelectorProps> = ({
|
||||
value,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const options = [
|
||||
{
|
||||
key: PreferredProviderTypeEnum.custom,
|
||||
text: 'API',
|
||||
},
|
||||
{
|
||||
key: PreferredProviderTypeEnum.system,
|
||||
text: t('common.modelProvider.quota'),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<Popover className='relative'>
|
||||
<Popover.Button>
|
||||
{
|
||||
({ open }) => (
|
||||
<Button className={`
|
||||
px-0 w-6 h-6 bg-white rounded-md
|
||||
${open && '!bg-gray-100'}
|
||||
`}>
|
||||
<DotsHorizontal className='w-3 h-3 text-gray-700' />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave='transition ease-in duration-100'
|
||||
leaveFrom='opacity-100'
|
||||
leaveTo='opacity-0'
|
||||
>
|
||||
<Popover.Panel className='absolute top-7 right-0 w-[144px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg z-10'>
|
||||
<div className='p-1'>
|
||||
<div className='px-3 pt-2 pb-1 text-sm font-medium text-gray-700'>{t('common.modelProvider.card.priorityUse')}</div>
|
||||
{
|
||||
options.map(option => (
|
||||
<Popover.Button as={Fragment} key={option.key}>
|
||||
<div
|
||||
className='flex items-center justify-between px-3 h-9 text-sm text-gray-700 rounded-lg cursor-pointer hover:bg-gray-50'
|
||||
onClick={() => onSelect(option.key)}
|
||||
>
|
||||
<div className='grow'>{option.text}</div>
|
||||
{value === option.key && <Check className='w-4 h-4 text-primary-600' />}
|
||||
</div>
|
||||
</Popover.Button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default Selector
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
|
||||
const PriorityUseTip = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
selector='provider-quota-credential-priority-using'
|
||||
content={t('common.modelProvider.priorityUsing') || ''}
|
||||
>
|
||||
<div className='absolute -right-[5px] -top-[5px] bg-indigo-50 rounded-[5px] border-[0.5px] border-indigo-100 cursor-pointer'>
|
||||
<ChevronDownDouble className='rotate-180 w-3 h-3 text-indigo-600' />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default PriorityUseTip
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import type { FC } from 'react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import {
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
QuotaUnitEnum,
|
||||
} from '../declarations'
|
||||
import {
|
||||
useAnthropicBuyQuota,
|
||||
useFreeQuota,
|
||||
} from '../hooks'
|
||||
import PriorityUseTip from './priority-use-tip'
|
||||
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type QuotaPanelProps = {
|
||||
provider: ModelProvider
|
||||
}
|
||||
const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
provider,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mutate } = useSWRConfig()
|
||||
const handlePay = useAnthropicBuyQuota()
|
||||
const handleFreeQuotaSuccess = () => {
|
||||
mutate('/workspaces/current/model-providers')
|
||||
}
|
||||
const handleFreeQuota = useFreeQuota(handleFreeQuotaSuccess)
|
||||
const customConfig = provider.custom_configuration
|
||||
const priorityUseType = provider.preferred_provider_type
|
||||
const systemConfig = provider.system_configuration
|
||||
const currentQuota = systemConfig.enabled && systemConfig.quota_configurations.find(item => item.quota_type === systemConfig.current_quota_type)
|
||||
|
||||
return (
|
||||
<div className='group relative shrink-0 min-w-[112px] px-3 py-2 rounded-lg bg-white/[0.3] border-[0.5px] border-black/5'>
|
||||
<div className='flex items-center mb-2 h-4 text-xs font-medium text-gray-500'>
|
||||
{t('common.modelProvider.quota')}
|
||||
<InfoCircle className='ml-0.5 w-3 h-3 text-gray-400' />
|
||||
</div>
|
||||
{
|
||||
currentQuota && (
|
||||
<div className='flex items-center h-4 text-xs text-gray-500'>
|
||||
<span className='mr-0.5 text-sm font-semibold text-gray-700'>{(currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0)}</span>
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
|
||||
}
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.times && t('common.modelProvider.callTimes')
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!currentQuota && provider.provider === 'anthropic' && (
|
||||
<Button
|
||||
className='h-6 bg-white text-xs font-medium rounded-md'
|
||||
onClick={handlePay}
|
||||
>
|
||||
{t('common.modelProvider.buyQuota')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
!currentQuota && ['minimax', 'spark', 'zhipuai'].includes(provider.provider) && (
|
||||
<Button
|
||||
className='h-6 bg-white text-xs font-medium rounded-md'
|
||||
onClick={() => handleFreeQuota(provider.provider)}
|
||||
>
|
||||
{t('common.modelProvider.getFreeTokens')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{
|
||||
provider.provider === 'anthropic' && systemConfig.enabled && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-0 bottom-0 hidden group-hover:flex items-center justify-center
|
||||
w-full h-[30px] backdrop-blur-[2px] bg-gradient-to-r from-[rgba(238,244,255,0.80)] to-[rgba(237,237,240,0.70)]
|
||||
text-xs font-medium text-primary-600 cursor-pointer rounded-b-lg
|
||||
`}
|
||||
onClick={handlePay}
|
||||
>
|
||||
{t('common.modelProvider.buyQuota')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
priorityUseType === PreferredProviderTypeEnum.system && customConfig.status === CustomConfigurationStatusEnum.active && (
|
||||
<PriorityUseTip />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuotaPanel
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import type { FC } from 'react'
|
||||
|
||||
type TabProps = {
|
||||
active: string
|
||||
onSelect: (active: string) => void
|
||||
}
|
||||
const Tab: FC<TabProps> = ({
|
||||
active,
|
||||
onSelect,
|
||||
}) => {
|
||||
const tabs = [
|
||||
{
|
||||
key: 'all',
|
||||
text: 'All',
|
||||
},
|
||||
{
|
||||
key: 'added',
|
||||
text: 'Added',
|
||||
},
|
||||
{
|
||||
key: 'build-in',
|
||||
text: 'Build-in',
|
||||
},
|
||||
]
|
||||
return (
|
||||
<div className='flex items-center'>
|
||||
{
|
||||
tabs.map(tab => (
|
||||
<div
|
||||
key={tab.key}
|
||||
className={`
|
||||
flex items-center mr-1 px-[5px] h-[18px] rounded-md text-xs cursor-pointer
|
||||
${active === tab.key ? 'bg-gray-200 font-medium text-gray-900' : 'text-gray-500 font-normal'}
|
||||
`}
|
||||
onClick={() => onSelect(tab.key)}
|
||||
>
|
||||
{tab.text}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tab
|
||||
Loading…
Add table
Add a link
Reference in a new issue