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:
takatost 2024-01-02 23:42:00 +08:00 committed by GitHub
commit d069c668f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
807 changed files with 171310 additions and 23806 deletions

View file

@ -0,0 +1,265 @@
import type { AnnotationItem, HitHistoryItem } from './type'
const list: AnnotationItem[] = [
// create some mock data
{
id: '1',
question: 'What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?',
answer: 'What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?',
created_at: '2020-01-01T00:00:00Z',
hit_count: 1,
},
{
id: '2',
question: 'What is the capital of Canada?',
answer: 'Ottawa',
created_at: '2020-01-02T00:00:00Z',
hit_count: 2,
},
{
id: '3',
question: 'What is the capital of Mexico?',
answer: 'Mexico City',
created_at: '2020-01-03T00:00:00Z',
hit_count: 3,
},
{
id: '4',
question: 'What is the capital of Brazil?',
answer: 'Brasilia',
created_at: '2020-01-04T00:00:00Z',
hit_count: 4,
},
{
id: '5',
question: 'What is the capital of Argentina?',
answer: 'Buenos Aires',
created_at: '2020-01-05T00:00:00Z',
hit_count: 5,
},
{
id: '6',
question: 'What is the capital of Chile?',
answer: 'Santiago',
created_at: '2020-01-06T00:00:00Z',
hit_count: 6,
},
{
id: '7',
question: 'What is the capital of Peru?',
answer: 'Lima',
created_at: '2020-01-07T00:00:00Z',
hit_count: 7,
},
{
id: '8',
question: 'What is the capital of Ecuador?',
answer: 'Quito',
created_at: '2020-01-08T00:00:00Z',
hit_count: 8,
},
{
id: '9',
question: 'What is the capital of Colombia?',
answer: 'Bogota',
created_at: '2020-01-09T00:00:00Z',
hit_count: 9,
},
]
export const hitHistoryList: HitHistoryItem[] = [
// create some mock data. source can only be: API/Webapp/Explore/Debug
{
id: '1',
question: 'What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?What is the capital of the United States?',
source: 'API',
score: 0.9,
created_at: '2020-01-01T00:00:00Z',
},
{
id: '2',
question: 'What is the capital of Canada?',
source: 'Webapp',
score: 0.8,
created_at: '2020-01-02T00:00:00Z',
},
{
id: '3',
question: 'What is the capital of Mexico?',
source: 'Explore',
score: 0.7,
created_at: '2020-01-03T00:00:00Z',
},
{
id: '4',
question: 'What is the capital of Brazil?',
source: 'Debug',
score: 0.6,
created_at: '2020-01-04T00:00:00Z',
},
{
id: '5',
question: 'What is the capital of Argentina?',
source: 'API',
score: 0.5,
created_at: '2020-01-05T00:00:00Z',
},
{
id: '6',
question: 'What is the capital of Chile?',
source: 'Webapp',
score: 0.4,
created_at: '2020-01-06T00:00:00Z',
},
{
id: '7',
question: 'What is the capital of Peru?',
source: 'Explore',
score: 0.3,
created_at: '2020-01-07T00:00:00Z',
},
{
id: '8',
question: 'What is the capital of Ecuador?',
source: 'Debug',
score: 0.2,
created_at: '2020-01-08T00:00:00Z',
},
{
id: '9',
question: 'What is the capital of Colombia?',
source: 'API',
score: 0.1,
created_at: '2020-01-09T00:00:00Z',
},
// make more mock data
{
id: '10',
question: 'What is the capital of the United States?',
source: 'API',
score: 0.9,
created_at: '2020-01-01T00:00:00Z',
},
{
id: '11',
question: 'What is the capital of Canada?',
source: 'Webapp',
score: 0.8,
created_at: '2020-01-02T00:00:00Z',
},
{
id: '12',
question: 'What is the capital of Mexico?',
source: 'Explore',
score: 0.7,
created_at: '2020-01-03T00:00:00Z',
},
{
id: '13',
question: 'What is the capital of Brazil?',
source: 'Debug',
score: 0.6,
created_at: '2020-01-04T00:00:00Z',
},
{
id: '14',
question: 'What is the capital of Argentina?',
source: 'API',
score: 0.5,
created_at: '2020-01-05T00:00:00Z',
},
{
id: '15',
question: 'What is the capital of Chile?',
source: 'Webapp',
score: 0.4,
created_at: '2020-01-06T00:00:00Z',
},
{
id: '16',
question: 'What is the capital of Peru?',
source: 'Explore',
score: 0.3,
created_at: '2020-01-07T00:00:00Z',
},
{
id: '17',
question: 'What is the capital of Ecuador?',
source: 'Debug',
score: 0.2,
created_at: '2020-01-08T00:00:00Z',
},
{
id: '18',
question: 'What is the capital of Colombia?',
source: 'API',
score: 0.1,
created_at: '2020-01-09T00:00:00Z',
},
// make more mock data
{
id: '19',
question: 'What is the capital of the United States?',
source: 'API',
score: 0.9,
created_at: '2020-01-01T00:00:00Z',
},
{
id: '20',
question: 'What is the capital of Canada?',
source: 'Webapp',
score: 0.8,
created_at: '2020-01-02T00:00:00Z',
},
{
id: '21',
question: 'What is the capital of Mexico?',
source: 'Explore',
score: 0.7,
created_at: '2020-01-03T00:00:00Z',
},
{
id: '22',
question: 'What is the capital of Brazil?',
source: 'Debug',
score: 0.6,
created_at: '2020-01-04T00:00:00Z',
},
{
id: '23',
question: 'What is the capital of Argentina?',
source: 'API',
score: 0.5,
created_at: '2020-01-05T00:00:00Z',
},
{
id: '24',
question: 'What is the capital of Chile?',
source: 'Webapp',
score: 0.4,
created_at: '2020-01-06T00:00:00Z',
},
{
id: '25',
question: 'What is the capital of Peru?',
source: 'Explore',
score: 0.3,
created_at: '2020-01-07T00:00:00Z',
},
{
id: '26',
question: 'What is the capital of Ecuador?',
source: 'Debug',
score: 0.2,
created_at: '2020-01-08T00:00:00Z',
},
{
id: '27',
question: 'What is the capital of Colombia?',
source: 'API',
score: 0.1,
created_at: '2020-01-09T00:00:00Z',
},
]
export default list

View file

