@lobehub/chat 0.147.7 → 0.147.8

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 (44) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/src/app/api/config/__snapshots__/route.test.ts.snap +45 -43
  5. package/src/app/api/config/parseDefaultAgent.test.ts +1 -1
  6. package/src/app/api/config/route.test.ts +2 -10
  7. package/src/app/api/config/route.ts +37 -16
  8. package/src/app/api/openai/createBizOpenAI/index.ts +3 -13
  9. package/src/app/api/openai/images/route.ts +1 -1
  10. package/src/app/api/openai/stt/route.ts +1 -1
  11. package/src/app/api/openai/tts/route.ts +1 -1
  12. package/src/app/chat/(desktop)/features/ChatHeader/Tags.tsx +1 -1
  13. package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +1 -1
  14. package/src/app/settings/llm/Azure/index.tsx +2 -2
  15. package/src/app/settings/llm/components/ProviderModelList/ModelFetcher.tsx +7 -3
  16. package/src/app/settings/llm/components/ProviderModelList/Option.tsx +4 -1
  17. package/src/app/settings/llm/components/ProviderModelList/index.tsx +4 -4
  18. package/src/config/__tests__/server.test.ts +0 -12
  19. package/src/config/server/provider.ts +12 -8
  20. package/src/features/AgentSetting/AgentConfig/ModelSelect.tsx +5 -2
  21. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +1 -1
  22. package/src/features/ChatInput/ActionBar/FileUpload.tsx +2 -2
  23. package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +1 -1
  24. package/src/features/ChatInput/ActionBar/Token/index.tsx +1 -1
  25. package/src/features/ChatInput/ActionBar/Tools/index.tsx +1 -1
  26. package/src/features/ChatInput/useChatInput.ts +1 -1
  27. package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +3 -3
  28. package/src/features/ModelSwitchPanel/index.tsx +5 -2
  29. package/src/migrations/FromV3ToV4/fixtures/openai-output-v4.json +1 -3
  30. package/src/migrations/FromV3ToV4/fixtures/openrouter-output-v4.json +2 -6
  31. package/src/migrations/FromV3ToV4/index.ts +8 -2
  32. package/src/services/_auth.ts +1 -3
  33. package/src/services/chat.ts +4 -5
  34. package/src/store/global/slices/settings/actions/llm.test.ts +2 -2
  35. package/src/store/global/slices/settings/actions/llm.ts +3 -3
  36. package/src/store/global/slices/settings/selectors/modelConfig.test.ts +0 -88
  37. package/src/store/global/slices/settings/selectors/modelConfig.ts +17 -81
  38. package/src/store/global/slices/settings/selectors/modelProvider.test.ts +99 -15
  39. package/src/store/global/slices/settings/selectors/modelProvider.ts +94 -30
  40. package/src/store/global/slices/settings/selectors/settings.ts +7 -1
  41. package/src/types/serverConfig.ts +1 -0
  42. package/src/types/settings/modelProvider.ts +0 -2
  43. package/src/utils/parseModels.test.ts +146 -0
  44. package/src/utils/parseModels.ts +67 -15
