@lobehub/chat 1.44.1 → 1.44.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/ar/modelProvider.json +2 -0
  4. package/locales/bg-BG/modelProvider.json +2 -0
  5. package/locales/de-DE/modelProvider.json +2 -0
  6. package/locales/en-US/modelProvider.json +2 -0
  7. package/locales/es-ES/modelProvider.json +2 -0
  8. package/locales/fa-IR/modelProvider.json +2 -0
  9. package/locales/fr-FR/modelProvider.json +2 -0
  10. package/locales/it-IT/modelProvider.json +2 -0
  11. package/locales/ja-JP/modelProvider.json +2 -0
  12. package/locales/ko-KR/modelProvider.json +2 -0
  13. package/locales/nl-NL/modelProvider.json +2 -0
  14. package/locales/pl-PL/modelProvider.json +2 -0
  15. package/locales/pt-BR/modelProvider.json +2 -0
  16. package/locales/ru-RU/modelProvider.json +2 -0
  17. package/locales/tr-TR/modelProvider.json +2 -0
  18. package/locales/vi-VN/modelProvider.json +2 -0
  19. package/locales/zh-CN/modelProvider.json +3 -1
  20. package/locales/zh-TW/modelProvider.json +2 -0
  21. package/package.json +1 -1
  22. package/src/app/(main)/settings/provider/features/CreateNewProvider/index.tsx +8 -1
  23. package/src/app/(main)/settings/provider/features/ProviderConfig/Checker.tsx +9 -6
  24. package/src/app/(main)/settings/provider/features/ProviderConfig/index.tsx +15 -3
  25. package/src/config/aiModels/siliconcloud.ts +0 -13
  26. package/src/database/repositories/aiInfra/index.ts +19 -7
  27. package/src/database/server/models/__tests__/aiModel.test.ts +4 -6
  28. package/src/database/server/models/aiModel.ts +6 -3
  29. package/src/locales/default/modelProvider.ts +3 -1
  30. package/src/store/aiInfra/slices/aiProvider/action.ts +18 -3
  31. package/src/store/aiInfra/slices/aiProvider/initialState.ts +2 -0
  32. package/src/store/aiInfra/slices/aiProvider/selectors.ts +4 -0
  33. package/src/types/aiProvider.ts +1 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.44.3](https://github.com/lobehub/lobe-chat/compare/v1.44.2...v1.44.3)
6
+
7
+ <sup>Released on **2025-01-08**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix provider enabled issue.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix provider enabled issue, closes [#5337](https://github.com/lobehub/lobe-chat/issues/5337) ([8e0b634](https://github.com/lobehub/lobe-chat/commit/8e0b634))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ### [Version 1.44.2](https://github.com/lobehub/lobe-chat/compare/v1.44.1...v1.44.2)
31
+
32
+ <sup>Released on **2025-01-08**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Add provider id validate.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Add provider id validate, closes [#5336](https://github.com/lobehub/lobe-chat/issues/5336) ([7f8a1b6](https://github.com/lobehub/lobe-chat/commit/7f8a1b6))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ### [Version 1.44.1](https://github.com/lobehub/lobe-chat/compare/v1.44.0...v1.44.1)
6
56
 
7
57
  <sup>Released on **2025-01-08**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix provider enabled issue."
6
+ ]
7
+ },
8
+ "date": "2025-01-08",
9
+ "version": "1.44.3"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Add provider id validate."
15
+ ]
16
+ },
17
+ "date": "2025-01-08",
18
+ "version": "1.44.2"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "fixes": [
@@ -78,6 +78,8 @@
78
78
  "title": "نبذة عن مزود الخدمة"
79
79
  },
80
80
  "id": {
81
+ "desc": "معرف فريد لمزود الخدمة، لا يمكن تعديله بعد الإنشاء",
82
+ "format": "يمكن أن يحتوي فقط على أحرف صغيرة، وشرطات (-) وشرطات سفلية (_)",
81
83
  "placeholder": "يفضل أن يكون بالكامل بحروف صغيرة، مثل openai، لن يمكن تعديله بعد الإنشاء",
82
84
  "required": "يرجى إدخال معرف المزود",
83
85
  "title": "معرف المزود"
@@ -78,6 +78,8 @@
78
78
  "title": "Описание на доставчика"
79
79
  },
