From 2898215a009eae05da5b410bf77e32406d17bcbb Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Fri, 7 Feb 2025 16:47:29 +0800 Subject: [PATCH] feat: add baidu cloud provider --- ...ngchain-openai-npm-0.3.16-e525b59526.patch | 19 +++++++ package.json | 4 +- .../assets/images/providers/baidu-cloud.svg | 1 + src/renderer/src/config/models.ts | 50 +++++++++++++++++++ src/renderer/src/config/providers.ts | 14 ++++++ src/renderer/src/i18n/locales/en-us.json | 4 ++ src/renderer/src/i18n/locales/ja-jp.json | 4 ++ src/renderer/src/i18n/locales/ru-ru.json | 4 ++ src/renderer/src/i18n/locales/zh-cn.json | 4 ++ src/renderer/src/i18n/locales/zh-tw.json | 4 ++ .../ProviderSettings/ApiCheckPopup.tsx | 2 +- .../ProviderSettings/ProviderSetting.tsx | 10 +++- .../SelectProviderModelPopup.tsx | 4 +- src/renderer/src/providers/OpenAIProvider.ts | 2 +- src/renderer/src/services/ApiService.ts | 22 ++++++-- src/renderer/src/store/llm.ts | 10 ++++ src/renderer/src/store/migrate.ts | 10 ++++ 17 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 .yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch create mode 100644 src/renderer/src/assets/images/providers/baidu-cloud.svg diff --git a/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch b/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch new file mode 100644 index 00000000..3f37fb3e --- /dev/null +++ b/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch @@ -0,0 +1,19 @@ +diff --git a/dist/embeddings.js b/dist/embeddings.js +index 1f8154be3e9c22442a915eb4b85fa6d2a21b0d0c..dc13ef4a30e6c282824a5357bcee9bd0ae222aab 100644 +--- a/dist/embeddings.js ++++ b/dist/embeddings.js +@@ -214,10 +214,12 @@ export class OpenAIEmbeddings extends Embeddings { + * @returns Promise that resolves to an embedding for the document. + */ + async embedQuery(text) { ++ const isBaiduCloud = this.clientConfig.baseURL.includes('baidubce.com') ++ const input = this.stripNewLines ? text.replace(/\n/g, ' ') : text + const params = { + model: this.model, +- input: this.stripNewLines ? text.replace(/\n/g, " ") : text, +- }; ++ input: isBaiduCloud ? [input] : input ++ } + if (this.dimensions) { + params.dimensions = this.dimensions; + } diff --git a/package.json b/package.json index 1d15015c..c88c54f9 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,9 @@ }, "resolutions": { "pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch", - "@llm-tools/embedjs-utils@npm:0.1.25": "patch:@llm-tools/embedjs-utils@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-utils-npm-0.1.25-fd8fe8a193.patch" + "@llm-tools/embedjs-utils@npm:0.1.25": "patch:@llm-tools/embedjs-utils@npm%3A0.1.25#~/.yarn/patches/@llm-tools-embedjs-utils-npm-0.1.25-fd8fe8a193.patch", + "@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch", + "@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch" }, "packageManager": "yarn@4.5.0" } diff --git a/src/renderer/src/assets/images/providers/baidu-cloud.svg b/src/renderer/src/assets/images/providers/baidu-cloud.svg new file mode 100644 index 00000000..40565efc --- /dev/null +++ b/src/renderer/src/assets/images/providers/baidu-cloud.svg @@ -0,0 +1 @@ +BaiduCloud \ No newline at end of file diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 23fb99e3..639550a9 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -991,6 +991,56 @@ export const SYSTEM_MODELS: Record = { name: 'Gemma 7B', group: 'Gemma' } + ], + 'baidu-cloud': [ + { + id: 'deepseek-r1', + provider: 'baidu-cloud', + name: 'DeepSeek R1', + group: 'DeepSeek' + }, + { + id: 'deepseek-v3', + provider: 'baidu-cloud', + name: 'DeepSeek V3', + group: 'DeepSeek' + }, + { + id: 'ernie-4.0-8k-latest', + provider: 'baidu-cloud', + name: 'ERNIE-4.0', + group: 'ERNIE' + }, + { + id: 'ernie-4.0-turbo-8k-latest', + provider: 'baidu-cloud', + name: 'ERNIE 4.0 Trubo', + group: 'ERNIE' + }, + { + id: 'ernie-speed-8k', + provider: 'baidu-cloud', + name: 'ERNIE Speed', + group: 'ERNIE' + }, + { + id: 'ernie-lite-8k', + provider: 'baidu-cloud', + name: 'ERNIE Lite', + group: 'ERNIE' + }, + { + id: 'bge-large-zh', + provider: 'baidu-cloud', + name: 'BGE Large ZH', + group: 'Embedding' + }, + { + id: 'bge-large-en', + provider: 'baidu-cloud', + name: 'BGE Large EN', + group: 'Embedding' + } ] } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index 03455bf6..574bc043 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -4,6 +4,7 @@ import AzureProviderLogo from '@renderer/assets/images/models/microsoft.png' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.jpg' import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png' import BaichuanProviderLogo from '@renderer/assets/images/providers/baichuan.png' +import BaiduCloudProviderLogo from '@renderer/assets/images/providers/baidu-cloud.svg' import BailianProviderLogo from '@renderer/assets/images/providers/bailian.png' import BytedanceProviderLogo from '@renderer/assets/images/providers/bytedance.png' import DeepSeekProviderLogo from '@renderer/assets/images/providers/deepseek.png' @@ -91,6 +92,8 @@ export function getProviderLogo(providerId: string) { return MistralProviderLogo case 'jina': return JinaProviderLogo + case 'baidu-cloud': + return BaiduCloudProviderLogo default: return undefined } @@ -418,5 +421,16 @@ export const PROVIDER_CONFIG = { docs: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/', models: 'https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/models' } + }, + 'baidu-cloud': { + api: { + url: 'https://qianfan.baidubce.com/v2/' + }, + websites: { + official: 'https://cloud.baidu.com/', + apiKey: 'https://cloud.baidu.com/console/qianfan/apikey', + docs: 'https://cloud.baidu.com/doc/index.html', + models: 'https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Fm2vrveyu' + } } } diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index cad2d5fb..9ec4b756 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -253,6 +253,9 @@ "error.chunk_overlap_too_large": "Chunk overlap cannot be greater than chunk size", "error.invalid.proxy.url": "Invalid proxy URL", "error.invalid.webdav": "Invalid WebDAV settings", + "error.invalid.enter.api.host": "Please enter a valid API host", + "error.invalid.enter.api.key": "Please enter a valid API key", + "error.invalid.enter.model": "Please select a model", "message.code_style": "Code style", "message.delete.content": "Are you sure you want to delete this message?", "message.delete.title": "Delete Message", @@ -316,6 +319,7 @@ "aihubmix": "AiHubMix", "anthropic": "Anthropic", "azure-openai": "Azure OpenAI", + "baidu-cloud": "Baidu Cloud", "baichuan": "Baichuan", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index ab51d583..cfdf243a 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -247,6 +247,9 @@ "error.chunk_overlap_too_large": "チャンクの重なりは、チャンクサイズを超えることはできません", "error.invalid.proxy.url": "無効なプロキシURL", "error.invalid.webdav": "無効なWebDAV設定", + "error.invalid.enter.api.host": "APIホストを入力してください", + "error.invalid.enter.api.key": "APIキーを入力してください", + "error.invalid.enter.model": "モデルを選択してください", "message.code_style": "コードスタイル", "message.delete.content": "このメッセージを削除してもよろしいですか?", "message.delete.title": "メッセージを削除", @@ -310,6 +313,7 @@ "aihubmix": "AiHubMix", "anthropic": "Anthropic", "azure-openai": "Azure OpenAI", + "baidu-cloud": "Baidu Cloud", "baichuan": "百川", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 4b8e4e88..3e45653d 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -248,6 +248,9 @@ "error.chunk_overlap_too_large": "Перекрытие фрагментов не может быть больше размера фрагмента.", "error.invalid.proxy.url": "Неверный URL прокси", "error.invalid.webdav": "Неверные настройки WebDAV", + "error.invalid.enter.api.host": "Пожалуйста, введите правильный API хост", + "error.invalid.enter.api.key": "Пожалуйста, введите правильный API ключ", + "error.invalid.enter.model": "Пожалуйста, выберите модель", "message.code_style": "Стиль кода", "message.delete.content": "Вы уверены, что хотите удалить это сообщение?", "message.delete.title": "Удалить сообщение", @@ -311,6 +314,7 @@ "aihubmix": "AiHubMix", "anthropic": "Anthropic", "azure-openai": "Azure OpenAI", + "baidu-cloud": "Baidu Cloud", "baichuan": "Baichuan", "dashscope": "Alibaba Cloud", "deepseek": "DeepSeek", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 36a53a3e..1ca0a49b 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -254,6 +254,9 @@ "error.chunk_overlap_too_large": "分段重叠不能大于分段大小", "error.invalid.proxy.url": "无效的代理地址", "error.invalid.webdav": "无效的 WebDAV 设置", + "error.invalid.api.key": "无效的 API 密钥", + "error.invalid.api.host": "无效的 API 地址", + "error.invalid.enter.model": "请选择一个模型", "message.code_style": "代码风格", "message.delete.content": "确定要删除此消息吗?", "message.delete.title": "删除消息", @@ -317,6 +320,7 @@ "aihubmix": "AiHubMix", "anthropic": "Anthropic", "azure-openai": "Azure OpenAI", + "baidu-cloud": "百度云千帆", "baichuan": "百川", "dashscope": "阿里云百炼", "deepseek": "深度求索", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 8e1e3f31..87d61eec 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -253,6 +253,9 @@ "error.chunk_overlap_too_large": "分段重疊不能大於分段大小", "error.invalid.proxy.url": "無效的代理 URL", "error.invalid.webdav": "無效的 WebDAV 設定", + "error.invalid.enter.api.host": "請輸入有效的 API 主機地址", + "error.invalid.enter.api.key": "請輸入有效的 API 密鑰", + "error.invalid.enter.model": "請選擇一個模型", "message.code_style": "程式碼風格", "message.delete.content": "確定要刪除此訊息嗎?", "message.delete.title": "刪除訊息", @@ -316,6 +319,7 @@ "aihubmix": "AiHubMix", "anthropic": "Anthropic", "azure-openai": "Azure OpenAI", + "baidu-cloud": "百度云千帆", "baichuan": "百川", "dashscope": "阿里雲百鍊", "deepseek": "深度求索", diff --git a/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx index 910acce2..b3c80dbc 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ApiCheckPopup.tsx @@ -42,7 +42,7 @@ const PopupContainer: React.FC = ({ title, provider, model, apiKeys, reso for (let i = 0; i < newStatuses.length; i++) { setKeyStatuses((prev) => prev.map((status, idx) => (idx === i ? { ...status, checking: true } : status))) - const valid = await checkApi({ ...provider, apiKey: newStatuses[i].key }, model) + const { valid } = await checkApi({ ...provider, apiKey: newStatuses[i].key }, model) setKeyStatuses((prev) => prev.map((status, idx) => (idx === i ? { ...status, checking: false, isValid: valid } : status)) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 52a27180..41f2cbe2 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -128,13 +128,19 @@ const ProviderSetting: FC = ({ provider: _provider }) => { } else { setApiChecking(true) - const valid = await checkApi({ ...provider, apiKey, apiHost }, model) + const { valid, error } = await checkApi({ ...provider, apiKey, apiHost }, model) + + const errorMessage = error && error?.message ? ' ' + error?.message : '' + window.message[valid ? 'success' : 'error']({ key: 'api-check', style: { marginTop: '3vh' }, duration: valid ? 2 : 8, - content: valid ? i18n.t('message.api.connection.success') : i18n.t('message.api.connection.failed') + content: valid + ? i18n.t('message.api.connection.success') + : i18n.t('message.api.connection.failed') + errorMessage }) + setApiValid(valid) setApiChecking(false) setTimeout(() => setApiValid(false), 3000) diff --git a/src/renderer/src/pages/settings/ProviderSettings/SelectProviderModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/SelectProviderModelPopup.tsx index d46afbe4..7b9297c6 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/SelectProviderModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/SelectProviderModelPopup.tsx @@ -3,7 +3,7 @@ import { isEmbeddingModel } from '@renderer/config/models' import i18n from '@renderer/i18n' import { Provider } from '@renderer/types' import { Modal, Select } from 'antd' -import { last, orderBy } from 'lodash' +import { first, orderBy } from 'lodash' import { useState } from 'react' interface ShowParams { @@ -18,7 +18,7 @@ interface Props extends ShowParams { const PopupContainer: React.FC = ({ provider, resolve, reject }) => { const models = orderBy(provider.models, 'group').filter((i) => !isEmbeddingModel(i)) const [open, setOpen] = useState(true) - const [model, setModel] = useState(last(models)) + const [model, setModel] = useState(first(models)) const onOk = () => { if (!model) { diff --git a/src/renderer/src/providers/OpenAIProvider.ts b/src/renderer/src/providers/OpenAIProvider.ts index 525350a8..6f82579a 100644 --- a/src/renderer/src/providers/OpenAIProvider.ts +++ b/src/renderer/src/providers/OpenAIProvider.ts @@ -461,7 +461,7 @@ export default class OpenAIProvider extends BaseProvider { public async getEmbeddingDimensions(model: Model): Promise { const data = await this.sdk.embeddings.create({ model: model.id, - input: 'hi' + input: model?.provider === 'baidu-cloud' ? ['hi'] : 'hi' }) return data.data[0].embedding.length } diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index e08fa4f7..c8648c99 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -206,25 +206,37 @@ export async function checkApi(provider: Provider, model: Model) { if (provider.id !== 'ollama') { if (!provider.apiKey) { window.message.error({ content: i18n.t('message.error.enter.api.key'), key, style }) - return false + return { + valid: false, + error: new Error('message.error.enter.api.key') + } } } if (!provider.apiHost) { window.message.error({ content: i18n.t('message.error.enter.api.host'), key, style }) - return false + return { + valid: false, + error: new Error('message.error.enter.api.host') + } } if (isEmpty(provider.models)) { window.message.error({ content: i18n.t('message.error.enter.model'), key, style }) - return false + return { + valid: false, + error: new Error('message.error.enter.model') + } } const AI = new AiProvider(provider) - const { valid } = await AI.check(model) + const { valid, error } = await AI.check(model) - return valid + return { + valid, + error + } } function hasApiKey(provider: Provider) { diff --git a/src/renderer/src/store/llm.ts b/src/renderer/src/store/llm.ts index 943c8002..f98b5b42 100644 --- a/src/renderer/src/store/llm.ts +++ b/src/renderer/src/store/llm.ts @@ -63,6 +63,16 @@ const initialState: LlmState = { isSystem: true, enabled: false }, + { + id: 'baidu-cloud', + name: 'Baidu Cloud', + type: 'openai', + apiKey: '', + apiHost: 'https://qianfan.baidubce.com/v2/', + models: SYSTEM_MODELS['baidu-cloud'], + isSystem: true, + enabled: false + }, { id: 'ollama', name: 'Ollama', diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 3d2b38ff..33fb5ace 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -910,6 +910,16 @@ const migrateConfig = { }, '64': (state: RootState) => { state.llm.providers = state.llm.providers.filter((provider) => provider.id !== 'qwenlm') + state.llm.providers.push({ + id: 'baidu-cloud', + name: 'Baidu Cloud', + type: 'openai', + apiKey: '', + apiHost: 'https://qianfan.baidubce.com/v2/', + models: SYSTEM_MODELS['baidu-cloud'], + isSystem: true, + enabled: false + }) return state } }