@lobehub/chat 0.147.9 → 0.147.10

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.147.10](https://github.com/lobehub/lobe-chat/compare/v0.147.9...v0.147.10)
6
+
7
+ <sup>Released on **2024-04-13**</sup>
8
+
9
+ <br/>
10
+
11
+ <details>
12
+ <summary><kbd>Improvements and Fixes</kbd></summary>
13
+
14
+ </details>
15
+
16
+ <div align="right">
17
+
18
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
19
+
20
+ </div>
21
+
5
22
  ### [Version 0.147.9](https://github.com/lobehub/lobe-chat/compare/v0.147.8...v0.147.9)
6
23
 
7
24
  <sup>Released on **2024-04-12**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.147.9",
3
+ "version": "0.147.10",
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",
@@ -1,6 +1,7 @@
1
1
  import { ChatModelCard, ModelProviderCard } from '@/types/llm';
2
2
 
3
3
  import AnthropicProvider from './anthropic';
4
+ import AzureProvider from './azure';
4
5
  import BedrockProvider from './bedrock';
5
6
  import GoogleProvider from './google';
6
7
  import GroqProvider from './groq';
@@ -30,6 +31,23 @@ export const LOBE_DEFAULT_MODEL_LIST: ChatModelCard[] = [
30
31
  ZeroOneProvider.chatModels,
31
32
  ].flat();
32
33
 
34
+ export const DEFAULT_MODEL_PROVIDER_LIST = [
35
+ OpenAIProvider,
36
+ { ...AzureProvider, chatModels: [] },
37
+ OllamaProvider,
38
+ AnthropicProvider,
39
+ GoogleProvider,
40
+ OpenRouterProvider,
41
+ TogetherAIProvider,
42
+ BedrockProvider,
43
+ PerplexityProvider,
44
+ MistralProvider,
45
+ GroqProvider,
46
+ MoonshotProvider,
47
+ ZeroOneProvider,
48
+ ZhiPuProvider,
49
+ ];
50
+
33
51
  export const filterEnabledModels = (provider: ModelProviderCard) => {
34
52
  return provider.chatModels.filter((v) => v.enabled).map((m) => m.id);
35
53
  };
@@ -29,6 +29,7 @@ const ModelSelect = memo(() => {
29
29
  modelProviderSelectors.modelProviderListForModelSelect,
30
30
  isEqual,
31
31
  );
32
+
32
33
  const { styles } = useStyles();
33
34
 
34
35
  const options = useMemo<SelectProps['options']>(() => {
@@ -62,6 +62,9 @@ export const createCommonSlice: StateCreator<
62
62
 
63
63
  refreshUserConfig: async () => {
64
64
  await mutate([USER_CONFIG_FETCH_KEY, true]);
65
+
66
+ // when get the user config ,refresh the model provider list to the latest
67
+ get().refreshModelProviderList();
65
68
  },
66
69
 
67
70
  switchBackToChat: (sessionId) => {
@@ -159,7 +162,10 @@ export const createCommonSlice: StateCreator<
159
162
  };
160
163
 
161
164
  const defaultSettings = merge(get().defaultSettings, serverSettings);
165
+
162
166
  set({ defaultSettings, serverConfig: data }, false, n('initGlobalConfig'));
167
+
168
+ get().refreshDefaultModelProviderList();
163
169
  }
164
170
  },
165
171
  revalidateOnFocus: false,
@@ -181,6 +187,9 @@ export const createCommonSlice: StateCreator<
181
187
  n('fetchUserConfig', data),
182
188
  );
183
189
 
190
+ // when get the user config ,refresh the model provider list to the latest
191
+ get().refreshModelProviderList();
192
+
184
193
  const { language } = settingsSelectors.currentSettings(get());
185
194
  if (language === 'auto') {
186
195
  switchLang('auto');
@@ -2,9 +2,18 @@ import { act, renderHook } from '@testing-library/react';
2
2
  import { describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { userService } from '@/services/user';
5
- import { useGlobalStore } from '@/store/global';
6
- import { modelConfigSelectors, settingsSelectors } from '@/store/global/slices/settings/selectors';
5
+ import { GlobalStore, useGlobalStore } from '@/store/global';
6
+ import {
7
+ GlobalSettingsState,
8
+ initialSettingsState,
9
+ } from '@/store/global/slices/settings/initialState';
10
+ import {
11
+ modelConfigSelectors,
12
+ modelProviderSelectors,
13
+ settingsSelectors,
14
+ } from '@/store/global/slices/settings/selectors';
7
15
  import { GeneralModelProviderConfig } from '@/types/settings';
16
+ import { merge } from '@/utils/merge';
8
17
 
9
18
  import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
10
19
 
@@ -15,9 +24,6 @@ vi.mock('@/services/user', () => ({
15
24
  resetUserSettings: vi.fn(),
16
25
  },
17
26
  }));
18
- vi.mock('../reducers/customModelCard', () => ({
19
- customModelCardsReducer: vi.fn().mockReturnValue([]),
20
- }));
21
27
 
22
28
  describe('LLMSettingsSliceAction', () => {
23
29
  describe('setModelProviderConfig', () => {
@@ -57,4 +63,84 @@ describe('LLMSettingsSliceAction', () => {
57
63
  expect(result.current.setModelProviderConfig).not.toHaveBeenCalled();
58
64
  });
59
65
  });
66
+
67
+ describe('refreshDefaultModelProviderList', () => {
68
+ it('default', async () => {
69
+ const { result } = renderHook(() => useGlobalStore());
70
+
71
+ act(() => {
72
+ useGlobalStore.setState({
73
+ serverConfig: {
74
+ languageModel: {
75
+ azure: { serverModelCards: [{ id: 'abc', deploymentName: 'abc' }] },
76
+ },
77
+ telemetry: {},
78
+ },
79
+ });
80
+ });
81
+
82
+ act(() => {
83
+ result.current.refreshDefaultModelProviderList();
84
+ });
85
+
86
+ // Assert that setModelProviderConfig was not called
87
+ const azure = result.current.defaultModelProviderList.find((m) => m.id === 'azure');
88
+ expect(azure?.chatModels).toEqual([{ id: 'abc', deploymentName: 'abc' }]);
89
+ });
90
+ });
91
+
92
+ describe('refreshModelProviderList', () => {
93
+ it('visible', async () => {
94
+ const { result } = renderHook(() => useGlobalStore());
95
+ act(() => {
96
+ useGlobalStore.setState({
97
+ settings: {
98
+ languageModel: {
99
+ ollama: { enabledModels: ['llava'] },
100
+ },
101
+ },
102
+ });
103
+ });
104
+
105
+ act(() => {
106
+ result.current.refreshModelProviderList();
107
+ });
108
+
109
+ const ollamaList = result.current.modelProviderList.find((r) => r.id === 'ollama');
110
+ // Assert that setModelProviderConfig was not called
111
+ expect(ollamaList?.chatModels.find((c) => c.id === 'llava')).toEqual({
112
+ displayName: 'LLaVA 7B',
113
+ functionCall: false,
114
+ enabled: true,
115
+ id: 'llava',
116
+ tokens: 4000,
117
+ vision: true,
118
+ });
119
+ });
120
+
121
+ it('modelProviderListForModelSelect should return only enabled providers', () => {
122
+ const { result } = renderHook(() => useGlobalStore());
123
+
124
+ act(() => {
125
+ useGlobalStore.setState({
126
+ settings: {
127
+ languageModel: {
128
+ perplexity: { enabled: true },
129
+ azure: { enabled: false },
130
+ },
131
+ },
132
+ });
133
+ });
134
+
135
+ act(() => {
136
+ result.current.refreshModelProviderList();
137
+ });
138
+
139
+ const enabledProviders = modelProviderSelectors.modelProviderListForModelSelect(
140
+ result.current,
141
+ );
142
+ expect(enabledProviders).toHaveLength(2);
143
+ expect(enabledProviders[1].id).toBe('perplexity');
144
+ });
145
+ });
60
146
  });
@@ -1,11 +1,28 @@
1
1
  import useSWR, { SWRResponse } from 'swr';
2
2
  import type { StateCreator } from 'zustand/vanilla';
3
3
 
4
+ import {
5
+ AnthropicProviderCard,
6
+ AzureProviderCard,
7
+ BedrockProviderCard,
8
+ GoogleProviderCard,
9
+ GroqProviderCard,
10
+ MistralProviderCard,
11
+ MoonshotProviderCard,
12
+ OllamaProviderCard,
13
+ OpenAIProviderCard,
14
+ OpenRouterProviderCard,
15
+ PerplexityProviderCard,
16
+ TogetherAIProviderCard,
17
+ ZeroOneProviderCard,
18
+ ZhiPuProviderCard,
19
+ } from '@/config/modelProviders';
4
20
  import { GlobalStore } from '@/store/global';
5
21
  import { ChatModelCard } from '@/types/llm';
6
22
  import { GlobalLLMConfig, GlobalLLMProviderKey } from '@/types/settings';
7
23
 
8
24
  import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
25
+ import { modelProviderSelectors } from '../selectors/modelProvider';
9
26
  import { settingsSelectors } from '../selectors/settings';
10
27
 
11
28
  /**
@@ -16,12 +33,18 @@ export interface LLMSettingsAction {
16
33
  provider: GlobalLLMProviderKey,
17
34
  payload: CustomModelCardDispatch,
18
35
  ) => Promise<void>;
36
+ /**
37
+ * make sure the default model provider list is sync to latest state
38
+ */
39
+ refreshDefaultModelProviderList: () => void;
40
+ refreshModelProviderList: () => void;
19
41
  removeEnabledModels: (provider: GlobalLLMProviderKey, model: string) => Promise<void>;
20
42
  setModelProviderConfig: <T extends GlobalLLMProviderKey>(
21
43
  provider: T,
22
44
  config: Partial<GlobalLLMConfig[T]>,
23
45
  ) => Promise<void>;
24
46
  toggleEditingCustomModelCard: (params?: { id: string; provider: GlobalLLMProviderKey }) => void;
47
+
25
48
  toggleProviderEnabled: (provider: GlobalLLMProviderKey, enabled: boolean) => Promise<void>;
26
49
 
27
50
  useFetchProviderModelList: (
@@ -46,6 +69,76 @@ export const llmSettingsSlice: StateCreator<
46
69
  await get().setModelProviderConfig(provider, { customModelCards: nextState });
47
70
  },
48
71
 
72
+ refreshDefaultModelProviderList: () => {
73
+ /**
74
+ * Because we have several model cards sources, we need to merge the model cards
75
+ * the priority is below:
76
+ * 1 - server side model cards
77
+ * 2 - remote model cards
78
+ * 3 - default model cards
79
+ */
80
+
81
+ // eslint-disable-next-line unicorn/consistent-function-scoping
82
+ const mergeModels = (provider: GlobalLLMProviderKey, defaultChatModels: ChatModelCard[]) => {
83
+ // if the chat model is config in the server side, use the server side model cards
84
+ const serverChatModels = modelProviderSelectors.serverProviderModelCards(provider)(get());
85
+ const remoteChatModels = modelProviderSelectors.remoteProviderModelCards(provider)(get());
86
+
87
+ return serverChatModels ?? remoteChatModels ?? defaultChatModels;
88
+ };
89
+
90
+ const defaultModelProviderList = [
91
+ {
92
+ ...OpenAIProviderCard,
93
+ chatModels: mergeModels('openai', OpenAIProviderCard.chatModels),
94
+ },
95
+ { ...AzureProviderCard, chatModels: mergeModels('azure', []) },
96
+ { ...OllamaProviderCard, chatModels: mergeModels('ollama', OllamaProviderCard.chatModels) },
97
+ AnthropicProviderCard,
98
+ GoogleProviderCard,
99
+ {
100
+ ...OpenRouterProviderCard,
101
+ chatModels: mergeModels('openrouter', OpenRouterProviderCard.chatModels),
102
+ },
103
+ {
104
+ ...TogetherAIProviderCard,
105
+ chatModels: mergeModels('togetherai', TogetherAIProviderCard.chatModels),
106
+ },
107
+ BedrockProviderCard,
108
+ PerplexityProviderCard,
109
+ MistralProviderCard,
110
+ GroqProviderCard,
111
+ MoonshotProviderCard,
112
+ ZeroOneProviderCard,
113
+ ZhiPuProviderCard,
114
+ ];
115
+
116
+ set({ defaultModelProviderList }, false, 'refreshDefaultModelProviderList');
117
+
118
+ get().refreshModelProviderList();
119
+ },
120
+
121
+ refreshModelProviderList: () => {
122
+ const modelProviderList = get().defaultModelProviderList.map((list) => ({
123
+ ...list,
124
+ chatModels: modelProviderSelectors
125
+ .getModelCardsById(list.id)(get())
126
+ ?.map((model) => {
127
+ const models = modelProviderSelectors.getEnableModelsById(list.id)(get());
128
+
129
+ if (!models) return model;
130
+
131
+ return {
132
+ ...model,
133
+ enabled: models?.some((m) => m === model.id),
134
+ };
135
+ }),
136
+ enabled: modelProviderSelectors.isProviderEnabled(list.id as any)(get()),
137
+ }));
138
+
139
+ set({ modelProviderList }, false, 'refreshModelProviderList');
140
+ },
141
+
49
142
  removeEnabledModels: async (provider, model) => {
50
143
  const config = settingsSelectors.providerConfig(provider)(get());
51
144
 
@@ -60,6 +153,7 @@ export const llmSettingsSlice: StateCreator<
60
153
  toggleEditingCustomModelCard: (params) => {
61
154
  set({ editingCustomCardModel: params }, false, 'toggleEditingCustomModelCard');
62
155
  },
156
+
63
157
  toggleProviderEnabled: async (provider, enabled) => {
64
158
  await get().setSettings({ languageModel: { [provider]: { enabled } } });
65
159
  },
@@ -79,6 +173,8 @@ export const llmSettingsSlice: StateCreator<
79
173
  latestFetchTime: Date.now(),
80
174
  remoteModelCards: data,
81
175
  });
176
+
177
+ get().refreshDefaultModelProviderList();
82
178
  }