80
80
  "id": {
81
+ "desc": "Уникален идентификатор за доставчика на услуги, който не може да бъде променян след създаването му",
82
+ "format": "Може да съдържа само малки букви, тирета (-) и долни черти (_)",
81
83
  "placeholder": "Препоръчително изцяло с малки букви, например openai, след създаването не може да се промени",
82
84
  "required": "Моля, въведете ID на доставчика",
83
85
  "title": "ID на доставчика"
@@ -78,6 +78,8 @@
78
78
  "title": "Beschreibung des Anbieters"
79
79
  },
80
80
  "id": {
81
+ "desc": "Eindeutige Kennung des Anbieters, die nach der Erstellung nicht mehr geändert werden kann",
82
+ "format": "Darf nur aus Kleinbuchstaben, Bindestrichen (-) und Unterstrichen (_) bestehen",
81
83
  "placeholder": "Empfohlen in Kleinbuchstaben, z.B. openai, nach der Erstellung nicht mehr änderbar",
82
84
  "required": "Bitte geben Sie die Anbieter-ID ein",
83
85
  "title": "Anbieter-ID"
@@ -78,6 +78,8 @@
78
78
  "title": "Provider Description"
79
79
  },
80
80
  "id": {
81
+ "desc": "Unique identifier for the service provider, which cannot be modified after creation",
82
+ "format": "Can only contain lowercase letters, hyphens (-), and underscores (_) ",
81
83
  "placeholder": "Suggested all lowercase, e.g., openai, cannot be modified after creation",
82
84
  "required": "Please enter the provider ID",
83
85
  "title": "Provider ID"
@@ -78,6 +78,8 @@
78
78
  "title": "Descripción del proveedor"
79
79
  },
80
80
  "id": {
81
+ "desc": "Identificador único del proveedor de servicios, no se puede modificar una vez creado",
82
+ "format": "Solo puede contener letras minúsculas, guiones (-) y guiones bajos (_)",
81
83
  "placeholder": "Se recomienda en minúsculas, por ejemplo openai, no se puede modificar después de crear",
82
84
  "required": "Por favor, introduce el ID del proveedor",
83
85
  "title": "ID del proveedor"
@@ -78,6 +78,8 @@
78
78
  "title": "توضیحات ارائه‌دهنده"
79
79
  },
80
80
  "id": {
81
+ "desc": "به عنوان شناسه منحصر به فرد ارائه‌دهنده خدمات، پس از ایجاد قابل ویرایش نخواهد بود",
82
+ "format": "فقط می‌تواند شامل حروف کوچک، خط تیره (-) و زیرخط (_) باشد",
81
83
  "placeholder": "توصیه می‌شود تماماً با حروف کوچک باشد، مانند openai، پس از ایجاد قابل ویرایش نخواهد بود",
82
84
  "required": "لطفاً شناسه ارائه‌دهنده را وارد کنید",
83
85
  "title": "شناسه ارائه‌دهنده"
@@ -78,6 +78,8 @@
78
78
  "title": "Description du fournisseur"
79
79
  },
80
80
  "id": {
81
+ "desc": "Identifiant unique du fournisseur de services, qui ne peut pas être modifié après sa création",
82
+ "format": "Ne peut contenir que des lettres minuscules, des tirets (-) et des underscores (_) ",
81
83
  "placeholder": "Utilisez uniquement des lettres minuscules, par exemple openai, non modifiable après création",
82
84
  "required": "Veuillez entrer l'ID du fournisseur",
83
85
  "title": "ID du fournisseur"
@@ -78,6 +78,8 @@
78
78
  "title": "Descrizione del fornitore"
79
79
  },
80
80
  "id": {
81
+ "desc": "Identificatore unico del fornitore di servizi, non modificabile dopo la creazione",
82
+ "format": "Può contenere solo lettere minuscole, trattini (-) e underscore (_)",
81
83
  "placeholder": "Si consiglia di utilizzare solo lettere minuscole, ad esempio openai, non modificabile dopo la creazione",
82
84
  "required": "Inserisci l'ID del fornitore",
83
85
  "title": "ID del fornitore"
@@ -78,6 +78,8 @@
78
78
  "title": "サービスプロバイダーの紹介"
79
79
  },
80
80
  "id": {
81
+ "desc": "サービスプロバイダーの一意の識別子であり、作成後は変更できません",
82
+ "format": "小文字のアルファベット、ハイフン(-)、およびアンダースコア(_)のみを含むことができます",
81
83
  "placeholder": "小文字で入力してください(例: openai)。作成後は変更できません",
82
84
  "required": "サービスプロバイダー ID を入力してください",
83
85
  "title": "サービスプロバイダー ID"
@@ -78,6 +78,8 @@
78
78
  "title": "서비스 제공자 소개"
79
79
  },
