setInviteModalVisible(true)}>
{t('common.members.invite')}
diff --git a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
index 86c871c1d4..0ad1ef8afd 100644
--- a/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
+++ b/web/app/components/header/account-setting/members-page/invited-modal/index.tsx
@@ -24,7 +24,7 @@ const InvitedModal = ({
diff --git a/web/app/components/header/account-setting/model-page/configs/anthropic.tsx b/web/app/components/header/account-setting/model-page/configs/anthropic.tsx
new file mode 100644
index 0000000000..6abe1a45c0
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/anthropic.tsx
@@ -0,0 +1,80 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.anthropic,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ subTitleIcon:
,
+ desc: {
+ 'en': 'Anthropic’s 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:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/configs/azure_openai.tsx b/web/app/components/header/account-setting/model-page/configs/azure_openai.tsx
new file mode 100644
index 0000000000..7a3c63ec31
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/azure_openai.tsx
@@ -0,0 +1,175 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.azure_openai,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ },
+ modal: {
+ key: ProviderEnum.azure_openai,
+ title: {
+ 'en': 'Azure OpenAI Service Model',
+ 'zh-Hans': 'Azure OpenAI Service Model',
+ },
+ icon:
,
+ 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',
+ 'zh-Hans': '在此输入您的部署名称',
+ },
+ },
+ {
+ 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: '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
diff --git a/web/app/components/header/account-setting/model-page/configs/chatglm.tsx b/web/app/components/header/account-setting/model-page/configs/chatglm.tsx
new file mode 100644
index 0000000000..c6828153ea
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/chatglm.tsx
@@ -0,0 +1,69 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.chatglm,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ 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:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/configs/huggingface_hub.tsx b/web/app/components/header/account-setting/model-page/configs/huggingface_hub.tsx
new file mode 100644
index 0000000000..420ee2b567
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/huggingface_hub.tsx
@@ -0,0 +1,127 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.huggingface_hub,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ 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:
,
+ 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',
+ },
+ validateKeys: (v?: FormValue) => {
+ if (v?.huggingfacehub_api_type === 'hosted_inference_api') {
+ return [
+ 'huggingfacehub_api_token',
+ 'model_name',
+ ]
+ }
+ if (v?.huggingfacehub_api_type === 'inference_endpoints') {
+ return [
+ 'huggingfacehub_api_token',
+ 'model_name',
+ 'huggingfacehub_endpoint_url',
+ ]
+ }
+ return []
+ },
+ fields: [
+ {
+ 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',
+ },
+ },
+ {
+ 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',
+ },
+ },
+ ],
+ },
+}
+
+export default config
diff --git a/web/app/components/header/account-setting/model-page/configs/index.ts b/web/app/components/header/account-setting/model-page/configs/index.ts
new file mode 100644
index 0000000000..2bb80349ea
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/index.ts
@@ -0,0 +1,23 @@
+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'
+
+export default {
+ openai,
+ anthropic,
+ azure_openai,
+ replicate,
+ huggingface_hub,
+ wenxin,
+ tongyi,
+ spark,
+ minimax,
+ chatglm,
+}
diff --git a/web/app/components/header/account-setting/model-page/configs/minimax.tsx b/web/app/components/header/account-setting/model-page/configs/minimax.tsx
new file mode 100644
index 0000000000..58e481fe01
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/minimax.tsx
@@ -0,0 +1,69 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.minimax,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ },
+ modal: {
+ key: ProviderEnum.minimax,
+ title: {
+ 'en': 'MiniMax',
+ 'zh-Hans': 'MiniMax',
+ },
+ icon:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/configs/openai.tsx b/web/app/components/header/account-setting/model-page/configs/openai.tsx
new file mode 100644
index 0000000000..104f1e2a59
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/openai.tsx
@@ -0,0 +1,93 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.openai,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ subTitleIcon:
,
+ 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:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/configs/replicate.tsx b/web/app/components/header/account-setting/model-page/configs/replicate.tsx
new file mode 100644
index 0000000000..b749ee886a
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/replicate.tsx
@@ -0,0 +1,115 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.replicate,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ hit: {
+ 'en': '🐑 Llama 2 Supported',
+ 'zh-Hans': '🐑 Llama 2 已支持',
+ },
+ },
+ modal: {
+ key: ProviderEnum.replicate,
+ title: {
+ 'en': 'Replicate Model',
+ 'zh-Hans': 'Replicate Model',
+ },
+ icon:
,
+ 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: '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
diff --git a/web/app/components/header/account-setting/model-page/configs/spark.tsx b/web/app/components/header/account-setting/model-page/configs/spark.tsx
new file mode 100644
index 0000000000..53e865ba98
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/spark.tsx
@@ -0,0 +1,83 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.spark,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ },
+ modal: {
+ key: ProviderEnum.spark,
+ title: {
+ 'en': 'iFLYTEK SPARK',
+ 'zh-Hans': '讯飞星火',
+ },
+ icon:
,
+ link: {
+ href: 'https://www.xfyun.cn/solutions/xinghuoAPI',
+ label: {
+ 'en': 'Get your API key from AliCloud',
+ 'zh-Hans': '从阿里云获取 API Key',
+ },
+ },
+ validateKeys: [
+ 'app_id',
+ 'api_key',
+ 'api_secret',
+ ],
+ fields: [
+ {
+ type: 'text',
+ key: 'app_id',
+ required: true,
+ label: {
+ 'en': 'API ID',
+ 'zh-Hans': 'API ID',
+ },
+ placeholder: {
+ 'en': 'Enter your API ID here',
+ 'zh-Hans': '在此输入您的 API ID',
+ },
+ },
+ {
+ 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: 'api_secret',
+ required: true,
+ label: {
+ 'en': 'API Secret',
+ 'zh-Hans': 'API Secret',
+ },
+ placeholder: {
+ 'en': 'Enter your API Secret here',
+ 'zh-Hans': '在此输入您的 API Secret',
+ },
+ },
+ ],
+ },
+}
+
+export default config
diff --git a/web/app/components/header/account-setting/model-page/configs/tongyi.tsx b/web/app/components/header/account-setting/model-page/configs/tongyi.tsx
new file mode 100644
index 0000000000..3faa812d94
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/tongyi.tsx
@@ -0,0 +1,53 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.tongyi,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ },
+ modal: {
+ key: ProviderEnum.tongyi,
+ title: {
+ 'en': 'Tongyi',
+ 'zh-Hans': '通义千问',
+ },
+ icon:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/configs/wenxin.tsx b/web/app/components/header/account-setting/model-page/configs/wenxin.tsx
new file mode 100644
index 0000000000..56dae8769e
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/configs/wenxin.tsx
@@ -0,0 +1,66 @@
+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:
,
+ },
+ item: {
+ key: ProviderEnum.wenxin,
+ titleIcon: {
+ 'en':
,
+ 'zh-Hans':
,
+ },
+ },
+ modal: {
+ key: ProviderEnum.wenxin,
+ title: {
+ 'en': 'WENXINYIYAN',
+ 'zh-Hans': '文心一言',
+ },
+ icon:
,
+ 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
diff --git a/web/app/components/header/account-setting/model-page/declarations.ts b/web/app/components/header/account-setting/model-page/declarations.ts
new file mode 100644
index 0000000000..6e9ad06d11
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/declarations.ts
@@ -0,0 +1,146 @@
+import type { ReactElement } from 'react'
+
+export type FormValue = Record
+
+export type TypeWithI18N = {
+ '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',
+}
+
+export type ProviderConfigItem = {
+ key: ProviderEnum
+ titleIcon: TypeWithI18N
+ 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',
+}
+
+export enum ModelFeature {
+ agentThought = 'agent_thought',
+}
+
+// backend defined model struct: /console/api/workspaces/current/models/model-type/:model_type
+export type BackendModel = {
+ model_name: string
+ model_type: ModelType
+ model_provider: {
+ provider_name: ProviderEnum
+ provider_type: PreferredProviderTypeEnum
+ }
+ features: ModelFeature[]
+}
+
+export type ProviderConfigModal = {
+ key: ProviderEnum
+ title: TypeWithI18N
+ icon: ReactElement
+ defaultValue?: FormValue
+ validateKeys?: string[] | ((v?: FormValue) => string[])
+ 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
+} & ProviderCommon
+
+export type Model = {
+ model_name: string
+ model_type: string
+ config: Record
+}
+
+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
+}
diff --git a/web/app/components/header/account-setting/model-page/index.tsx b/web/app/components/header/account-setting/model-page/index.tsx
new file mode 100644
index 0000000000..62f33e86aa
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/index.tsx
@@ -0,0 +1,298 @@
+import { useState } from 'react'
+import useSWR from 'swr'
+import { useTranslation } from 'react-i18next'
+import type {
+ BackendModel,
+ FormValue,
+ ProviderConfigModal,
+ ProviderEnum,
+} from './declarations'
+import ModelSelector from './model-selector'
+import ModelCard from './model-card'
+import ModelItem from './model-item'
+import ModelModal from './model-modal'
+import config from './configs'
+import { ConfigurableProviders } from './utils'
+import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
+// import { HelpCircle } from '@/app/components/base/icons/src/vender/line/general'
+import {
+ changeModelProviderPriority,
+ deleteModelProvider,
+ deleteModelProviderModel,
+ fetchDefaultModal,
+ fetchModelProviders,
+ setModelProvider,
+ updateDefaultModel,
+} 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'
+
+const MODEL_CARD_LIST = [
+ config.openai,
+ config.anthropic,
+]
+
+const MODEL_LIST = [
+ config.azure_openai,
+ config.replicate,
+ config.huggingface_hub,
+ config.minimax,
+ config.spark,
+ config.tongyi,
+ config.wenxin,
+ config.chatglm,
+]
+
+const titleClassName = `
+flex items-center h-9 text-sm font-medium text-gray-900
+`
+const tipClassName = `
+ml-0.5 w-[14px] h-[14px] text-gray-400
+`
+
+type DeleteModel = {
+ model_name: string
+ model_type: string
+}
+
+const ModelPage = () => {
+ const { t } = useTranslation()
+ const { updateModelList } = useProviderContext()
+ const { data: providers, mutate: mutateProviders } = useSWR('/workspaces/current/model-providers', fetchModelProviders)
+ const { data: textGenerationDefaultModel, mutate: mutateTextGenerationDefaultModel } = useSWR('/workspaces/current/default-model?model_type=text-generation', fetchDefaultModal)
+ const { data: embeddingsDefaultModel, mutate: mutateEmbeddingsDefaultModel } = useSWR('/workspaces/current/default-model?model_type=embeddings', fetchDefaultModal)
+ const { data: speech2textDefaultModel, mutate: mutateSpeech2textDefaultModel } = useSWR('/workspaces/current/default-model?model_type=speech2text', fetchDefaultModal)
+ const [showMoreModel, setShowMoreModel] = useState(false)
+ const [showModal, setShowModal] = useState(false)
+ const { notify } = useToastContext()
+ const { eventEmitter } = useEventEmitterContextContext()
+ const [modelModalConfig, setModelModalConfig] = useState(undefined)
+ const [confirmShow, setConfirmShow] = useState(false)
+ const [deleteModel, setDeleteModel] = useState()
+ const [modalMode, setModalMode] = useState('add')
+
+ 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)
+ mutateProviders()
+ }
+ const handleSave = async (v?: FormValue) => {
+ if (v && modelModalConfig) {
+ 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, 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 mutateDefaultModel = (type: ModelType) => {
+ if (type === ModelType.textGeneration)
+ mutateTextGenerationDefaultModel()
+ if (type === ModelType.embeddings)
+ mutateEmbeddingsDefaultModel()
+ if (type === ModelType.speech2text)
+ mutateSpeech2textDefaultModel()
+ }
+ const handleChangeDefaultModel = async (type: ModelType, v: BackendModel) => {
+ const res = await updateDefaultModel({
+ url: '/workspaces/current/default-model',
+ body: {
+ model_type: type,
+ provider_name: v.model_provider.provider_name,
+ model_name: v.model_name,
+ },
+ })
+ if (res.result === 'success') {
+ notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
+ mutateDefaultModel(type)
+ }
+ }
+
+ return (
+
+
+
+
+ {t('common.modelProvider.systemReasoningModel.key')}
+ {/* */}
+
+
+ handleChangeDefaultModel(ModelType.textGeneration, v)}
+ />
+
+
+
+
+ {t('common.modelProvider.embeddingModel.key')}
+ {/* */}
+
+
+ handleChangeDefaultModel(ModelType.embeddings, v)}
+ />
+
+
+
+
+ {t('common.modelProvider.speechToTextModel.key')}
+ {/* */}
+
+
+ handleChangeDefaultModel(ModelType.speech2text, v)}
+ />
+
+
+
+
+
{t('common.modelProvider.models')}
+
+ {
+ MODEL_CARD_LIST.map((model, index) => (
+ handleOpenModal(model.modal, editValue)}
+ onOperate={v => handleOperate(v, model.item.key)}
+ />
+ ))
+ }
+
+ {
+ MODEL_LIST.slice(0, showMoreModel ? MODEL_LIST.length : 3).map((model, index) => (
+
handleOpenModal(model.modal, editValue)}
+ onOperate={v => handleOperate(v, model.item.key)}
+ onUpdate={mutateProviders}
+ />
+ ))
+ }
+ {
+ !showMoreModel && (
+ setShowMoreModel(true)}>
+
+
{t('common.modelProvider.showMoreModelProvider')}
+
+ )
+ }
+
+ setConfirmShow(false)}
+ title={deleteModel?.model_name || ''}
+ desc={t('common.modelProvider.item.deleteDesc', { modelName: deleteModel?.model_name }) || ''}
+ onConfirm={handleDeleteModel}
+ />
+
+ )
+}
+
+export default ModelPage
diff --git a/web/app/components/header/account-setting/model-page/model-card/Quota.tsx b/web/app/components/header/account-setting/model-page/model-card/Quota.tsx
new file mode 100644
index 0000000000..de21737bd3
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/model-card/Quota.tsx
@@ -0,0 +1,116 @@
+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'
+
+type QuotaProps = {
+ currentProvider: Provider
+}
+const Quota: FC = ({
+ 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 = {
+ 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 (
+
+ {t('common.modelProvider.card.quotaExhausted')}
+
+ )
+ }
+ if (systemPaid?.is_valid) {
+ return (
+
+ {t('common.modelProvider.card.paid')}
+
+ )
+ }
+ return (
+
+ {t('common.modelProvider.card.onTrial')}
+
+ )
+ }
+
+ const renderQuota = () => {
+ if (systemPaid?.is_valid)
+ return systemPaid.quota_limit - systemPaid.quota_used
+
+ if (systemTrial.is_valid)
+ return 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 (
+
+
+
+
+ {t('common.modelProvider.card.quota')}
+
+ {renderStatus()}
+
+
+
{renderQuota()}
+
+ {renderUnit()}
+
+
{t('common.modelProvider.card.tip')}
+ }
+ >
+
+
+
+
+ {
+ systemPaid && (
+
+ )
+ }
+
+ )
+}
+
+export default Quota
diff --git a/web/app/components/header/account-setting/model-page/model-card/index.tsx b/web/app/components/header/account-setting/model-page/model-card/index.tsx
new file mode 100644
index 0000000000..3d5bdd4289
--- /dev/null
+++ b/web/app/components/header/account-setting/model-page/model-card/index.tsx
@@ -0,0 +1,81 @@
+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