83
179
  },
84
180
  revalidateOnFocus: false,
@@ -1,20 +1,26 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
+ import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
3
4
  import { DEFAULT_SETTINGS } from '@/const/settings';
5
+ import { ModelProviderCard } from '@/types/llm';
4
6
  import { GlobalServerConfig } from '@/types/serverConfig';
5
7
  import { GlobalSettings } from '@/types/settings';
6
8
 
7
9
  export interface GlobalSettingsState {
8
10
  avatar?: string;
11
+ defaultModelProviderList: ModelProviderCard[];
9
12
  defaultSettings: GlobalSettings;
10
13
  editingCustomCardModel?: { id: string; provider: string } | undefined;
14
+ modelProviderList: ModelProviderCard[];
11
15
  serverConfig: GlobalServerConfig;
12
16
  settings: DeepPartial<GlobalSettings>;
13
17
  userId?: string;
14
18
  }
15
19
 
16
20
  export const initialSettingsState: GlobalSettingsState = {
21
+ defaultModelProviderList: DEFAULT_MODEL_PROVIDER_LIST,
17
22
  defaultSettings: DEFAULT_SETTINGS,
23
+ modelProviderList: DEFAULT_MODEL_PROVIDER_LIST,
18
24
  serverConfig: {
19
25
  telemetry: {},
20
26
  },
@@ -7,71 +7,7 @@ import { GlobalSettingsState, initialSettingsState } from '../initialState';
7
7
  import { getDefaultModeProviderById, modelProviderSelectors } from './modelProvider';
8
8
 
9
9
  describe('modelProviderSelectors', () => {
10
- describe('providerListWithConfig', () => {
11
- it('visible', () => {
12
- const s = merge(initialSettingsState, {
13
- settings: {
14
- languageModel: {
15
- ollama: {
16
- enabledModels: ['llava'],
17
- },
18
- },
19
- },
20
- } as GlobalSettingsState) as unknown as GlobalStore;
21
-
22
- const ollamaList = modelProviderSelectors.modelProviderList(s).find((r) => r.id === 'ollama');
23
-
24
- expect(ollamaList?.chatModels.find((c) => c.id === 'llava')).toEqual({
25
- displayName: 'LLaVA 7B',
26
- functionCall: false,
27
- enabled: true,
28
- id: 'llava',
29
- tokens: 4000,
30
- vision: true,
31
- });
32
- });
33
- it('with user custom models', () => {
34
- const s = merge(initialSettingsState, {
35
- settings: {
36
- languageModel: {
37
- perplexity: {
38
- customModelCards: [{ id: 'sonar-online', displayName: 'Sonar Online' }],
39
- },
40
- },
41
- },
42
- } as GlobalSettingsState) as unknown as GlobalStore;
43
-
44
- const providerList = modelProviderSelectors
45
- .modelProviderList(s)
46
- .find((r) => r.id === 'perplexity');
47
-
48
- expect(providerList?.chatModels.find((c) => c.id === 'sonar-online')).toEqual({
49
- id: 'sonar-online',
50
- displayName: 'Sonar Online',
51
- enabled: false,
52
- isCustom: true,
53
- });
54
- });
55
- });
56
-
57
- describe('providerListForModelSelect', () => {
58
- it('should return only enabled providers', () => {
59
- const s = merge(initialSettingsState, {
60
- settings: {
61
- languageModel: {
62
- perplexity: { enabled: true },
63
- azure: { enabled: false },
64
- },
65
- },
66
- } as GlobalSettingsState) as unknown as GlobalStore;
67
-
68
- const enabledProviders = modelProviderSelectors.modelProviderListForModelSelect(s);
69
- expect(enabledProviders).toHaveLength(2);
70
- expect(enabledProviders[1].id).toBe('perplexity');
71
- });
72
- });
73
-
74
- describe('providerCard', () => {
10
+ describe('getDefaultModeProviderById', () => {
75
11
  it('should return the correct ModelProviderCard when provider ID matches', () => {
76
12
  const s = merge(initialSettingsState, {}) as unknown as GlobalStore;
77
13
 
@@ -1,22 +1,6 @@
1
1
  import { uniqBy } from 'lodash-es';
2
2
 
3
- import {
4
- AnthropicProviderCard,
5
- AzureProviderCard,
6
- BedrockProviderCard,
7
- GoogleProviderCard,
8
- GroqProviderCard,
9
- MistralProviderCard,
10
- MoonshotProviderCard,
11
- OllamaProviderCard,
12
- OpenAIProviderCard,
13
- OpenRouterProviderCard,
14
- PerplexityProviderCard,
15
- TogetherAIProviderCard,
16
- ZeroOneProviderCard,
17
- ZhiPuProviderCard,
18
- filterEnabledModels,
19
- } from '@/config/modelProviders';
3
+ import { filterEnabledModels } from '@/config/modelProviders';
20
4
  import { ChatModelCard, ModelProviderCard } from '@/types/llm';
21
5
  import { ServerModelProviderConfig } from '@/types/serverConfig';
22
6
  import { GlobalLLMProviderKey } from '@/types/settings';
@@ -59,49 +43,8 @@ const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: GlobalStore) =
59
43
  /**
60
44
  * define all the model list of providers
61
45
  */
62
- const defaultModelProviderList = (s: GlobalStore): ModelProviderCard[] => {
63
- /**
64
- * Because we have several model cards sources, we need to merge the model cards
65
- * the priority is below:
66
- * 1 - server side model cards
67
- * 2 - remote model cards
68
- * 3 - default model cards
69
- */
70
-
71
- const mergeModels = (provider: GlobalLLMProviderKey, defaultChatModels: ChatModelCard[]) => {
72
- // if the chat model is config in the server side, use the server side model cards
73
- const serverChatModels = serverProviderModelCards(provider)(s);
74
- const remoteChatModels = remoteProviderModelCards(provider)(s);
75
-
76
- return serverChatModels ?? remoteChatModels ?? defaultChatModels;
77
- };
78
-
79
- return [
80
- {
81
- ...OpenAIProviderCard,
82
- chatModels: mergeModels('openai', OpenAIProviderCard.chatModels),
83
- },
84
- { ...AzureProviderCard, chatModels: mergeModels('azure', []) },
85
- { ...OllamaProviderCard, chatModels: mergeModels('ollama', OllamaProviderCard.chatModels) },
86
- AnthropicProviderCard,
87
- GoogleProviderCard,
88
- {
89
- ...OpenRouterProviderCard,
90
- chatModels: mergeModels('openrouter', OpenRouterProviderCard.chatModels),
91
- },
92
- {
93
- ...TogetherAIProviderCard,
94
- chatModels: mergeModels('togetherai', TogetherAIProviderCard.chatModels),
95
- },
96
- BedrockProviderCard,
97
- PerplexityProviderCard,
98
- MistralProviderCard,
99
- GroqProviderCard,
100
- MoonshotProviderCard,
101
- ZeroOneProviderCard,
102
- ZhiPuProviderCard,
103
- ];
104
- };
46
+ const defaultModelProviderList = (s: GlobalStore): ModelProviderCard[] =>
47
+ s.defaultModelProviderList;
105
48
 
106
49
  export const getDefaultModeProviderById = (provider: string) => (s: GlobalStore) =>
107
50
  defaultModelProviderList(s).find((s) => s.id === provider);
@@ -146,21 +89,7 @@ const getEnableModelsById = (provider: string) => (s: GlobalStore) => {
146
89
  return getProviderConfigById(provider)(s)?.enabledModels?.filter(Boolean);
147
90
  };
148
91
 
149
- const modelProviderList = (s: GlobalStore): ModelProviderCard[] =>
150
- defaultModelProviderList(s).map((list) => ({
151
- ...list,
152
- chatModels: getModelCardsById(list.id)(s)?.map((model) => {
153
- const models = getEnableModelsById(list.id)(s);
154
-
155
- if (!models) return model;
156
-
157
- return {
158
- ...model,
159
- enabled: models?.some((m) => m === model.id),
160
- };
161
- }),
162
- enabled: isProviderEnabled(list.id as any)(s),
163
- }));
92
+ const modelProviderList = (s: GlobalStore): ModelProviderCard[] => s.modelProviderList;
164
93
 
165
94
  const modelProviderListForModelSelect = (s: GlobalStore): ModelProviderCard[] =>
166
95
  modelProviderList(s)
@@ -196,22 +125,26 @@ const modelMaxToken = (id: string) => (s: GlobalStore) => getModelCardById(id)(s
196
125
 
197
126
  export const modelProviderSelectors = {
198
127
  defaultModelProviderList,
199
-
200
128
  getDefaultEnabledModelsById,
201
129
  getDefaultModelCardById,
202
130
 
203
131
  getEnableModelsById,
204
132
  getModelCardById,
205
- getModelCardsById,
206
133
 
134
+ getModelCardsById,
207
135
  isModelEnabledFiles,
208
136
  isModelEnabledFunctionCall,
209
137
  isModelEnabledUpload,
210
138
  isModelEnabledVision,
211
139
  isModelHasMaxToken,
212
140
 
213
- modelMaxToken,
141
+ isProviderEnabled,
214
142
 
143
+ modelMaxToken,
215
144
  modelProviderList,
145
+
216
146
  modelProviderListForModelSelect,
147
+
148
+ remoteProviderModelCards,
149
+ serverProviderModelCards,
217
150
  };