80
80
  "id": {
81
+ "desc": "서비스 제공자의 고유 식별자로, 생성 후에는 수정할 수 없습니다.",
82
+ "format": "소문자, 하이픈(-), 및 언더스코어(_)만 포함할 수 있습니다.",
81
83
  "placeholder": "소문자로 입력하세요, 예: openai, 생성 후 수정할 수 없습니다",
82
84
  "required": "서비스 제공자 ID를 입력하세요",
83
85
  "title": "서비스 제공자 ID"
@@ -78,6 +78,8 @@
78
78
  "title": "Beschrijving van de provider"
79
79
  },
80
80
  "id": {
81
+ "desc": "Een unieke identificatie voor de dienstverlener, kan na creatie niet meer worden gewijzigd",
82
+ "format": "Mag alleen kleine letters, koppeltekens (-) en underscores (_) bevatten",
81
83
  "placeholder": "Gebruik alleen kleine letters, bijvoorbeeld openai, kan niet worden gewijzigd na aanmaak",
82
84
  "required": "Vul de provider ID in",
83
85
  "title": "Provider ID"
@@ -78,6 +78,8 @@
78
78
  "title": "Opis dostawcy usług"
79
79
  },
80
80
  "id": {
81
+ "desc": "Unikalny identyfikator dostawcy usług, po utworzeniu nie można go zmienić",
82
+ "format": "Może zawierać tylko małe litery, myślniki (-) i podkreślenia (_)",
81
83
  "placeholder": "Zaleca się użycie małych liter, np. openai, po utworzeniu nie można edytować",
82
84
  "required": "Proszę wpisać identyfikator dostawcy",
83
85
  "title": "Identyfikator dostawcy"
@@ -78,6 +78,8 @@
78
78
  "title": "Descrição do Provedor"
79
79
  },
80
80
  "id": {
81
+ "desc": "Identificador único do provedor de serviços, não pode ser modificado após a criação",
82
+ "format": "Pode conter apenas letras minúsculas, hífens (-) e sublinhados (_)",
81
83
  "placeholder": "Sugestão: tudo em minúsculas, por exemplo, openai, não poderá ser modificado após a criação",
82
84
  "required": "Por favor, insira o ID do provedor",
83
85
  "title": "ID do Provedor"
@@ -78,6 +78,8 @@
78
78
  "title": "Описание провайдера"
79
79
  },
80
80
  "id": {
81
+ "desc": "Уникальный идентификатор для поставщика услуг, который нельзя изменить после создания",
82
+ "format": "Может содержать только строчные буквы, дефисы (-) и подчеркивания (_)",
81
83
  "placeholder": "Рекомендуется использовать строчные буквы, например, openai, после создания изменить нельзя",
82
84
  "required": "Пожалуйста, введите ID провайдера",
83
85
  "title": "ID провайдера"
@@ -78,6 +78,8 @@
78
78
  "title": "Hizmet Sağlayıcı Tanımı"
79
79
  },
80
80
  "id": {
81
+ "desc": "Hizmet sağlayıcının benzersiz kimliği, oluşturulduktan sonra değiştirilemez",
82
+ "format": "Sadece küçük harfler, tire (-) ve alt çizgi (_) içerebilir",
81
83
  "placeholder": "Küçük harflerle yazılması önerilir, örneğin openai, oluşturduktan sonra değiştirilemez",
82
84
  "required": "Lütfen hizmet sağlayıcı ID'sini girin",
83
85
  "title": "Hizmet Sağlayıcı ID"
@@ -78,6 +78,8 @@
78
78
  "title": "Giới thiệu về nhà cung cấp"
79
79
  },
80
80
  "id": {
81
+ "desc": "Là định danh duy nhất của nhà cung cấp dịch vụ, không thể sửa đổi sau khi tạo",
82
+ "format": "Chỉ có thể chứa chữ cái thường, dấu gạch ngang (-) và dấu gạch dưới (_)",
81
83
  "placeholder": "Nên viết toàn bộ bằng chữ thường, ví dụ openai, không thể sửa sau khi tạo",
82
84
  "required": "Vui lòng nhập ID nhà cung cấp",
83
85
  "title": "ID nhà cung cấp"
@@ -78,7 +78,9 @@
78
78
  "title": "服务商简介"
79
79
  },