@ -72,7 +72,16 @@ export type IChatItem = {
export type MessageEnd = {
id: string
retriever_resources?: CitationItem[]
metadata: {
retriever_resources?: CitationItem[]
annotation_reply: {
id: string
account: {
id: string
name: string
}
}
}
}
export type MessageReplace = {

View file

@ -7,9 +7,6 @@ import { useBoolean, useClickAway, useGetState } from 'ahooks'
import { InformationCircleIcon } from '@heroicons/react/24/outline'
import produce from 'immer'
import ParamItem from './param-item'
import ModelIcon from './model-icon'
import ModelName from './model-name'
import ModelModeTypeLabel from './model-mode-type-label'
import { SlidersH } from '@/app/components/base/icons/src/vender/line/mediaAndDevices'
import Radio from '@/app/components/base/radio'
import Panel from '@/app/components/base/panel'
@ -24,17 +21,19 @@ import { Target04 } from '@/app/components/base/icons/src/vender/solid/general'
import { Sliders02 } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
import { fetchModelParams } from '@/service/debug'
import Loading from '@/app/components/base/loading'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import { ModelType, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { useProviderContext } from '@/context/provider-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import type { ModelModeType } from '@/types/app'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
export type IConfigModelProps = {
isAdvancedMode: boolean
mode: string
modelId: string
provider: ProviderEnum
setModel: (model: { id: string; provider: ProviderEnum; mode: ModelModeType; features: string[] }) => void
provider: string
setModel: (model: { id: string; provider: string; mode: ModelModeType; features: string[] }) => void
completionParams: CompletionParams
onCompletionParamsChange: (newParams: CompletionParams) => void
disabled: boolean
@ -50,11 +49,16 @@ const ConfigModel: FC<IConfigModelProps> = ({
disabled,
}) => {
const { t } = useTranslation()
const { textGenerationModelList } = useProviderContext()
const [isShowConfig, { setFalse: hideConfig, toggle: toogleShowConfig }] = useBoolean(false)
const [maxTokenSettingTipVisible, setMaxTokenSettingTipVisible] = useState(false)
const configContentRef = React.useRef(null)
const currModel = textGenerationModelList.find(item => item.model_name === modelId)
const {
currentProvider,
currentModel: currModel,
textGenerationModelList,
} = useTextGenerationCurrentProviderAndModelAndModelList(
{ provider, model: modelId },
)
const media = useBreakpoints()
const isMobile = media === MediaType.mobile
@ -81,7 +85,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
setAllParams(newAllParams)
}
})()
}, [provider, modelId])
}, [provider, modelId, allParams, setAllParams])
useClickAway(() => {
hideConfig()
@ -89,7 +93,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
const selectedModel = { name: modelId } // options.find(option => option.id === modelId)
const ensureModelParamLoaded = (provider: ProviderEnum, modelId: string) => {
const ensureModelParamLoaded = (provider: string, modelId: string) => {
return new Promise<void>((resolve) => {
if (getAllParams()[provider]?.[modelId]) {
resolve()
@ -126,13 +130,13 @@ const ConfigModel: FC<IConfigModelProps> = ({
return adjustedValue
}
const handleSelectModel = ({ id, provider: nextProvider, mode, features }: { id: string; provider: ProviderEnum; mode: ModelModeType; features: string[] }) => {
const handleSelectModel = ({ id, provider: nextProvider, mode, features }: { id: string; provider: string; mode: ModelModeType; features: string[] }) => {
return async () => {
const prevParamsRule = getAllParams()[provider]?.[modelId]
setModel({
id,
provider: nextProvider || ProviderEnum.openai,
provider: nextProvider || 'openai',
mode,
features,
})
@ -269,26 +273,34 @@ const ConfigModel: FC<IConfigModelProps> = ({
const max = currParams.max_tokens.max
const isSupportMaxToken = currParams.max_tokens.enabled
if (isSupportMaxToken && currModel?.model_provider.provider_name !== ProviderEnum.anthropic && completionParams.max_tokens > max * 2 / 3)
if (isSupportMaxToken && currentProvider?.provider !== 'anthropic' && completionParams.max_tokens > max * 2 / 3)
setMaxTokenSettingTipVisible(true)
else
setMaxTokenSettingTipVisible(false)
}, [currParams, completionParams.max_tokens, setMaxTokenSettingTipVisible])
}, [currParams, completionParams.max_tokens, setMaxTokenSettingTipVisible, currentProvider])
return (
<div className='relative' ref={configContentRef}>
<div
className={cn('flex items-center border h-8 px-2 space-x-2 rounded-lg', disabled ? diabledStyle : ableStyle)}
onClick={() => !disabled && toogleShowConfig()}
>
<ModelIcon
className='!w-5 !h-5'
modelId={modelId}
providerName={provider}
/>
<div className='text-[13px] text-gray-900 font-medium'>
<ModelName modelId={selectedModel.name} modelDisplayName={currModel?.model_display_name} />
</div>
{isAdvancedMode && <ModelModeTypeLabel type={currModel?.model_mode as ModelModeType} isHighlight />}
{
currentProvider && (
<ModelIcon
className='!w-5 !h-5'
provider={currentProvider}
/>
)
}
{
currModel && (
<ModelName
className='text-gray-900'
modelItem={currModel}
showMode={isAdvancedMode}
/>
)
}
{disabled ? <InformationCircleIcon className='w-4 h-4 text-[#F79009]' /> : <SlidersH className='w-4 h-4 text-indigo-600' />}
</div>
{isShowConfig && (
@ -312,21 +324,16 @@ const ConfigModel: FC<IConfigModelProps> = ({
<div className="flex items-center justify-between my-5 h-9">
<div>{t('appDebug.modelConfig.model')}</div>
<ModelSelector
isShowModelModeType={isAdvancedMode}
isShowAddModel
popClassName='right-0'
triggerIconSmall
value={{
modelName: modelId,
providerName: provider,
}}
modelType={ModelType.textGeneration}
onChange={(model) => {
defaultModel={{ model: modelId, provider }}
modelList={textGenerationModelList}
onSelect={({ provider, model }) => {
const targetProvider = textGenerationModelList.find(modelItem => modelItem.provider === provider)
const targetModelItem = targetProvider?.models.find(modelItem => modelItem.model === model)
handleSelectModel({
id: model.model_name,
provider: model.model_provider.provider_name as ProviderEnum,
mode: model.model_mode,
features: model.features,
id: model,
provider,
mode: targetModelItem?.model_properties.mode as ModelModeType,
features: targetModelItem?.features || [],
})()
}}
/>
@ -336,7 +343,7 @@ const ConfigModel: FC<IConfigModelProps> = ({
)}
{/* Tone type */}
{[ProviderEnum.openai, ProviderEnum.azure_openai].includes(provider) && (
{['openai', 'azure_openai'].includes(provider) && (
<div className="mt-5 mb-4">
<div className="mb-3 text-sm text-gray-900">{t('appDebug.modelConfig.setTone')}</div>
<Radio.Group className={cn('!rounded-lg', toneTabBgClassName)} value={toneId} onChange={handleToneChange}>

View file

@ -1,32 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import cn from 'classnames'
import {
OpenaiGreen,
OpenaiViolet,
} from '@/app/components/base/icons/src/public/llm'
import { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import ProviderConfig from '@/app/components/header/account-setting/model-page/configs'
export type IModelIconProps = {
modelId: string
providerName: ProviderEnum
className?: string
}
const ModelIcon: FC<IModelIconProps> = ({ modelId, providerName, className }) => {
let Icon = <OpenaiGreen className='w-full h-full' />
if (providerName === ProviderEnum.openai)
Icon = modelId.includes('gpt-4') ? <OpenaiViolet className='w-full h-full' /> : <OpenaiGreen className='w-full h-full' />
else
Icon = ProviderConfig[providerName]?.selector.icon
return (
<div className={cn(className, 'w-4 h-4')}>
{Icon}
</div>
)
}
export default React.memo(ModelIcon)

View file

@ -21,10 +21,10 @@ import ConfigPrompt from '@/app/components/app/configuration/config-prompt'
import ConfigVar from '@/app/components/app/configuration/config-var'
import type { CitationConfig, ModelConfig, ModerationConfig, MoreLikeThisConfig, PromptVariable, SpeechToTextConfig, SuggestedQuestionsAfterAnswerConfig } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import ConfigParamModal from '@/app/components/app/configuration/toolbox/annotation/config-param-modal'
import AnnotationFullModal from '@/app/components/billing/annotation-full/modal'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
const Config: FC = () => {
const {
@ -55,7 +55,7 @@ const Config: FC = () => {
setModerationConfig,
} = useContext(ConfigContext)
const isChatApp = mode === AppType.chat
const { speech2textDefaultModel } = useProviderContext()
const { data: speech2textDefaultModel } = useDefaultModel(4)
const { setShowModerationSettingModal } = useModalContext()
const promptTemplate = modelConfig.configs.prompt_template

View file

@ -12,15 +12,14 @@ import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import RadioCard from '@/app/components/base/radio-card/simple'
import { RETRIEVE_TYPE } from '@/types/app'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import { useProviderContext } from '@/context/provider-context'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import Toast from '@/app/components/base/toast'
import { DATASET_DEFAULT } from '@/config'
import {
MultiPathRetrieval,
NTo1Retrieval,
} from '@/app/components/base/icons/src/public/common'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
const ParamsConfig: FC = () => {
const { t } = useTranslation()
@ -38,11 +37,11 @@ const ParamsConfig: FC = () => {
retrieval_model: value,
})
}
const {
rerankDefaultModel,
isRerankDefaultModelVaild,
} = useProviderContext()
modelList: rerankModelList,
defaultModel: rerankDefaultModel,
currentModel: isRerankDefaultModelVaild,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
const rerankModel = (() => {
if (tempDataSetConfigs.reranking_model) {
@ -53,8 +52,8 @@ const ParamsConfig: FC = () => {
}
else if (rerankDefaultModel) {
return {
provider_name: rerankDefaultModel.model_provider.provider_name,
model_name: rerankDefaultModel.model_name,
provider_name: rerankDefaultModel.provider.provider,
model_name: rerankDefaultModel.model,
}
}
})()
@ -104,8 +103,8 @@ const ParamsConfig: FC = () => {
const config = { ...tempDataSetConfigs }
if (config.retrieval_model === RETRIEVE_TYPE.multiWay && !config.reranking_model) {
config.reranking_model = {
reranking_provider_name: rerankDefaultModel?.model_provider.provider_name,
reranking_model_name: rerankDefaultModel?.model_name,
reranking_provider_name: rerankDefaultModel?.provider,
reranking_model_name: rerankDefaultModel?.model,
} as any
}
setDatasetConfigs(config)
@ -163,18 +162,17 @@ const ParamsConfig: FC = () => {
<div className='leading-[32px] text-[13px] font-medium text-gray-900'>{t('common.modelProvider.rerankModel.key')}</div>
<div>
<ModelSelector
popClassName='!max-w-[100%] !w-full'
value={rerankModel && { providerName: rerankModel.provider_name, modelName: rerankModel.model_name } as any}
modelType={ModelType.reranking}
onChange={(v) => {
defaultModel={rerankModel && { provider: rerankModel?.provider_name, model: rerankModel?.model_name }}
onSelect={(v) => {
setTempDataSetConfigs({
...tempDataSetConfigs,
reranking_model: {
reranking_provider_name: v.model_provider.provider_name,
reranking_model_name: v.model_name,
reranking_provider_name: v.provider,
reranking_model_name: v.model,
},
})
}}
modelList={rerankModelList}
/>
</div>
</div>

View file

@ -6,9 +6,6 @@ import cn from 'classnames'
import { BookOpenIcon } from '@heroicons/react/24/outline'
import IndexMethodRadio from '@/app/components/datasets/settings/index-method-radio'
import Button from '@/app/components/base/button'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import type { DataSet } from '@/models/datasets'
import { useToastContext } from '@/app/components/base/toast'
import { updateDatasetSetting } from '@/service/datasets'
@ -17,10 +14,14 @@ import { XClose } from '@/app/components/base/icons/src/vender/line/general'
import type { RetrievalConfig } from '@/types/app'
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
import { useProviderContext } from '@/context/provider-context'
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import PermissionsRadio from '@/app/components/datasets/settings/permissions-radio'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import {
useModelList,
useModelListAndDefaultModelAndCurrentProviderAndModel,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
type SettingsModalProps = {
currentDataset: DataSet
@ -41,6 +42,12 @@ const SettingsModal: FC<SettingsModalProps> = ({
onCancel,
onSave,
}) => {
const { data: embeddingsModelList } = useModelList(2)
const {
modelList: rerankModelList,
defaultModel: rerankDefaultModel,
currentModel: isRerankDefaultModelVaild,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
const { t } = useTranslation()
const { notify } = useToastContext()
const ref = useRef(null)
@ -51,12 +58,6 @@ const SettingsModal: FC<SettingsModalProps> = ({
const [indexMethod, setIndexMethod] = useState(currentDataset.indexing_technique)
const [retrievalConfig, setRetrievalConfig] = useState(localeCurrentDataset?.retrieval_model_dict as RetrievalConfig)
const {
rerankDefaultModel,
isRerankDefaultModelVaild,
rerankModelList,
} = useProviderContext()
const handleValueChange = (type: string, value: string) => {
setLocaleCurrentDataset({ ...localeCurrentDataset, [type]: value })
}
@ -73,7 +74,7 @@ const SettingsModal: FC<SettingsModalProps> = ({
if (
!isReRankModelSelected({
rerankDefaultModel,
isRerankDefaultModelVaild,
isRerankDefaultModelVaild: !!isRerankDefaultModelVaild,
rerankModelList,
retrievalConfig,
indexMethod,
@ -204,12 +205,11 @@ const SettingsModal: FC<SettingsModalProps> = ({
<div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'>
<ModelSelector
readonly
value={{
providerName: localeCurrentDataset.embedding_model_provider as ProviderEnum,
modelName: localeCurrentDataset.embedding_model,
defaultModel={{
provider: localeCurrentDataset.embedding_model_provider,
model: localeCurrentDataset.embedding_model,
}}
modelType={ModelType.embeddings}
onChange={() => {}}
modelList={embeddingsModelList}
/>
</div>
<div className='mt-2 w-full text-xs leading-6 text-gray-500'>

View file

@ -24,10 +24,11 @@ import type { ModelConfig as BackendModelConfig, VisionFile } from '@/types/app'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import TextGeneration from '@/app/components/app/text-generate/item'
import { IS_CE_EDITION } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import type { Inputs } from '@/models/debug'
import { fetchFileUploadConfig } from '@/service/common'
import type { Annotation as AnnotationType } from '@/models/log'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type IDebug = {
hasSetAPIKEY: boolean
onSetting: () => void
@ -69,7 +70,7 @@ const Debug: FC<IDebug> = ({
visionConfig,
annotationConfig,
} = useContext(ConfigContext)
const { speech2textDefaultModel } = useProviderContext()
const { data: speech2textDefaultModel } = useDefaultModel(4)
const [chatList, setChatList, getChatList] = useGetState<IChatItem[]>([])
const chatListDomRef = useRef<HTMLDivElement>(null)
const { data: fileUploadConfigResponse } = useSWR({ url: '/files/upload' }, fetchFileUploadConfig)
@ -346,7 +347,27 @@ const Debug: FC<IDebug> = ({
}
},
onMessageEnd: (messageEnd) => {
responseItem.citation = messageEnd.retriever_resources
// TODO
if (messageEnd.metadata?.annotation_reply) {
responseItem.id = messageEnd.id
responseItem.annotation = ({
id: messageEnd.metadata.annotation_reply.id,
authorName: messageEnd.metadata.annotation_reply.account.name,
} as AnnotationType)
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
(draft) => {
if (!draft.find(item => item.id === questionId))
draft.push({ ...questionItem })
draft.push({
...responseItem,
})
})
setChatList(newListWithAnswer)
return
}
responseItem.citation = messageEnd.metadata?.retriever_resources || []
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
@ -362,6 +383,7 @@ const Debug: FC<IDebug> = ({
responseItem.content = messageReplace.answer
},
onAnnotationReply: (annotationReply) => {
// TODO: temp debug
responseItem.id = annotationReply.id
responseItem.content = annotationReply.answer
responseItem.annotation = ({

View file

@ -1,12 +1,13 @@
import { useState } from 'react'
import { clone } from 'lodash-es'
import produce from 'immer'
import type { ChatPromptConfig, CompletionParams, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug'
import type { ChatPromptConfig, CompletionPromptConfig, ConversationHistoriesRole, PromptItem } from '@/models/debug'
import { PromptMode } from '@/models/debug'
import { AppType, ModelModeType } from '@/types/app'
import { DEFAULT_CHAT_PROMPT_CONFIG, DEFAULT_COMPLETION_PROMPT_CONFIG } from '@/config'
import { PRE_PROMPT_PLACEHOLDER_TEXT, checkHasContextBlock, checkHasHistoryBlock, checkHasQueryBlock } from '@/app/components/base/prompt-editor/constants'
import { fetchPromptTemplate } from '@/service/debug'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
type Param = {
appMode: string
@ -16,8 +17,8 @@ type Param = {
prePrompt: string
onUserChangedPrompt: () => void
hasSetDataSet: boolean
completionParams: CompletionParams
setCompletionParams: (params: CompletionParams) => void
completionParams: FormValue
setCompletionParams: (params: FormValue) => void
setStop: (stop: string[]) => void
}

View file

@ -16,7 +16,6 @@ import useAdvancedPromptConfig from './hooks/use-advanced-prompt-config'
import EditHistoryModal from './config-prompt/conversation-histroy/edit-modal'
import type {
AnnotationReplyConfig,
CompletionParams,
DatasetConfigs,
Inputs,
ModelConfig,
@ -29,11 +28,11 @@ import type { ExternalDataTool } from '@/models/common'
import type { DataSet } from '@/models/datasets'
import type { ModelConfig as BackendModelConfig, VisionSettings } from '@/types/app'
import ConfigContext from '@/context/debug-configuration'
import ConfigModel from '@/app/components/app/configuration/config-model'
// import ConfigModel from '@/app/components/app/configuration/config-model'
import Config from '@/app/components/app/configuration/config'
import Debug from '@/app/components/app/configuration/debug'
import Confirm from '@/app/components/base/confirm'
import { ModelFeature, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { ModelFeatureEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { ToastContext } from '@/app/components/base/toast'
import { fetchAppDetail, updateAppModelConfig } from '@/service/apps'
import { promptVariablesToUserInputsForm, userInputsFormToPromptVariables } from '@/utils/model-config'
@ -48,10 +47,13 @@ import I18n from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Drawer from '@/app/components/base/drawer'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
type PublichConfig = {
modelConfig: ModelConfig
completionParams: CompletionParams
completionParams: FormValue
}
const Configuration: FC = () => {
@ -112,16 +114,9 @@ const Configuration: FC = () => {
const [externalDataToolsConfig, setExternalDataToolsConfig] = useState<ExternalDataTool[]>([])
const [inputs, setInputs] = useState<Inputs>({})
const [query, setQuery] = useState('')
const [completionParams, doSetCompletionParams] = useState<CompletionParams>({
max_tokens: 16,
temperature: 1, // 0-2
top_p: 1,
presence_penalty: 1, // -2-2
frequency_penalty: 1, // -2-2
stop: [],
})
const [completionParams, doSetCompletionParams] = useState<FormValue>({})
const [tempStop, setTempStop, getTempStop] = useGetState<string[]>([])
const setCompletionParams = (value: CompletionParams) => {
const setCompletionParams = (value: FormValue) => {
const params = { ...value }
// eslint-disable-next-line @typescript-eslint/no-use-before-define
@ -133,7 +128,7 @@ const Configuration: FC = () => {
}
const [modelConfig, doSetModelConfig] = useState<ModelConfig>({
provider: ProviderEnum.openai,
provider: 'openai',
model_id: 'gpt-3.5-turbo',
mode: ModelModeType.unset,
configs: {
@ -223,28 +218,21 @@ const Configuration: FC = () => {
})
}
const { textGenerationModelList } = useProviderContext()
const currModel = textGenerationModelList.find(item => item.model_name === modelConfig.model_id)
const hasSetCustomAPIKEY = !!textGenerationModelList?.find(({ model_provider: provider }) => {
if (provider.provider_type === 'system' && provider.quota_type === 'paid')
return true
if (provider.provider_type === 'custom')
return true
return false
})
const isTrailFinished = !hasSetCustomAPIKEY && textGenerationModelList
.filter(({ model_provider: provider }) => provider.quota_type === 'trial')
.every(({ model_provider: provider }) => {
const { quota_used, quota_limit } = provider
return quota_used === quota_limit
})
const { hasSettedApiKey } = useProviderContext()
const {
currentModel: currModel,
textGenerationModelList,
} = useTextGenerationCurrentProviderAndModelAndModelList(
{
provider: modelConfig.provider,
model: modelConfig.model_id,
},
)
// Fill old app data missing model mode.
useEffect(() => {
if (hasFetchedDetail && !modelModeType) {
const mode = textGenerationModelList.find(({ model_name }) => model_name === modelConfig.model_id)?.model_mode
const mode = currModel?.model_properties.mode as (ModelModeType | undefined)
if (mode) {
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.mode = mode
@ -252,9 +240,7 @@ const Configuration: FC = () => {
setModelConfig(newModelConfig)
}
}
}, [textGenerationModelList, hasFetchedDetail])
const hasSetAPIKEY = hasSetCustomAPIKEY || !isTrailFinished
}, [textGenerationModelList, hasFetchedDetail, modelModeType, currModel, modelConfig])
const [promptMode, doSetPromptMode] = useState(PromptMode.simple)
const isAdvancedMode = promptMode === PromptMode.advanced
@ -295,11 +281,11 @@ const Configuration: FC = () => {
})
const setModel = async ({
id: modelId,
modelId,
provider,
mode: modeMode,
features,
}: { id: string; provider: ProviderEnum; mode: ModelModeType; features: string[] }) => {
}: { modelId: string; provider: string; mode: string; features: string[] }) => {
if (isAdvancedMode) {
const appMode = mode
@ -321,11 +307,11 @@ const Configuration: FC = () => {
const newModelConfig = produce(modelConfig, (draft: ModelConfig) => {
draft.provider = provider
draft.model_id = modelId
draft.mode = modeMode
draft.mode = modeMode as ModelModeType
})
setModelConfig(newModelConfig)
const supportVision = features && features.includes(ModelFeature.vision)
const supportVision = features && features.includes(ModelFeatureEnum.vision)
// eslint-disable-next-line @typescript-eslint/no-use-before-define
setVisionConfig({
// eslint-disable-next-line @typescript-eslint/no-use-before-define
@ -334,7 +320,7 @@ const Configuration: FC = () => {
}, true)
}
const isShowVisionConfig = !!currModel?.features.includes(ModelFeature.vision)
const isShowVisionConfig = !!currModel?.features?.includes(ModelFeatureEnum.vision)
const [visionConfig, doSetVisionConfig] = useState({
enabled: false,
number_limits: 2,
@ -566,8 +552,8 @@ const Configuration: FC = () => {
return (
<ConfigContext.Provider value={{
appId,
hasSetAPIKEY,
isTrailFinished,
hasSetAPIKEY: hasSettedApiKey,
isTrailFinished: false,
mode,
modelModeType,
promptMode,
@ -660,17 +646,17 @@ const Configuration: FC = () => {
<div className='flex items-center flex-wrap gap-y-2 gap-x-2'>
{/* Model and Parameters */}
<ConfigModel
<ModelParameterModal
isAdvancedMode={isAdvancedMode}
mode={mode}
provider={modelConfig.provider as ProviderEnum}
provider={modelConfig.provider}
completionParams={completionParams}
modelId={modelConfig.model_id}
setModel={setModel}
onCompletionParamsChange={(newParams: CompletionParams) => {
setModel={setModel as any}
onCompletionParamsChange={(newParams: FormValue) => {
setCompletionParams(newParams)
}}
disabled={!hasSetAPIKEY}
disabled={!hasSettedApiKey}
/>
<div className='w-[1px] h-[14px] bg-gray-200'></div>
<Button onClick={() => setShowConfirm(true)} className='shrink-0 mr-2 w-[70px] !h-8 !text-[13px] font-medium'>{t('appDebug.operation.resetConfig')}</Button>
@ -689,7 +675,7 @@ const Configuration: FC = () => {
</div>
{!isMobile && <div className="relative w-1/2 grow h-full overflow-y-auto py-4 px-6 bg-gray-50 flex flex-col rounded-tl-2xl border-t border-l" style={{ borderColor: 'rgba(0, 0, 0, 0.02)' }}>
<Debug
hasSetAPIKEY={hasSetAPIKEY}
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
/>
@ -744,7 +730,7 @@ const Configuration: FC = () => {
{isMobile && (
<Drawer showClose isOpen={isShowDebugPanel} onClose={hideDebugPanel} mask footer={null} panelClassname='!bg-gray-50'>
<Debug
hasSetAPIKEY={hasSetAPIKEY}
hasSetAPIKEY={hasSettedApiKey}
onSetting={() => setShowAccountSettingModal({ payload: 'provider' })}
inputs={inputs}
/>

View file

@ -6,12 +6,11 @@ import ScoreSlider from '../score-slider'
import { Item } from './config-param'
import Modal from '@/app/components/base/modal'
import Button from '@/app/components/base/button'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector/portal-select'
import { useProviderContext } from '@/context/provider-context'
import Toast from '@/app/components/base/toast'
import type { AnnotationReplyConfig } from '@/models/debug'
import { ANNOTATION_DEFAULT } from '@/config'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type Props = {
appId: string
@ -34,9 +33,10 @@ const ConfigParamModal: FC<Props> = ({
}) => {
const { t } = useTranslation()
const {
embeddingsDefaultModel,
isEmbeddingsDefaultModelValid,
} = useProviderContext()
modelList: embeddingsModelList,
defaultModel: embeddingsDefaultModel,
currentModel: isEmbeddingsDefaultModelValid,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(2)
const [annotationConfig, setAnnotationConfig] = useState(oldAnnotationConfig)
const [isLoading, setLoading] = useState(false)
@ -47,8 +47,8 @@ const ConfigParamModal: FC<Props> = ({
}
: (embeddingsDefaultModel
? {
providerName: embeddingsDefaultModel.model_provider.provider_name,
modelName: embeddingsDefaultModel.model_name,
providerName: embeddingsDefaultModel.provider.provider,
modelName: embeddingsDefaultModel.model,
}
: undefined))
const onHide = () => {
@ -57,7 +57,7 @@ const ConfigParamModal: FC<Props> = ({
}
const handleSave = async () => {
if (!embeddingModel || !embeddingModel.modelName || (embeddingModel.modelName === embeddingsDefaultModel?.model_name && !isEmbeddingsDefaultModelValid)) {
if (!embeddingModel || !embeddingModel.modelName || (embeddingModel.modelName === embeddingsDefaultModel?.model && !isEmbeddingsDefaultModelValid)) {
Toast.notify({
message: t('common.modelProvider.embeddingModel.required'),
type: 'error',
@ -106,13 +106,15 @@ const ConfigParamModal: FC<Props> = ({
>
<div className='pt-1'>
<ModelSelector
widthSameToTrigger
value={embeddingModel as any}
modelType={ModelType.embeddings}
onChange={(val) => {
defaultModel={embeddingModel && {
provider: embeddingModel.providerName,
model: embeddingModel.modelName,
}}
modelList={embeddingsModelList}
onSelect={(val) => {
setEmbeddingModel({
providerName: val.model_provider.provider_name,
modelName: val.model_name,
providerName: val.provider,
modelName: val.model,
})
}}
/>

View file

@ -19,6 +19,7 @@ import type { CodeBasedExtensionItem } from '@/models/common'
import I18n from '@/context/i18n'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
import { useModalContext } from '@/context/modal-context'
import { CustomConfigurationStatusEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
const systemTypes = ['openai_moderation', 'keywords', 'api']
@ -57,10 +58,11 @@ const ModerationSettingModal: FC<ModerationSettingModalProps> = ({
'/code-based-extension?module=moderation',
fetchCodeBasedExtensionList,
)
const systemOpenaiProvider = modelProviders?.openai.providers.find(item => item.provider_type === 'system')
const systemOpenaiProviderCanUse = systemOpenaiProvider && (((systemOpenaiProvider as any).quota_limit - (systemOpenaiProvider as any).quota_used) > 0)
const customOpenaiProviders = modelProviders?.openai.providers.filter(item => item.provider_type !== 'system')
const customOpenaiProvidersCanUse = customOpenaiProviders?.some(item => item.is_valid)
const openaiProvider = modelProviders?.data.find(item => item.provider === 'openai')
const systemOpenaiProviderEnabled = openaiProvider?.system_configuration.enabled
const systemOpenaiProviderQuota = systemOpenaiProviderEnabled ? openaiProvider?.system_configuration.quota_configurations.find(item => item.quota_type === openaiProvider.system_configuration.current_quota_type) : undefined
const systemOpenaiProviderCanUse = systemOpenaiProviderQuota?.is_valid
const customOpenaiProvidersCanUse = openaiProvider?.custom_configuration.status === CustomConfigurationStatusEnum.active
const openaiProviderConfiged = customOpenaiProvidersCanUse || systemOpenaiProviderCanUse
const providers: Provider[] = [
{

View file

@ -29,9 +29,9 @@ import Tooltip from '@/app/components/base/tooltip'
import { ToastContext } from '@/app/components/base/toast'
import { fetchChatConversationDetail, fetchChatMessages, fetchCompletionConversationDetail, updateLogMessageAnnotations, updateLogMessageFeedbacks } from '@/service/log'
import { TONE_LIST } from '@/config'
import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
import ModelName from '@/app/components/app/configuration/config-model/model-name'
import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import TextGeneration from '@/app/components/app/text-generate/item'
@ -188,6 +188,12 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
const modelName = (detail.model_config as any).model.name
const provideName = (detail.model_config as any).model.provider as any
const {
currentModel,
currentProvider,
} = useTextGenerationCurrentProviderAndModelAndModelList(
{ provider: provideName, model: modelName },
)
const varList = (detail.model_config as any).user_input_form.map((item: any) => {
const itemContent = item[Object.keys(item)[0]]
return {
@ -224,13 +230,13 @@ function DetailPanel<T extends ChatConversationFullDetailResponse | CompletionCo
>
<ModelIcon
className='!w-5 !h-5'
modelId={modelName}
providerName={provideName}
provider={currentProvider}
modelName={currentModel?.model}
/>
<ModelName
modelItem={currentModel!}
showMode
/>
<div className='text-[13px] text-gray-900 font-medium'>
<ModelName modelId={modelName} modelDisplayName={modelName} />
</div>
<ModelModeTypeLabel type={detail?.model_config.model.mode as any} isHighlight />
</div>
<Popover
position='br'

View file

@ -3,78 +3,35 @@ import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import cn from 'classnames'
import { useContext } from 'use-context-selector'
import Progress from './progress'
import Button from '@/app/components/base/button'
import { LinkExternal02, XClose } from '@/app/components/base/icons/src/vender/line/general'
import { IS_CE_EDITION } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import { formatNumber } from '@/utils/format'
import I18n from '@/context/i18n'
import ProviderConfig from '@/app/components/header/account-setting/model-page/configs'
import { useModalContext } from '@/context/modal-context'
const APIKeyInfoPanel: FC = () => {
const isCloud = !IS_CE_EDITION
const { locale } = useContext(I18n)
const { textGenerationModelList } = useProviderContext()
const { hasSettedApiKey } = useProviderContext()
const { setShowAccountSettingModal } = useModalContext()
const { t } = useTranslation()
const [isShow, setIsShow] = useState(true)
const hasSetAPIKEY = !!textGenerationModelList?.find(({ model_provider: provider }) => {
if (provider.provider_type === 'system' && provider.quota_type === 'paid')
return true
if (provider.provider_type === 'custom')
return true
return false
})
if (hasSetAPIKEY)
if (hasSettedApiKey)
return null
// first show in trail and not used exhausted, else find the exhausted
const [used, total, unit, providerName] = (() => {
if (!textGenerationModelList || !isCloud)
return [0, 0, '', '']
let used = 0
let total = 0
let unit = 'times'
let trailProviderName = ''
let hasFoundNotExhausted = false
textGenerationModelList?.filter(({ model_provider: provider }) => {
return provider.quota_type === 'trial'
}).forEach(({ model_provider: provider }) => {
if (hasFoundNotExhausted)
return
const { provider_name, quota_used, quota_limit, quota_unit } = provider
if (quota_limit !== quota_used)
hasFoundNotExhausted = true
used = quota_used
total = quota_limit
unit = quota_unit
trailProviderName = provider_name
})
return [used, total, unit, trailProviderName]
})()
const usedPercent = Math.round(used / total * 100)
const exhausted = isCloud && usedPercent === 100
if (!(isShow))
return null
return (
<div className={cn(exhausted ? 'bg-[#FEF3F2] border-[#FEE4E2]' : 'bg-[#EFF4FF] border-[#D1E0FF]', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}>
<div className={cn('bg-[#EFF4FF] border-[#D1E0FF]', 'mb-6 relative rounded-2xl shadow-md border p-8 ')}>
<div className={cn('text-[24px] text-gray-800 font-semibold', isCloud ? 'flex items-center h-8 space-x-1' : 'leading-8 mb-6')}>
{isCloud && <em-emoji id={exhausted ? '🤔' : '😀'} />}
{isCloud && <em-emoji id={'😀'} />}
{isCloud
? (
<div>{t(`appOverview.apiKeyInfo.cloud.${exhausted ? 'exhausted' : 'trial'}.title`, { providerName: (ProviderConfig as any)[providerName as string]?.selector?.name[locale] || providerName })}</div>
<div>{t('appOverview.apiKeyInfo.cloud.trial.title', { providerName: 'OpenAI' })}</div>
)
: (
<div>
@ -84,18 +41,7 @@ const APIKeyInfoPanel: FC = () => {
)}
</div>
{isCloud && (
<div className='mt-1 text-sm text-gray-600 font-normal'>{t(`appOverview.apiKeyInfo.cloud.${exhausted ? 'exhausted' : 'trial'}.description`)}</div>
)}
{/* Call times info */}
{isCloud && (
<div className='my-5'>
<div className='flex items-center h-5 space-x-2 text-sm text-gray-700 font-medium'>
<div>{t(`appOverview.apiKeyInfo.${unit === 'times' ? 'callTimes' : 'usedToken'}`)}</div>
<div>·</div>
<div className={cn('font-semibold', exhausted && 'text-[#D92D20]')}>{formatNumber(used)}/{formatNumber(total)}</div>
</div>
<Progress className='mt-2' value={usedPercent} />
</div>
<div className='mt-1 text-sm text-gray-600 font-normal'>{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}</div>
)}
<Button
type='primary'

View file

@ -0,0 +1,31 @@
@tailwind components;
@layer components {
.btn {
@apply inline-flex justify-center items-center content-center h-9 leading-5 rounded-lg px-4 py-2 text-base cursor-pointer;
}
.btn-default {
@apply border-solid border border-gray-200 cursor-pointer text-gray-500 hover:bg-white hover:shadow-sm hover:border-gray-300;
}
.btn-default-disabled {
@apply border-solid border border-gray-200 bg-gray-200 cursor-not-allowed text-gray-800;
}
.btn-primary {
@apply bg-primary-600 hover:bg-primary-600/75 cursor-pointer text-white hover:shadow-sm;
}
.btn-primary-disabled {
@apply bg-primary-200 cursor-not-allowed text-white;
}
.btn-warning {
@apply bg-red-600 hover:bg-red-600/75 cursor-pointer text-white hover:shadow-sm;
}
.btn-warning-disabled {
@apply bg-red-600/75 cursor-not-allowed text-white;
}
}

View file

@ -24,19 +24,19 @@ const Button: FC<IButtonProps> = ({
let style = 'cursor-pointer'
switch (type) {
case 'primary':
style = (disabled || loading) ? 'bg-primary-200 cursor-not-allowed text-white' : 'bg-primary-600 hover:bg-primary-600/75 hover:shadow-md cursor-pointer text-white hover:shadow-sm'
style = (disabled || loading) ? 'btn-primary-disabled' : 'btn-primary'
break
case 'warning':
style = (disabled || loading) ? 'bg-red-600/75 cursor-not-allowed text-white' : 'bg-red-600 hover:bg-red-600/75 hover:shadow-md cursor-pointer text-white hover:shadow-sm'
style = (disabled || loading) ? 'btn-warning-disabled' : 'btn-warning'
break
default:
style = disabled ? 'border-solid border border-gray-200 bg-gray-200 cursor-not-allowed text-gray-800' : 'border-solid border border-gray-200 cursor-pointer text-gray-500 hover:bg-white hover:shadow-sm hover:border-gray-300'
style = disabled ? 'btn-default-disabled' : 'btn-default'
break
}
return (
<div
className={`inline-flex justify-center items-center content-center h-9 leading-5 rounded-lg px-4 py-2 text-base ${style} ${className && className}`}
className={`btn ${style} ${className && className}`}
tabIndex={tabIndex}
onClick={disabled ? undefined : onClick}
>

View file

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="alert-triangle">
<path id="Icon" d="M7.99977 5.33314V7.99981M7.99977 10.6665H8.00644M6.85977 1.90648L1.2131 11.3331C1.09668 11.5348 1.03508 11.7633 1.03443 11.9962C1.03378 12.229 1.0941 12.4579 1.20939 12.6602C1.32468 12.8624 1.49092 13.031 1.69157 13.149C1.89223 13.2671 2.1203 13.3306 2.3531 13.3331H13.6464C13.8792 13.3306 14.1073 13.2671 14.308 13.149C14.5086 13.031 14.6749 12.8624 14.7902 12.6602C14.9054 12.4579 14.9658 12.229 14.9651 11.9962C14.9645 11.7633 14.9029 11.5348 14.7864 11.3331L9.13977 1.90648C9.02092 1.71055 8.85358 1.54856 8.6539 1.43613C8.45422 1.32371 8.22893 1.26465 7.99977 1.26465C7.77061 1.26465 7.54532 1.32371 7.34564 1.43613C7.14596 1.54856 6.97862 1.71055 6.85977 1.90648Z" stroke="#F79009" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 908 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="coins-stacked-01">
<path id="Icon" d="M12 17C12 19.7614 14.2386 22 17 22C19.7614 22 22 19.7614 22 17C22 14.2386 19.7614 12 17 12C14.2386 12 12 14.2386 12 17ZM12 17C12 15.8742 12.3721 14.8353 13 13.9995V5M12 17C12 17.8254 12.2 18.604 12.5541 19.2901C11.7117 20.0018 9.76584 20.5 7.5 20.5C4.46243 20.5 2 19.6046 2 18.5V5M13 5C13 6.10457 10.5376 7 7.5 7C4.46243 7 2 6.10457 2 5M13 5C13 3.89543 10.5376 3 7.5 3C4.46243 3 2 3.89543 2 5M2 14C2 15.1046 4.46243 16 7.5 16C9.689 16 11.5793 15.535 12.4646 14.8618M13 9.5C13 10.6046 10.5376 11.5 7.5 11.5C4.46243 11.5 2 10.6046 2 9.5" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 770 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="plus-circle">
<path id="Solid" fill-rule="evenodd" clip-rule="evenodd" d="M12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1ZM12 7C12.5523 7 13 7.44772 13 8V11H16C16.5523 11 17 11.4477 17 12C17 12.5523 16.5523 13 16 13H13V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V13H8C7.44772 13 7 12.5523 7 12C7 11.4477 7.44772 11 8 11H11V8C11 7.44772 11.4477 7 12 7Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 562 B

View file

@ -0,0 +1,9 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="box-sparkle, magic box">
<g id="Icon">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.76205 2.07424C9.99723 2.21897 10.0706 2.52694 9.92583 2.76212L8.85632 4.50007H9.5C9.77614 4.50007 10 4.72393 10 5.00007V9.00007C10 10.1046 9.10457 11.0001 8 11.0001H4C2.89543 11.0001 2 10.1046 2 9.00007V5.00007C2 4.72393 2.22386 4.50007 2.5 4.50007H7.68214L9.07417 2.23802C9.2189 2.00284 9.52687 1.92952 9.76205 2.07424ZM5 6.50007C4.72386 6.50007 4.5 6.72393 4.5 7.00007C4.5 7.27621 4.72386 7.50007 5 7.50007H7C7.27614 7.50007 7.5 7.27621 7.5 7.00007C7.5 6.72393 7.27614 6.50007 7 6.50007H5Z" fill="#667085"/>
<path d="M5.92504 1.53733C5.97342 1.51314 6.01265 1.47391 6.03684 1.42553L6.27597 0.947279C6.3681 0.763016 6.63105 0.763017 6.72318 0.947279L6.96231 1.42553C6.9865 1.47391 7.02573 1.51314 7.07411 1.53733L7.55236 1.77646C7.73663 1.86859 7.73663 2.13154 7.55236 2.22367L7.07411 2.4628C7.02573 2.48699 6.9865 2.52622 6.96231 2.5746L6.72318 3.05285C6.63105 3.23711 6.3681 3.23711 6.27597 3.05285L6.03684 2.5746C6.01265 2.52622 5.97342 2.48699 5.92504 2.4628L5.44679 2.22367C5.26253 2.13154 5.26253 1.86859 5.44679 1.77646L5.92504 1.53733Z" fill="#667085"/>
<path d="M3.25837 2.37067C3.30676 2.34648 3.34599 2.30724 3.37018 2.25886L3.52597 1.94728C3.6181 1.76302 3.88105 1.76302 3.97318 1.94728L4.12898 2.25886C4.15317 2.30724 4.1924 2.34648 4.24078 2.37067L4.55236 2.52646C4.73662 2.61859 4.73663 2.88154 4.55236 2.97367L4.24078 3.12946C4.1924 3.15365 4.15317 3.19289 4.12898 3.24127L3.97318 3.55285C3.88105 3.73711 3.6181 3.73711 3.52597 3.55285L3.37018 3.24127C3.34599 3.19289 3.30676 3.15365 3.25837 3.12946L2.94679 2.97367C2.76253 2.88154 2.76253 2.61859 2.94679 2.52646L3.25837 2.37067Z" fill="#667085"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="eye-sparkle, magic eyes">
<path id="Icon" fill-rule="evenodd" clip-rule="evenodd" d="M11.0338 5.05688C9.75366 3.05335 7.90203 1.99999 6.00017 2C4.09831 2.00001 2.24669 3.05341 0.966566 5.05693C0.599687 5.63113 0.599686 6.36892 0.966566 6.94312C2.24669 8.94665 4.09832 10 6.00018 10C7.90204 9.99999 9.75366 8.94659 11.0338 6.94307C11.4007 6.36887 11.4007 5.63108 11.0338 5.05688ZM5.77639 4.44721L5.3706 5.2588C5.34641 5.30718 5.30718 5.34641 5.2588 5.3706L4.44721 5.77639C4.26295 5.86852 4.26295 6.13148 4.44721 6.22361L5.2588 6.6294C5.30718 6.65359 5.34641 6.69282 5.3706 6.7412L5.77639 7.55279C5.86852 7.73705 6.13148 7.73705 6.22361 7.55279L6.6294 6.7412C6.65359 6.69282 6.69282 6.65359 6.7412 6.6294L7.55279 6.22361C7.73705 6.13148 7.73705 5.86852 7.55279 5.77639L6.7412 5.3706C6.69282 5.34641 6.65359 5.30718 6.6294 5.2588L6.22361 4.44721C6.13148 4.26295 5.86852 4.26295 5.77639 4.44721Z" fill="#667085"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="magic-wand-2, magic stick, star">
<g id="Icon">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.27056 1.77151C8.811 1.23107 9.68723 1.23107 10.2277 1.77151C10.7681 2.31195 10.7681 3.18818 10.2277 3.72862L3.72767 10.2286C3.18723 10.7691 2.31101 10.7691 1.77056 10.2286C1.23012 9.68818 1.23012 8.81195 1.77056 8.27151L8.27056 1.77151ZM9.52056 2.47862C9.37065 2.3287 9.12759 2.3287 8.97767 2.47862L8.08122 3.37506L8.62412 3.91796L9.52056 3.02151C9.67048 2.87159 9.67048 2.62853 9.52056 2.47862Z" fill="#667085"/>
<path d="M4.92504 1.03733C4.97342 1.01314 5.01265 0.973911 5.03684 0.92553L5.27597 0.447279C5.3681 0.263016 5.63105 0.263017 5.72318 0.447279L5.96231 0.92553C5.9865 0.973911 6.02573 1.01314 6.07411 1.03733L6.55236 1.27646C6.73663 1.36859 6.73663 1.63154 6.55236 1.72367L6.07411 1.9628C6.02573 1.98699 5.9865 2.02622 5.96231 2.0746L5.72318 2.55285C5.63105 2.73711 5.3681 2.73711 5.27597 2.55285L5.03684 2.0746C5.01265 2.02622 4.97342 1.98699 4.92504 1.9628L4.44679 1.72367C4.26253 1.63154 4.26253 1.36859 4.44679 1.27646L4.92504 1.03733Z" fill="#667085"/>
<path d="M9.42504 6.53733C9.47342 6.51314 9.51265 6.47391 9.53684 6.42553L9.77597 5.94728C9.8681 5.76302 10.1311 5.76302 10.2232 5.94728L10.4623 6.42553C10.4865 6.47391 10.5257 6.51314 10.5741 6.53733L11.0524 6.77646C11.2366 6.86859 11.2366 7.13154 11.0524 7.22367L10.5741 7.4628C10.5257 7.48699 10.4865 7.52622 10.4623 7.5746L10.2232 8.05285C10.1311 8.23711 9.8681 8.23711 9.77597 8.05285L9.53684 7.5746C9.51265 7.52622 9.47342 7.48699 9.42504 7.4628L8.94679 7.22367C8.76253 7.13154 8.76253 6.86859 8.94679 6.77646L9.42504 6.53733Z" fill="#667085"/>
<path d="M2.42504 3.53733C2.47342 3.51314 2.51265 3.47391 2.53684 3.42553L2.77597 2.94728C2.8681 2.76302 3.13105 2.76302 3.22318 2.94728L3.46231 3.42553C3.4865 3.47391 3.52573 3.51314 3.57411 3.53733L4.05236 3.77646C4.23663 3.86859 4.23663 4.13154 4.05236 4.22367L3.57411 4.4628C3.52573 4.48699 3.4865 4.52622 3.46231 4.5746L3.22318 5.05285C3.13105 5.23711 2.8681 5.23711 2.77597 5.05285L2.53684 4.5746C2.51265 4.52622 2.47342 4.48699 2.42504 4.4628L1.94679 4.22367C1.76253 4.13154 1.76253 3.86859 1.94679 3.77646L2.42504 3.53733Z" fill="#667085"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,5 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="robot, bot">
<path id="Icon" fill-rule="evenodd" clip-rule="evenodd" d="M6 0.5C6.27614 0.5 6.5 0.723858 6.5 1V1.5H8.5C9.32843 1.5 10 2.17157 10 3V5.5C10 5.94425 9.80688 6.34339 9.5 6.61805V7.29289L10.3536 8.14645C10.5488 8.34171 10.5488 8.65829 10.3536 8.85355C10.1583 9.04882 9.84171 9.04882 9.64645 8.85355L9.34052 8.54762C8.89526 9.96884 7.56805 11 6 11C4.43195 11 3.10474 9.96884 2.65948 8.54762L2.35355 8.85355C2.15829 9.04882 1.84171 9.04882 1.64645 8.85355C1.45118 8.65829 1.45118 8.34171 1.64645 8.14645L2.5 7.29289V6.61805C2.19313 6.34339 2 5.94425 2 5.5V3C2 2.17157 2.67157 1.5 3.5 1.5H5.5V1C5.5 0.723858 5.72386 0.5 6 0.5ZM3.5 2.5C3.22386 2.5 3 2.72386 3 3V5.5C3 5.77614 3.22386 6 3.5 6H8.5C8.77614 6 9 5.77614 9 5.5V3C9 2.72386 8.77614 2.5 8.5 2.5H3.5ZM4.5 3.5C4.77614 3.5 5 3.72386 5 4V4.5C5 4.77614 4.77614 5 4.5 5C4.22386 5 4 4.77614 4 4.5V4C4 3.72386 4.22386 3.5 4.5 3.5ZM7.5 3.5C7.77614 3.5 8 3.72386 8 4V4.5C8 4.77614 7.77614 5 7.5 5C7.22386 5 7 4.77614 7 4.5V4C7 3.72386 7.22386 3.5 7.5 3.5Z" fill="#667085"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "16",
"height": "16",
"viewBox": "0 0 16 16",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "alert-triangle"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M7.99977 5.33314V7.99981M7.99977 10.6665H8.00644M6.85977 1.90648L1.2131 11.3331C1.09668 11.5348 1.03508 11.7633 1.03443 11.9962C1.03378 12.229 1.0941 12.4579 1.20939 12.6602C1.32468 12.8624 1.49092 13.031 1.69157 13.149C1.89223 13.2671 2.1203 13.3306 2.3531 13.3331H13.6464C13.8792 13.3306 14.1073 13.2671 14.308 13.149C14.5086 13.031 14.6749 12.8624 14.7902 12.6602C14.9054 12.4579 14.9658 12.229 14.9651 11.9962C14.9645 11.7633 14.9029 11.5348 14.7864 11.3331L9.13977 1.90648C9.02092 1.71055 8.85358 1.54856 8.6539 1.43613C8.45422 1.32371 8.22893 1.26465 7.99977 1.26465C7.77061 1.26465 7.54532 1.32371 7.34564 1.43613C7.14596 1.54856 6.97862 1.71055 6.85977 1.90648Z",
"stroke": "currentColor",
"stroke-width": "1.25",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "AlertTriangle"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './AlertTriangle.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'AlertTriangle'
export default Icon

View file

@ -1 +1,2 @@
export { default as AlertCircle } from './AlertCircle'
export { default as AlertTriangle } from './AlertTriangle'

View file

@ -0,0 +1,39 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "coins-stacked-01"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"d": "M12 17C12 19.7614 14.2386 22 17 22C19.7614 22 22 19.7614 22 17C22 14.2386 19.7614 12 17 12C14.2386 12 12 14.2386 12 17ZM12 17C12 15.8742 12.3721 14.8353 13 13.9995V5M12 17C12 17.8254 12.2 18.604 12.5541 19.2901C11.7117 20.0018 9.76584 20.5 7.5 20.5C4.46243 20.5 2 19.6046 2 18.5V5M13 5C13 6.10457 10.5376 7 7.5 7C4.46243 7 2 6.10457 2 5M13 5C13 3.89543 10.5376 3 7.5 3C4.46243 3 2 3.89543 2 5M2 14C2 15.1046 4.46243 16 7.5 16C9.689 16 11.5793 15.535 12.4646 14.8618M13 9.5C13 10.6046 10.5376 11.5 7.5 11.5C4.46243 11.5 2 10.6046 2 9.5",
"stroke": "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round"
},
"children": []
}
]
}
]
},
"name": "CoinsStacked01"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './CoinsStacked01.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'CoinsStacked01'
export default Icon

View file

@ -1,2 +1,3 @@
export { default as CoinsStacked01 } from './CoinsStacked01'
export { default as GoldCoin } from './GoldCoin'
export { default as ReceiptList } from './ReceiptList'

View file

@ -0,0 +1,38 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "24",
"height": "24",
"viewBox": "0 0 24 24",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "plus-circle"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Solid",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1ZM12 7C12.5523 7 13 7.44772 13 8V11H16C16.5523 11 17 11.4477 17 12C17 12.5523 16.5523 13 16 13H13V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V13H8C7.44772 13 7 12.5523 7 12C7 11.4477 7.44772 11 8 11H11V8C11 7.44772 11.4477 7 12 7Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "PlusCircle"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './PlusCircle.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'PlusCircle'
export default Icon

View file

@ -4,6 +4,7 @@ export { default as Download02 } from './Download02'
export { default as Edit04 } from './Edit04'
export { default as Eye } from './Eye'
export { default as MessageClockCircle } from './MessageClockCircle'
export { default as PlusCircle } from './PlusCircle'
export { default as Target04 } from './Target04'
export { default as Tool03 } from './Tool03'
export { default as XCircle } from './XCircle'

View file

@ -0,0 +1,64 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "box-sparkle, magic box"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M9.76205 2.07424C9.99723 2.21897 10.0706 2.52694 9.92583 2.76212L8.85632 4.50007H9.5C9.77614 4.50007 10 4.72393 10 5.00007V9.00007C10 10.1046 9.10457 11.0001 8 11.0001H4C2.89543 11.0001 2 10.1046 2 9.00007V5.00007C2 4.72393 2.22386 4.50007 2.5 4.50007H7.68214L9.07417 2.23802C9.2189 2.00284 9.52687 1.92952 9.76205 2.07424ZM5 6.50007C4.72386 6.50007 4.5 6.72393 4.5 7.00007C4.5 7.27621 4.72386 7.50007 5 7.50007H7C7.27614 7.50007 7.5 7.27621 7.5 7.00007C7.5 6.72393 7.27614 6.50007 7 6.50007H5Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M5.92504 1.53733C5.97342 1.51314 6.01265 1.47391 6.03684 1.42553L6.27597 0.947279C6.3681 0.763016 6.63105 0.763017 6.72318 0.947279L6.96231 1.42553C6.9865 1.47391 7.02573 1.51314 7.07411 1.53733L7.55236 1.77646C7.73663 1.86859 7.73663 2.13154 7.55236 2.22367L7.07411 2.4628C7.02573 2.48699 6.9865 2.52622 6.96231 2.5746L6.72318 3.05285C6.63105 3.23711 6.3681 3.23711 6.27597 3.05285L6.03684 2.5746C6.01265 2.52622 5.97342 2.48699 5.92504 2.4628L5.44679 2.22367C5.26253 2.13154 5.26253 1.86859 5.44679 1.77646L5.92504 1.53733Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M3.25837 2.37067C3.30676 2.34648 3.34599 2.30724 3.37018 2.25886L3.52597 1.94728C3.6181 1.76302 3.88105 1.76302 3.97318 1.94728L4.12898 2.25886C4.15317 2.30724 4.1924 2.34648 4.24078 2.37067L4.55236 2.52646C4.73662 2.61859 4.73663 2.88154 4.55236 2.97367L4.24078 3.12946C4.1924 3.15365 4.15317 3.19289 4.12898 3.24127L3.97318 3.55285C3.88105 3.73711 3.6181 3.73711 3.52597 3.55285L3.37018 3.24127C3.34599 3.19289 3.30676 3.15365 3.25837 3.12946L2.94679 2.97367C2.76253 2.88154 2.76253 2.61859 2.94679 2.52646L3.25837 2.37067Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "MagicBox"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './MagicBox.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'MagicBox'
export default Icon

View file

@ -0,0 +1,38 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "eye-sparkle, magic eyes"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M11.0338 5.05688C9.75366 3.05335 7.90203 1.99999 6.00017 2C4.09831 2.00001 2.24669 3.05341 0.966566 5.05693C0.599687 5.63113 0.599686 6.36892 0.966566 6.94312C2.24669 8.94665 4.09832 10 6.00018 10C7.90204 9.99999 9.75366 8.94659 11.0338 6.94307C11.4007 6.36887 11.4007 5.63108 11.0338 5.05688ZM5.77639 4.44721L5.3706 5.2588C5.34641 5.30718 5.30718 5.34641 5.2588 5.3706L4.44721 5.77639C4.26295 5.86852 4.26295 6.13148 4.44721 6.22361L5.2588 6.6294C5.30718 6.65359 5.34641 6.69282 5.3706 6.7412L5.77639 7.55279C5.86852 7.73705 6.13148 7.73705 6.22361 7.55279L6.6294 6.7412C6.65359 6.69282 6.69282 6.65359 6.7412 6.6294L7.55279 6.22361C7.73705 6.13148 7.73705 5.86852 7.55279 5.77639L6.7412 5.3706C6.69282 5.34641 6.65359 5.30718 6.6294 5.2588L6.22361 4.44721C6.13148 4.26295 5.86852 4.26295 5.77639 4.44721Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "MagicEyes"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './MagicEyes.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'MagicEyes'
export default Icon

View file

@ -0,0 +1,73 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "magic-wand-2, magic stick, star"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "Icon"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M8.27056 1.77151C8.811 1.23107 9.68723 1.23107 10.2277 1.77151C10.7681 2.31195 10.7681 3.18818 10.2277 3.72862L3.72767 10.2286C3.18723 10.7691 2.31101 10.7691 1.77056 10.2286C1.23012 9.68818 1.23012 8.81195 1.77056 8.27151L8.27056 1.77151ZM9.52056 2.47862C9.37065 2.3287 9.12759 2.3287 8.97767 2.47862L8.08122 3.37506L8.62412 3.91796L9.52056 3.02151C9.67048 2.87159 9.67048 2.62853 9.52056 2.47862Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M4.92504 1.03733C4.97342 1.01314 5.01265 0.973911 5.03684 0.92553L5.27597 0.447279C5.3681 0.263016 5.63105 0.263017 5.72318 0.447279L5.96231 0.92553C5.9865 0.973911 6.02573 1.01314 6.07411 1.03733L6.55236 1.27646C6.73663 1.36859 6.73663 1.63154 6.55236 1.72367L6.07411 1.9628C6.02573 1.98699 5.9865 2.02622 5.96231 2.0746L5.72318 2.55285C5.63105 2.73711 5.3681 2.73711 5.27597 2.55285L5.03684 2.0746C5.01265 2.02622 4.97342 1.98699 4.92504 1.9628L4.44679 1.72367C4.26253 1.63154 4.26253 1.36859 4.44679 1.27646L4.92504 1.03733Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M9.42504 6.53733C9.47342 6.51314 9.51265 6.47391 9.53684 6.42553L9.77597 5.94728C9.8681 5.76302 10.1311 5.76302 10.2232 5.94728L10.4623 6.42553C10.4865 6.47391 10.5257 6.51314 10.5741 6.53733L11.0524 6.77646C11.2366 6.86859 11.2366 7.13154 11.0524 7.22367L10.5741 7.4628C10.5257 7.48699 10.4865 7.52622 10.4623 7.5746L10.2232 8.05285C10.1311 8.23711 9.8681 8.23711 9.77597 8.05285L9.53684 7.5746C9.51265 7.52622 9.47342 7.48699 9.42504 7.4628L8.94679 7.22367C8.76253 7.13154 8.76253 6.86859 8.94679 6.77646L9.42504 6.53733Z",
"fill": "currentColor"
},
"children": []
},
{
"type": "element",
"name": "path",
"attributes": {
"d": "M2.42504 3.53733C2.47342 3.51314 2.51265 3.47391 2.53684 3.42553L2.77597 2.94728C2.8681 2.76302 3.13105 2.76302 3.22318 2.94728L3.46231 3.42553C3.4865 3.47391 3.52573 3.51314 3.57411 3.53733L4.05236 3.77646C4.23663 3.86859 4.23663 4.13154 4.05236 4.22367L3.57411 4.4628C3.52573 4.48699 3.4865 4.52622 3.46231 4.5746L3.22318 5.05285C3.13105 5.23711 2.8681 5.23711 2.77597 5.05285L2.53684 4.5746C2.51265 4.52622 2.47342 4.48699 2.42504 4.4628L1.94679 4.22367C1.76253 4.13154 1.76253 3.86859 1.94679 3.77646L2.42504 3.53733Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
}
]
},
"name": "MagicWand"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './MagicWand.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'MagicWand'
export default Icon

View file

@ -0,0 +1,38 @@
{
"icon": {
"type": "element",
"isRootNode": true,
"name": "svg",
"attributes": {
"width": "12",
"height": "12",
"viewBox": "0 0 12 12",
"fill": "none",
"xmlns": "http://www.w3.org/2000/svg"
},
"children": [
{
"type": "element",
"name": "g",
"attributes": {
"id": "robot, bot"
},
"children": [
{
"type": "element",
"name": "path",
"attributes": {
"id": "Icon",
"fill-rule": "evenodd",
"clip-rule": "evenodd",
"d": "M6 0.5C6.27614 0.5 6.5 0.723858 6.5 1V1.5H8.5C9.32843 1.5 10 2.17157 10 3V5.5C10 5.94425 9.80688 6.34339 9.5 6.61805V7.29289L10.3536 8.14645C10.5488 8.34171 10.5488 8.65829 10.3536 8.85355C10.1583 9.04882 9.84171 9.04882 9.64645 8.85355L9.34052 8.54762C8.89526 9.96884 7.56805 11 6 11C4.43195 11 3.10474 9.96884 2.65948 8.54762L2.35355 8.85355C2.15829 9.04882 1.84171 9.04882 1.64645 8.85355C1.45118 8.65829 1.45118 8.34171 1.64645 8.14645L2.5 7.29289V6.61805C2.19313 6.34339 2 5.94425 2 5.5V3C2 2.17157 2.67157 1.5 3.5 1.5H5.5V1C5.5 0.723858 5.72386 0.5 6 0.5ZM3.5 2.5C3.22386 2.5 3 2.72386 3 3V5.5C3 5.77614 3.22386 6 3.5 6H8.5C8.77614 6 9 5.77614 9 5.5V3C9 2.72386 8.77614 2.5 8.5 2.5H3.5ZM4.5 3.5C4.77614 3.5 5 3.72386 5 4V4.5C5 4.77614 4.77614 5 4.5 5C4.22386 5 4 4.77614 4 4.5V4C4 3.72386 4.22386 3.5 4.5 3.5ZM7.5 3.5C7.77614 3.5 8 3.72386 8 4V4.5C8 4.77614 7.77614 5 7.5 5C7.22386 5 7 4.77614 7 4.5V4C7 3.72386 7.22386 3.5 7.5 3.5Z",
"fill": "currentColor"
},
"children": []
}
]
}
]
},
"name": "Robot"
}

View file

@ -0,0 +1,16 @@
// GENERATE BY script
// DON NOT EDIT IT MANUALLY
import * as React from 'react'
import data from './Robot.json'
import IconBase from '@/app/components/base/icons/IconBase'
import type { IconBaseProps, IconData } from '@/app/components/base/icons/IconBase'
const Icon = React.forwardRef<React.MutableRefObject<SVGElement>, Omit<IconBaseProps, 'data'>>((
props,
ref,
) => <IconBase {...props} ref={ref} data={data as IconData} />)
Icon.displayName = 'Robot'
export default Icon

View file

@ -1,3 +1,7 @@
export { default as MagicBox } from './MagicBox'
export { default as MagicEyes } from './MagicEyes'
export { default as MagicWand } from './MagicWand'
export { default as Microphone01 } from './Microphone01'
export { default as Robot } from './Robot'
export { default as Sliders02 } from './Sliders02'
export { default as StopCircle } from './StopCircle'

View file

@ -27,6 +27,9 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
setMounted(true)
}, [])
if (!log)
return null
return (
<div
className='fixed top-16 left-2 bottom-2 flex flex-col bg-white border-[0.5px] border-gray-200 rounded-xl shadow-xl z-10'
@ -37,7 +40,7 @@ const PromptLogModal: FC<PromptLogModalProps> = ({
<div className='text-base font-semibold text-gray-900'>PROMPT LOG</div>
<div className='flex items-center'>
{
log.length === 1 && (
log?.length === 1 && (
<>
<CopyFeedbackNew className='w-6 h-6' content={log[0].text} />
<div className='mx-2.5 w-[1px] h-[14px] bg-gray-200' />

View file

@ -6,12 +6,13 @@
}
.slider-thumb {
width: 18px;
height: 18px;
width: 16px;
height: 16px;
background-color: white;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.08);
position: absolute;
top: -9px;
top: -8px;
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.08);
cursor: pointer;

View file

@ -39,7 +39,7 @@ const Toast = ({
return <div className={classNames(
className,
'fixed rounded-md p-4 my-4 mx-8 z-50',
'fixed rounded-md p-4 my-4 mx-8 z-[9999]',
'top-0',
'right-0',
type === 'success' ? 'bg-green-50' : '',

View file

@ -1,5 +1,8 @@
import type { BackendModel } from '../../header/account-setting/model-page/declarations'
import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
import type {
DefaultModelResponse,
Model,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
export const isReRankModelSelected = ({
rerankDefaultModel,
@ -8,15 +11,18 @@ export const isReRankModelSelected = ({
rerankModelList,
indexMethod,
}: {
rerankDefaultModel?: BackendModel
rerankDefaultModel?: DefaultModelResponse
isRerankDefaultModelVaild: boolean
retrievalConfig: RetrievalConfig
rerankModelList: BackendModel[]
rerankModelList: Model[]
indexMethod?: string
}) => {
const rerankModelSelected = (() => {
if (retrievalConfig.reranking_model?.reranking_model_name)
return !!rerankModelList.find(({ model_name }) => model_name === retrievalConfig.reranking_model?.reranking_model_name)
if (retrievalConfig.reranking_model?.reranking_model_name) {
const provider = rerankModelList.find(({ provider }) => provider === retrievalConfig.reranking_model?.reranking_provider_name)
return provider?.models.find(({ model }) => model === retrievalConfig.reranking_model?.reranking_model_name)
}
if (isRerankDefaultModelVaild)
return !!rerankDefaultModel
@ -39,7 +45,7 @@ export const ensureRerankModelSelected = ({
indexMethod,
retrievalConfig,
}: {
rerankDefaultModel: BackendModel
rerankDefaultModel: DefaultModelResponse
retrievalConfig: RetrievalConfig
indexMethod?: string
}) => {
@ -52,8 +58,8 @@ export const ensureRerankModelSelected = ({
return {
...retrievalConfig,
reranking_model: {
reranking_provider_name: rerankDefaultModel.model_provider.provider_name,
reranking_model_name: rerankDefaultModel.model_name,
reranking_provider_name: rerankDefaultModel.provider.provider,
reranking_model_name: rerankDefaultModel.model,
},
}
}

View file

@ -9,6 +9,7 @@ import RadioCard from '@/app/components/base/radio-card'
import { PatternRecognition, Semantic } from '@/app/components/base/icons/src/vender/solid/development'
import { FileSearch02 } from '@/app/components/base/icons/src/vender/solid/files'
import { useProviderContext } from '@/context/provider-context'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type Props = {
value: RetrievalConfig
@ -20,14 +21,15 @@ const RetrievalMethodConfig: FC<Props> = ({
onChange,
}) => {
const { t } = useTranslation()
const { supportRetrievalMethods, rerankDefaultModel } = useProviderContext()
const { supportRetrievalMethods } = useProviderContext()
const { data: rerankDefaultModel } = useDefaultModel(3)
const value = (() => {
if (!passValue.reranking_model.reranking_model_name) {
return {
...passValue,
reranking_model: {
reranking_provider_name: rerankDefaultModel?.model_provider.provider_name || '',
reranking_model_name: rerankDefaultModel?.model_name || '',
reranking_provider_name: rerankDefaultModel?.provider.provider || '',
reranking_model_name: rerankDefaultModel?.model || '',
},
}
}

View file

@ -9,10 +9,9 @@ import { RETRIEVE_METHOD } from '@/types/app'
import Switch from '@/app/components/base/switch'
import Tooltip from '@/app/components/base/tooltip-plus'
import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import type { RetrievalConfig } from '@/types/app'
import { useProviderContext } from '@/context/provider-context'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type Props = {
type: RETRIEVE_METHOD
@ -29,8 +28,9 @@ const RetrievalParamConfig: FC<Props> = ({
const canToggleRerankModalEnable = type !== RETRIEVE_METHOD.hybrid
const isEconomical = type === RETRIEVE_METHOD.invertedIndex
const {
rerankDefaultModel,
} = useProviderContext()
defaultModel: rerankDefaultModel,
modelList: rerankModelList,
} = useModelListAndDefaultModel(3)
const rerankModel = (() => {
if (value.reranking_model) {
@ -41,8 +41,8 @@ const RetrievalParamConfig: FC<Props> = ({
}
else if (rerankDefaultModel) {
return {
provider_name: rerankDefaultModel.model_provider.provider_name,
model_name: rerankDefaultModel.model_name,
provider_name: rerankDefaultModel.provider.provider,
model_name: rerankDefaultModel.model,
}
}
})()
@ -71,24 +71,21 @@ const RetrievalParamConfig: FC<Props> = ({
</Tooltip>
</div>
</div>
<div>
<ModelSelector
whenEmptyGoToSetting
popClassName='!max-w-[100%] !w-full'
value={rerankModel && { providerName: rerankModel.provider_name, modelName: rerankModel.model_name } as any}
modelType={ModelType.reranking}
readonly={!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid}
onChange={(v) => {
onChange({
...value,
reranking_model: {
reranking_provider_name: v.model_provider.provider_name,
reranking_model_name: v.model_name,
},
})
}}
/>
</div>
<ModelSelector
triggerClassName={`${!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid && '!opacity-60 !cursor-not-allowed'}`}
defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
modelList={rerankModelList}
readonly={!value.reranking_enable && type !== RETRIEVE_METHOD.hybrid}
onSelect={(v) => {
onChange({
...value,
reranking_model: {
reranking_provider_name: v.provider,
reranking_model_name: v.model,
},
})
}}
/>
</div>
)}

View file

@ -11,8 +11,8 @@ import type { DataSet, FileItem, createDocumentResponse } from '@/models/dataset
import { fetchDataSource } from '@/service/common'
import { fetchDatasetDetail } from '@/service/datasets'
import type { NotionPage } from '@/models/common'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type DatasetUpdateFormProps = {
datasetId?: string
@ -28,7 +28,7 @@ const DatasetUpdateForm = ({ datasetId }: DatasetUpdateFormProps) => {
const [fileList, setFiles] = useState<FileItem[]>([])
const [result, setResult] = useState<createDocumentResponse | undefined>()
const [hasError, setHasError] = useState(false)
const { embeddingsDefaultModel } = useProviderContext()
const { data: embeddingsDefaultModel } = useDefaultModel(2)
const [notionPages, setNotionPages] = useState<NotionPage[]>([])
const updateNotionPages = (value: NotionPage[]) => {

View file

@ -38,9 +38,9 @@ import { useDatasetDetailContext } from '@/context/dataset-detail'
import I18n from '@/context/i18n'
import { IS_CE_EDITION } from '@/config'
import { RETRIEVE_METHOD } from '@/types/app'
import { useProviderContext } from '@/context/provider-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Tooltip from '@/app/components/base/tooltip'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type ValueOf<T> = T[keyof T]
type StepTwoProps = {
@ -268,10 +268,10 @@ const StepTwo = ({
}
}
const {
rerankDefaultModel,
isRerankDefaultModelVaild,
rerankModelList,
} = useProviderContext()
modelList: rerankModelList,
defaultModel: rerankDefaultModel,
currentModel: isRerankDefaultModelVaild,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
const getCreationParams = () => {
let params
if (isSetting) {
@ -289,7 +289,7 @@ const StepTwo = ({
if (
!isReRankModelSelected({
rerankDefaultModel,
isRerankDefaultModelVaild,
isRerankDefaultModelVaild: !!isRerankDefaultModelVaild,
rerankModelList,
// eslint-disable-next-line @typescript-eslint/no-use-before-define
retrievalConfig,
@ -489,8 +489,8 @@ const StepTwo = ({
search_method: RETRIEVE_METHOD.semantic,
reranking_enable: false,
reranking_model: {
reranking_provider_name: rerankDefaultModel?.model_provider.provider_name,
reranking_model_name: rerankDefaultModel?.model_name,
reranking_provider_name: rerankDefaultModel?.provider.provider,
reranking_model_name: rerankDefaultModel?.model,
},
top_k: 3,
score_threshold_enabled: false,

View file

@ -13,7 +13,7 @@ import Loading from '@/app/components/base/loading'
import StepTwo from '@/app/components/datasets/create/step-two'
import AccountSetting from '@/app/components/header/account-setting'
import AppUnavailable from '@/app/components/base/app-unavailable'
import { useProviderContext } from '@/context/provider-context'
import { useDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type DocumentSettingsProps = {
datasetId: string
@ -26,7 +26,7 @@ const DocumentSettings = ({ datasetId, documentId }: DocumentSettingsProps) => {
const [isShowSetAPIKey, { setTrue: showSetAPIKey, setFalse: hideSetAPIkey }] = useBoolean()
const [hasError, setHasError] = useState(false)
const { indexingTechnique, dataset } = useContext(DatasetDetailContext)
const { embeddingsDefaultModel } = useProviderContext()
const { data: embeddingsDefaultModel } = useDefaultModel(2)
const saveHandler = () => router.push(`/datasets/${datasetId}/documents/${documentId}`)

View file

@ -8,8 +8,8 @@ import type { RetrievalConfig } from '@/types/app'
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
import Button from '@/app/components/base/button'
import { useProviderContext } from '@/context/provider-context'
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
type Props = {
indexMethod: string
@ -36,16 +36,16 @@ const ModifyRetrievalModal: FC<Props> = ({
// }, ref)
const {
rerankDefaultModel,
isRerankDefaultModelVaild,
rerankModelList,
} = useProviderContext()
modelList: rerankModelList,
defaultModel: rerankDefaultModel,
currentModel: isRerankDefaultModelVaild,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
const handleSave = () => {
if (
!isReRankModelSelected({
rerankDefaultModel,
isRerankDefaultModelVaild,
isRerankDefaultModelVaild: !!isRerankDefaultModelVaild,
rerankModelList,
retrievalConfig,
indexMethod,

View file

@ -15,14 +15,16 @@ import { ToastContext } from '@/app/components/base/toast'
import Button from '@/app/components/base/button'
import { updateDatasetSetting } from '@/service/datasets'
import type { DataSet, DataSetListResponse } from '@/models/datasets'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import DatasetDetailContext from '@/context/dataset-detail'
import { type RetrievalConfig } from '@/types/app'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { ensureRerankModelSelected, isReRankModelSelected } from '@/app/components/datasets/common/check-rerank-model'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import {
useModelList,
useModelListAndDefaultModelAndCurrentProviderAndModel,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
const rowClass = `
flex justify-between py-4 flex-wrap gap-y-2
`
@ -56,11 +58,13 @@ const Form = () => {
const [permission, setPermission] = useState(currentDataset?.permission)
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig)
const {
rerankDefaultModel,
isRerankDefaultModelVaild,
rerankModelList,
} = useProviderContext()
modelList: rerankModelList,
defaultModel: rerankDefaultModel,
currentModel: isRerankDefaultModelVaild,
} = useModelListAndDefaultModelAndCurrentProviderAndModel(3)
const { data: embeddingModelList } = useModelList(2)
const handleSave = async () => {
if (loading)
@ -72,7 +76,7 @@ const Form = () => {
if (
!isReRankModelSelected({
rerankDefaultModel,
isRerankDefaultModelVaild,
isRerankDefaultModelVaild: !!isRerankDefaultModelVaild,
rerankModelList,
retrievalConfig,
indexMethod,
@ -183,17 +187,15 @@ const Form = () => {
<div>{t('datasetSettings.form.embeddingModel')}</div>
</div>
<div className='w-[480px]'>
<div className='w-full h-9 rounded-lg bg-gray-100 opacity-60'>
<ModelSelector
readonly
value={{
providerName: currentDataset.embedding_model_provider as ProviderEnum,
modelName: currentDataset.embedding_model,
}}
modelType={ModelType.embeddings}
onChange={() => {}}
/>
</div>
<ModelSelector
readonly
triggerClassName='!h-9 !cursor-not-allowed opacity-60'
defaultModel={{
provider: currentDataset.embedding_model_provider,
model: currentDataset.embedding_model,
}}
modelList={embeddingModelList}
/>
<div className='mt-2 w-full text-xs leading-6 text-gray-500'>
{t('datasetSettings.form.embeddingModelTip')}
<span className='text-[#155eef] cursor-pointer' onClick={() => setShowAccountSettingModal({ payload: 'provider' })}>{t('datasetSettings.form.embeddingModelTipLink')}</span>

View file

@ -77,7 +77,6 @@ const CreateAppModal = ({
</Modal>
{showEmojiPicker && <EmojiPicker
onSelect={(icon, icon_background) => {
console.log(icon, icon_background)
setEmoji({ icon, icon_background })
setShowEmojiPicker(false)
}}

View file

@ -5,12 +5,11 @@ import cn from 'classnames'
import { useTranslation } from 'react-i18next'
import s from './style.module.css'
import Config from '@/app/components/explore/universal-chat/config'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import type { DataSet } from '@/models/datasets'
type Props = {
modelId: string
providerName: ProviderEnum
providerName: string
plugins: Record<string, boolean>
dataSets: DataSet[]
}

View file

@ -4,17 +4,16 @@ import React from 'react'
import cn from 'classnames'
import { useBoolean, useClickAway } from 'ahooks'
import s from './style.module.css'
import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
import ModelIcon from '@/app/components/header/account-setting/model-provider-page/model-icon'
import ModelName from '@/app/components/header/account-setting/model-provider-page/model-name'
import { Google, WebReader, Wikipedia } from '@/app/components/base/icons/src/public/plugins'
import ConfigDetail from '@/app/components/explore/universal-chat/config-view/detail'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import ModelName from '@/app/components/app/configuration/config-model/model-name'
import { useProviderContext } from '@/context/provider-context'
import type { DataSet } from '@/models/datasets'
import { useAgentThoughtCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks'
export type ISummaryProps = {
modelId: string
providerName: ProviderEnum
providerName: string
plugins: Record<string, boolean>
dataSets: DataSet[]
}
@ -49,9 +48,12 @@ const Summary: FC<ISummaryProps> = ({
plugins,
dataSets,
}) => {
const { agentThoughtModelList } = useProviderContext()
const currModel = agentThoughtModelList.find(item => item.model_name === modelId && item.model_provider.provider_name === providerName)
const {
currentModel: currModel,
currentProvider,
} = useAgentThoughtCurrentProviderAndModelAndModelList(
{ provider: providerName, model: modelId },
)
// current_datetime is not configable and do not have icon
const pluginIds = Object.keys(plugins).filter(key => plugins[key] && key !== 'current_datetime')
const [isShowConfig, { setFalse: hideConfig, toggle: toggleShowConfig }] = useBoolean(false)
@ -63,8 +65,16 @@ const Summary: FC<ISummaryProps> = ({
return (
<div ref={configContentRef} className='relative'>
<div onClick={toggleShowConfig} className={cn(getColorInfo(modelId), 'flex items-center px-1 h-8 rounded-lg border cursor-pointer')}>
<ModelIcon providerName={providerName} modelId={modelId} className='!w-6 !h-6' />
<div className='ml-2 text-[13px] font-medium text-gray-900'><ModelName modelId={modelId} modelDisplayName={currModel?.model_display_name} /></div>
<ModelIcon
provider={currentProvider}
modelName={currModel?.model}
className='!w-6 !h-6'
/>
<div className='ml-2 text-[13px] font-medium text-gray-900'>
<ModelName
modelItem={currModel!}
/>
</div>
{
pluginIds.length > 0 && (
<div className='ml-1.5 flex items-center'>

View file

@ -4,15 +4,14 @@ import React from 'react'
import ModelConfig from './model-config'
import DataConfig from './data-config'
import PluginConfig from './plugins-config'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import type { DataSet } from '@/models/datasets'
export type IConfigProps = {
className?: string
readonly?: boolean
modelId: string
providerName: ProviderEnum
onModelChange?: (modelId: string, providerName: ProviderEnum) => void
providerName: string
onModelChange?: (modelId: string, providerName: string) => void
plugins: Record<string, boolean>
onPluginChange?: (key: string, value: boolean) => void
dataSets: DataSet[]

View file

@ -2,12 +2,13 @@
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { ModelType, type ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import ModelSelector from '@/app/components/header/account-setting/model-page/model-selector'
import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
import { useProviderContext } from '@/context/provider-context'
export type IModelConfigProps = {
modelId: string
providerName: ProviderEnum
onChange?: (modelId: string, providerName: ProviderEnum) => void
providerName: string
onChange?: (modelId: string, providerName: string) => void
readonly?: boolean
}
@ -18,21 +19,17 @@ const ModelConfig: FC<IModelConfigProps> = ({
readonly,
}) => {
const { t } = useTranslation()
const { agentThoughtModelList } = useProviderContext()
return (
<div className='flex items-center justify-between h-[52px] px-3 rounded-xl bg-gray-50'>
<div className='text-sm font-semibold text-gray-800'>{t('explore.universalChat.model')}</div>
<ModelSelector
popClassName="right-0"
triggerIconSmall
modelType={ModelType.textGeneration}
supportAgentThought
value={{
modelName: modelId,
providerName,
}}
onChange={(model) => {
onChange?.(model.model_name, model.model_provider.provider_name)
triggerClassName={`${readonly && '!cursor-not-allowed !opacity-60'}`}
defaultModel={{ provider: providerName, model: modelId }}
modelList={agentThoughtModelList}
onSelect={(model) => {
onChange?.(model.model, model.provider)
}}
readonly={readonly}
/>

View file

@ -39,8 +39,8 @@ import type { DataSet } from '@/models/datasets'
import ConfigSummary from '@/app/components/explore/universal-chat/config-view/summary'
import { fetchDatasets } from '@/service/datasets'
import ItemOperation from '@/app/components/explore/item-operation'
import { useCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
import { useProviderContext } from '@/context/provider-context'
import type { ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
const APP_ID = 'universal-chat'
const DEFAULT_PLUGIN = {
@ -76,8 +76,8 @@ const Main: FC<IMainProps> = () => {
const getInitConfig = (type: 'model' | 'plugin') => {
if (type === 'model') {
return {
providerName: prevConfig?.providerName || agentThoughtModelList?.[0]?.model_provider.provider_name,
modelId: prevConfig?.modelId || agentThoughtModelList?.[0]?.model_name,
providerName: prevConfig?.providerName || agentThoughtModelList[0]?.provider,
modelId: prevConfig?.modelId || agentThoughtModelList[0]?.models[0]?.model,
}
}
@ -452,9 +452,16 @@ const Main: FC<IMainProps> = () => {
const [hasStopResponded, setHasStopResponded, getHasStopResponded] = useGetState(false)
const [errorHappened, setErrorHappened] = useState(false)
const [isResponsingConIsCurrCon, setIsResponsingConCurrCon, getIsResponsingConIsCurrCon] = useGetState(true)
const initConfig = getInitConfig('model')
const [modelId, setModeId] = useState<string>((initConfig as any)?.modelId as string)
const [providerName, setProviderName] = useState<string>((initConfig as any)?.providerName)
const { currentModel } = useCurrentProviderAndModel(
agentThoughtModelList,
{ provider: providerName, model: modelId },
)
const handleSend = async (message: string) => {
if (isNewConversation) {
const isModelSelected = modelId && !!agentThoughtModelList.find(item => item.model_name === modelId)
const isModelSelected = modelId && !!currentModel
if (!isModelSelected) {
notify({ type: 'error', message: t('appDebug.errorMessage.notSelectModel') })
return
@ -601,7 +608,7 @@ const Main: FC<IMainProps> = () => {
setChatList(newListWithAnswer)
},
onMessageEnd: (messageEnd) => {
responseItem.citation = messageEnd.retriever_resources
responseItem.citation = messageEnd.metadata?.retriever_resources
const newListWithAnswer = produce(
getChatList().filter(item => item.id !== responseItem.id && item.id !== placeholderAnswerId),
@ -691,9 +698,6 @@ const Main: FC<IMainProps> = () => {
/>
)
}
const initConfig = getInitConfig('model')
const [modelId, setModeId] = useState<string>((initConfig as any)?.modelId as string)
const [providerName, setProviderName] = useState<ProviderEnum>((initConfig as any)?.providerName as ProviderEnum)
// const currModel = MODEL_LIST.find(item => item.id === modelId)
const [plugins, setPlugins] = useState<Record<string, boolean>>(getInitConfig('plugin') as Record<string, boolean>)
@ -707,7 +711,7 @@ const Main: FC<IMainProps> = () => {
const configSetDefaultValue = () => {
const initConfig = getInitConfig('model')
setModeId((initConfig as any)?.modelId as string)
setProviderName((initConfig as any)?.providerName as ProviderEnum)
setProviderName((initConfig as any)?.providerName)
setPlugins(getInitConfig('plugin') as any)
setDateSets([])
}

View file

@ -30,6 +30,7 @@ const WorkplaceSelector = () => {
const currentWorkspace = workspaces.find(v => v.current)
const handleSwitchWorkspace = async (tenant_id: string) => {
console.log(tenant_id, currentWorkspace?.id)
try {
if (currentWorkspace?.id === tenant_id)
return

View file

@ -11,7 +11,7 @@ import LanguagePage from './language-page'
import PluginPage from './plugin-page'
import ApiBasedExtensionPage from './api-based-extension-page'
import DataSourcePage from './data-source-page'
import ModelPage from './model-page'
import ModelProviderPage from './model-provider-page'
import s from './index.module.css'
import BillingPage from '@/app/components/billing/billing-page'
import CustomPage from '@/app/components/custom/custom-page'
@ -211,7 +211,7 @@ export default function AccountSetting({
{activeMenu === 'billing' && <BillingPage />}
{activeMenu === 'integrations' && <IntegrationsPage />}
{activeMenu === 'language' && <LanguagePage />}
{activeMenu === 'provider' && <ModelPage />}
{activeMenu === 'provider' && <ModelProviderPage />}
{activeMenu === 'data-source' && <DataSourcePage />}
{activeMenu === 'plugin' && <PluginPage />}
{activeMenu === 'api-based-extension' && <ApiBasedExtensionPage /> }

View file

@ -13,7 +13,7 @@ export type ValidatedStatusState = {
export type Status = 'add' | 'fail' | 'success'
export type ValidateValue = Record<string, string | undefined | boolean>
export type ValidateValue = Record<string, any>
export type ValidateCallback = {
before: (v?: ValidateValue) => boolean | undefined

View file

@ -1,80 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Anthropic, AnthropicText } from '@/app/components/base/icons/src/public/llm'
import { IS_CE_EDITION } from '@/config'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Anthropic',
'zh-Hans': 'Anthropic',
},
icon: <Anthropic className='w-full h-full' />,
},
item: {
key: ProviderEnum.anthropic,
titleIcon: {
'en': <AnthropicText className='h-5' />,
'zh-Hans': <AnthropicText className='h-5' />,
},
subTitleIcon: <Anthropic className='h-6' />,
desc: {
'en': 'Anthropics powerful models, such as Claude 2 and Claude Instant.',
'zh-Hans': 'Anthropic 的强大模型,例如 Claude 2 和 Claude Instant。',
},
bgColor: 'bg-[#F0F0EB]',
},
modal: {
key: ProviderEnum.anthropic,
title: {
'en': 'Anthropic',
'zh-Hans': 'Anthropic',
},
icon: <Anthropic className='h-6' />,
link: {
href: 'https://console.anthropic.com/account/keys',
label: {
'en': 'Get your API key from Anthropic',
'zh-Hans': '从 Anthropic 获取 API Key',
},
},
validateKeys: ['anthropic_api_key'],
fields: [
{
type: 'text',
key: 'anthropic_api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
...(
IS_CE_EDITION
? [{
type: 'text',
key: 'anthropic_api_url',
required: false,
label: {
'en': 'Custom API Domain',
'zh-Hans': '自定义 API 域名',
},
placeholder: {
'en': 'Enter your API domain, eg: https://example.com/xxx(Optional)',
'zh-Hans': '在此输入您的 API 域名https://example.com/xxx选填',
},
help: {
'en': 'Configurable custom Anthropic API server url.',
'zh-Hans': '可配置自定义 Anthropic API 服务器地址。',
},
}]
: []
),
],
},
}
export default config

View file

@ -1,189 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { AzureOpenaiService, AzureOpenaiServiceText, OpenaiBlue } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Azure OpenAI Service',
'zh-Hans': 'Azure OpenAI Service',
},
icon: <OpenaiBlue className='w-full h-full' />,
},
item: {
key: ProviderEnum.azure_openai,
titleIcon: {
'en': <AzureOpenaiServiceText className='h-6' />,
'zh-Hans': <AzureOpenaiServiceText className='h-6' />,
},
},
modal: {
key: ProviderEnum.azure_openai,
title: {
'en': 'Azure OpenAI Service Model',
'zh-Hans': 'Azure OpenAI Service Model',
},
icon: <AzureOpenaiService className='h-6' />,
link: {
href: 'https://azure.microsoft.com/en-us/products/ai-services/openai-service',
label: {
'en': 'Get your API key from Azure',
'zh-Hans': '从 Azure 获取 API Key',
},
},
defaultValue: {
model_type: 'text-generation',
},
validateKeys: [
'model_name',
'model_type',
'openai_api_base',
'openai_api_key',
'base_model_name',
],
fields: [
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Deployment Name',
'zh-Hans': '部署名称',
},
placeholder: {
'en': 'Enter your Deployment Name here, matching the Azure deployment name.',
'zh-Hans': '在此输入您的部署名称,需要与 Azure 的部署名称匹配',
},
},
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
],
},
{
type: 'text',
key: 'openai_api_base',
required: true,
label: {
'en': 'API Endpoint URL',
'zh-Hans': 'API 域名',
},
placeholder: {
'en': 'Enter your API Endpoint, eg: https://example.com/xxx',
'zh-Hans': '在此输入您的 API 域名https://example.com/xxx',
},
},
{
type: 'text',
key: 'openai_api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
{
type: 'select',
key: 'base_model_name',
required: true,
label: {
'en': 'Base Model',
'zh-Hans': '基础模型',
},
options: (v) => {
if (v.model_type === 'text-generation') {
return [
{
key: 'gpt-35-turbo',
label: {
'en': 'gpt-35-turbo',
'zh-Hans': 'gpt-35-turbo',
},
},
{
key: 'gpt-35-turbo-16k',
label: {
'en': 'gpt-35-turbo-16k',
'zh-Hans': 'gpt-35-turbo-16k',
},
},
{
key: 'gpt-4',
label: {
'en': 'gpt-4',
'zh-Hans': 'gpt-4',
},
},
{
key: 'gpt-4-32k',
label: {
'en': 'gpt-4-32k',
'zh-Hans': 'gpt-4-32k',
},
},
{
key: 'gpt-4-1106-preview',
label: {
'en': 'gpt-4-1106-preview',
'zh-Hans': 'gpt-4-1106-preview',
},
},
{
key: 'gpt-4-vision-preview',
label: {
'en': 'gpt-4-vision-preview',
'zh-Hans': 'gpt-4-vision-preview',
},
},
{
key: 'text-davinci-003',
label: {
'en': 'text-davinci-003',
'zh-Hans': 'text-davinci-003',
},
},
]
}
if (v.model_type === 'embeddings') {
return [
{
key: 'text-embedding-ada-002',
label: {
'en': 'text-embedding-ada-002',
'zh-Hans': 'text-embedding-ada-002',
},
},
]
}
return []
},
},
],
},
}
export default config

View file

@ -1,70 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { BaichuanTextCn } from '@/app/components/base/icons/src/image/llm'
import {
Baichuan,
BaichuanText,
} from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'BAICHUAN AI',
'zh-Hans': '百川智能',
},
icon: <Baichuan className='w-full h-full' />,
},
item: {
key: ProviderEnum.baichuan,
titleIcon: {
'en': <BaichuanText className='w-[124px] h-6' />,
'zh-Hans': <BaichuanTextCn className='w-[100px] h-6' />,
},
},
modal: {
key: ProviderEnum.baichuan,
title: {
'en': 'BAICHUAN AI',
'zh-Hans': '百川智能',
},
icon: <Baichuan className='w-6 h-6' />,
link: {
href: 'https://platform.baichuan-ai.com/console/apikey',
label: {
'en': 'Get your API key from BAICHUAN AI',
'zh-Hans': '从百川智能获取 API Key',
},
},
validateKeys: ['api_key', 'secret_key'],
fields: [
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
{
type: 'text',
key: 'secret_key',
required: true,
label: {
'en': 'Secret Key',
'zh-Hans': 'Secret Key',
},
placeholder: {
'en': 'Enter your Secret key here',
'zh-Hans': '在此输入您的 Secret Key',
},
},
],
},
}
export default config

View file

@ -1,69 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Chatglm, ChatglmText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'ChatGLM',
'zh-Hans': 'ChatGLM',
},
icon: <Chatglm className='w-full h-full' />,
},
item: {
key: ProviderEnum.chatglm,
titleIcon: {
'en': <ChatglmText className='h-6' />,
'zh-Hans': <ChatglmText className='h-6' />,
},
disable: {
tip: {
'en': 'Only supports the ',
'zh-Hans': '仅支持',
},
link: {
href: {
'en': 'https://docs.dify.ai/getting-started/install-self-hosted',
'zh-Hans': 'https://docs.dify.ai/v/zh-hans/getting-started/install-self-hosted',
},
label: {
'en': 'community open-source version',
'zh-Hans': '社区开源版本',
},
},
},
},
modal: {
key: ProviderEnum.chatglm,
title: {
'en': 'ChatGLM',
'zh-Hans': 'ChatGLM',
},
icon: <Chatglm className='h-6' />,
link: {
href: 'https://github.com/THUDM/ChatGLM-6B#api%E9%83%A8%E7%BD%B2',
label: {
'en': 'How to deploy ChatGLM',
'zh-Hans': '如何部署 ChatGLM',
},
},
validateKeys: ['api_base'],
fields: [
{
type: 'text',
key: 'api_base',
required: true,
label: {
'en': 'Custom API Domain',
'zh-Hans': '自定义 API 域名',
},
placeholder: {
'en': 'Enter your API domain, eg: https://example.com/xxx',
'zh-Hans': '在此输入您的 API 域名https://example.com/xxx',
},
},
],
},
}
export default config

View file

@ -1,57 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Cohere, CohereText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'cohere',
'zh-Hans': 'cohere',
},
icon: <Cohere className='w-full h-full' />,
},
item: {
key: ProviderEnum.cohere,
titleIcon: {
'en': <CohereText className='w-[120px] h-6' />,
'zh-Hans': <CohereText className='w-[120px] h-6' />,
},
hit: {
'en': 'Rerank Model Supported',
'zh-Hans': '支持 Rerank 模型',
},
},
modal: {
key: ProviderEnum.cohere,
title: {
'en': 'Rerank Model',
'zh-Hans': 'Rerank 模型',
},
icon: <Cohere className='w-6 h-6' />,
link: {
href: 'https://dashboard.cohere.com/api-keys',
label: {
'en': 'Get your API key from cohere',
'zh-Hans': '从 cohere 获取 API Key',
},
},
validateKeys: ['api_key'],
fields: [
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
],
},
}
export default config

View file

@ -1,278 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { FormValue, ProviderConfig } from '../declarations'
import { Huggingface, HuggingfaceText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Hugging Face',
'zh-Hans': 'Hugging Face',
},
icon: <Huggingface className='w-full h-full' />,
},
item: {
key: ProviderEnum.huggingface_hub,
titleIcon: {
'en': <HuggingfaceText className='h-6' />,
'zh-Hans': <HuggingfaceText className='h-6' />,
},
hit: {
'en': '🐑 Llama 2 Supported',
'zh-Hans': '🐑 Llama 2 已支持',
},
},
modal: {
key: ProviderEnum.huggingface_hub,
title: {
'en': 'Hugging Face Model',
'zh-Hans': 'Hugging Face Model',
},
icon: <Huggingface className='h-6' />,
link: {
href: 'https://huggingface.co/settings/tokens',
label: {
'en': 'Get your API key from Hugging Face Hub',
'zh-Hans': '从 Hugging Face Hub 获取 API Key',
},
},
defaultValue: {
model_type: 'text-generation',
huggingfacehub_api_type: 'hosted_inference_api',
task_type: 'text-generation',
},
validateKeys: (v?: FormValue) => {
if (v?.huggingfacehub_api_type === 'hosted_inference_api') {
return [
'huggingfacehub_api_token',
'model_name',
]
}
if (v?.huggingfacehub_api_type === 'inference_endpoints') {
if (v.model_type === 'embeddings') {
return [
'huggingfacehub_api_token',
'huggingface_namespace',
'model_name',
'huggingfacehub_endpoint_url',
'task_type',
]
}
return [
'huggingfacehub_api_token',
'model_name',
'huggingfacehub_endpoint_url',
'task_type',
]
}
return []
},
filterValue: (v?: FormValue) => {
let filteredKeys: string[] = []
if (v?.huggingfacehub_api_type === 'hosted_inference_api') {
filteredKeys = [
'huggingfacehub_api_type',
'huggingfacehub_api_token',
'model_name',
'model_type',
]
}
if (v?.huggingfacehub_api_type === 'inference_endpoints') {
if (v.model_type === 'embeddings') {
filteredKeys = [
'huggingfacehub_api_type',
'huggingfacehub_api_token',
'huggingface_namespace',
'model_name',
'huggingfacehub_endpoint_url',
'task_type',
'model_type',
]
}
else {
filteredKeys = [
'huggingfacehub_api_type',
'huggingfacehub_api_token',
'model_name',
'huggingfacehub_endpoint_url',
'task_type',
'model_type',
]
}
}
return filteredKeys.reduce((prev: FormValue, next: string) => {
prev[next] = v?.[next] || ''
return prev
}, {})
},
fields: [
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
// {
// key: 'chat',
// label: {
// 'en': 'Chat',
// 'zh-Hans': '聊天',
// },
// },
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
],
},
{
type: 'radio',
key: 'huggingfacehub_api_type',
required: true,
label: {
'en': 'Endpoint Type',
'zh-Hans': '端点类型',
},
options: [
{
key: 'hosted_inference_api',
label: {
'en': 'Hosted Inference API',
'zh-Hans': 'Hosted Inference API',
},
},
{
key: 'inference_endpoints',
label: {
'en': 'Inference Endpoints',
'zh-Hans': 'Inference Endpoints',
},
},
],
},
{
type: 'text',
key: 'huggingfacehub_api_token',
required: true,
label: {
'en': 'API Token',
'zh-Hans': 'API Token',
},
placeholder: {
'en': 'Enter your Hugging Face Hub API Token here',
'zh-Hans': '在此输入您的 Hugging Face Hub API Token',
},
},
{
hidden: (value?: FormValue) => !(value?.huggingfacehub_api_type === 'inference_endpoints' && value?.model_type === 'embeddings'),
type: 'text',
key: 'huggingface_namespace',
required: true,
label: {
'en': 'User Name / Organization Name',
'zh-Hans': '用户名 / 组织名称',
},
placeholder: {
'en': 'Enter your User Name / Organization Name here',
'zh-Hans': '在此输入您的用户名 / 组织名称',
},
},
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Model Name',
'zh-Hans': '模型名称',
},
placeholder: {
'en': 'Enter your Model Name here',
'zh-Hans': '在此输入您的模型名称',
},
},
{
hidden: (value?: FormValue) => value?.huggingfacehub_api_type === 'hosted_inference_api',
type: 'text',
key: 'huggingfacehub_endpoint_url',
label: {
'en': 'Endpoint URL',
'zh-Hans': '端点 URL',
},
placeholder: {
'en': 'Enter your Endpoint URL here',
'zh-Hans': '在此输入您的端点 URL',
},
},
{
hidden: (value?: FormValue) => value?.huggingfacehub_api_type === 'hosted_inference_api' || value?.model_type === 'embeddings',
type: 'radio',
key: 'task_type',
required: true,
label: {
'en': 'Task',
'zh-Hans': 'Task',
},
options: (value?: FormValue) => {
if (value?.model_type === 'chat') {
return [{
key: 'question-answer',
label: {
'en': '问答',
'zh-Hans': 'Question Answer',
},
}]
}
return [
{
key: 'text2text-generation',
label: {
'en': 'Text-to-Text Generation',
'zh-Hans': 'Text-to-Text Generation',
},
},
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': 'Text Generation',
},
},
]
},
},
{
hidden: (value?: FormValue) => !(value?.huggingfacehub_api_type === 'inference_endpoints' && value?.model_type === 'embeddings'),
type: 'radio',
key: 'task_type',
required: true,
label: {
'en': 'Task',
'zh-Hans': 'Task',
},
options: [
{
key: 'feature-extraction',
label: {
'en': 'Feature Extraction',
'zh-Hans': 'Feature Extraction',
},
},
],
},
],
},
}
export default config

View file

@ -1,37 +0,0 @@
import openai from './openai'
import anthropic from './anthropic'
import azure_openai from './azure_openai'
import replicate from './replicate'
import huggingface_hub from './huggingface_hub'
import wenxin from './wenxin'
import tongyi from './tongyi'
import spark from './spark'
import minimax from './minimax'
import chatglm from './chatglm'
import xinference from './xinference'
import openllm from './openllm'
import localai from './localai'
import zhipuai from './zhipuai'
import baichuan from './baichuan'
import cohere from './cohere'
import jina from './jina'
export default {
openai,
anthropic,
azure_openai,
replicate,
huggingface_hub,
wenxin,
tongyi,
spark,
minimax,
chatglm,
xinference,
openllm,
localai,
zhipuai,
baichuan,
cohere,
jina,
}

View file

@ -1,57 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Jina, JinaText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Jina AI',
'zh-Hans': 'Jina AI',
},
icon: <Jina className='w-full h-full' />,
},
item: {
key: ProviderEnum.jina,
titleIcon: {
'en': <JinaText className='w-[58px] h-6' />,
'zh-Hans': <JinaText className='w-[58px] h-6' />,
},
hit: {
'en': 'Embedding Model Supported',
'zh-Hans': '支持 Embedding 模型',
},
},
modal: {
key: ProviderEnum.jina,
title: {
'en': 'Embedding Model',
'zh-Hans': 'Embedding 模型',
},
icon: <JinaText className='w-[58px] h-6' />,
link: {
href: 'https://jina.ai/embeddings/',
label: {
'en': 'Get your API key from Jina AI',
'zh-Hans': '从 Jina AI 获取 API Key',
},
},
validateKeys: ['api_key'],
fields: [
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
],
},
}
export default config

View file

@ -1,176 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { FormValue, ProviderConfig } from '../declarations'
import { Localai, LocalaiText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'LocalAI',
'zh-Hans': 'LocalAI',
},
icon: <Localai className='w-full h-full' />,
},
item: {
key: ProviderEnum.localai,
titleIcon: {
'en': <LocalaiText className='h-6' />,
'zh-Hans': <LocalaiText className='h-6' />,
},
disable: {
tip: {
'en': 'Only supports the ',
'zh-Hans': '仅支持',
},
link: {
href: {
'en': 'https://docs.dify.ai/getting-started/install-self-hosted',
'zh-Hans': 'https://docs.dify.ai/v/zh-hans/getting-started/install-self-hosted',
},
label: {
'en': 'community open-source version',
'zh-Hans': '社区开源版本',
},
},
},
},
modal: {
key: ProviderEnum.localai,
title: {
'en': 'LocalAI',
'zh-Hans': 'LocalAI',
},
icon: <Localai className='h-6' />,
link: {
href: 'https://github.com/go-skynet/LocalAI',
label: {
'en': 'How to deploy LocalAI',
'zh-Hans': '如何部署 LocalAI',
},
},
defaultValue: {
model_type: 'text-generation',
completion_type: 'completion',
},
validateKeys: (v?: FormValue) => {
if (v?.model_type === 'text-generation') {
return [
'model_type',
'model_name',
'server_url',
'completion_type',
]
}
if (v?.model_type === 'embeddings') {
return [
'model_type',
'model_name',
'server_url',
]
}
return []
},
filterValue: (v?: FormValue) => {
let filteredKeys: string[] = []
if (v?.model_type === 'text-generation') {
filteredKeys = [
'model_type',
'model_name',
'server_url',
'completion_type',
]
}
if (v?.model_type === 'embeddings') {
filteredKeys = [
'model_type',
'model_name',
'server_url',
]
}
return filteredKeys.reduce((prev: FormValue, next: string) => {
prev[next] = v?.[next] || ''
return prev
}, {})
},
fields: [
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
],
},
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Model Name',
'zh-Hans': '模型名称',
},
placeholder: {
'en': 'Enter your Model Name here',
'zh-Hans': '在此输入您的模型名称',
},
},
{
hidden: (value?: FormValue) => value?.model_type === 'embeddings',
type: 'radio',
key: 'completion_type',
required: true,
label: {
'en': 'Completion Type',
'zh-Hans': 'Completion Type',
},
options: [
{
key: 'completion',
label: {
'en': 'Completion',
'zh-Hans': 'Completion',
},
},
{
key: 'chat_completion',
label: {
'en': 'Chat Completion',
'zh-Hans': 'Chat Completion',
},
},
],
},
{
type: 'text',
key: 'server_url',
required: true,
label: {
'en': 'Server url',
'zh-Hans': 'Server url',
},
placeholder: {
'en': 'Enter your Server Url, eg: https://example.com/xxx',
'zh-Hans': '在此输入您的 Server Urlhttps://example.com/xxx',
},
},
],
},
}
export default config

View file

@ -1,69 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Minimax, MinimaxText } from '@/app/components/base/icons/src/image/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'MINIMAX',
'zh-Hans': 'MINIMAX',
},
icon: <Minimax className='w-full h-full' />,
},
item: {
key: ProviderEnum.minimax,
titleIcon: {
'en': <MinimaxText className='w-[84px] h-6' />,
'zh-Hans': <MinimaxText className='w-[84px] h-6' />,
},
},
modal: {
key: ProviderEnum.minimax,
title: {
'en': 'MiniMax',
'zh-Hans': 'MiniMax',
},
icon: <Minimax className='w-6 h-6' />,
link: {
href: 'https://api.minimax.chat/user-center/basic-information/interface-key',
label: {
'en': 'Get your API key from MiniMax',
'zh-Hans': '从 MiniMax 获取 API Key',
},
},
validateKeys: [
'minimax_api_key',
'minimax_group_id',
],
fields: [
{
type: 'text',
key: 'minimax_api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
{
type: 'text',
key: 'minimax_group_id',
required: true,
label: {
'en': 'Group ID',
'zh-Hans': 'Group ID',
},
placeholder: {
'en': 'Enter your Group ID here',
'zh-Hans': '在此输入您的 Group ID',
},
},
],
},
}
export default config

View file

@ -1,93 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { OpenaiBlack, OpenaiText, OpenaiTransparent } from '@/app/components/base/icons/src/public/llm'
import { IS_CE_EDITION } from '@/config'
const config: ProviderConfig = {
selector: {
name: {
'en': 'OpenAI',
'zh-Hans': 'OpenAI',
},
icon: <OpenaiBlack className='w-full h-full' />,
},
item: {
key: ProviderEnum.openai,
titleIcon: {
'en': <OpenaiText className='h-5' />,
'zh-Hans': <OpenaiText className='h-5' />,
},
subTitleIcon: <OpenaiBlack className='w-6 h-6' />,
desc: {
'en': 'Models provided by OpenAI, such as GPT-3.5-Turbo and GPT-4.',
'zh-Hans': 'OpenAI 提供的模型,例如 GPT-3.5-Turbo 和 GPT-4。',
},
bgColor: 'bg-gray-200',
},
modal: {
key: ProviderEnum.openai,
title: {
'en': 'OpenAI',
'zh-Hans': 'OpenAI',
},
icon: <OpenaiTransparent className='w-6 h-6' />,
link: {
href: 'https://platform.openai.com/account/api-keys',
label: {
'en': 'Get your API key from OpenAI',
'zh-Hans': '从 OpenAI 获取 API Key',
},
},
validateKeys: ['openai_api_key'],
fields: [
{
type: 'text',
key: 'openai_api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
{
type: 'text',
key: 'openai_organization',
required: false,
label: {
'en': 'Organization ID',
'zh-Hans': '组织 ID',
},
placeholder: {
'en': 'Enter your Organization ID(Optional)',
'zh-Hans': '在此输入您的组织 ID选填',
},
},
...(
IS_CE_EDITION
? [{
type: 'text',
key: 'openai_api_base',
required: false,
label: {
'en': 'Custom API Domain',
'zh-Hans': '自定义 API 域名',
},
placeholder: {
'en': 'Enter your API domain, eg: https://example.com/xxx(Optional)',
'zh-Hans': '在此输入您的 API 域名https://example.com/xxx选填',
},
help: {
'en': 'You can configure your server compatible with the OpenAI API specification, or proxy mirror address',
'zh-Hans': '可配置您的兼容 OpenAI API 规范的服务器,或者代理镜像地址',
},
}]
: []
),
],
},
}
export default config

View file

@ -1,114 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Openllm, OpenllmText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'OpenLLM',
'zh-Hans': 'OpenLLM',
},
icon: <Openllm className='w-full h-full' />,
},
item: {
key: ProviderEnum.openllm,
titleIcon: {
'en': <OpenllmText className='h-6' />,
'zh-Hans': <OpenllmText className='h-6' />,
},
disable: {
tip: {
'en': 'Only supports the ',
'zh-Hans': '仅支持',
},
link: {
href: {
'en': 'https://docs.dify.ai/getting-started/install-self-hosted',
'zh-Hans': 'https://docs.dify.ai/v/zh-hans/getting-started/install-self-hosted',
},
label: {
'en': 'community open-source version',
'zh-Hans': '社区开源版本',
},
},
},
},
modal: {
key: ProviderEnum.openllm,
title: {
'en': 'OpenLLM',
'zh-Hans': 'OpenLLM',
},
icon: <Openllm className='h-6' />,
link: {
href: 'https://github.com/bentoml/OpenLLM',
label: {
'en': 'How to deploy OpenLLM',
'zh-Hans': '如何部署 OpenLLM',
},
},
defaultValue: {
model_type: 'text-generation',
},
validateKeys: [
'model_type',
'model_name',
'server_url',
],
fields: [
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
],
},
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Model Name',
'zh-Hans': '模型名称',
},
placeholder: {
'en': 'Enter your Model Name here',
'zh-Hans': '在此输入您的模型名称',
},
},
{
type: 'text',
key: 'server_url',
required: true,
label: {
'en': 'Server url',
'zh-Hans': 'Server url',
},
placeholder: {
'en': 'Enter your Server Url, eg: https://example.com/xxx',
'zh-Hans': '在此输入您的 Server Urlhttps://example.com/xxx',
},
},
],
},
}
export default config

View file

@ -1,122 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Replicate, ReplicateText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Replicate',
'zh-Hans': 'Replicate',
},
icon: <Replicate className='w-full h-full' />,
},
item: {
key: ProviderEnum.replicate,
titleIcon: {
'en': <ReplicateText className='h-6' />,
'zh-Hans': <ReplicateText className='h-6' />,
},
hit: {
'en': '🐑 Llama 2 Supported',
'zh-Hans': '🐑 Llama 2 已支持',
},
},
modal: {
key: ProviderEnum.replicate,
title: {
'en': 'Replicate Model',
'zh-Hans': 'Replicate Model',
},
icon: <Replicate className='h-6' />,
link: {
href: 'https://replicate.com/account/api-tokens',
label: {
'en': 'Get your API key from Replicate',
'zh-Hans': '从 Replicate 获取 API Key',
},
},
defaultValue: {
model_type: 'text-generation',
},
validateKeys: [
'model_type',
'replicate_api_token',
'model_name',
'model_version',
],
fields: [
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
// {
// key: 'chat',
// label: {
// 'en': 'Chat',
// 'zh-Hans': '聊天',
// },
// },
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
],
},
{
type: 'text',
key: 'replicate_api_token',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your Replicate API key here',
'zh-Hans': '在此输入您的 Replicate API Key',
},
},
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Model Name',
'zh-Hans': '模型名称',
},
placeholder: {
'en': 'Enter your Model Name here',
'zh-Hans': '在此输入您的模型名称',
},
},
{
type: 'text',
key: 'model_version',
label: {
'en': 'Model Version',
'zh-Hans': '模型版本',
},
placeholder: {
'en': 'Enter your Model Version here',
'zh-Hans': '在此输入您的模型版本',
},
},
],
},
}
export default config

View file

@ -1,83 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { IflytekSpark, IflytekSparkText, IflytekSparkTextCn } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'iFLYTEK SPARK',
'zh-Hans': '讯飞星火',
},
icon: <IflytekSpark className='w-full h-full' />,
},
item: {
key: ProviderEnum.spark,
titleIcon: {
'en': <IflytekSparkText className='h-6' />,
'zh-Hans': <IflytekSparkTextCn className='h-6' />,
},
},
modal: {
key: ProviderEnum.spark,
title: {
'en': 'iFLYTEK SPARK',
'zh-Hans': '讯飞星火',
},
icon: <IflytekSpark className='w-6 h-6' />,
link: {
href: 'https://www.xfyun.cn/solutions/xinghuoAPI',
label: {
'en': 'Get your API key from iFLYTEK SPARK',
'zh-Hans': '从讯飞星火获取 API Key',
},
},
validateKeys: [
'app_id',
'api_key',
'api_secret',
],
fields: [
{
type: 'text',
key: 'app_id',
required: true,
label: {
'en': 'APPID',
'zh-Hans': 'APPID',
},
placeholder: {
'en': 'Enter your APPID here',
'zh-Hans': '在此输入您的 APPID',
},
},
{
type: 'text',
key: 'api_secret',
required: true,
label: {
'en': 'APISecret',
'zh-Hans': 'APISecret',
},
placeholder: {
'en': 'Enter your APISecret here',
'zh-Hans': '在此输入您的 APISecret',
},
},
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'APIKey',
'zh-Hans': 'APIKey',
},
placeholder: {
'en': 'Enter your APIKey here',
'zh-Hans': '在此输入您的 APIKey',
},
},
],
},
}
export default config

View file

@ -1,53 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Tongyi, TongyiText, TongyiTextCn } from '@/app/components/base/icons/src/image/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'TONGYI QIANWEN',
'zh-Hans': '通义千问',
},
icon: <Tongyi className='w-full h-full' />,
},
item: {
key: ProviderEnum.tongyi,
titleIcon: {
'en': <TongyiText className='w-[88px] h-6' />,
'zh-Hans': <TongyiTextCn className='w-[100px] h-6' />,
},
},
modal: {
key: ProviderEnum.tongyi,
title: {
'en': 'Tongyi',
'zh-Hans': '通义千问',
},
icon: <Tongyi className='w-6 h-6' />,
link: {
href: 'https://dashscope.console.aliyun.com/api-key_management',
label: {
'en': 'Get your API key from AliCloud',
'zh-Hans': '从阿里云获取 API Key',
},
},
validateKeys: ['dashscope_api_key'],
fields: [
{
type: 'text',
key: 'dashscope_api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
],
},
}
export default config

View file

@ -1,66 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Wxyy, WxyyText, WxyyTextCn } from '@/app/components/base/icons/src/image/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'WENXIN YIYAN',
'zh-Hans': '文心一言',
},
icon: <Wxyy className='w-full h-full' />,
},
item: {
key: ProviderEnum.wenxin,
titleIcon: {
'en': <WxyyText className='w-[124px] h-6' />,
'zh-Hans': <WxyyTextCn className='w-[100px] h-6' />,
},
},
modal: {
key: ProviderEnum.wenxin,
title: {
'en': 'WENXINYIYAN',
'zh-Hans': '文心一言',
},
icon: <Wxyy className='w-6 h-6' />,
link: {
href: 'https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application',
label: {
'en': 'Get your API key from Baidu',
'zh-Hans': '从百度获取 API Key',
},
},
validateKeys: ['api_key', 'secret_key'],
fields: [
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'API Key',
'zh-Hans': 'API Key',
},
placeholder: {
'en': 'Enter your API key here',
'zh-Hans': '在此输入您的 API Key',
},
},
{
type: 'text',
key: 'secret_key',
required: true,
label: {
'en': 'Secret Key',
'zh-Hans': 'Secret Key',
},
placeholder: {
'en': 'Enter your Secret key here',
'zh-Hans': '在此输入您的 Secret Key',
},
},
],
},
}
export default config

View file

@ -1,135 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { XorbitsInference, XorbitsInferenceText } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'Xinference',
'zh-Hans': 'Xinference',
},
icon: <XorbitsInference className='w-full h-full' />,
},
item: {
key: ProviderEnum.xinference,
titleIcon: {
'en': <XorbitsInferenceText className='h-6' />,
'zh-Hans': <XorbitsInferenceText className='h-6' />,
},
disable: {
tip: {
'en': 'Only supports the ',
'zh-Hans': '仅支持',
},
link: {
href: {
'en': 'https://docs.dify.ai/getting-started/install-self-hosted',
'zh-Hans': 'https://docs.dify.ai/v/zh-hans/getting-started/install-self-hosted',
},
label: {
'en': 'community open-source version',
'zh-Hans': '社区开源版本',
},
},
},
},
modal: {
key: ProviderEnum.xinference,
title: {
'en': 'Xinference',
'zh-Hans': 'Xinference',
},
icon: <XorbitsInference className='h-6' />,
link: {
href: 'https://github.com/xorbitsai/inference',
label: {
'en': 'How to deploy Xinference',
'zh-Hans': '如何部署 Xinference',
},
},
defaultValue: {
model_type: 'text-generation',
},
validateKeys: [
'model_type',
'model_name',
'server_url',
'model_uid',
],
fields: [
{
type: 'radio',
key: 'model_type',
required: true,
label: {
'en': 'Model Type',
'zh-Hans': '模型类型',
},
options: [
{
key: 'text-generation',
label: {
'en': 'Text Generation',
'zh-Hans': '文本生成',
},
},
{
key: 'embeddings',
label: {
'en': 'Embeddings',
'zh-Hans': 'Embeddings',
},
},
{
key: 'reranking',
label: {
'en': 'Rerank',
'zh-Hans': 'Rerank',
},
},
],
},
{
type: 'text',
key: 'model_name',
required: true,
label: {
'en': 'Model Name',
'zh-Hans': '模型名称',
},
placeholder: {
'en': 'Enter your Model Name here',
'zh-Hans': '在此输入您的模型名称',
},
},
{
type: 'text',
key: 'server_url',
required: true,
label: {
'en': 'Server Url',
'zh-Hans': 'Server Url',
},
placeholder: {
'en': 'Enter your Server url, eg: https://example.com/xxx',
'zh-Hans': '在此输入您的 Server urlhttps://example.com/xxx',
},
},
{
type: 'text',
key: 'model_uid',
required: true,
label: {
'en': 'Model UID',
'zh-Hans': 'Model UID',
},
placeholder: {
'en': 'Enter your Model UID',
'zh-Hans': '在此输入您的 Model UID',
},
},
],
},
}
export default config

View file

@ -1,55 +0,0 @@
import { ProviderEnum } from '../declarations'
import type { ProviderConfig } from '../declarations'
import { Zhipuai, ZhipuaiText, ZhipuaiTextCn } from '@/app/components/base/icons/src/public/llm'
const config: ProviderConfig = {
selector: {
name: {
'en': 'ZHIPU AI',
'zh-Hans': '智谱 AI',
},
icon: <Zhipuai className='w-full h-full' />,
},
item: {
key: ProviderEnum.zhipuai,
titleIcon: {
'en': <ZhipuaiText className='-ml-1 h-7' />,
'zh-Hans': <ZhipuaiTextCn className='h-8' />,
},
},
modal: {
key: ProviderEnum.zhipuai,
title: {
'en': 'ZHIPU AI',
'zh-Hans': '智谱 AI',
},
icon: <Zhipuai className='w-6 h-6' />,
link: {
href: 'https://open.bigmodel.cn/usercenter/apikeys',
label: {
'en': 'Get your API key from ZHIPU AI',
'zh-Hans': '从智谱 AI 获取 API Key',
},
},
validateKeys: [
'api_key',
],
fields: [
{
type: 'text',
key: 'api_key',
required: true,
label: {
'en': 'APIKey',
'zh-Hans': 'APIKey',
},
placeholder: {
'en': 'Enter your APIKey here',
'zh-Hans': '在此输入您的 APIKey',
},
},
],
},
}
export default config

View file

@ -1,163 +0,0 @@
import type { ReactElement } from 'react'
import type { ModelModeType } from '@/types/app'
export type FormValue = Record<string, string>
export type TypeWithI18N<T = string> = {
'en': T
'zh-Hans': T
}
export type Option = {
key: string
label: TypeWithI18N
}
export type ProviderSelector = {
name: TypeWithI18N
icon: ReactElement
}
export type Field = {
hidden?: (v?: FormValue) => boolean
type: string
key: string
required?: boolean
label: TypeWithI18N
options?: Option[] | ((v: FormValue) => Option[])
placeholder?: TypeWithI18N
help?: TypeWithI18N
}
export enum ProviderEnum {
'openai' = 'openai',
'anthropic' = 'anthropic',
'replicate' = 'replicate',
'azure_openai' = 'azure_openai',
'huggingface_hub' = 'huggingface_hub',
'tongyi' = 'tongyi',
'wenxin' = 'wenxin',
'spark' = 'spark',
'minimax' = 'minimax',
'chatglm' = 'chatglm',
'xinference' = 'xinference',
'openllm' = 'openllm',
'localai' = 'localai',
'zhipuai' = 'zhipuai',
'baichuan' = 'baichuan',
'cohere' = 'cohere',
'jina' = 'jina',
}
export type ProviderConfigItem = {
key: ProviderEnum
titleIcon: TypeWithI18N<ReactElement>
subTitleIcon?: ReactElement
desc?: TypeWithI18N
bgColor?: string
hit?: TypeWithI18N
disable?: {
tip: TypeWithI18N
link: {
href: TypeWithI18N
label: TypeWithI18N
}
}
}
export enum ModelType {
textGeneration = 'text-generation',
embeddings = 'embeddings',
speech2text = 'speech2text',
reranking = 'reranking',
}
export enum ModelFeature {
agentThought = 'agent_thought',
vision = 'vision',
}
// backend defined model struct: /console/api/workspaces/current/models/model-type/:model_type
export type BackendModel = {
model_name: string
model_display_name: string // not always exist
model_type: ModelType
model_mode: ModelModeType
model_provider: {
provider_name: ProviderEnum
provider_type: PreferredProviderTypeEnum
quota_type: 'trial' | 'paid'
quota_unit: 'times' | 'tokens'
quota_used: number
quota_limit: number
}
features: ModelFeature[]
}
export type ProviderConfigModal = {
key: ProviderEnum
title: TypeWithI18N
icon: ReactElement
defaultValue?: FormValue
validateKeys?: string[] | ((v?: FormValue) => string[])
filterValue?: (v?: FormValue) => FormValue
fields: Field[]
link: {
href: string
label: TypeWithI18N
}
}
export type ProviderConfig = {
selector: ProviderSelector
item: ProviderConfigItem
modal: ProviderConfigModal
}
export enum PreferredProviderTypeEnum {
'system' = 'system',
'custom' = 'custom',
}
export enum ModelFlexibilityEnum {
'fixed' = 'fixed',
'configurable' = 'configurable',
}
export type ProviderCommon = {
provider_name: ProviderEnum
provider_type: PreferredProviderTypeEnum
is_valid: boolean
last_used: number
}
export type ProviderWithQuota = {
quota_type: string
quota_unit: string
quota_limit: number
quota_used: number
} & ProviderCommon
export type ProviderWithConfig = {
config: Record<string, string>
} & ProviderCommon
export type Model = {
model_name: string
model_type: string
config: Record<string, string>
}
export type ProviderWithModels = {
models: Model[]
} & ProviderCommon
export type ProviderInstance = ProviderWithQuota | ProviderWithConfig | ProviderWithModels
export type Provider = {
preferred_provider_type: PreferredProviderTypeEnum
model_flexibility: ModelFlexibilityEnum
providers: ProviderInstance[]
}
export type ProviderMap = {
[k in ProviderEnum]: Provider
}

View file

@ -1,267 +0,0 @@
import { useState } from 'react'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type {
FormValue,
ProviderConfigModal,
ProviderEnum,
} from './declarations'
import ModelCard from './model-card'
import ModelItem from './model-item'
import ModelModal from './model-modal'
import SystemModel from './system-model'
import config from './configs'
import { ConfigurableProviders } from './utils'
import {
changeModelProviderPriority,
deleteModelProvider,
deleteModelProviderModel,
fetchModelProviders,
setModelProvider,
} from '@/service/common'
import { useToastContext } from '@/app/components/base/toast'
import Confirm from '@/app/components/base/confirm/common'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { useProviderContext } from '@/context/provider-context'
import I18n from '@/context/i18n'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
const MODEL_CARD_LIST = [
config.openai,
config.anthropic,
]
type DeleteModel = {
model_name: string
model_type: string
}
const ModelPage = () => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const {
updateModelList,
textGenerationDefaultModel,
embeddingsDefaultModel,
speech2textDefaultModel,
rerankDefaultModel,
} = useProviderContext()
const { data: providers, mutate: mutateProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
const [showModal, setShowModal] = useState(false)
const { notify } = useToastContext()
const { eventEmitter } = useEventEmitterContextContext()
const [modelModalConfig, setModelModalConfig] = useState<ProviderConfigModal | undefined>(undefined)
const [confirmShow, setConfirmShow] = useState(false)
const [deleteModel, setDeleteModel] = useState<DeleteModel & { providerKey: ProviderEnum }>()
const [modalMode, setModalMode] = useState('add')
let modelList = []
if (locale === 'en') {
modelList = [
config.azure_openai,
config.replicate,
config.huggingface_hub,
config.cohere,
config.zhipuai,
config.baichuan,
config.spark,
config.minimax,
config.tongyi,
config.wenxin,
config.jina,
config.chatglm,
config.xinference,
config.openllm,
config.localai,
]
}
else {
modelList = [
config.huggingface_hub,
config.cohere,
config.zhipuai,
config.spark,
config.baichuan,
config.minimax,
config.azure_openai,
config.replicate,
config.tongyi,
config.wenxin,
config.jina,
config.chatglm,
config.xinference,
config.openllm,
config.localai,
]
}
const handleOpenModal = (newModelModalConfig: ProviderConfigModal | undefined, editValue?: FormValue) => {
if (newModelModalConfig) {
setShowModal(true)
const defaultValue = editValue ? { ...newModelModalConfig.defaultValue, ...editValue } : newModelModalConfig.defaultValue
setModelModalConfig({
...newModelModalConfig,
defaultValue,
})
if (editValue)
setModalMode('edit')
else
setModalMode('add')
}
}
const handleCancelModal = () => {
setShowModal(false)
}
const handleUpdateProvidersAndModelList = () => {
updateModelList(ModelType.textGeneration)
updateModelList(ModelType.embeddings)
updateModelList(ModelType.speech2text)
updateModelList(ModelType.reranking)
mutateProviders()
}
const handleSave = async (originValue?: FormValue) => {
if (originValue && modelModalConfig) {
const v = modelModalConfig.filterValue ? modelModalConfig.filterValue(originValue) : originValue
let body, url
if (ConfigurableProviders.includes(modelModalConfig.key)) {
const { model_name, model_type, ...config } = v
body = {
model_name,
model_type,
config,
}
url = `/workspaces/current/model-providers/${modelModalConfig.key}/models`
}
else {
body = {
config: v,
}
url = `/workspaces/current/model-providers/${modelModalConfig.key}`
}
try {
eventEmitter?.emit('provider-save')
const res = await setModelProvider({ url, body })
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
handleUpdateProvidersAndModelList()
handleCancelModal()
}
eventEmitter?.emit('')
}
catch (e) {
eventEmitter?.emit('')
}
}
}
const handleConfirm = (deleteModel: DeleteModel, providerKey: ProviderEnum) => {
setDeleteModel({ ...deleteModel, providerKey })
setConfirmShow(true)
}
const handleOperate = async ({ type, value }: Record<string, any>, provierKey: ProviderEnum) => {
if (type === 'delete') {
if (!value) {
const res = await deleteModelProvider({ url: `/workspaces/current/model-providers/${provierKey}` })
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
handleUpdateProvidersAndModelList()
}
}
else {
handleConfirm(value, provierKey)
}
}
if (type === 'priority') {
const res = await changeModelProviderPriority({
url: `/workspaces/current/model-providers/${provierKey}/preferred-provider-type`,
body: {
preferred_provider_type: value,
},
})
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
mutateProviders()
}
}
}
const handleDeleteModel = async () => {
const { model_name, model_type, providerKey } = deleteModel || {}
const res = await deleteModelProviderModel({
url: `/workspaces/current/model-providers/${providerKey}/models?model_name=${model_name}&model_type=${model_type}`,
})
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
setConfirmShow(false)
handleUpdateProvidersAndModelList()
}
}
const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel
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>
}
<SystemModel onUpdate={() => mutateProviders()} />
</div>
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6'>
{
MODEL_CARD_LIST.map((model, index) => (
<ModelCard
key={index}
modelItem={model.item}
currentProvider={providers?.[model.item.key]}
onOpenModal={editValue => handleOpenModal(model.modal, editValue)}
onOperate={v => handleOperate(v, model.item.key)}
/>
))
}
</div>
{
modelList.map((model, index) => (
<ModelItem
key={index}
modelItem={model.item}
currentProvider={providers?.[model.item.key]}
onOpenModal={editValue => handleOpenModal(model.modal, editValue)}
onOperate={v => handleOperate(v, model.item.key)}
onUpdate={mutateProviders}
/>
))
}
<ModelModal
isShow={showModal}
modelModal={modelModalConfig}
onCancel={handleCancelModal}
onSave={handleSave}
mode={modalMode}
/>
<Confirm
isShow={confirmShow}
onCancel={() => setConfirmShow(false)}
title={deleteModel?.model_name || ''}
desc={t('common.modelProvider.item.deleteDesc', { modelName: deleteModel?.model_name }) || ''}
onConfirm={handleDeleteModel}
confirmWrapperClassName='!z-30'
/>
</div>
)
}
export default ModelPage

View file

@ -1,117 +0,0 @@
import { useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import type { Provider, ProviderWithQuota } from '../declarations'
import Tooltip from '@/app/components/base/tooltip'
import { InfoCircle } from '@/app/components/base/icons/src/vender/line/general'
import { getPayUrl } from '@/service/common'
import Button from '@/app/components/base/button'
import { formatNumber } from '@/utils/format'
type QuotaProps = {
currentProvider: Provider
}
const Quota: FC<QuotaProps> = ({
currentProvider,
}) => {
const { t } = useTranslation()
const [loading, setLoading] = useState(false)
const systemTrial = currentProvider.providers.find(p => p.provider_type === 'system' && (p as ProviderWithQuota)?.quota_type === 'trial') as ProviderWithQuota
const systemPaid = currentProvider.providers.find(p => p.provider_type === 'system' && (p as ProviderWithQuota)?.quota_type === 'paid') as ProviderWithQuota
const QUOTA_UNIT_MAP: Record<string, string> = {
times: t('common.modelProvider.card.callTimes'),
tokens: 'Tokens',
}
const renderStatus = () => {
const totalQuota = (systemPaid?.is_valid ? systemPaid.quota_limit : 0) + systemTrial.quota_limit
const totalUsed = (systemPaid?.is_valid ? systemPaid.quota_used : 0) + systemTrial.quota_used
if (totalQuota === totalUsed) {
return (
<div className='px-1.5 bg-[#FEF3F2] rounded-md text-xs font-semibold text-[#D92D20]'>
{t('common.modelProvider.card.quotaExhausted')}
</div>
)
}
if (systemPaid?.is_valid) {
return (
<div className='px-1.5 bg-[#FFF6ED] rounded-md text-xs font-semibold text-[#EC4A0A]'>
{t('common.modelProvider.card.paid')}
</div>
)
}
return (
<div className='px-1.5 bg-primary-50 rounded-md text-xs font-semibold text-primary-600'>
{t('common.modelProvider.card.onTrial')}
</div>
)
}
const renderQuota = () => {
if (systemPaid?.is_valid)
return formatNumber(systemPaid.quota_limit - systemPaid.quota_used)
if (systemTrial.is_valid)
return formatNumber(systemTrial.quota_limit - systemTrial.quota_used)
}
const renderUnit = () => {
if (systemPaid?.is_valid)
return QUOTA_UNIT_MAP[systemPaid.quota_unit]
if (systemTrial.is_valid)
return QUOTA_UNIT_MAP[systemTrial.quota_unit]
}
const handleGetPayUrl = async () => {
setLoading(true)
try {
const res = await getPayUrl(`/workspaces/current/model-providers/${systemPaid.provider_name}/checkout-url`)
window.location.href = res.url
}
finally {
setLoading(false)
}
}
return (
<div className='flex justify-between px-4 py-3 border-b-[0.5px] border-b-[rgba(0, 0, 0, 0.5)]'>
<div>
<div className='flex items-center mb-1 h-5'>
<div className='mr-1 text-xs font-medium text-gray-500'>
{t('common.modelProvider.card.quota')}
</div>
{renderStatus()}
</div>
<div className='flex items-center text-gray-700'>
<div className='mr-1 text-sm font-medium'>{renderQuota()}</div>
<div className='mr-1 text-sm'>
{renderUnit()}
</div>
<Tooltip
selector='setting-model-card'
htmlContent={
<div className='w-[261px] text-gray-500'>{t('common.modelProvider.card.tip')}</div>
}
>
<InfoCircle className='w-3 h-3 text-gray-400 hover:text-gray-700' />
</Tooltip>
</div>
</div>
{
systemPaid && (
<Button
type='primary'
className='mt-1.5 !px-3 !h-8 !text-[13px] font-medium !rounded-lg'
onClick={handleGetPayUrl}
disabled={loading}
>
{t('common.modelProvider.card.buyQuota')}
</Button>
)
}
</div>
)
}
export default Quota

View file

@ -1,81 +0,0 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type {
FormValue,
Provider,
ProviderConfigItem,
ProviderWithConfig,
} from '../declarations'
import Indicator from '../../../indicator'
import Selector from '../selector'
import Quota from './Quota'
import { IS_CE_EDITION } from '@/config'
import I18n from '@/context/i18n'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
type ModelCardProps = {
currentProvider?: Provider
modelItem: ProviderConfigItem
onOpenModal: (v?: FormValue) => void
onOperate: (v: Record<string, any>) => void
}
const ModelCard: FC<ModelCardProps> = ({
currentProvider,
modelItem,
onOpenModal,
onOperate,
}) => {
const { locale } = useContext(I18n)
const { t } = useTranslation()
const custom = currentProvider?.providers.find(p => p.provider_type === 'custom') as ProviderWithConfig
return (
<div className='rounded-xl border-[0.5px] border-gray-200 shadow-xs'>
<div className={`flex px-4 pt-4 pb-3 rounded-t-lg ${modelItem.bgColor}`}>
<div className='grow mr-3'>
<div className='mb-1'>
{modelItem.titleIcon[locale]}
</div>
<div className='h-9 text-xs text-black opacity-60'>{modelItem.desc?.[locale]}</div>
</div>
{modelItem.subTitleIcon}
</div>
{
!IS_CE_EDITION && currentProvider && <Quota currentProvider={currentProvider} />
}
{
custom?.is_valid
? (
<div className='flex items-center px-4 h-12'>
<Indicator color='green' className='mr-2' />
<div className='grow text-[13px] font-medium text-gray-700'>API key</div>
<div
className='mr-1 px-2 leading-6 rounded-md text-xs font-medium text-gray-500 hover:bg-gray-50 cursor-pointer'
onClick={() => onOpenModal(custom?.config)}
>
{t('common.operation.edit')}
</div>
<Selector
onOperate={onOperate}
value={currentProvider?.preferred_provider_type}
hiddenOptions={IS_CE_EDITION}
/>
</div>
)
: (
<div
className='inline-flex items-center px-4 h-12 text-gray-500 cursor-pointer hover:text-primary-600'
onClick={() => onOpenModal()}
>
<Plus className='mr-1.5 w-4 h-4'/>
<div className='text-xs font-medium'>{t('common.modelProvider.addApiKey')}</div>
</div>
)
}
</div>
)
}
export default ModelCard

View file

@ -1,69 +0,0 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import Indicator from '../../../indicator'
import Selector from '../selector'
import type { Model, ProviderEnum } from '../declarations'
import { ProviderEnum as ProviderEnumValue } from '../declarations'
import Button from '@/app/components/base/button'
type CardProps = {
providerType: ProviderEnum
models: Model[]
onOpenModal: (v: Omit<Model, 'config'> & Model['config']) => void
onOperate: (v: Record<string, any>) => void
}
const Card: FC<CardProps> = ({
providerType,
models,
onOpenModal,
onOperate,
}) => {
const { t } = useTranslation()
const renderDesc = (model: Model) => {
if (providerType === ProviderEnumValue.azure_openai)
return model.config.openai_api_base
if (providerType === ProviderEnumValue.replicate)
return `version: ${model.config.model_version}`
if (providerType === ProviderEnumValue.huggingface_hub)
return model.config.huggingfacehub_endpoint_url
}
return (
<div className='px-3 pb-3'>
{
models.map(model => (
<div key={`${model.model_name}-${model.model_type}`} className='flex mb-1 px-3 py-2 bg-white rounded-lg shadow-xs last:mb-0'>
<div className='grow'>
<div className='flex items-center mb-0.5 h-[18px] text-[13px] font-medium text-gray-700'>
{model.model_name}
<div className='ml-2 px-1.5 rounded-md border border-[rgba(0,0,0,0.08)] text-xs text-gray-600'>{model.model_type}</div>
</div>
<div className='text-xs text-gray-500'>
{renderDesc(model)}
</div>
</div>
<div className='flex items-center'>
<Indicator className='mr-3' />
<Button
className='mr-1 !px-3 !h-7 rounded-md bg-white !text-xs font-medium text-gray-700'
onClick={() => onOpenModal({ model_name: model.model_name, model_type: model.model_type, ...model.config })}
>
{t('common.operation.edit')}
</Button>
<Selector
hiddenOptions
onOperate={v => onOperate({ ...v, value: model })}
className={open => `${open && '!bg-gray-100 shadow-none'} flex justify-center items-center w-7 h-7 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer hover:bg-gray-100`}
deleteText={t('common.operation.remove') || ''}
/>
</div>
</div>
))
}
</div>
)
}
export default Card

View file

@ -1,79 +0,0 @@
import { useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type { ProviderConfigItem, TypeWithI18N } from '../declarations'
import { ProviderEnum as ProviderEnumValue } from '../declarations'
import s from './index.module.css'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import { submitFreeQuota } from '@/service/common'
import { LinkExternal01 } from '@/app/components/base/icons/src/vender/line/general'
const TIP_MAP: { [k: string]: TypeWithI18N } = {
[ProviderEnumValue.minimax]: {
'en': 'Earn 1 million tokens for free',
'zh-Hans': '免费获取 100 万个 token',
},
[ProviderEnumValue.spark]: {
'en': 'Earn 3 million tokens (v3.0) for free',
'zh-Hans': '免费获取 300 万个 token (v3.0)',
},
[ProviderEnumValue.zhipuai]: {
'en': 'Earn 10 million tokens for free',
'zh-Hans': '免费获取 1000 万个 token',
},
}
type FreeQuotaProps = {
modelItem: ProviderConfigItem
onUpdate: () => void
}
const FreeQuota: FC<FreeQuotaProps> = ({
modelItem,
onUpdate,
}) => {
const { locale } = useContext(I18n)
const { t } = useTranslation()
const [loading, setLoading] = useState(false)
const handleClick = async () => {
try {
setLoading(true)
const res = await submitFreeQuota(`/workspaces/current/model-providers/${modelItem.key}/free-quota-submit`)
if (res.type === 'redirect' && res.redirect_url)
window.location.href = res.redirect_url
else if (res.type === 'submit' && res.result === 'success')
onUpdate()
}
finally {
setLoading(false)
}
}
return (
<div className='flex items-center'>
📣
<div className={`${s.vender} ml-1 text-xs font-medium text-transparent`}>{TIP_MAP[modelItem.key][locale]}</div>
<div className='mx-1 text-xs font-medium text-gray-400'>·</div>
<a
href='https://docs.dify.ai/v/zh-hans/getting-started/faq/llms-use-faq#8.-ru-he-mian-fei-shen-ling-xun-fei-xing-huo-minimax-mo-xing-de-ti-yanedu'
target='_blank'
className='flex items-center text-xs font-medium text-[#155EEF]'>
{t('common.modelProvider.freeQuota.howToEarn')}
<LinkExternal01 className='ml-0.5 w-3 h-3' />
</a>
<Button
type='primary'
className='ml-3 !px-3 !h-7 !rounded-md !text-xs !font-medium'
onClick={handleClick}
disabled={loading}
>
{t('common.operation.getForFree')}
</Button>
<div className='mx-2 w-[1px] h-4 bg-black/5' />
</div>
)
}
export default FreeQuota

View file

@ -1,29 +0,0 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { formatNumber } from '@/utils/format'
type QuotaCardProps = {
remainTokens: number
}
const QuotaCard: FC<QuotaCardProps> = ({
remainTokens,
}) => {
const { t } = useTranslation()
return (
<div className='px-3 pb-3'>
<div className='px-3 py-2 bg-white rounded-lg shadow-xs last:mb-0'>
<div className='flex items-center h-[18px] text-xs font-medium text-gray-500'>
{t('common.modelProvider.item.freeQuota')}
</div>
<div className='flex items-center h-5 text-sm font-medium text-gray-700'>
{formatNumber(remainTokens)}
<div className='ml-1 font-normal'>Tokens</div>
</div>
</div>
</div>
)
}
export default QuotaCard

View file

@ -1,104 +0,0 @@
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type { FormValue, Provider, ProviderConfigItem, ProviderWithConfig, ProviderWithQuota } from '../declarations'
import { ProviderEnum } from '../declarations'
import Indicator from '../../../indicator'
import Selector from '../selector'
import FreeQuota from './FreeQuota'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import { IS_CE_EDITION } from '@/config'
type SettingProps = {
currentProvider?: Provider
modelItem: ProviderConfigItem
onOpenModal: (v?: FormValue) => void
onOperate: (v: Record<string, any>) => void
onUpdate: () => void
}
const Setting: FC<SettingProps> = ({
currentProvider,
modelItem,
onOpenModal,
onOperate,
onUpdate,
}) => {
const { locale } = useContext(I18n)
const { t } = useTranslation()
const configurable = currentProvider?.model_flexibility === 'configurable'
const systemFree = currentProvider?.providers.find(p => p.provider_type === 'system' && (p as ProviderWithQuota).quota_type === 'free') as ProviderWithQuota
const custom = currentProvider?.providers.find(p => p.provider_type === 'custom') as ProviderWithConfig
return (
<div className='flex items-center'>
{
(modelItem.key === ProviderEnum.minimax || modelItem.key === ProviderEnum.spark || modelItem.key === ProviderEnum.zhipuai) && systemFree && !systemFree?.is_valid && !IS_CE_EDITION && locale === 'zh-Hans' && (
<FreeQuota
modelItem={modelItem}
onUpdate={onUpdate}
/>
)
}
{
modelItem.disable && !IS_CE_EDITION && (
<div className='flex items-center text-xs text-gray-500'>
{modelItem.disable.tip[locale]}
<a
className={`${locale === 'en' && 'ml-1'} text-primary-600 cursor-pointer`}
href={modelItem.disable.link.href[locale]}
target='_blank'
>
{modelItem.disable.link.label[locale]}
</a>
<div className='mx-2 w-[1px] h-4 bg-black/5' />
</div>
)
}
{
configurable && (
<Button
className={`!px-3 !h-7 rounded-md bg-white !text-xs font-medium text-gray-700 ${!!modelItem.disable && !IS_CE_EDITION && '!text-gray-300'}`}
onClick={() => onOpenModal()}
disabled={!!modelItem.disable && !IS_CE_EDITION}
>
{t('common.operation.add')}
</Button>
)
}
{
!configurable && custom?.config && (
<div className='flex items-center'>
<Indicator className='mr-3' />
<Button
className='mr-1 !px-3 !h-7 rounded-md bg-white !text-xs font-medium text-gray-700'
onClick={() => onOpenModal(custom.config)}
>
{t('common.operation.edit')}
</Button>
<Selector
hiddenOptions={!systemFree?.is_valid || IS_CE_EDITION}
value={currentProvider?.preferred_provider_type}
onOperate={onOperate}
className={open => `${open && '!bg-gray-100 shadow-none'} flex justify-center items-center w-7 h-7 bg-white rounded-md border-[0.5px] border-gray-200 shadow-xs cursor-pointer hover:bg-gray-100`}
/>
</div>
)
}
{
!configurable && !custom?.config && (
<Button
className={`!px-3 !h-7 rounded-md bg-white !text-xs font-medium text-gray-700 ${!!modelItem.disable && !IS_CE_EDITION && '!text-gray-300'}`}
onClick={() => onOpenModal()}
disabled={!!modelItem.disable && !IS_CE_EDITION}
>
{t('common.operation.setup')}
</Button>
)
}
</div>
)
}
export default Setting

View file

@ -1,73 +0,0 @@
import type { FC } from 'react'
import { useContext } from 'use-context-selector'
import type {
FormValue,
Provider,
ProviderConfigItem,
ProviderWithModels,
ProviderWithQuota,
} from '../declarations'
import Setting from './Setting'
import Card from './Card'
import QuotaCard from './QuotaCard'
import I18n from '@/context/i18n'
import { IS_CE_EDITION } from '@/config'
type ModelItemProps = {
currentProvider?: Provider
modelItem: ProviderConfigItem
onOpenModal: (v?: FormValue) => void
onOperate: (v: Record<string, any>) => void
onUpdate: () => void
}
const ModelItem: FC<ModelItemProps> = ({
currentProvider,
modelItem,
onOpenModal,
onOperate,
onUpdate,
}) => {
const { locale } = useContext(I18n)
const custom = currentProvider?.providers.find(p => p.provider_type === 'custom') as ProviderWithModels
const systemFree = currentProvider?.providers.find(p => p.provider_type === 'system' && (p as ProviderWithQuota).quota_type === 'free') as ProviderWithQuota
return (
<div className='mb-2 bg-gray-50 rounded-xl'>
<div className='flex justify-between items-center p-4 min-h-[56px] flex-wrap gap-y-1'>
<div className='flex items-center'>
{modelItem.titleIcon[locale]}
{
modelItem.hit && (
<div className='ml-2 text-xs text-gray-500'>{modelItem.hit[locale]}</div>
)
}
</div>
<Setting
currentProvider={currentProvider}
modelItem={modelItem}
onOpenModal={onOpenModal}
onOperate={onOperate}
onUpdate={onUpdate}
/>
</div>
{
!!custom?.models?.length && (
<Card
providerType={modelItem.key}
models={custom?.models}
onOpenModal={onOpenModal}
onOperate={onOperate}
/>
)
}
{
systemFree?.is_valid && !IS_CE_EDITION && (
<QuotaCard remainTokens={systemFree.quota_limit - systemFree.quota_used}/>
)
}
</div>
)
}
export default ModelItem

View file

@ -1,208 +0,0 @@
import { useEffect, useState } from 'react'
import type { Dispatch, FC, SetStateAction } from 'react'
import { useContext } from 'use-context-selector'
import { type Field, type FormValue, type ProviderConfigModal, ProviderEnum } from '../declarations'
import { useValidate } from '../../key-validator/hooks'
import { ValidatingTip } from '../../key-validator/ValidateStatus'
import { validateModelProviderFn } from '../utils'
import Input from './Input'
import I18n from '@/context/i18n'
import { SimpleSelect } from '@/app/components/base/select'
type FormProps = {
modelModal?: ProviderConfigModal
initValue?: FormValue
fields: Field[]
onChange: (v: FormValue) => void
onValidatedError: (v: string) => void
mode: string
cleared: boolean
onClearedChange: Dispatch<SetStateAction<boolean>>
onValidating: (validating: boolean) => void
}
const nameClassName = `
py-2 text-sm text-gray-900
`
const Form: FC<FormProps> = ({
modelModal,
initValue = {},
fields,
onChange,
onValidatedError,
mode,
cleared,
onClearedChange,
onValidating,
}) => {
const { locale } = useContext(I18n)
const [value, setValue] = useState(initValue)
const [validate, validating, validatedStatusState] = useValidate(value)
const [changeKey, setChangeKey] = useState('')
useEffect(() => {
onValidatedError(validatedStatusState.message || '')
}, [validatedStatusState, onValidatedError])
useEffect(() => {
onValidating(validating)
}, [validating, onValidating])
const updateValue = (v: FormValue) => {
setValue(v)
onChange(v)
}
const handleMultiFormChange = (v: FormValue, newChangeKey: string) => {
updateValue(v)
setChangeKey(newChangeKey)
const validateKeys = (typeof modelModal?.validateKeys === 'function' ? modelModal?.validateKeys(v) : modelModal?.validateKeys) || []
if (validateKeys.length) {
validate({
before: () => {
for (let i = 0; i < validateKeys.length; i++) {
if (!v[validateKeys[i]])
return false
}
return true
},
run: () => {
return validateModelProviderFn(modelModal!.key, modelModal?.filterValue ? modelModal?.filterValue(v) : v)
},
})
}
}
const handleClear = (saveValue?: FormValue) => {
const needClearFields = modelModal?.fields.filter(field => field.type !== 'radio')
const newValue: Record<string, string> = {}
needClearFields?.forEach((field) => {
newValue[field.key] = ''
})
updateValue({ ...value, ...newValue, ...saveValue })
onClearedChange(true)
}
const handleFormChange = (k: string, v: string) => {
if (mode === 'edit' && !cleared) {
handleClear({ [k]: v })
}
else {
const extraValue: Record<string, string> = {}
if (
(
(k === 'model_type' && v === 'embeddings' && value.huggingfacehub_api_type === 'inference_endpoints')
|| (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'embeddings')
)
&& modelModal?.key === ProviderEnum.huggingface_hub
)
extraValue.task_type = 'feature-extraction'
if (
(
(k === 'model_type' && v === 'text-generation' && value.huggingfacehub_api_type === 'inference_endpoints')
|| (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'text-generation')
)
&& modelModal?.key === ProviderEnum.huggingface_hub
)
extraValue.task_type = 'text-generation'
if (
(
(k === 'model_type' && v === 'chat' && value.huggingfacehub_api_type === 'inference_endpoints')
|| (k === 'huggingfacehub_api_type' && v === 'inference_endpoints' && value.model_type === 'chat')
)
&& modelModal?.key === ProviderEnum.huggingface_hub
)
extraValue.task_type = 'question-answer'
handleMultiFormChange({ ...value, [k]: v, ...extraValue }, k)
}
}
const handleFocus = () => {
if (mode === 'edit' && !cleared)
handleClear()
}
const renderField = (field: Field) => {
const hidden = typeof field.hidden === 'function' ? field.hidden(value) : field.hidden
if (hidden)
return null
if (field.type === 'text') {
return (
<div key={field.key} className='py-3'>
<div className={nameClassName}>{field.label[locale]}</div>
<Input
field={field}
value={value}
onChange={v => handleMultiFormChange(v, field.key)}
onFocus={handleFocus}
validatedStatusState={validatedStatusState}
/>
{validating && changeKey === field.key && <ValidatingTip />}
</div>
)
}
if (field.type === 'radio') {
const options = typeof field.options === 'function' ? field.options(value) : field.options
return (
<div key={field.key} className='py-3'>
<div className={nameClassName}>{field.label[locale]}</div>
<div className={`grid grid-cols-${options?.length} gap-3`}>
{
options?.map(option => (
<div
className={`
flex items-center px-3 py-2 rounded-lg border border-gray-100 bg-gray-25 cursor-pointer
${value?.[field.key] === option.key && 'bg-white border-[1.5px] border-primary-400 shadow-sm'}
`}
onClick={() => handleFormChange(field.key, option.key)}
key={`${field.key}-${option.key}`}
>
<div className={`
flex justify-center items-center mr-2 w-4 h-4 border border-gray-300 rounded-full
${value?.[field.key] === option.key && 'border-[5px] border-primary-600'}
`} />
<div className='text-sm text-gray-900'>{option.label[locale]}</div>
</div>
))
}
</div>
{validating && changeKey === field.key && <ValidatingTip />}
</div>
)
}
if (field.type === 'select') {
const options = typeof field.options === 'function' ? field.options(value) : field.options
return (
<div key={field.key} className='py-3'>
<div className={nameClassName}>{field.label[locale]}</div>
<SimpleSelect
defaultValue={value[field.key]}
items={options!.map(option => ({ value: option.key, name: option.label[locale] }))}
onSelect={item => handleFormChange(field.key, item.value as string)}
/>
{validating && changeKey === field.key && <ValidatingTip />}
</div>
)
}
}
return (
<div>
{
fields.map(field => renderField(field))
}
</div>
)
}
export default Form

View file

@ -1,58 +0,0 @@
import type { FC } from 'react'
import { useContext } from 'use-context-selector'
import type { Field, FormValue } from '../declarations'
import { ValidatedSuccessIcon } from '../../key-validator/ValidateStatus'
import { ValidatedStatus } from '../../key-validator/declarations'
import type { ValidatedStatusState } from '../../key-validator/declarations'
import I18n from '@/context/i18n'
type InputProps = {
field: Field
value: FormValue
onChange: (v: FormValue) => void
onFocus: () => void
validatedStatusState: ValidatedStatusState
}
const Input: FC<InputProps> = ({
field,
value,
onChange,
onFocus,
validatedStatusState,
}) => {
const { locale } = useContext(I18n)
const showValidatedIcon = validatedStatusState.status === ValidatedStatus.Success && value[field.key]
const getValidatedIcon = () => {
if (showValidatedIcon)
return <div className='absolute top-2.5 right-2.5'><ValidatedSuccessIcon /></div>
}
const handleChange = (v: string) => {
const newFormValue = { ...value, [field.key]: v }
onChange(newFormValue)
}
return (
<div className='relative'>
<input
tabIndex={-1}
className={`
block px-3 w-full h-9 bg-gray-100 text-sm rounded-lg border border-transparent
appearance-none outline-none caret-primary-600
hover:border-[rgba(0,0,0,0.08)] hover:bg-gray-50
focus:bg-white focus:border-gray-300 focus:shadow-xs
placeholder:text-sm placeholder:text-gray-400
${showValidatedIcon && 'pr-[30px]'}
`}
placeholder={field?.placeholder?.[locale] || ''}
onChange={e => handleChange(e.target.value)}
onFocus={onFocus}
value={value[field.key] || ''}
/>
{getValidatedIcon()}
</div>
)
}
export default Input

View file

@ -1,170 +0,0 @@
import { useCallback, useState } from 'react'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector'
import type { FormValue, ProviderConfigModal } from '../declarations'
import { ConfigurableProviders } from '../utils'
import Form from './Form'
import I18n from '@/context/i18n'
import Button from '@/app/components/base/button'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import { LinkExternal02 } from '@/app/components/base/icons/src/vender/line/general'
import { AlertCircle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import {
PortalToFollowElem,
PortalToFollowElemContent,
} from '@/app/components/base/portal-to-follow-elem'
type ModelModalProps = {
isShow: boolean
onCancel: () => void
modelModal?: ProviderConfigModal
onSave: (v?: FormValue) => void
mode: string
}
const ModelModal: FC<ModelModalProps> = ({
isShow,
onCancel,
modelModal,
onSave,
mode,
}) => {
const { t } = useTranslation()
const { locale } = useContext(I18n)
const { eventEmitter } = useEventEmitterContextContext()
const [value, setValue] = useState<FormValue | undefined>()
const [loading, setLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [cleared, setCleared] = useState(false)
const [prevIsShow, setPrevIsShow] = useState(isShow)
const [validating, setValidating] = useState(false)
if (prevIsShow !== isShow) {
setCleared(false)
setPrevIsShow(isShow)
}
eventEmitter?.useSubscription((v) => {
if (v === 'provider-save')
setLoading(true)
else
setLoading(false)
})
const handleValidatedError = useCallback((newErrorMessage: string) => {
setErrorMessage(newErrorMessage)
}, [])
const handleValidating = useCallback((newValidating: boolean) => {
setValidating(newValidating)
}, [])
const validateRequiredValue = () => {
const validateValue = value || modelModal?.defaultValue
if (modelModal) {
const { fields } = modelModal
const requiredFields = fields.filter(field => !(typeof field.hidden === 'function' ? field.hidden(validateValue) : field.hidden) && field.required)
for (let i = 0; i < requiredFields.length; i++) {
const currentField = requiredFields[i]
if (!validateValue?.[currentField.key]) {
setErrorMessage(t('appDebug.errorMessage.valueOfVarRequired', { key: currentField.label[locale] }) || '')
return false
}
}
return true
}
}
const handleSave = () => {
if (validateRequiredValue())
onSave(value || modelModal?.defaultValue)
}
const renderTitlePrefix = () => {
let prefix
if (mode === 'edit')
prefix = t('common.operation.edit')
else
prefix = ConfigurableProviders.includes(modelModal!.key) ? t('common.operation.create') : t('common.operation.setup')
return `${prefix} ${modelModal?.title[locale]}`
}
if (!isShow)
return null
return (
<PortalToFollowElem open>
<PortalToFollowElemContent className='w-full h-full z-[60]'>
<div className='fixed inset-0 flex items-center justify-center bg-black/[.25]'>
<div className='mx-2 w-[640px] max-h-[calc(100vh-120px)] bg-white shadow-xl rounded-2xl overflow-y-auto'>
<div className='px-8 pt-8'>
<div className='flex justify-between items-center mb-2'>
<div className='text-xl font-semibold text-gray-900'>{renderTitlePrefix()}</div>
{modelModal?.icon}
</div>
<Form
modelModal={modelModal}
fields={modelModal?.fields || []}
initValue={modelModal?.defaultValue}
onChange={newValue => setValue(newValue)}
onValidatedError={handleValidatedError}
mode={mode}
cleared={cleared}
onClearedChange={setCleared}
onValidating={handleValidating}
/>
<div className='flex justify-between items-center py-6 flex-wrap gap-y-2'>
<a
href={modelModal?.link.href}
target='_blank'
className='inline-flex items-center text-xs text-primary-600'
>
{modelModal?.link.label[locale]}
<LinkExternal02 className='ml-1 w-3 h-3' />
</a>
<div>
<Button className='mr-2 !h-9 !text-sm font-medium text-gray-700' onClick={onCancel}>{t('common.operation.cancel')}</Button>
<Button
className='!h-9 !text-sm font-medium'
type='primary'
onClick={handleSave}
disabled={loading || (mode === 'edit' && !cleared) || validating}
>
{t('common.operation.save')}
</Button>
</div>
</div>
</div>
<div className='border-t-[0.5px] border-t-[rgba(0,0,0,0.05)]'>
{
errorMessage
? (
<div className='flex px-[10px] py-3 bg-[#FEF3F2] text-xs text-[#D92D20]'>
<AlertCircle className='mt-[1px] mr-2 w-[14px] h-[14px]' />
{errorMessage}
</div>
)
: (
<div className='flex justify-center items-center py-3 bg-gray-50 text-xs text-gray-500'>
<Lock01 className='mr-1 w-3 h-3 text-gray-500' />
{t('common.modelProvider.encrypted.front')}
<a
className='text-primary-600 mx-1'
target={'_blank'}
href='https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html'
>
PKCS1_OAEP
</a>
{t('common.modelProvider.encrypted.back')}
</div>
)
}
</div>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default ModelModal

View file

@ -1,347 +0,0 @@
import type { FC } from 'react'
import React, { Fragment, useEffect, useState } from 'react'
import useSWR from 'swr'
import { Popover, Transition } from '@headlessui/react'
import { useTranslation } from 'react-i18next'
import _ from 'lodash-es'
import cn from 'classnames'
import ModelModal from '../model-modal'
import cohereConfig from '../configs/cohere'
import s from './style.module.css'
import type { BackendModel, FormValue, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { Check, LinkExternal01, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import Tooltip from '@/app/components/base/tooltip'
import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
import ModelName from '@/app/components/app/configuration/config-model/model-name'
import ProviderName from '@/app/components/app/configuration/config-model/provider-name'
import { useProviderContext } from '@/context/provider-context'
import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
import type { ModelModeType } from '@/types/app'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { useModalContext } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { fetchDefaultModal, setModelProvider } from '@/service/common'
import { useToastContext } from '@/app/components/base/toast'
type Props = {
value: {
providerName: ProviderEnum
modelName: string
} | undefined
modelType: ModelType
isShowModelModeType?: boolean
isShowAddModel?: boolean
supportAgentThought?: boolean
onChange: (value: BackendModel) => void
popClassName?: string
readonly?: boolean
triggerIconSmall?: boolean
whenEmptyGoToSetting?: boolean
onUpdate?: () => void
}
type ModelOption = {
type: 'model'
value: string
providerName: ProviderEnum
modelDisplayName: string
model_mode: ModelModeType
} | {
type: 'provider'
value: ProviderEnum
}
const ModelSelector: FC<Props> = ({
value,
modelType,
isShowModelModeType,
isShowAddModel,
supportAgentThought,
onChange,
popClassName,
readonly,
triggerIconSmall,
whenEmptyGoToSetting,
onUpdate,
}) => {
const { t } = useTranslation()
const { setShowAccountSettingModal } = useModalContext()
const {
textGenerationModelList,
embeddingsModelList,
speech2textModelList,
rerankModelList,
agentThoughtModelList,
updateModelList,
} = useProviderContext()
const [search, setSearch] = useState('')
const modelList = supportAgentThought
? agentThoughtModelList
: ({
[ModelType.textGeneration]: textGenerationModelList,
[ModelType.embeddings]: embeddingsModelList,
[ModelType.speech2text]: speech2textModelList,
[ModelType.reranking]: rerankModelList,
})[modelType]
const currModel = modelList.find(item => item.model_name === value?.modelName && item.model_provider.provider_name === value.providerName)
const allModelNames = (() => {
if (!search)
return {}
const res: Record<string, string> = {}
modelList.forEach(({ model_name, model_display_name }) => {
res[model_name] = model_display_name
})
return res
})()
const filteredModelList = search
? modelList.filter(({ model_name }) => {
if (allModelNames[model_name].includes(search))
return true
return false
})
: modelList
const hasRemoved = (value && value.modelName && value.providerName) && !modelList.find(({ model_name, model_provider }) => model_name === value.modelName && model_provider.provider_name === value.providerName)
const modelOptions: ModelOption[] = (() => {
const providers = _.uniq(filteredModelList.map(item => item.model_provider.provider_name))
const res: ModelOption[] = []
providers.forEach((providerName) => {
res.push({
type: 'provider',
value: providerName,
})
const models = filteredModelList.filter(m => m.model_provider.provider_name === providerName)
models.forEach(({ model_name, model_display_name, model_mode }) => {
res.push({
type: 'model',
providerName,
value: model_name,
modelDisplayName: model_display_name,
model_mode,
})
})
})
return res
})()
const { eventEmitter } = useEventEmitterContextContext()
const [showRerankModal, setShowRerankModal] = useState(false)
const [shouldFetchRerankDefaultModel, setShouldFetchRerankDefaultModel] = useState(false)
const { notify } = useToastContext()
const { data: rerankDefaultModel } = useSWR(shouldFetchRerankDefaultModel ? '/workspaces/current/default-model?model_type=reranking' : null, fetchDefaultModal)
const handleOpenRerankModal = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
setShowRerankModal(true)
}
const handleRerankModalSave = async (originValue?: FormValue) => {
if (originValue) {
try {
eventEmitter?.emit('provider-save')
const res = await setModelProvider({
url: `/workspaces/current/model-providers/${cohereConfig.modal.key}`,
body: {
config: originValue,
},
})
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
updateModelList(ModelType.reranking)
setShowRerankModal(false)
setShouldFetchRerankDefaultModel(true)
if (onUpdate)
onUpdate()
}
eventEmitter?.emit('')
}
catch (e) {
eventEmitter?.emit('')
}
}
}
useEffect(() => {
if (rerankDefaultModel && whenEmptyGoToSetting)
onChange(rerankDefaultModel)
}, [rerankDefaultModel])
return (
<div className=''>
<Popover className='relative'>
<Popover.Button className={cn('flex items-center px-2.5 w-full h-9 rounded-lg', readonly ? '!cursor-auto bg-gray-100 opacity-50' : 'bg-gray-100', hasRemoved && '!bg-[#FEF3F2]')}>
{
({ open }) => (
<>
{
(value && value.modelName && value.providerName)
? (
<>
<ModelIcon
className={cn('mr-1.5', !triggerIconSmall && 'w-5 h-5')}
modelId={value.modelName}
providerName={value.providerName}
/>
<div className='mr-1.5 grow flex items-center text-left text-sm text-gray-900 truncate'>
<ModelName modelId={value.modelName} modelDisplayName={currModel?.model_display_name || value.modelName} />
{isShowModelModeType && (
<ModelModeTypeLabel className='ml-2' type={currModel?.model_mode as ModelModeType} />
)}
</div>
</>
)
: whenEmptyGoToSetting
? (
<div className='grow flex items-center h-9 justify-between' onClick={handleOpenRerankModal}>
<div className='flex items-center text-[13px] font-medium text-primary-500'>
<CubeOutline className='mr-1.5 w-4 h-4' />
{t('common.modelProvider.selector.rerankTip')}
</div>
<LinkExternal01 className='w-3 h-3 text-gray-500' />
</div>
)
: (
<div className='grow text-left text-sm text-gray-800 opacity-60'>{t('common.modelProvider.selectModel')}</div>
)
}
{
hasRemoved && (
<Tooltip
selector='model-selector-remove-tip'
htmlContent={
<div className='w-[261px] text-gray-500'>{t('common.modelProvider.selector.tip')}</div>
}
>
<AlertCircle className='mr-1 w-4 h-4 text-[#F04438]' />
</Tooltip>
)
}
{
!readonly && !whenEmptyGoToSetting && (
<ChevronDown className={`w-4 h-4 text-gray-700 ${open ? 'opacity-100' : 'opacity-60'}`} />
)
}
{
whenEmptyGoToSetting && (value && value.modelName && value.providerName) && (
<ChevronDown className={`w-4 h-4 text-gray-700 ${open ? 'opacity-100' : 'opacity-60'}`} />
)
}
</>
)
}
</Popover.Button>
{!readonly && (
<Transition
as={Fragment}
leave='transition ease-in duration-100'
leaveFrom='opacity-100'
leaveTo='opacity-0'
>
<Popover.Panel className={cn(popClassName, isShowModelModeType ? 'max-w-[312px]' : 'max-w-[260px]', 'absolute top-10 p-1 min-w-[232px] max-h-[366px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg overflow-auto z-10')}>
<div className='px-2 pt-2 pb-1'>
<div className='flex items-center px-2 h-8 bg-gray-100 rounded-lg'>
<div className='mr-1.5 p-[1px]'><SearchLg className='w-[14px] h-[14px] text-gray-400' /></div>
<div className='grow px-0.5'>
<input
value={search}
onChange={e => setSearch(e.target.value)}
className={`
block w-full h-8 bg-transparent text-[13px] text-gray-700
outline-none appearance-none border-none
`}
placeholder={t('common.modelProvider.searchModel') || ''}
/>
</div>
{
search && (
<div className='ml-1 p-0.5 cursor-pointer' onClick={() => setSearch('')}>
<XCircle className='w-3 h-3 text-gray-400' />
</div>
)
}
</div>
</div>
{
modelOptions.map((model) => {
if (model.type === 'provider') {
return (
<div
className='px-3 pt-2 pb-1 text-xs font-medium text-gray-500'
key={`${model.type}-${model.value}`}
>
<ProviderName provideName={model.value} />
</div>
)
}
if (model.type === 'model') {
return (
<Popover.Button
key={`${model.providerName}-${model.value}`}
className={`${s.optionItem}
flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50
${!readonly ? 'cursor-pointer' : 'cursor-auto'}
${(value?.providerName === model.providerName && value?.modelName === model.value) && 'bg-gray-50'}
`}
onClick={() => {
const selectedModel = modelList.find((item) => {
return item.model_name === model.value && item.model_provider.provider_name === model.providerName
})
onChange(selectedModel as BackendModel)
}}
>
<ModelIcon
className='mr-2 shrink-0'
modelId={model.value}
providerName={model.providerName}
/>
<div className='mr-2 grow flex items-center text-left text-sm text-gray-900 truncate'>
<ModelName modelId={model.value} modelDisplayName={model.modelDisplayName} />
{isShowModelModeType && (
<ModelModeTypeLabel className={`${s.modelModeLabel} ml-2`} type={model.model_mode} />
)}
</div>
{ (value?.providerName === model.providerName && value?.modelName === model.value) && <Check className='shrink-0 w-4 h-4 text-primary-600' /> }
</Popover.Button>
)
}
return null
})
}
{modelList.length !== 0 && (search && filteredModelList.length === 0) && (
<div className='px-3 pt-1.5 h-[30px] text-center text-xs text-gray-500'>{t('common.modelProvider.noModelFound', { model: search })}</div>
)}
{isShowAddModel && (
<div
className='border-t flex items-center h-9 pl-3 text-xs text-[#155EEF] cursor-pointer'
style={{
borderColor: 'rgba(0, 0, 0, 0.05)',
}}
onClick={() => setShowAccountSettingModal({ payload: 'provider' })}
>
<CubeOutline className='w-4 h-4 mr-2' />
<div>{t('common.model.addMoreModel')}</div>
</div>
)}
</Popover.Panel>
</Transition>
)}
</Popover>
<ModelModal
isShow={showRerankModal}
modelModal={cohereConfig.modal}
onCancel={() => setShowRerankModal(false)}
onSave={handleRerankModalSave}
mode={'add'}
/>
</div>
)
}
export default ModelSelector

View file

@ -1,358 +0,0 @@
import type { FC } from 'react'
import React, { Fragment, useEffect, useRef, useState } from 'react'
import useSWR from 'swr'
import { useTranslation } from 'react-i18next'
import _ from 'lodash-es'
import cn from 'classnames'
import ModelModal from '../model-modal'
import cohereConfig from '../configs/cohere'
import s from './style.module.css'
import type { BackendModel, FormValue, ProviderEnum } from '@/app/components/header/account-setting/model-page/declarations'
import { ModelType } from '@/app/components/header/account-setting/model-page/declarations'
import { ChevronDown } from '@/app/components/base/icons/src/vender/line/arrows'
import { Check, LinkExternal01, SearchLg } from '@/app/components/base/icons/src/vender/line/general'
import { XCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { AlertCircle } from '@/app/components/base/icons/src/vender/line/alertsAndFeedback'
import Tooltip from '@/app/components/base/tooltip'
import ModelIcon from '@/app/components/app/configuration/config-model/model-icon'
import ModelName from '@/app/components/app/configuration/config-model/model-name'
import ProviderName from '@/app/components/app/configuration/config-model/provider-name'
import { useProviderContext } from '@/context/provider-context'
import ModelModeTypeLabel from '@/app/components/app/configuration/config-model/model-mode-type-label'
import type { ModelModeType } from '@/types/app'
import { CubeOutline } from '@/app/components/base/icons/src/vender/line/shapes'
import { useModalContext } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import { fetchDefaultModal, setModelProvider } from '@/service/common'
import { useToastContext } from '@/app/components/base/toast'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
type Props = {
value: {
providerName: ProviderEnum
modelName: string
} | undefined
modelType: ModelType
isShowModelModeType?: boolean
isShowAddModel?: boolean
supportAgentThought?: boolean
onChange: (value: BackendModel) => void
popClassName?: string
readonly?: boolean
triggerIconSmall?: boolean
whenEmptyGoToSetting?: boolean
onUpdate?: () => void
widthSameToTrigger?: boolean
}
type ModelOption = {
type: 'model'
value: string
providerName: ProviderEnum
modelDisplayName: string
model_mode: ModelModeType
} | {
type: 'provider'
value: ProviderEnum
}
const ModelSelector: FC<Props> = ({
value,
modelType,
isShowModelModeType,
isShowAddModel,
supportAgentThought,
onChange,
popClassName,
readonly,
triggerIconSmall,
whenEmptyGoToSetting,
onUpdate,
widthSameToTrigger,
}) => {
const { t } = useTranslation()
const { setShowAccountSettingModal } = useModalContext()
const {
textGenerationModelList,
embeddingsModelList,
speech2textModelList,
rerankModelList,
agentThoughtModelList,
updateModelList,
} = useProviderContext()
const [search, setSearch] = useState('')
const modelList = supportAgentThought
? agentThoughtModelList
: ({
[ModelType.textGeneration]: textGenerationModelList,
[ModelType.embeddings]: embeddingsModelList,
[ModelType.speech2text]: speech2textModelList,
[ModelType.reranking]: rerankModelList,
})[modelType]
const currModel = modelList.find(item => item.model_name === value?.modelName && item.model_provider.provider_name === value.providerName)
const allModelNames = (() => {
if (!search)
return {}
const res: Record<string, string> = {}
modelList.forEach(({ model_name, model_display_name }) => {
res[model_name] = model_display_name
})
return res
})()
const filteredModelList = search
? modelList.filter(({ model_name }) => {
if (allModelNames[model_name].includes(search))
return true
return false
})
: modelList
const hasRemoved = (value && value.modelName && value.providerName) && !modelList.find(({ model_name, model_provider }) => model_name === value.modelName && model_provider.provider_name === value.providerName)
const modelOptions: ModelOption[] = (() => {
const providers = _.uniq(filteredModelList.map(item => item.model_provider.provider_name))
const res: ModelOption[] = []
providers.forEach((providerName) => {
res.push({
type: 'provider',
value: providerName,
})
const models = filteredModelList.filter(m => m.model_provider.provider_name === providerName)
models.forEach(({ model_name, model_display_name, model_mode }) => {
res.push({
type: 'model',
providerName,
value: model_name,
modelDisplayName: model_display_name,
model_mode,
})
})
})
return res
})()
const { eventEmitter } = useEventEmitterContextContext()
const [showRerankModal, setShowRerankModal] = useState(false)
const [shouldFetchRerankDefaultModel, setShouldFetchRerankDefaultModel] = useState(false)
const { notify } = useToastContext()
const { data: rerankDefaultModel } = useSWR(shouldFetchRerankDefaultModel ? '/workspaces/current/default-model?model_type=reranking' : null, fetchDefaultModal)
const handleOpenRerankModal = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation()
setShowRerankModal(true)
}
const handleRerankModalSave = async (originValue?: FormValue) => {
if (originValue) {
try {
eventEmitter?.emit('provider-save')
const res = await setModelProvider({
url: `/workspaces/current/model-providers/${cohereConfig.modal.key}`,
body: {
config: originValue,
},
})
if (res.result === 'success') {
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
updateModelList(ModelType.reranking)
setShowRerankModal(false)
setShouldFetchRerankDefaultModel(true)
if (onUpdate)
onUpdate()
}
eventEmitter?.emit('')
}
catch (e) {
eventEmitter?.emit('')
}
}
}
const [open, setOpen] = useState(false)
const triggerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (rerankDefaultModel && whenEmptyGoToSetting)
onChange(rerankDefaultModel)
}, [rerankDefaultModel])
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={4}
>
<div className='relative'>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)} className={cn('flex items-center px-2.5 w-full h-9 rounded-lg', readonly ? '!cursor-auto bg-gray-100 opacity-50' : 'bg-gray-100', hasRemoved && '!bg-[#FEF3F2]')}>
{
<div ref={triggerRef} className='flex items-center w-full cursor-pointer'>
{
(value && value.modelName && value.providerName)
? (
<>
<ModelIcon
className={cn('mr-1.5', !triggerIconSmall && 'w-5 h-5')}
modelId={value.modelName}
providerName={value.providerName}
/>
<div className='mr-1.5 grow flex items-center text-left text-sm text-gray-900 truncate'>
<ModelName modelId={value.modelName} modelDisplayName={currModel?.model_display_name || value.modelName} />
{isShowModelModeType && (
<ModelModeTypeLabel className='ml-2' type={currModel?.model_mode as ModelModeType} />
)}
</div>
</>
)
: whenEmptyGoToSetting
? (
<div className='grow flex items-center h-9 justify-between' onClick={handleOpenRerankModal}>
<div className='flex items-center text-[13px] font-medium text-primary-500'>
<CubeOutline className='mr-1.5 w-4 h-4' />
{t('common.modelProvider.selector.rerankTip')}
</div>
<LinkExternal01 className='w-3 h-3 text-gray-500' />
</div>
)
: (
<div className='grow text-left text-sm text-gray-800 opacity-60'>{t('common.modelProvider.selectModel')}</div>
)
}
{
hasRemoved && (
<Tooltip
selector='model-selector-remove-tip'
htmlContent={
<div className='w-[261px] text-gray-500'>{t('common.modelProvider.selector.tip')}</div>
}
>
<AlertCircle className='mr-1 w-4 h-4 text-[#F04438]' />
</Tooltip>
)
}
{
!readonly && !whenEmptyGoToSetting && (
<ChevronDown className={`w-4 h-4 text-gray-700 ${open ? 'opacity-100' : 'opacity-60'}`} />
)
}
{
whenEmptyGoToSetting && (value && value.modelName && value.providerName) && (
<ChevronDown className={`w-4 h-4 text-gray-700 ${open ? 'opacity-100' : 'opacity-60'}`} />
)
}
</div>
}
</PortalToFollowElemTrigger>
{!readonly && (
<PortalToFollowElemContent
className={cn(popClassName, !widthSameToTrigger && (isShowModelModeType ? 'max-w-[312px]' : 'max-w-[260px]'), 'absolute top-10 p-1 min-w-[232px] max-h-[366px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg overflow-auto z-[999]')}
style={{
width: (widthSameToTrigger && triggerRef.current?.offsetWidth) ? `${triggerRef.current?.offsetWidth}px` : 'auto',
}}
>
<div className='px-2 pt-2 pb-1'>
<div className='flex items-center px-2 h-8 bg-gray-100 rounded-lg'>
<div className='mr-1.5 p-[1px]'><SearchLg className='w-[14px] h-[14px] text-gray-400' /></div>
<div className='grow px-0.5'>
<input
value={search}
onChange={e => setSearch(e.target.value)}
className={`
block w-full h-8 bg-transparent text-[13px] text-gray-700
outline-none appearance-none border-none
`}
placeholder={t('common.modelProvider.searchModel') || ''}
/>
</div>
{
search && (
<div className='ml-1 p-0.5 cursor-pointer' onClick={() => setSearch('')}>
<XCircle className='w-3 h-3 text-gray-400' />
</div>
)
}
</div>
</div>
{
modelOptions.map((model) => {
if (model.type === 'provider') {
return (
<div
className='px-3 pt-2 pb-1 text-xs font-medium text-gray-500'
key={`${model.type}-${model.value}`}
>
<ProviderName provideName={model.value} />
</div>
)
}
if (model.type === 'model') {
return (
<div
key={`${model.providerName}-${model.value}`}
className={`${s.optionItem}
flex items-center px-3 w-full h-8 rounded-lg hover:bg-gray-50
${!readonly ? 'cursor-pointer' : 'cursor-auto'}
${(value?.providerName === model.providerName && value?.modelName === model.value) && 'bg-gray-50'}
`}
onClick={() => {
const selectedModel = modelList.find((item) => {
return item.model_name === model.value && item.model_provider.provider_name === model.providerName
})
onChange(selectedModel as BackendModel)
setOpen(false)
}}
>
<ModelIcon
className='mr-2 shrink-0'
modelId={model.value}
providerName={model.providerName}
/>
<div className='mr-2 grow flex items-center text-left text-sm text-gray-900 truncate'>
<ModelName modelId={model.value} modelDisplayName={model.modelDisplayName} />
{isShowModelModeType && (
<ModelModeTypeLabel className={`${s.modelModeLabel} ml-2`} type={model.model_mode} />
)}
</div>
{(value?.providerName === model.providerName && value?.modelName === model.value) && <Check className='shrink-0 w-4 h-4 text-primary-600' />}
</div>
)
}
return null
})
}
{modelList.length !== 0 && (search && filteredModelList.length === 0) && (
<div className='px-3 pt-1.5 h-[30px] text-center text-xs text-gray-500'>{t('common.modelProvider.noModelFound', { model: search })}</div>
)}
{isShowAddModel && (
<div
className='border-t flex items-center h-9 pl-3 text-xs text-[#155EEF] cursor-pointer'
style={{
borderColor: 'rgba(0, 0, 0, 0.05)',
}}
onClick={() => setShowAccountSettingModal({ payload: 'provider' })}
>
<CubeOutline className='w-4 h-4 mr-2' />
<div>{t('common.model.addMoreModel')}</div>
</div>
)}
</PortalToFollowElemContent>
)}
</div>
<ModelModal
isShow={showRerankModal}
modelModal={cohereConfig.modal}
onCancel={() => setShowRerankModal(false)}
onSave={handleRerankModalSave}
mode={'add'}
/>
</PortalToFollowElem>
)
}
export default ModelSelector

View file

@ -1,7 +0,0 @@
.modelModeLabel {
visibility: hidden;
}
.optionItem:hover .modelModeLabel {
visibility: visible;
}

View file

@ -1,97 +0,0 @@
import { Fragment } from 'react'
import type { FC } from 'react'
import { Popover, Transition } from '@headlessui/react'
import { useTranslation } from 'react-i18next'
import { Check, DotsHorizontal, Trash03 } from '@/app/components/base/icons/src/vender/line/general'
const itemClassName = `
flex items-center px-3 h-9 text-sm text-gray-700 rounded-lg cursor-pointer
`
type SelectorProps = {
value?: string
onOperate: (v: Record<string, string>) => void
hiddenOptions?: boolean
className?: (v: boolean) => string
deleteText?: string
}
const Selector: FC<SelectorProps> = ({
value,
onOperate,
hiddenOptions,
className,
deleteText,
}) => {
const { t } = useTranslation()
const options = [
{
key: 'custom',
text: 'API',
},
{
key: 'system',
text: t('common.modelProvider.quota'),
},
]
return (
<Popover className='relative'>
<Popover.Button>
{
({ open }) => (
<div className={`
flex justify-center items-center w-6 h-6 rounded-md hover:bg-gray-50 cursor-pointer
${open && 'bg-gray-50'}
${className && className(open)}
`}>
<DotsHorizontal className='w-3 h-3 text-gray-700' />
</div>
)
}
</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-[192px] bg-white border-[0.5px] border-gray-200 rounded-lg shadow-lg z-10'>
{
!hiddenOptions && (
<>
<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={`${itemClassName} hover:bg-gray-50`}
onClick={() => onOperate({ type: 'priority', value: option.key })}>
<div className='grow'>{option.text}</div>
{value === option.key && <Check className='w-4 h-4 text-primary-600' />}
</div>
</Popover.Button>
))
}
</div>
<div className='h-[1px] bg-gray-100' />
</>
)
}
<div className='p-1'>
<Popover.Button as={Fragment}>
<div
className={`group ${itemClassName} hover:bg-[#FEF3F2] hover:text-[#D92D20]`}
onClick={() => onOperate({ type: 'delete' })}>
<Trash03 className='mr-2 w-4 h-4 text-gray-500 group-hover:text-[#D92D20]' />
{deleteText || t('common.modelProvider.card.removeKey')}
</div>
</Popover.Button>
</div>
</Popover.Panel>
</Transition>
</Popover>
)
}
export default Selector

View file

@ -1,35 +0,0 @@
import { ValidatedStatus } from '../key-validator/declarations'
import { ProviderEnum } from './declarations'
import { validateModelProvider } from '@/service/common'
export const ConfigurableProviders = [ProviderEnum.azure_openai, ProviderEnum.replicate, ProviderEnum.huggingface_hub, ProviderEnum.xinference, ProviderEnum.openllm, ProviderEnum.localai]
export const validateModelProviderFn = async (providerName: ProviderEnum, v: any) => {
let body, url
if (ConfigurableProviders.includes(providerName)) {
const { model_name, model_type, ...config } = v
body = {
model_name,
model_type,
config,
}
url = `/workspaces/current/model-providers/${providerName}/models/validate`
}
else {
body = {
config: v,
}
url = `/workspaces/current/model-providers/${providerName}/validate`
}
try {
const res = await validateModelProvider({ url, body })
if (res.result === 'success')
return Promise.resolve({ status: ValidatedStatus.Success })
else
return Promise.resolve({ status: ValidatedStatus.Error, message: res.error })
}
catch (e: any) {
return Promise.resolve({ status: ValidatedStatus.Error, message: e.message })
}
}

View file

@ -0,0 +1,216 @@
export type FormValue = Record<string, any>
export type TypeWithI18N<T = string> = {
'en_US': T
'zh_Hans': T
}
export enum FormTypeEnum {
textInput = 'text-input',
secretInput = 'secret-input',
select = 'select',
radio = 'radio',
}
export type FormOption = {
label: TypeWithI18N
value: string
show_on: FormShowOnObject[]
}
export enum ModelTypeEnum {
textGeneration = 'llm',
textEmbedding = 'text-embedding',
rerank = 'rerank',
speech2text = 'speech2text',
moderation = 'moderation',
}
export const MODEL_TYPE_TEXT = {
[ModelTypeEnum.textGeneration]: 'LLM',
[ModelTypeEnum.textEmbedding]: 'Text Embedding',
[ModelTypeEnum.rerank]: 'Rerank',
[ModelTypeEnum.speech2text]: 'Speech2text',
[ModelTypeEnum.moderation]: 'Moderation',
}
export enum ConfigurateMethodEnum {
predefinedModel = 'predefined-model',
customizableModel = 'customizable-model',
fetchFromRemote = 'fetch-from-remote',
}
export enum ModelFeatureEnum {
toolCall = 'tool-call',
multiToolCall = 'multi-tool-call',
agentThought = 'agent-thought',
vision = 'vision',
}
export enum ModelFeatureTextEnum {
toolCall = 'Tool Call',
multiToolCall = 'Multi Tool Call',
agentThought = 'Agent Thought',
vision = 'Vision',
}
export enum ModelStatusEnum {
active = 'active',
noConfigure = 'no-configure',
quotaExceeded = 'quota-exceeded',
noPermission = 'no-permission',
}
export const MODEL_STATUS_TEXT = {
[ModelStatusEnum.noConfigure]: {
en_US: 'No Configure',
zh_Hans: '未配置凭据',
},
[ModelStatusEnum.quotaExceeded]: {
en_US: 'Quota Exceeded',
zh_Hans: '额度不足',
},
[ModelStatusEnum.noPermission]: {
en_US: 'No Permission',
zh_Hans: '无使用权限',
},
}
export enum CustomConfigurationStatusEnum {
active = 'active',
noConfigure = 'no-configure',
}
export type FormShowOnObject = {
variable: string
value: string
}
export type CredentialFormSchemaBase = {
variable: string
label: TypeWithI18N
type: FormTypeEnum
required: boolean
default?: string
show_on: FormShowOnObject[]
}
export type CredentialFormSchemaTextInput = CredentialFormSchemaBase & { max_length?: number; placeholder?: TypeWithI18N }
export type CredentialFormSchemaSelect = CredentialFormSchemaBase & { options: FormOption[]; placeholder?: TypeWithI18N }
export type CredentialFormSchemaRadio = CredentialFormSchemaBase & { options: FormOption[] }
export type CredentialFormSchemaSecretInput = CredentialFormSchemaBase & { placeholder?: TypeWithI18N }
export type CredentialFormSchema = CredentialFormSchemaTextInput | CredentialFormSchemaSelect | CredentialFormSchemaRadio | CredentialFormSchemaSecretInput
export type ModelItem = {
model: string
label: TypeWithI18N
model_type: ModelTypeEnum
features?: ModelFeatureEnum[]
fetch_from: ConfigurateMethodEnum
status: ModelStatusEnum
model_properties: Record<string, string | number>
deprecated?: boolean
}
export enum PreferredProviderTypeEnum {
system = 'system',
custom = 'custom',
}
export enum CurrentSystemQuotaTypeEnum {
trial = 'trial',
free = 'free',
paid = 'paid',
}
export enum QuotaUnitEnum {
times = 'times',
tokens = 'tokens',
}
export type QuotaConfiguration = {
quota_type: CurrentSystemQuotaTypeEnum
quota_unit: QuotaUnitEnum
quota_limit: number
quota_used: number
last_used: number
is_valid: boolean
}
export type ModelProvider = {
provider: string
label: TypeWithI18N
description?: TypeWithI18N
help: {
title: TypeWithI18N
url: TypeWithI18N
}
icon_small: TypeWithI18N
icon_large: TypeWithI18N
background?: string
supported_model_types: ModelTypeEnum[]
configurate_methods: ConfigurateMethodEnum[]
provider_credential_schema: {
credential_form_schemas: CredentialFormSchema[]
}
model_credential_schema: {
model: {
label: TypeWithI18N
placeholder: TypeWithI18N
}
credential_form_schemas: CredentialFormSchema[]
}
preferred_provider_type: PreferredProviderTypeEnum
custom_configuration: {
status: CustomConfigurationStatusEnum
}
system_configuration: {
enabled: boolean
current_quota_type: CurrentSystemQuotaTypeEnum
quota_configurations: QuotaConfiguration[]
}
}
export type Model = {
provider: string
icon_large: TypeWithI18N
icon_small: TypeWithI18N
label: TypeWithI18N
models: ModelItem[]
status: ModelStatusEnum
}
export type DefaultModelResponse = {
model: string
model_type: ModelTypeEnum
provider: {
provider: string
icon_large: TypeWithI18N
icon_small: TypeWithI18N
}
}
export type DefaultModel = {
provider: string
model: string
}
export type CustomConfigrationModelFixedFields = {
__model_name: string
__model_type: ModelTypeEnum
}
export type ModelParameterRule = {
default?: number | string | boolean | string[]
help?: TypeWithI18N
label: TypeWithI18N
min?: number
max?: number
name: string
precision?: number
required: false
type: string
use_template?: string
options?: string[]
tagPlaceholder?: TypeWithI18N
}

View file

@ -0,0 +1,271 @@
import {
useCallback,
useEffect,
useMemo,
useState,
} from 'react'
import useSWR, { useSWRConfig } from 'swr'
import { useContext } from 'use-context-selector'
import type {
CustomConfigrationModelFixedFields,
DefaultModel,
DefaultModelResponse,
Model,
} from './declarations'
import {
ConfigurateMethodEnum,
ModelTypeEnum,
} from './declarations'
import { languageMaps } from './utils'
import I18n from '@/context/i18n'
import {
fetchDefaultModal,
fetchModelList,
fetchModelProviderCredentials,
fetchModelProviders,
getPayUrl,
submitFreeQuota,
} from '@/service/common'
import { useProviderContext } from '@/context/provider-context'
type UseDefaultModelAndModelList = (
defaultModel: DefaultModelResponse | undefined,
modelList: Model[],
) => [DefaultModel | undefined, (model: DefaultModel) => void]
export const useSystemDefaultModelAndModelList: UseDefaultModelAndModelList = (
defaultModel,
modelList,
) => {
const currentDefaultModel = useMemo(() => {
const currentProvider = modelList.find(provider => provider.provider === defaultModel?.provider.provider)
const currentModel = currentProvider?.models.find(model => model.model === defaultModel?.model)
const currentDefaultModel = currentProvider && currentModel && {
model: currentModel.model,
provider: currentProvider.provider,
}
return currentDefaultModel
}, [defaultModel, modelList])
const [defaultModelState, setDefaultModelState] = useState<DefaultModel | undefined>(currentDefaultModel)
const handleDefaultModelChange = useCallback((model: DefaultModel) => {
setDefaultModelState(model)
}, [])
useEffect(() => {
setDefaultModelState(currentDefaultModel)
}, [currentDefaultModel])
return [defaultModelState, handleDefaultModelChange]
}
export const useLanguage = () => {
const { locale } = useContext(I18n)
return languageMaps[locale]
}
export const useProviderCrenditialsFormSchemasValue = (
provider: string,
configurateMethod: ConfigurateMethodEnum,
configured?: boolean,
currentCustomConfigrationModelFixedFields?: CustomConfigrationModelFixedFields,
) => {
const { data: predefinedFormSchemasValue } = useSWR(
(configurateMethod === ConfigurateMethodEnum.predefinedModel && configured)
? `/workspaces/current/model-providers/${provider}/credentials`
: null,
fetchModelProviderCredentials,
)
const { data: customFormSchemasValue } = useSWR(
(configurateMethod === ConfigurateMethodEnum.customizableModel && currentCustomConfigrationModelFixedFields)
? `/workspaces/current/model-providers/${provider}/models/credentials?model=${currentCustomConfigrationModelFixedFields?.__model_name}&model_type=${currentCustomConfigrationModelFixedFields?.__model_type}`
: null,
fetchModelProviderCredentials,
)
const value = useMemo(() => {
return configurateMethod === ConfigurateMethodEnum.predefinedModel
? predefinedFormSchemasValue?.credentials
: customFormSchemasValue?.credentials
? {
...customFormSchemasValue?.credentials,
...currentCustomConfigrationModelFixedFields,
}
: undefined
}, [
configurateMethod,
currentCustomConfigrationModelFixedFields,
customFormSchemasValue?.credentials,
predefinedFormSchemasValue?.credentials,
])
return value
}
export type ModelTypeIndex = 1 | 2 | 3 | 4
export const MODEL_TYPE_MAPS = {
1: ModelTypeEnum.textGeneration,
2: ModelTypeEnum.textEmbedding,
3: ModelTypeEnum.rerank,
4: ModelTypeEnum.speech2text,
}
export const useModelList = (type: ModelTypeIndex) => {
const { data, mutate, isLoading } = useSWR(`/workspaces/current/models/model-types/${MODEL_TYPE_MAPS[type]}`, fetchModelList)
return {
data: data?.data || [],
mutate,
isLoading,
}
}
export const useDefaultModel = (type: ModelTypeIndex) => {
const { data, mutate, isLoading } = useSWR(`/workspaces/current/default-model?model_type=${MODEL_TYPE_MAPS[type]}`, fetchDefaultModal)
return {
data: data?.data,
mutate,
isLoading,
}
}
export const useCurrentProviderAndModel = (modelList: Model[], defaultModel?: DefaultModel) => {
const currentProvider = modelList.find(provider => provider.provider === defaultModel?.provider)
const currentModel = currentProvider?.models.find(model => model.model === defaultModel?.model)
return {
currentProvider,
currentModel,
}
}
export const useTextGenerationCurrentProviderAndModelAndModelList = (defaultModel?: DefaultModel) => {
const { textGenerationModelList } = useProviderContext()
const {
currentProvider,
currentModel,
} = useCurrentProviderAndModel(textGenerationModelList, defaultModel)
return {
currentProvider,
currentModel,
textGenerationModelList,
}
}
export const useAgentThoughtCurrentProviderAndModelAndModelList = (defaultModel?: DefaultModel) => {
const { agentThoughtModelList } = useProviderContext()
const {
currentProvider,
currentModel,
} = useCurrentProviderAndModel(agentThoughtModelList, defaultModel)
return {
currentProvider,
currentModel,
agentThoughtModelList,
}
}
export const useModelListAndDefaultModel = (type: ModelTypeIndex) => {
const { data: modelList } = useModelList(type)
const { data: defaultModel } = useDefaultModel(type)
return {
modelList,
defaultModel,
}
}
export const useModelListAndDefaultModelAndCurrentProviderAndModel = (type: ModelTypeIndex) => {
const { modelList, defaultModel } = useModelListAndDefaultModel(type)
const { currentProvider, currentModel } = useCurrentProviderAndModel(
modelList,
{ provider: defaultModel?.provider.provider || '', model: defaultModel?.model || '' },
)
return {
modelList,
defaultModel,
currentProvider,
currentModel,
}
}
export const useUpdateModelList = () => {
const { mutate } = useSWRConfig()
const updateModelList = useCallback((type: ModelTypeIndex | ModelTypeEnum) => {
const modelType = typeof type === 'number' ? MODEL_TYPE_MAPS[type] : type
mutate(`/workspaces/current/models/model-types/${modelType}`)
}, [mutate])
return updateModelList
}
export const useAnthropicBuyQuota = () => {
const [loading, setLoading] = useState(false)
const handleGetPayUrl = async () => {
if (loading)
return
setLoading(true)
try {
const res = await getPayUrl('/workspaces/current/model-providers/anthropic/checkout-url')
window.location.href = res.url
}
finally {
setLoading(false)
}
}
return handleGetPayUrl
}
export const useFreeQuota = (onSuccess: () => void) => {
const [loading, setLoading] = useState(false)
const handleClick = async (type: string) => {
if (loading)
return
try {
setLoading(true)
const res = await submitFreeQuota(`/workspaces/current/model-providers/${type}/free-quota-submit`)
if (res.type === 'redirect' && res.redirect_url)
window.location.href = res.redirect_url
else if (res.type === 'submit' && res.result === 'success')
onSuccess()
}
finally {
setLoading(false)
}
}
return handleClick
}
export const useModelProviders = () => {
const { data: providersData, mutate, isLoading } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
return {
data: providersData?.data || [],
mutate,
isLoading,
}
}
export const useUpdateModelProvidersAndModelList = () => {
const { mutate } = useSWRConfig()
const updateModelList = useUpdateModelList()
const updateModelProvidersAndModelList = useCallback(() => {
mutate('/workspaces/current/model-providers')
updateModelList(1)
}, [mutate, updateModelList])
return updateModelProvidersAndModelList
}

View file

@ -0,0 +1,130 @@
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import SystemModelSelector from './system-model-selector'
import ProviderAddedCard, { UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST } from './provider-added-card'
import ProviderCard from './provider-card'
import type {
ConfigurateMethodEnum,
CustomConfigrationModelFixedFields,
ModelProvider,
} from './declarations'
import { CustomConfigurationStatusEnum } from './declarations'
import {
useDefaultModel,
useUpdateModelProvidersAndModelList,
} from './hooks'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import { useProviderContext } from '@/context/provider-context'
import { useModalContext } from '@/context/modal-context'
import { useEventEmitterContextContext } from '@/context/event-emitter'
const ModelProviderPage = () => {
const { t } = useTranslation()
const { eventEmitter } = useEventEmitterContextContext()
const updateModelProvidersAndModelList = useUpdateModelProvidersAndModelList()
const { data: textGenerationDefaultModel } = useDefaultModel(1)
const { data: embeddingsDefaultModel } = useDefaultModel(2)
const { data: rerankDefaultModel } = useDefaultModel(3)
const { data: speech2textDefaultModel } = useDefaultModel(4)
const { modelProviders: providers } = useProviderContext()
const { setShowModelModal } = useModalContext()
const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel
const [configedProviders, notConfigedProviders] = useMemo(() => {
const configedProviders: ModelProvider[] = []
const notConfigedProviders: ModelProvider[] = []
providers.forEach((provider) => {
if (provider.custom_configuration.status === CustomConfigurationStatusEnum.active || provider.system_configuration.enabled === true)
configedProviders.push(provider)
else
notConfigedProviders.push(provider)
})
return [configedProviders, notConfigedProviders]
}, [providers])
const handleOpenModal = (
provider: ModelProvider,
configurateMethod: ConfigurateMethodEnum,
customConfigrationModelFixedFields?: CustomConfigrationModelFixedFields,
) => {
setShowModelModal({
payload: {
currentProvider: provider,
currentConfigurateMethod: configurateMethod,
currentCustomConfigrationModelFixedFields: customConfigrationModelFixedFields,
},
onSaveCallback: () => {
updateModelProvidersAndModelList()
if (customConfigrationModelFixedFields && provider.custom_configuration.status === CustomConfigurationStatusEnum.active) {
eventEmitter?.emit({
type: UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST,
payload: provider.provider,
} as any)
}
},
})
}
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}
/>
</div>
{
!!configedProviders?.length && (
<div className='pb-3'>
{
configedProviders?.map(provider => (
<ProviderAddedCard
key={provider.provider}
provider={provider}
onOpenModal={(configurateMethod: ConfigurateMethodEnum, currentCustomConfigrationModelFixedFields?: CustomConfigrationModelFixedFields) => handleOpenModal(provider, configurateMethod, currentCustomConfigrationModelFixedFields)}
/>
))
}
</div>
)
}
{
!!notConfigedProviders?.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'>
{
notConfigedProviders?.map(provider => (
<ProviderCard
key={provider.provider}
provider={provider}
onOpenModal={(configurateMethod: ConfigurateMethodEnum) => handleOpenModal(provider, configurateMethod)}
/>
))
}
</div>
</>
)
}
</div>
)
}
export default ModelProviderPage

View file

@ -0,0 +1,22 @@
import type { FC, ReactNode } from 'react'
type ModelBadgeProps = {
className?: string
children?: ReactNode
}
const ModelBadge: FC<ModelBadgeProps> = ({
className,
children,
}) => {
return (
<div className={`
flex items-center px-1 h-[18px] rounded-[5px] border border-black/[0.08] bg-white/[0.48]
text-[10px] font-medium text-gray-500
${className}
`}>
{children}
</div>
)
}
export default ModelBadge

Some files were not shown because too many files have changed in this diff Show more