@@ -46,7 +46,7 @@ const Tools = memo(() => {
46
46
  const { styles } = useStyles();
47
47
 
48
48
  const model = useSessionStore(agentSelectors.currentAgentModel);
49
- const enableFC = useGlobalStore(modelProviderSelectors.modelEnabledFunctionCall(model));
49
+ const enableFC = useGlobalStore(modelProviderSelectors.isModelEnabledFunctionCall(model));
50
50
 
51
51
  return (
52
52
  <>
@@ -15,7 +15,7 @@ export const useChatInput = () => {
15
15
  const onSend = useSendMessage();
16
16
 
17
17
  const model = useSessionStore(agentSelectors.currentAgentModel);
18
- const canUpload = useGlobalStore(modelProviderSelectors.modelEnabledUpload(model));
18
+ const canUpload = useGlobalStore(modelProviderSelectors.isModelEnabledUpload(model));
19
19
 
20
20
  const [loading, value, onInput, onStop] = useChatStore((s) => [
21
21
  !!s.chatLoadingId,
@@ -5,7 +5,7 @@ import { ReactNode, memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
7
  import { useGlobalStore } from '@/store/global';
8
- import { modelConfigSelectors } from '@/store/global/selectors';
8
+ import { settingsSelectors } from '@/store/global/selectors';
9
9
  import { GlobalLLMProviderKey } from '@/types/settings';
10
10
 
11
11
  import { FormAction } from '../style';
@@ -24,8 +24,8 @@ const ProviderApiKeyForm = memo<ProviderApiKeyFormProps>(
24
24
  const [showProxy, setShow] = useState(false);
25
25
 
26
26
  const [apiKey, proxyUrl, setConfig] = useGlobalStore((s) => [
27
- modelConfigSelectors.getConfigByProviderId(provider)(s)?.apiKey,
28
- modelConfigSelectors.getConfigByProviderId(provider)(s)?.endpoint,
27
+ settingsSelectors.providerConfig(provider)(s)?.apiKey,
28
+ settingsSelectors.providerConfig(provider)(s)?.endpoint,
29
29
  s.setModelProviderConfig,
30
30
  ]);
31
31
 
@@ -10,7 +10,7 @@ import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import { ModelItemRender, ProviderItemRender } from '@/components/ModelSelect';
12
12
  import { useGlobalStore } from '@/store/global';
13
- import { modelConfigSelectors } from '@/store/global/selectors';
13
+ import { modelProviderSelectors } from '@/store/global/selectors';
14
14
  import { useSessionStore } from '@/store/session';
15
15
  import { agentSelectors } from '@/store/session/selectors';
16
16
  import { ModelProviderCard } from '@/types/llm';
@@ -44,7 +44,10 @@ const ModelSwitchPanel = memo<PropsWithChildren>(({ children }) => {
44
44
  const updateAgentConfig = useSessionStore((s) => s.updateAgentConfig);
45
45
 
46
46
  const router = useRouter();
47
- const enabledList = useGlobalStore(modelConfigSelectors.providerListForModelSelect, isEqual);
47
+ const enabledList = useGlobalStore(
48
+ modelProviderSelectors.modelProviderListForModelSelect,
49
+ isEqual,
50
+ );
48
51
 
49
52
  const items = useMemo(() => {
50
53
  const getModelItems = (provider: ModelProviderCard) => {
@@ -54,9 +54,7 @@
54
54
  {
55
55
  "displayName": "deerercsds",
56
56
  "enabled": true,
57
- "functionCall": true,
58
- "id": "deerercsds",
59
- "vision": true
57
+ "id": "deerercsds"
60
58
  }
61
59
  ]
62
60
  }
@@ -48,16 +48,12 @@
48
48
  {
49
49
  "displayName": "01-ai/yi-34b-chat",
50
50
  "enabled": true,
51
- "functionCall": true,
52
- "id": "01-ai/yi-34b-chat",
53
- "vision": true
51
+ "id": "01-ai/yi-34b-chat"
54
52
  },
55
53
  {
56
54
  "displayName": "huggingfaceh4/zephyr-7b-beta",
57
55
  "enabled": true,
58
- "functionCall": true,
59
- "id": "huggingfaceh4/zephyr-7b-beta",
60
- "vision": true
56
+ "id": "huggingfaceh4/zephyr-7b-beta"
61
57
  }
62
58
  ]
63
59
  },
@@ -62,7 +62,10 @@ export class MigrationV3ToV4 implements Migration {
62
62
  };
63
63
  }
64
64
 
65
- const customModelCards = transformToChatModelCards(openai.customModelName, []);
65
+ const customModelCards = transformToChatModelCards({
66
+ defaultChatModels: [],
67
+ modelString: openai.customModelName,
68
+ });
66
69
 
67
70
  return {
68
71
  azure: {
@@ -81,7 +84,10 @@ export class MigrationV3ToV4 implements Migration {
81
84
  };
82
85
 
83
86
  static migrateProvider = (provider: V3LegacyConfig): V4ProviderConfig => {
84
- const customModelCards = transformToChatModelCards(provider.customModelName, []);
87
+ const customModelCards = transformToChatModelCards({
88
+ defaultChatModels: [],
89
+ modelString: provider.customModelName,
90
+ });
85
91
 
86
92
  return {
87
93
  apiKey: provider.apiKey,
@@ -35,9 +35,7 @@ export const getProviderAuthPayload = (provider: string) => {
35
35
  }
36
36
 
37
37
  default: {
38
- const config = modelConfigSelectors.getConfigByProviderId(provider)(
39
- useGlobalStore.getState(),
40
- );
38
+ const config = settingsSelectors.providerConfig(provider)(useGlobalStore.getState());
41
39
 
42
40
  return { apiKey: config?.apiKey, endpoint: config?.endpoint };
43
41
  }
@@ -9,7 +9,6 @@ import { filesSelectors, useFileStore } from '@/store/file';
9
9
  import { useGlobalStore } from '@/store/global';
10
10
  import {
11
11
  commonSelectors,
12
- modelConfigSelectors,
13
12
  modelProviderSelectors,
14
13
  preferenceSelectors,
15
14
  } from '@/store/global/selectors';
@@ -91,7 +90,7 @@ class ChatService {
91
90
  const filterTools = toolSelectors.enabledSchema(enabledPlugins)(useToolStore.getState());
92
91
 
93
92
  // check this model can use function call
94
- const canUseFC = modelProviderSelectors.modelEnabledFunctionCall(payload.model)(
93
+ const canUseFC = modelProviderSelectors.isModelEnabledFunctionCall(payload.model)(
95
94
  useGlobalStore.getState(),
96
95
  );
97
96
  // the rule that model can use tools:
@@ -137,7 +136,7 @@ class ChatService {
137
136
 
138
137
  // if the provider is Azure, get the deployment name as the request model
139
138
  if (provider === ModelProvider.Azure) {
140
- const chatModelCards = modelConfigSelectors.getModelCardsByProviderId(provider)(
139
+ const chatModelCards = modelProviderSelectors.getModelCardsById(provider)(
141
140
  useGlobalStore.getState(),
142
141
  );
143
142
 
@@ -257,7 +256,7 @@ class ChatService {
257
256
 
258
257
  if (imageList.length === 0) return m.content;
259
258
 
260
- const canUploadFile = modelProviderSelectors.modelEnabledUpload(model)(
259
+ const canUploadFile = modelProviderSelectors.isModelEnabledUpload(model)(
261
260
  useGlobalStore.getState(),
262
261
  );
263
262
 
@@ -292,7 +291,7 @@ class ChatService {
292
291
 
293
292
  return produce(postMessages, (draft) => {
294
293
  if (!tools || tools.length === 0) return;
295
- const hasFC = modelProviderSelectors.modelEnabledFunctionCall(model)(
294
+ const hasFC = modelProviderSelectors.isModelEnabledFunctionCall(model)(
296
295
  useGlobalStore.getState(),
297
296
  );
298
297
  if (!hasFC) return;
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { userService } from '@/services/user';
5
5
  import { useGlobalStore } from '@/store/global';
6
- import { modelConfigSelectors } from '@/store/global/slices/settings/selectors';
6
+ import { modelConfigSelectors, settingsSelectors } from '@/store/global/slices/settings/selectors';
7
7
  import { GeneralModelProviderConfig } from '@/types/settings';
8
8
 
9
9
  import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
@@ -46,7 +46,7 @@ describe('LLMSettingsSliceAction', () => {
46
46
  const payload: CustomModelCardDispatch = { type: 'add', modelCard: { id: 'test-id' } };
47
47
 
48
48
  // Mock the selector to return undefined
49
- vi.spyOn(modelConfigSelectors, 'getConfigByProviderId').mockReturnValue(() => undefined);
49
+ vi.spyOn(settingsSelectors, 'providerConfig').mockReturnValue(() => undefined);
50
50
  vi.spyOn(result.current, 'setModelProviderConfig');
51
51
 
52
52
  await act(async () => {
@@ -6,7 +6,7 @@ import { ChatModelCard } from '@/types/llm';
6
6
  import { GlobalLLMConfig, GlobalLLMProviderKey } from '@/types/settings';
7
7
 
8
8
  import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
9
- import { modelConfigSelectors } from '../selectors/modelConfig';
9
+ import { settingsSelectors } from '../selectors/settings';
10
10
 
11
11
  /**
12
12
  * 设置操作
@@ -37,7 +37,7 @@ export const llmSettingsSlice: StateCreator<
37
37
  LLMSettingsAction
38
38
  > = (set, get) => ({
39
39
  dispatchCustomModelCards: async (provider, payload) => {
40
- const prevState = modelConfigSelectors.getConfigByProviderId(provider)(get());
40
+ const prevState = settingsSelectors.providerConfig(provider)(get());
41
41
 
42
42
  if (!prevState) return;
43
43
 
@@ -47,7 +47,7 @@ export const llmSettingsSlice: StateCreator<
47
47
  },
48
48
 
49
49
  removeEnabledModels: async (provider, model) => {
50
- const config = modelConfigSelectors.getConfigByProviderId(provider)(get());
50
+ const config = settingsSelectors.providerConfig(provider)(get());
51
51
 
52
52
  await get().setModelProviderConfig(provider, {
53
53
  enabledModels: config?.enabledModels?.filter((s) => s !== model).filter(Boolean),
@@ -7,55 +7,6 @@ import { GlobalSettingsState, initialSettingsState } from '../initialState';
7
7
  import { modelConfigSelectors } from './modelConfig';
8
8
 
9
9
  describe('modelConfigSelectors', () => {
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 = modelConfigSelectors
23
- .providerListWithConfig(s)
24
- .find((r) => r.id === 'ollama');
25
-
26
- expect(ollamaList?.chatModels.find((c) => c.id === 'llava')).toEqual({
27
- displayName: 'LLaVA 7B',
28
- functionCall: false,
29
- enabled: true,
30
- id: 'llava',
31
- tokens: 4000,
32
- vision: true,
33
- });
34
- });
35
- it('with user custom models', () => {
36
- const s = merge(initialSettingsState, {
37
- settings: {
38
- languageModel: {
39
- perplexity: {
40
- customModelCards: [{ id: 'sonar-online', displayName: 'Sonar Online' }],
41
- },
42
- },
43
- },
44
- } as GlobalSettingsState) as unknown as GlobalStore;
45
-
46
- const providerList = modelConfigSelectors
47
- .providerListWithConfig(s)
48
- .find((r) => r.id === 'perplexity');
49
-
50
- expect(providerList?.chatModels.find((c) => c.id === 'sonar-online')).toEqual({
51
- id: 'sonar-online',
52
- displayName: 'Sonar Online',
53
- enabled: false,
54
- isCustom: true,
55
- });
56
- });
57
- });
58
-
59
10
  describe('isProviderEnabled', () => {
60
11
  it('should return true if provider is enabled', () => {
61
12
  const s = merge(initialSettingsState, {
@@ -82,45 +33,6 @@ describe('modelConfigSelectors', () => {
82
33
  });
83
34
  });
84
35
 
85
- describe('getModelCardsByProviderId', () => {
86
- it('should return model cards including custom model cards', () => {
87
- const s = merge(initialSettingsState, {
88
- settings: {
89
- languageModel: {
90
- perplexity: {
91
- customModelCards: [{ id: 'custom-model', displayName: 'Custom Model' }],
92
- },
93
- },
94
- },
95
- } as GlobalSettingsState) as unknown as GlobalStore;
96
-
97
- const modelCards = modelConfigSelectors.getModelCardsByProviderId('perplexity')(s);
98
-
99
- expect(modelCards).toContainEqual({
100
- id: 'custom-model',
101
- displayName: 'Custom Model',
102
- isCustom: true,
103
- });
104
- });
105
- });
106
-
107
- describe('providerListForModelSelect', () => {
108
- it('should return only enabled providers', () => {
109
- const s = merge(initialSettingsState, {
110
- settings: {
111
- languageModel: {
112
- perplexity: { enabled: true },
113
- azure: { enabled: false },
114
- },
115
- },
116
- } as GlobalSettingsState) as unknown as GlobalStore;
117
-
118
- const enabledProviders = modelConfigSelectors.providerListForModelSelect(s);
119
- expect(enabledProviders).toHaveLength(2);
120
- expect(enabledProviders[1].id).toBe('perplexity');
121
- });
122
- });
123
-
124
36
  describe('getCustomModelCardById', () => {
125
37
  it('should return the custom model card with the given id and provider', () => {
126
38
  const s = merge(initialSettingsState, {
@@ -1,76 +1,21 @@
1
- import { uniqBy } from 'lodash-es';
2
-
3
- import { ChatModelCard, ModelProviderCard } from '@/types/llm';
4
- import { GeneralModelProviderConfig, GlobalLLMProviderKey } from '@/types/settings';
1
+ import { GlobalLLMProviderKey } from '@/types/settings';
5
2
 
6
3
  import { GlobalStore } from '../../../store';
7
- import { modelProviderSelectors } from './modelProvider';
8
- import { currentSettings } from './settings';
9
-
10
- const getConfigByProviderId = (provider: string) => (s: GlobalStore) =>
11
- currentSettings(s).languageModel[provider as GlobalLLMProviderKey] as
12
- | GeneralModelProviderConfig
13
- | undefined;
14
-
15
- const getModeProviderById = (provider: string) => (s: GlobalStore) =>
16
- modelProviderSelectors.providerModelList(s).find((s) => s.id === provider);
4
+ import { currentLLMSettings, getProviderConfigById } from './settings';
17
5
 
18
6
  const isProviderEnabled = (provider: GlobalLLMProviderKey) => (s: GlobalStore) =>
19
- currentSettings(s).languageModel[provider]?.enabled || false;
20
-
21
- const getEnableModelsByProviderId = (provider: string) => (s: GlobalStore) => {
22
- if (!getConfigByProviderId(provider)(s)?.enabledModels) return;
23
-
24
- return getConfigByProviderId(provider)(s)?.enabledModels?.filter(Boolean);
25
- };
26
-
27
- const getModelCardsByProviderId =
28
- (provider: string) =>
29
- (s: GlobalStore): ChatModelCard[] => {
30
- const builtinCards = getModeProviderById(provider)(s)?.chatModels || [];
31
-
32
- const userCards = (getConfigByProviderId(provider)(s)?.customModelCards || []).map((model) => ({
33
- ...model,
34
- isCustom: true,
35
- }));
36
-
37
- return uniqBy([...userCards, ...builtinCards], 'id');
38
- };
7
+ getProviderConfigById(provider)(s)?.enabled || false;
39
8
 
40
9
  const getCustomModelCard =
41
10
  ({ id, provider }: { id?: string; provider?: string }) =>
42
11
  (s: GlobalStore) => {
43
12
  if (!provider) return;
44
13
 
45
- const config = getConfigByProviderId(provider)(s);
14
+ const config = getProviderConfigById(provider)(s);
46
15
 
47
16
  return config?.customModelCards?.find((m) => m.id === id);
48
17
  };
49
18
 
50
- const providerListWithConfig = (s: GlobalStore): ModelProviderCard[] =>
51
- modelProviderSelectors.providerModelList(s).map((list) => ({
52
- ...list,
53
- chatModels: getModelCardsByProviderId(list.id)(s)?.map((model) => {
54
- const models = getEnableModelsByProviderId(list.id)(s);
55
-
56
- if (!models) return model;
57
-
58
- return {
59
- ...model,
60
- enabled: models?.some((m) => m === model.id),
61
- };
62
- }),
63
- enabled: isProviderEnabled(list.id as any)(s),
64
- }));
65
-
66
- const providerListForModelSelect = (s: GlobalStore): ModelProviderCard[] =>
67
- providerListWithConfig(s)
68
- .filter((s) => s.enabled)
69
- .map((provider) => ({
70
- ...provider,
71
- chatModels: provider.chatModels.filter((model) => model.enabled),
72
- }));
73
-
74
19
  const currentEditingCustomModelCard = (s: GlobalStore) => {
75
20
  if (!s.editingCustomCardModel) return;
76
21
  const { id, provider } = s.editingCustomCardModel;
@@ -81,36 +26,27 @@ const currentEditingCustomModelCard = (s: GlobalStore) => {
81
26
  const isAutoFetchModelsEnabled =
82
27
  (provider: GlobalLLMProviderKey) =>
83
28
  (s: GlobalStore): boolean => {
84
- return getConfigByProviderId(provider)(s)?.autoFetchModelLists || false;
29
+ return getProviderConfigById(provider)(s)?.autoFetchModelLists || false;
85
30
  };
86
31
 
87
- const llmSettings = (s: GlobalStore) => currentSettings(s).languageModel;
88
-
89
- const openAIConfig = (s: GlobalStore) => llmSettings(s).openai;
90
- const bedrockConfig = (s: GlobalStore) => llmSettings(s).bedrock;
91
- const ollamaConfig = (s: GlobalStore) => llmSettings(s).ollama;
92
- const azureConfig = (s: GlobalStore) => llmSettings(s).azure;
32
+ const openAIConfig = (s: GlobalStore) => currentLLMSettings(s).openai;
33
+ const bedrockConfig = (s: GlobalStore) => currentLLMSettings(s).bedrock;
34
+ const ollamaConfig = (s: GlobalStore) => currentLLMSettings(s).ollama;
35
+ const azureConfig = (s: GlobalStore) => currentLLMSettings(s).azure;
93
36
 
94
- const isAzureEnabled = (s: GlobalStore) => llmSettings(s).azure.enabled;
37
+ const isAzureEnabled = (s: GlobalStore) => currentLLMSettings(s).azure.enabled;
95
38
 
96
- /* eslint-disable sort-keys-fix/sort-keys-fix, */
97
39
  export const modelConfigSelectors = {
98
- isAutoFetchModelsEnabled,
99
- isProviderEnabled,
100
- currentEditingCustomModelCard,
40
+ azureConfig,
41
+ bedrockConfig,
101
42
 
102
- getConfigByProviderId,
103
- getEnableModelsByProviderId,
104
- getModelCardsByProviderId,
43
+ currentEditingCustomModelCard,
105
44
  getCustomModelCard,
106
45
 
107
- providerListWithConfig,
108
- providerListForModelSelect,
109
-
110
- openAIConfig,
111
- azureConfig,
112
- bedrockConfig,
46
+ isAutoFetchModelsEnabled,
47
+ isAzureEnabled,
48
+ isProviderEnabled,
113
49
  ollamaConfig,
114
50
 
115
- isAzureEnabled,
51
+ openAIConfig,
116
52
  };
@@ -1,54 +1,138 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { DEFAULT_SETTINGS } from '@/const/settings';
4
- import { agentSelectors } from '@/store/session/slices/agent';
5
3
  import { merge } from '@/utils/merge';
6
4
 
7
5
  import { GlobalStore, useGlobalStore } from '../../../store';
8
- import { initialSettingsState } from '../initialState';
9
- import { modelProviderSelectors } from './modelProvider';
6
+ import { GlobalSettingsState, initialSettingsState } from '../initialState';
7
+ import { getDefaultModeProviderById, modelProviderSelectors } from './modelProvider';
10
8
 
11
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
+
12
74
  describe('providerCard', () => {
13
75
  it('should return the correct ModelProviderCard when provider ID matches', () => {
14
76
  const s = merge(initialSettingsState, {}) as unknown as GlobalStore;
15
77
 
16
- const result = modelProviderSelectors.providerCard('openai')(s);
78
+ const result = getDefaultModeProviderById('openai')(s);
17
79
  expect(result).not.toBeUndefined();
18
80
  });
19
81
 
20
82
  it('should return undefined when provider ID does not exist', () => {
21
83
  const s = merge(initialSettingsState, {}) as unknown as GlobalStore;
22
- const result = modelProviderSelectors.providerCard('nonExistingProvider')(s);
84
+ const result = getDefaultModeProviderById('nonExistingProvider')(s);
23
85
  expect(result).toBeUndefined();
24
86
  });
25
87
  });
26
88
 
89
+ describe('getModelCardsById', () => {
90
+ it('should return model cards including custom model cards', () => {
91
+ const s = merge(initialSettingsState, {
92
+ settings: {
93
+ languageModel: {
94
+ perplexity: {
95
+ customModelCards: [{ id: 'custom-model', displayName: 'Custom Model' }],
96
+ },
97
+ },
98
+ },
99
+ } as GlobalSettingsState) as unknown as GlobalStore;
100
+
101
+ const modelCards = modelProviderSelectors.getModelCardsById('perplexity')(s);
102
+
103
+ expect(modelCards).toContainEqual({
104
+ id: 'custom-model',
105
+ displayName: 'Custom Model',
106
+ isCustom: true,
107
+ });
108
+ });
109
+ });
110
+
27
111
  describe('defaultEnabledProviderModels', () => {
28
112
  it('should return enabled models for a given provider', () => {
29
113
  const s = merge(initialSettingsState, {}) as unknown as GlobalStore;
30
114
 
31
- const result = modelProviderSelectors.defaultEnabledProviderModels('openai')(s);
115
+ const result = modelProviderSelectors.getDefaultEnabledModelsById('openai')(s);
32
116
  expect(result).toEqual(['gpt-3.5-turbo', 'gpt-4-turbo']);
33
117
  });
34
118
 
35
119
  it('should return undefined for a non-existing provider', () => {
36
120
  const s = merge(initialSettingsState, {}) as unknown as GlobalStore;
37
121
 
38
- const result = modelProviderSelectors.defaultEnabledProviderModels('nonExistingProvider')(s);
122
+ const result = modelProviderSelectors.getDefaultEnabledModelsById('nonExistingProvider')(s);
39
123
  expect(result).toBeUndefined();
40
124
  });
41
125
  });
42
126
  describe('modelEnabledVision', () => {
43
127
  it('should return true if the model has vision ability', () => {
44
- const hasAbility = modelProviderSelectors.modelEnabledVision('gpt-4-vision-preview')(
128
+ const hasAbility = modelProviderSelectors.isModelEnabledVision('gpt-4-vision-preview')(
45
129
  useGlobalStore.getState(),
46
130
  );
47
131
  expect(hasAbility).toBeTruthy();
48
132
  });
49
133
 
50
134
  it('should return false if the model does not have vision ability', () => {
51
- const hasAbility = modelProviderSelectors.modelEnabledVision('some-other-model')(
135
+ const hasAbility = modelProviderSelectors.isModelEnabledVision('some-other-model')(
52
136
  useGlobalStore.getState(),
53
137
  );
54
138
 
@@ -56,7 +140,7 @@ describe('modelProviderSelectors', () => {
56
140
  });
57
141
 
58
142
  it('should return false if the model include vision in id', () => {
59
- const hasAbility = modelProviderSelectors.modelEnabledVision('some-other-model-vision')(
143
+ const hasAbility = modelProviderSelectors.isModelEnabledVision('some-other-model-vision')(
60
144
  useGlobalStore.getState(),
61
145
  );
62
146
 
@@ -66,14 +150,14 @@ describe('modelProviderSelectors', () => {
66
150
 
67
151
  describe('modelEnabledFiles', () => {
68
152
  it('should return false if the model does not have file ability', () => {
69
- const enabledFiles = modelProviderSelectors.modelEnabledFiles('gpt-4-vision-preview')(
153
+ const enabledFiles = modelProviderSelectors.isModelEnabledFiles('gpt-4-vision-preview')(
70
154
  useGlobalStore.getState(),
71
155
  );
72
156
  expect(enabledFiles).toBeFalsy();
73
157
  });
74
158
 
75
159
  it('should return true if the model has file ability', () => {
76
- const enabledFiles = modelProviderSelectors.modelEnabledFiles('gpt-4-all')(
160
+ const enabledFiles = modelProviderSelectors.isModelEnabledFiles('gpt-4-all')(
77
161
  useGlobalStore.getState(),
78
162
  );
79
163
  expect(enabledFiles).toBeTruthy();
@@ -82,14 +166,14 @@ describe('modelProviderSelectors', () => {
82
166
 
83
167
  describe('modelHasMaxToken', () => {
84
168
  it('should return true if the model is in the list of models that show tokens', () => {
85
- const show = modelProviderSelectors.modelHasMaxToken('gpt-3.5-turbo')(
169
+ const show = modelProviderSelectors.isModelHasMaxToken('gpt-3.5-turbo')(
86
170
  useGlobalStore.getState(),
87
171
  );
88
172
  expect(show).toBeTruthy();
89
173
  });
90
174
 
91
175
  it('should return false if the model is not in the list of models that show tokens', () => {
92
- const show = modelProviderSelectors.modelHasMaxToken('some-other-model')(
176
+ const show = modelProviderSelectors.isModelHasMaxToken('some-other-model')(
93
177
  useGlobalStore.getState(),
94
178
  );
95
179
  expect(show).toBe(false);