80
80
  "id": {
81
- "placeholder": "建议全小写,例如 openai,创建后将不可修改",
81
+ "desc": "作为服务商唯一标识,创建后将不可修改",
82
+ "format": "只能包含小写字母、连字符(-)和下划线(_)",
83
+ "placeholder": "例如 openai、gemini 等",
82
84
  "required": "请填写服务商 ID",
83
85
  "title": "服务商 ID"
84
86
  },
@@ -78,6 +78,8 @@
78
78
  "title": "服務商簡介"
79
79
  },
80
80
  "id": {
81
+ "desc": "作為服務商唯一標識,創建後將不可修改",
82
+ "format": "只能包含小寫字母、連字符(-)和下劃線(_)",
81
83
  "placeholder": "建議全小寫,例如 openai,創建後將不可修改",
82
84
  "required": "請填寫服務商 ID",
83
85
  "title": "服務商 ID"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.44.1",
3
+ "version": "1.44.3",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -43,10 +43,17 @@ const CreateNewProvider = memo<CreateNewProviderProps>(({ onClose, open }) => {
43
43
  children: (
44
44
  <Input autoFocus placeholder={t('createNewAiProvider.id.placeholder')} variant={'filled'} />
45
45
  ),
46
+ desc: t('createNewAiProvider.id.desc'),
46
47
  label: t('createNewAiProvider.id.title'),
47
48
  minWidth: 400,
48
49
  name: 'id',
49
- rules: [{ message: t('createNewAiProvider.id.required'), required: true }],
50
+ rules: [
51
+ { message: t('createNewAiProvider.id.required'), required: true },
52
+ {
53
+ message: t('createNewAiProvider.id.format'),
54
+ pattern: /^[_a-z-]+$/,
55
+ },
56
+ ],
50
57
  },
51
58
  {
52
59
  children: (
@@ -12,13 +12,9 @@ import { TraceNameMap } from '@/const/trace';
12
12
  import { useIsMobile } from '@/hooks/useIsMobile';
13
13
  import { useProviderName } from '@/hooks/useProviderName';
14
14
  import { chatService } from '@/services/chat';
15
+ import { aiProviderSelectors, useAiInfraStore } from '@/store/aiInfra';
15
16
  import { ChatMessageError } from '@/types/message';
16
17
 
17
- interface ConnectionCheckerProps {
18
- model: string;
19
- provider: string;
20
- }
21
-
22
18
  const Error = memo<{ error: ChatMessageError }>(({ error }) => {
23
19
  const { t } = useTranslation('error');
24
20
  const providerName = useProviderName(error.body?.provider);
@@ -42,9 +38,16 @@ const Error = memo<{ error: ChatMessageError }>(({ error }) => {
42
38
  );
43
39
  });
44
40
 
41
+ interface ConnectionCheckerProps {
42
+ model: string;
43
+ provider: string;
44
+ }
45
+
45
46
  const Checker = memo<ConnectionCheckerProps>(({ model, provider }) => {
46
47
  const { t } = useTranslation('setting');
47
48
 
49
+ const disabled = useAiInfraStore(aiProviderSelectors.isProviderConfigUpdating(provider));
50
+
48
51
  const [loading, setLoading] = useState(false);
49
52
  const [pass, setPass] = useState(false);
50
53
 
@@ -108,7 +111,7 @@ const Checker = memo<ConnectionCheckerProps>(({ model, provider }) => {
108
111
  {t('llm.checker.pass')}
109
112
  </Flexbox>
110
113
  )}
111
- <Button loading={loading} onClick={checkConnection}>
114
+ <Button disabled={disabled} loading={loading} onClick={checkConnection}>
112
115
  {t('llm.checker.button')}
113
116
  </Button>
114
117
  </Flexbox>
@@ -5,7 +5,7 @@ import { Avatar, Form, type FormItemProps, Icon, type ItemGroup, Tooltip } from
5
5
  import { useDebounceFn } from 'ahooks';
6
6
  import { Input, Skeleton, Switch } from 'antd';
7
7
  import { createStyles } from 'antd-style';
8
- import { LockIcon } from 'lucide-react';
8
+ import { Loader2Icon, LockIcon } from 'lucide-react';
9
9
  import Link from 'next/link';
10
10
  import { ReactNode, memo, useLayoutEffect } from 'react';
11
11
  import { Trans, useTranslation } from 'react-i18next';
@@ -128,7 +128,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
128
128
  } = settings;
129
129
  const { t } = useTranslation('modelProvider');
130
130
  const [form] = Form.useForm();
131
- const { cx, styles } = useStyles();
131
+ const { cx, styles, theme } = useStyles();
132
132
 
133
133
  const [
134
134
  toggleProviderEnabled,
@@ -136,6 +136,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
136
136
  updateAiProviderConfig,
137
137
  enabled,
138
138
  isLoading,
139
+ configUpdating,
139
140
  isFetchOnClient,
140
141
  isProviderEndpointNotEmpty,
141
142
  isProviderApiKeyNotEmpty,
@@ -145,6 +146,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
145
146
  s.updateAiProviderConfig,
146
147
  aiProviderSelectors.isProviderEnabled(id)(s),
147
148
  aiProviderSelectors.isAiProviderConfigLoading(id)(s),
149
+ aiProviderSelectors.isProviderConfigUpdating(id)(s),
148
150
  aiProviderSelectors.isProviderFetchOnClient(id)(s),
149
151
  aiProviderSelectors.isActiveProviderEndpointNotEmpty(s),
150
152
  aiProviderSelectors.isActiveProviderApiKeyNotEmpty(s),
@@ -173,6 +175,11 @@ const ProviderConfig = memo<ProviderConfigProps>(
173
175
  <Input.Password
174
176
  autoComplete={'new-password'}
175
177
  placeholder={t(`providerModels.config.apiKey.placeholder`, { name })}
178
+ suffix={
179
+ configUpdating && (
180
+ <Icon icon={Loader2Icon} spin style={{ color: theme.colorTextTertiary }} />
181
+ )
182
+ }
176
183
  />
177
184
  ),
178
185
  desc: t(`providerModels.config.apiKey.desc`, { name }),
@@ -211,6 +218,11 @@ const ProviderConfig = memo<ProviderConfigProps>(
211
218
  (!!proxyUrl && proxyUrl?.placeholder) ||
212
219
  t('providerModels.config.baseURL.placeholder')
213
220
  }
221
+ suffix={
222
+ configUpdating && (
223
+ <Icon icon={Loader2Icon} spin style={{ color: theme.colorTextTertiary }} />
224
+ )
225
+ }
214
226
  />
215
227
  ),
216
228
  desc: (!!proxyUrl && proxyUrl?.desc) || t('providerModels.config.baseURL.desc'),
@@ -235,7 +247,7 @@ const ProviderConfig = memo<ProviderConfigProps>(
235
247
  children: isLoading ? (
236
248
  <Skeleton.Button active className={styles.switchLoading} />
237
249
  ) : (
238
- <Switch disabled={isLoading} value={isFetchOnClient} />
250
+ <Switch disabled={configUpdating} value={isFetchOnClient} />
239
251
  ),
240
252
  desc: t('providerModels.config.fetchOnClient.desc'),
241
253
  label: t('providerModels.config.fetchOnClient.title'),
@@ -303,19 +303,6 @@ const siliconcloudChatModels: AIChatModelCard[] = [
303
303
  },
304
304
  type: 'chat',
305
305
  },
306
- {
307
- contextWindowTokens: 32_768,
308
- description:
309
- 'Qwen2-72B-Instruct 是 Qwen2 系列中的指令微调大语言模型,参数规模为 72B。该模型基于 Transformer 架构,采用了 SwiGLU 激活函数、注意力 QKV 偏置和组查询注意力等技术。它能够处理大规模输入。该模型在语言理解、生成、多语言能力、编码、数学和推理等多个基准测试中表现出色,超越了大多数开源模型,并在某些任务上展现出与专有模型相当的竞争力',
310
- displayName: 'Qwen2 72B Instruct',
311
- id: 'Qwen/Qwen2-7B-Instruct',
312
- pricing: {
313
- currency: 'CNY',
314
- input: 4.13,
315
- output: 4.13,
316
- },
317
- type: 'chat',
318
- },
319
306
  {
320
307
  contextWindowTokens: 32_768,
321
308
  description:
@@ -69,19 +69,28 @@ export class AiInfraRepos {
69
69
  const providers = await this.getAiProviderList();
70
70
  const enabledProviders = providers.filter((item) => item.enabled);
71
71
 
72
- const userEnabledModels = await this.aiModelModel.getEnabledModels();
72
+ const allModels = await this.aiModelModel.getAllModels();
73
+ const userEnabledModels = allModels.filter((item) => item.enabled);
74
+
73
75
  const modelList = await pMap(
74
76
  enabledProviders,
75
77
  async (provider) => {
76
78
  const aiModels = await this.fetchBuiltinModels(provider.id);
77
79
 
78
80
  return (aiModels || [])
79
- .filter((i) => i.enabled)
80
- .map<EnabledAiModel>((item) => ({
81
- ...item,
82
- abilities: item.abilities || {},
83
- providerId: provider.id,
84
- }));
81
+ .map<EnabledAiModel & { enabled?: boolean | null }>((item) => {
82
+ const user = allModels.find((m) => m.id === item.id && m.providerId === provider.id);
83
+
84
+ const enabled = !!user ? user.enabled : item.enabled;
85
+
86
+ return {
87
+ ...item,
88
+ abilities: item.abilities || {},
89
+ enabled,
90
+ providerId: provider.id,
91
+ };
92
+ })
93
+ .filter((i) => i.enabled);
85
94
  },
86
95
  { concurrency: 10 },
87
96
  );
@@ -100,6 +109,9 @@ export class AiInfraRepos {
100
109
  return mergeArrayById(defaultModels, aiModels) as AiProviderModelListItem[];
101
110
  };
102
111
 
112
+ /**
113
+ * Fetch builtin models from config
114
+ */
103
115
  private fetchBuiltinModels = async (
104
116
  providerId: string,
105
117
  ): Promise<AiProviderModelListItem[] | undefined> => {
@@ -193,17 +193,15 @@ describe('AiModelModel', () => {
193
193
  });
194
194
  });
195
195
 
196
- describe('getEnabledModels', () => {
196
+ describe('getAllModels', () => {
197
197
  it('should only return enabled models', async () => {
198
198
  await serverDB.insert(aiModels).values([
199
199
  { id: 'model1', providerId: 'openai', enabled: true, source: 'custom', userId },
200
- { id: 'model2', providerId: 'openai', enabled: false, source: 'custom', userId },
200
+ { id: 'model2', providerId: 'b', enabled: false, source: 'custom', userId },
201
201
  ]);
202
202
 
203
- const models = await aiProviderModel.getEnabledModels();
204
- expect(models).toHaveLength(1);
205
- expect(models[0].id).toBe('model1');
206
- expect(models[0].enabled).toBe(true);
203
+ const models = await aiProviderModel.getAllModels();
204
+ expect(models).toHaveLength(2);
207
205
  });
208
206
  });
209
207
 
@@ -8,6 +8,7 @@ import {
8
8
  AiProviderModelListItem,
9
9
  ToggleAiModelEnableParams,
10
10
  } from '@/types/aiModel';
11
+ import { EnabledAiModel } from '@/types/aiProvider';
11
12
 
12
13
  import { AiModelSelectItem, NewAiModelItem, aiModels } from '../../schemas';
13
14
 
@@ -83,8 +84,8 @@ export class AiModelModel {
83
84
  return result as AiProviderModelListItem[];
84
85
  };
85
86
 
86
- getEnabledModels = async () => {
87
- return this.db
87
+ getAllModels = async () => {
88
+ const data = await this.db
88
89
  .select({
89
90
  abilities: aiModels.abilities,
90
91
  config: aiModels.config,
@@ -98,7 +99,9 @@ export class AiModelModel {
98
99
  type: aiModels.type,
99
100
  })
100
101
  .from(aiModels)
101
- .where(and(eq(aiModels.userId, this.userId), eq(aiModels.enabled, true)));
102
+ .where(and(eq(aiModels.userId, this.userId)));
103
+
104
+ return data as EnabledAiModel[];
102
105
  };
103
106
 
104
107
  findById = async (id: string) => {
@@ -79,7 +79,9 @@ export default {
79
79
  title: '服务商简介',
80
80
  },
81
81
  id: {
82
- placeholder: '建议全小写,例如 openai,创建后将不可修改',
82
+ desc: '作为服务商唯一标识,创建后将不可修改',
83
+ format: '只能包含小写字母、连字符(-)和下划线(_)',
84
+ placeholder: '例如 openai、gemini 等',
83
85
  required: '请填写服务商 ID',
84
86
  title: '服务商 ID',
85
87
  },
@@ -28,6 +28,7 @@ enum AiProviderSwrKey {
28
28
  export interface AiProviderAction {
29
29
  createNewAiProvider: (params: CreateAiProviderParams) => Promise<void>;
30
30
  deleteAiProvider: (id: string) => Promise<void>;
31
+ internal_toggleAiProviderConfigUpdating: (id: string, loading: boolean) => void;
31
32
  internal_toggleAiProviderLoading: (id: string, loading: boolean) => void;
32
33
  refreshAiProviderDetail: () => Promise<void>;
33
34
  refreshAiProviderList: () => Promise<void>;
@@ -64,6 +65,20 @@ export const createAiProviderSlice: StateCreator<
64
65
 
65
66
  await get().refreshAiProviderList();
66
67
  },
68
+ internal_toggleAiProviderConfigUpdating: (id, loading) => {
69
+ set(
70
+ (state) => {
71
+ if (loading)
72
+ return { aiProviderConfigUpdatingIds: [...state.aiProviderConfigUpdatingIds, id] };
73
+
74
+ return {
75
+ aiProviderConfigUpdatingIds: state.aiProviderConfigUpdatingIds.filter((i) => i !== id),
76
+ };
77
+ },
78
+ false,
79
+ 'toggleAiProviderLoading',
80
+ );
81
+ },
67
82
  internal_toggleAiProviderLoading: (id, loading) => {
68
83
  set(
69
84
  (state) => {
@@ -109,11 +124,11 @@ export const createAiProviderSlice: StateCreator<
109
124
  },
110
125
 
111
126
  updateAiProviderConfig: async (id, value) => {
112
- get().internal_toggleAiProviderLoading(id, true);
127
+ get().internal_toggleAiProviderConfigUpdating(id, true);
113
128
  await aiProviderService.updateAiProviderConfig(id, value);
114
129
  await get().refreshAiProviderDetail();
115
130
 
116
- get().internal_toggleAiProviderLoading(id, false);
131
+ get().internal_toggleAiProviderConfigUpdating(id, false);
117
132
  },
118
133
 
119
134
  updateAiProviderSort: async (items) => {
@@ -200,7 +215,7 @@ export const createAiProviderSlice: StateCreator<
200
215
  enabledChatModelList,
201
216
  },
202
217
  false,
203
- 'useInitAiProviderKeyVaults',
218
+ 'useFetchAiProviderRuntimeState',
204
219
  );
205
220
  },
206
221
  },
@@ -10,6 +10,7 @@ import {
10
10
  export interface AIProviderState {
11
11
  activeAiProvider?: string;
12
12
  activeProviderModelList: any[];
13
+ aiProviderConfigUpdatingIds: string[];
13
14
  aiProviderDetail?: AiProviderDetailItem | null;
14
15
  aiProviderList: AiProviderListItem[];
15
16
  aiProviderLoadingIds: string[];
@@ -24,6 +25,7 @@ export interface AIProviderState {
24
25
 
25
26
  export const initialAIProviderState: AIProviderState = {
26
27
  activeProviderModelList: [],
28
+ aiProviderConfigUpdatingIds: [],
27
29
  aiProviderList: [],
28
30
  aiProviderLoadingIds: [],
29
31
  aiProviderRuntimeConfig: {},
@@ -84,6 +84,9 @@ const providerConfigById =
84
84
  return s.aiProviderRuntimeConfig?.[id];
85
85
  };
86
86
 
87
+ const isProviderConfigUpdating = (id: string) => (s: AIProviderStoreState) =>
88
+ s.aiProviderConfigUpdatingIds.includes(id);
89
+
87
90
  export const aiProviderSelectors = {
88
91
  activeProviderConfig,
89
92
  disabledAiProviderList,
@@ -91,6 +94,7 @@ export const aiProviderSelectors = {
91
94
  isActiveProviderApiKeyNotEmpty,
92
95
  isActiveProviderEndpointNotEmpty,
93
96
  isAiProviderConfigLoading,
97
+ isProviderConfigUpdating,
94
98
  isProviderEnabled,
95
99
  isProviderFetchOnClient,
96
100
  isProviderLoading,
@@ -192,6 +192,7 @@ export interface EnabledAiModel {
192
192
  config?: AiModelConfig;
193
193
  contextWindowTokens?: number;
194
194
  displayName?: string;
195
+ enabled?: boolean;
195
196
  id: string;
196
197
  providerId: string;
197
198
  sort?: number;