@lobehub/chat 0.161.8 → 0.161.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 +50 -0
- package/docs/self-hosting/advanced/feature-flags.mdx +45 -0
- package/docs/self-hosting/advanced/feature-flags.zh-CN.mdx +42 -0
- package/docs/self-hosting/environment-variables/basic.mdx +11 -2
- package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +11 -2
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/DragUpload.tsx +92 -42
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Desktop/Footer/index.tsx +2 -2
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatInput/Mobile/Files.tsx +4 -3
- package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +5 -2
- package/src/app/(main)/settings/llm/components/ProviderModelList/CustomModelOption.tsx +6 -7
- package/src/app/(main)/settings/llm/components/ProviderModelList/{ModelConfigModal.tsx → ModelConfigModal/Form.tsx} +19 -63
- package/src/app/(main)/settings/llm/components/ProviderModelList/ModelConfigModal/index.tsx +78 -0
- package/src/app/(main)/settings/llm/components/ProviderModelList/Option.tsx +35 -11
- package/src/app/(main)/settings/llm/components/ProviderModelList/index.tsx +15 -18
- package/src/app/(main)/settings/system-agent/features/Translation.tsx +0 -2
- package/src/components/FileList/ImageFileItem.tsx +1 -1
- package/src/components/ModelProviderIcon/index.tsx +2 -2
- package/src/components/ModelSelect/index.tsx +5 -14
- package/src/const/meta.ts +1 -2
- package/src/features/User/UserPanel/PanelContent.tsx +1 -1
- package/src/hooks/useSyncData.ts +3 -1
- package/src/layout/AuthProvider/Clerk/useAppearance.ts +1 -1
- package/src/layout/GlobalProvider/StoreInitialization.tsx +17 -9
- package/src/layout/GlobalProvider/index.tsx +1 -1
- package/src/locales/default/components.ts +1 -0
- package/src/services/message/client.test.ts +0 -24
- package/src/services/message/client.ts +0 -5
- package/src/services/message/type.ts +0 -1
- package/src/services/user/client.test.ts +100 -0
- package/src/services/user/client.ts +16 -14
- package/src/services/user/index.ts +0 -2
- package/src/services/user/type.ts +2 -4
- package/src/store/user/initialState.ts +10 -1
- package/src/store/user/selectors.ts +3 -7
- package/src/store/user/slices/auth/action.test.ts +5 -87
- package/src/store/user/slices/auth/action.ts +3 -58
- package/src/store/user/slices/auth/initialState.ts +2 -1
- package/src/store/user/slices/common/action.test.ts +196 -20
- package/src/store/user/slices/common/action.ts +55 -26
- package/src/store/user/slices/common/initialState.ts +9 -0
- package/src/store/user/slices/modelList/action.test.ts +363 -0
- package/src/store/user/slices/{settings/actions/llm.ts → modelList/action.ts} +66 -60
- package/src/store/user/slices/modelList/initialState.ts +15 -0
- package/src/store/user/slices/modelList/selectors/index.ts +2 -0
- package/src/store/user/slices/{settings → modelList}/selectors/modelConfig.test.ts +3 -2
- package/src/store/user/slices/{settings → modelList}/selectors/modelConfig.ts +1 -1
- package/src/store/user/slices/{settings → modelList}/selectors/modelProvider.test.ts +7 -7
- package/src/store/user/slices/{settings → modelList}/selectors/modelProvider.ts +2 -4
- package/src/store/user/slices/preference/action.test.ts +0 -52
- package/src/store/user/slices/preference/action.ts +1 -17
- package/src/store/user/slices/preference/initialState.ts +0 -5
- package/src/store/user/slices/preference/selectors.test.ts +2 -2
- package/src/store/user/slices/preference/selectors.ts +1 -1
- package/src/store/user/slices/settings/{actions/general.ts → action.ts} +5 -5
- package/src/store/user/slices/settings/initialState.ts +0 -12
- package/src/store/user/slices/settings/selectors/index.ts +0 -3
- package/src/store/user/slices/sync/action.test.ts +19 -5
- package/src/store/user/slices/sync/action.ts +9 -6
- package/src/store/user/slices/{settings/selectors/sync.ts → sync/selectors.ts} +2 -2
- package/src/store/user/store.ts +5 -2
- package/src/styles/antdOverride.ts +6 -0
- package/src/types/serverConfig.ts +3 -1
- package/src/types/user/index.ts +13 -0
- package/src/utils/parseModels.test.ts +121 -1
- package/src/utils/parseModels.ts +9 -4
- package/src/store/user/slices/settings/actions/index.ts +0 -18
- package/src/store/user/slices/settings/actions/llm.test.ts +0 -136
- /package/src/app/(main)/settings/llm/components/ProviderModelList/{MaxTokenSlider.tsx → ModelConfigModal/MaxTokenSlider.tsx} +0 -0
- /package/src/store/user/slices/{settings → modelList}/reducers/customModelCard.test.ts +0 -0
- /package/src/store/user/slices/{settings → modelList}/reducers/customModelCard.ts +0 -0
- /package/src/store/user/slices/settings/{actions/general.test.ts → action.test.ts} +0 -0
- /package/src/store/user/slices/settings/selectors/__snapshots__/{selectors.test.ts.snap → settings.test.ts.snap} +0 -0
- /package/src/store/user/slices/settings/selectors/{selectors.test.ts → settings.test.ts} +0 -0
|
@@ -40,56 +40,4 @@ describe('createPreferenceSlice', () => {
|
|
|
40
40
|
expect(result.current.preference.hideSyncAlert).toEqual(true);
|
|
41
41
|
});
|
|
42
42
|
});
|
|
43
|
-
|
|
44
|
-
describe('useInitPreference', () => {
|
|
45
|
-
it('should return false when userId is empty', async () => {
|
|
46
|
-
const { result } = renderHook(() => useUserStore());
|
|
47
|
-
|
|
48
|
-
vi.spyOn(userService, 'getPreference').mockResolvedValueOnce({} as any);
|
|
49
|
-
|
|
50
|
-
const { result: prefernce } = renderHook(() => result.current.useInitPreference(), {
|
|
51
|
-
wrapper: withSWR,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await waitFor(() => {
|
|
55
|
-
expect(prefernce.current.data).toEqual({});
|
|
56
|
-
expect(result.current.isPreferenceInit).toBeTruthy();
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
it('should return default preference when local storage is empty', async () => {
|
|
60
|
-
const { result } = renderHook(() => useUserStore());
|
|
61
|
-
|
|
62
|
-
vi.spyOn(userService, 'getPreference').mockResolvedValueOnce({} as any);
|
|
63
|
-
|
|
64
|
-
renderHook(() => result.current.useInitPreference(), {
|
|
65
|
-
wrapper: withSWR,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await waitFor(() => {
|
|
69
|
-
expect(result.current.preference).toEqual(DEFAULT_PREFERENCE);
|
|
70
|
-
expect(result.current.isPreferenceInit).toBeTruthy();
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('should return saved preference when local storage has data', async () => {
|
|
75
|
-
const { result } = renderHook(() => useUserStore());
|
|
76
|
-
const savedPreference: UserPreference = {
|
|
77
|
-
...DEFAULT_PREFERENCE,
|
|
78
|
-
hideSyncAlert: true,
|
|
79
|
-
guide: { topic: false, moveSettingsToAvatar: true },
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
vi.spyOn(userService, 'getPreference').mockResolvedValueOnce(savedPreference);
|
|
83
|
-
|
|
84
|
-
const { result: prefernce } = renderHook(() => result.current.useInitPreference(), {
|
|
85
|
-
wrapper: withSWR,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
await waitFor(() => {
|
|
89
|
-
expect(prefernce.current.data).toEqual(savedPreference);
|
|
90
|
-
expect(result.current.isPreferenceInit).toBeTruthy();
|
|
91
|
-
expect(result.current.preference).toEqual(savedPreference);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
43
|
});
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import { SWRResponse } from 'swr';
|
|
2
1
|
import type { StateCreator } from 'zustand/vanilla';
|
|
3
2
|
|
|
4
|
-
import { DEFAULT_PREFERENCE } from '@/const/user';
|
|
5
|
-
import { useClientDataSWR } from '@/libs/swr';
|
|
6
3
|
import { userService } from '@/services/user';
|
|
7
4
|
import type { UserStore } from '@/store/user';
|
|
8
5
|
import { UserGuide, UserPreference } from '@/types/user';
|
|
@@ -14,7 +11,6 @@ const n = setNamespace('preference');
|
|
|
14
11
|
export interface PreferenceAction {
|
|
15
12
|
updateGuideState: (guide: Partial<UserGuide>) => Promise<void>;
|
|
16
13
|
updatePreference: (preference: Partial<UserPreference>, action?: any) => Promise<void>;
|
|
17
|
-
useInitPreference: () => SWRResponse;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
export const createPreferenceSlice: StateCreator<
|
|
@@ -28,6 +24,7 @@ export const createPreferenceSlice: StateCreator<
|
|
|
28
24
|
const nextGuide = merge(get().preference.guide, guide);
|
|
29
25
|
await updatePreference({ guide: nextGuide });
|
|
30
26
|
},
|
|
27
|
+
|
|
31
28
|
updatePreference: async (preference, action) => {
|
|
32
29
|
const nextPreference = merge(get().preference, preference);
|
|
33
30
|
|
|
@@ -35,17 +32,4 @@ export const createPreferenceSlice: StateCreator<
|
|
|
35
32
|
|
|
36
33
|
await userService.updatePreference(nextPreference);
|
|
37
34
|
},
|
|
38
|
-
|
|
39
|
-
useInitPreference: () =>
|
|
40
|
-
useClientDataSWR<UserPreference>('initUserPreference', userService.getPreference, {
|
|
41
|
-
onSuccess: (preference) => {
|
|
42
|
-
const isEmpty = Object.keys(preference).length === 0;
|
|
43
|
-
|
|
44
|
-
set(
|
|
45
|
-
{ isPreferenceInit: true, preference: isEmpty ? DEFAULT_PREFERENCE : preference },
|
|
46
|
-
false,
|
|
47
|
-
n('initPreference'),
|
|
48
|
-
);
|
|
49
|
-
},
|
|
50
|
-
}),
|
|
51
35
|
});
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
import { DEFAULT_PREFERENCE } from '@/const/user';
|
|
2
2
|
import { UserPreference } from '@/types/user';
|
|
3
|
-
import { AsyncLocalStorage } from '@/utils/localStorage';
|
|
4
3
|
|
|
5
4
|
export interface UserPreferenceState {
|
|
6
|
-
isPreferenceInit: boolean;
|
|
7
5
|
/**
|
|
8
6
|
* the user preference, which only store in local storage
|
|
9
7
|
*/
|
|
10
8
|
preference: UserPreference;
|
|
11
|
-
preferenceStorage: AsyncLocalStorage<UserPreference>;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
export const initialPreferenceState: UserPreferenceState = {
|
|
15
|
-
isPreferenceInit: false,
|
|
16
12
|
preference: DEFAULT_PREFERENCE,
|
|
17
|
-
preferenceStorage: new AsyncLocalStorage('LOBE_PREFERENCE'),
|
|
18
13
|
};
|
|
@@ -72,10 +72,10 @@ describe('preferenceSelectors', () => {
|
|
|
72
72
|
|
|
73
73
|
describe('isPreferenceInit', () => {
|
|
74
74
|
it('should return the value of isPreferenceInit state', () => {
|
|
75
|
-
store.
|
|
75
|
+
store.isUserStateInit = true;
|
|
76
76
|
expect(preferenceSelectors.isPreferenceInit(store)).toBe(true);
|
|
77
77
|
|
|
78
|
-
store.
|
|
78
|
+
store.isUserStateInit = false;
|
|
79
79
|
expect(preferenceSelectors.isPreferenceInit(store)).toBe(false);
|
|
80
80
|
});
|
|
81
81
|
});
|
|
@@ -8,7 +8,7 @@ const hideSyncAlert = (s: UserStore) => s.preference.hideSyncAlert;
|
|
|
8
8
|
|
|
9
9
|
const hideSettingsMoveGuide = (s: UserStore) => s.preference.guide?.moveSettingsToAvatar;
|
|
10
10
|
|
|
11
|
-
const isPreferenceInit = (s: UserStore) => s.
|
|
11
|
+
const isPreferenceInit = (s: UserStore) => s.isUserStateInit;
|
|
12
12
|
|
|
13
13
|
export const preferenceSelectors = {
|
|
14
14
|
hideSettingsMoveGuide,
|
|
@@ -12,7 +12,7 @@ import { switchLang } from '@/utils/client/switchLang';
|
|
|
12
12
|
import { difference } from '@/utils/difference';
|
|
13
13
|
import { merge } from '@/utils/merge';
|
|
14
14
|
|
|
15
|
-
export interface
|
|
15
|
+
export interface UserSettingsAction {
|
|
16
16
|
importAppSettings: (settings: GlobalSettings) => Promise<void>;
|
|
17
17
|
resetSettings: () => Promise<void>;
|
|
18
18
|
setSettings: (settings: DeepPartial<GlobalSettings>) => Promise<void>;
|
|
@@ -22,11 +22,11 @@ export interface GeneralSettingsAction {
|
|
|
22
22
|
updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export const
|
|
25
|
+
export const createSettingsSlice: StateCreator<
|
|
26
26
|
UserStore,
|
|
27
27
|
[['zustand/devtools', never]],
|
|
28
28
|
[],
|
|
29
|
-
|
|
29
|
+
UserSettingsAction
|
|
30
30
|
> = (set, get) => ({
|
|
31
31
|
importAppSettings: async (importAppSettings) => {
|
|
32
32
|
const { setSettings } = get();
|
|
@@ -37,7 +37,7 @@ export const generalSettingsSlice: StateCreator<
|
|
|
37
37
|
},
|
|
38
38
|
resetSettings: async () => {
|
|
39
39
|
await userService.resetUserSettings();
|
|
40
|
-
await get().
|
|
40
|
+
await get().refreshUserState();
|
|
41
41
|
},
|
|
42
42
|
setSettings: async (settings) => {
|
|
43
43
|
const { settings: prevSetting, defaultSettings } = get();
|
|
@@ -49,7 +49,7 @@ export const generalSettingsSlice: StateCreator<
|
|
|
49
49
|
const diffs = difference(nextSettings, defaultSettings);
|
|
50
50
|
|
|
51
51
|
await userService.updateUserSettings(diffs);
|
|
52
|
-
await get().
|
|
52
|
+
await get().refreshUserState();
|
|
53
53
|
},
|
|
54
54
|
setTranslationSystemAgent: async (provider, model) => {
|
|
55
55
|
await get().setSettings({
|
|
@@ -1,26 +1,14 @@
|
|
|
1
1
|
import { DeepPartial } from 'utility-types';
|
|
2
2
|
|
|
3
|
-
import { DEFAULT_MODEL_PROVIDER_LIST } from '@/config/modelProviders';
|
|
4
3
|
import { DEFAULT_SETTINGS } from '@/const/settings';
|
|
5
|
-
import { ModelProviderCard } from '@/types/llm';
|
|
6
|
-
import { GlobalServerConfig } from '@/types/serverConfig';
|
|
7
4
|
import { GlobalSettings } from '@/types/settings';
|
|
8
5
|
|
|
9
6
|
export interface UserSettingsState {
|
|
10
|
-
defaultModelProviderList: ModelProviderCard[];
|
|
11
7
|
defaultSettings: GlobalSettings;
|
|
12
|
-
editingCustomCardModel?: { id: string; provider: string } | undefined;
|
|
13
|
-
modelProviderList: ModelProviderCard[];
|
|
14
|
-
serverConfig: GlobalServerConfig;
|
|
15
8
|
settings: DeepPartial<GlobalSettings>;
|
|
16
9
|
}
|
|
17
10
|
|
|
18
11
|
export const initialSettingsState: UserSettingsState = {
|
|
19
|
-
defaultModelProviderList: DEFAULT_MODEL_PROVIDER_LIST,
|
|
20
12
|
defaultSettings: DEFAULT_SETTINGS,
|
|
21
|
-
modelProviderList: DEFAULT_MODEL_PROVIDER_LIST,
|
|
22
|
-
serverConfig: {
|
|
23
|
-
telemetry: {},
|
|
24
|
-
},
|
|
25
13
|
settings: {},
|
|
26
14
|
};
|
|
@@ -107,9 +107,17 @@ describe('createSyncSlice', () => {
|
|
|
107
107
|
|
|
108
108
|
describe('useEnabledSync', () => {
|
|
109
109
|
it('should return false when userId is empty', async () => {
|
|
110
|
-
const { result } = renderHook(
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
const { result } = renderHook(
|
|
111
|
+
() =>
|
|
112
|
+
useUserStore().useEnabledSync(true, {
|
|
113
|
+
userEnableSync: true,
|
|
114
|
+
userId: undefined,
|
|
115
|
+
onEvent: vi.fn(),
|
|
116
|
+
}),
|
|
117
|
+
{
|
|
118
|
+
wrapper: withSWR,
|
|
119
|
+
},
|
|
120
|
+
);
|
|
113
121
|
|
|
114
122
|
await waitFor(() => expect(result.current.data).toBe(false));
|
|
115
123
|
});
|
|
@@ -118,7 +126,13 @@ describe('createSyncSlice', () => {
|
|
|
118
126
|
const disableSyncSpy = vi.spyOn(syncService, 'disableSync').mockResolvedValueOnce(false);
|
|
119
127
|
|
|
120
128
|
const { result } = renderHook(
|
|
121
|
-
() =>
|
|
129
|
+
() =>
|
|
130
|
+
useUserStore().useEnabledSync(true, {
|
|
131
|
+
userEnableSync: false,
|
|
132
|
+
userId: 'user-id',
|
|
133
|
+
onEvent: vi.fn(),
|
|
134
|
+
}),
|
|
135
|
+
|
|
122
136
|
{ wrapper: withSWR },
|
|
123
137
|
);
|
|
124
138
|
|
|
@@ -137,7 +151,7 @@ describe('createSyncSlice', () => {
|
|
|
137
151
|
result.current.triggerEnableSync = triggerEnableSyncSpy;
|
|
138
152
|
|
|
139
153
|
const { result: swrResult } = renderHook(
|
|
140
|
-
() => result.current.useEnabledSync(true, userId, onEvent),
|
|
154
|
+
() => result.current.useEnabledSync(true, { userEnableSync: true, userId, onEvent }),
|
|
141
155
|
{
|
|
142
156
|
wrapper: withSWR,
|
|
143
157
|
},
|
|
@@ -8,7 +8,7 @@ import { browserInfo } from '@/utils/platform';
|
|
|
8
8
|
import { setNamespace } from '@/utils/storeDebug';
|
|
9
9
|
|
|
10
10
|
import { userProfileSelectors } from '../auth/selectors';
|
|
11
|
-
import { syncSettingsSelectors } from '
|
|
11
|
+
import { syncSettingsSelectors } from './selectors';
|
|
12
12
|
|
|
13
13
|
const n = setNamespace('sync');
|
|
14
14
|
|
|
@@ -19,9 +19,12 @@ export interface SyncAction {
|
|
|
19
19
|
refreshConnection: (onEvent: OnSyncEvent) => Promise<void>;
|
|
20
20
|
triggerEnableSync: (userId: string, onEvent: OnSyncEvent) => Promise<boolean>;
|
|
21
21
|
useEnabledSync: (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
systemEnable: boolean | undefined,
|
|
23
|
+
params: {
|
|
24
|
+
onEvent: OnSyncEvent;
|
|
25
|
+
userEnableSync: boolean;
|
|
26
|
+
userId: string | undefined;
|
|
27
|
+
},
|
|
25
28
|
) => SWRResponse;
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -72,9 +75,9 @@ export const createSyncSlice: StateCreator<
|
|
|
72
75
|
});
|
|
73
76
|
},
|
|
74
77
|
|
|
75
|
-
useEnabledSync: (userEnableSync, userId, onEvent) =>
|
|
78
|
+
useEnabledSync: (systemEnable, { userEnableSync, userId, onEvent }) =>
|
|
76
79
|
useSWR<boolean>(
|
|
77
|
-
['enableSync', userEnableSync, userId],
|
|
80
|
+
systemEnable ? ['enableSync', userEnableSync, userId] : null,
|
|
78
81
|
async () => {
|
|
79
82
|
// if user don't enable sync or no userId ,don't start sync
|
|
80
83
|
if (!userId) return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { UserStore } from '
|
|
2
|
-
import { currentSettings } from '
|
|
1
|
+
import { UserStore } from '../../store';
|
|
2
|
+
import { currentSettings } from '../settings/selectors/settings';
|
|
3
3
|
|
|
4
4
|
const webrtcConfig = (s: UserStore) => currentSettings(s).sync.webrtc;
|
|
5
5
|
const webrtcChannelName = (s: UserStore) => webrtcConfig(s).channelName;
|
package/src/store/user/store.ts
CHANGED
|
@@ -8,16 +8,18 @@ import { isDev } from '@/utils/env';
|
|
|
8
8
|
import { type UserState, initialState } from './initialState';
|
|
9
9
|
import { type UserAuthAction, createAuthSlice } from './slices/auth/action';
|
|
10
10
|
import { type CommonAction, createCommonSlice } from './slices/common/action';
|
|
11
|
+
import { type ModelListAction, createModelListSlice } from './slices/modelList/action';
|
|
11
12
|
import { type PreferenceAction, createPreferenceSlice } from './slices/preference/action';
|
|
12
|
-
import { type
|
|
13
|
+
import { type UserSettingsAction, createSettingsSlice } from './slices/settings/action';
|
|
13
14
|
import { type SyncAction, createSyncSlice } from './slices/sync/action';
|
|
14
15
|
|
|
15
16
|
// =============== 聚合 createStoreFn ============ //
|
|
16
17
|
|
|
17
18
|
export type UserStore = SyncAction &
|
|
18
19
|
UserState &
|
|
19
|
-
|
|
20
|
+
UserSettingsAction &
|
|
20
21
|
PreferenceAction &
|
|
22
|
+
ModelListAction &
|
|
21
23
|
UserAuthAction &
|
|
22
24
|
CommonAction;
|
|
23
25
|
|
|
@@ -28,6 +30,7 @@ const createStore: StateCreator<UserStore, [['zustand/devtools', never]]> = (...
|
|
|
28
30
|
...createPreferenceSlice(...parameters),
|
|
29
31
|
...createAuthSlice(...parameters),
|
|
30
32
|
...createCommonSlice(...parameters),
|
|
33
|
+
...createModelListSlice(...parameters),
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
// =============== 实装 useStore ============ //
|
|
@@ -9,4 +9,10 @@ export default ({ token }: { prefixCls: string; token: Theme }) => css`
|
|
|
9
9
|
border: 1px solid ${token.colorBorder};
|
|
10
10
|
box-shadow: ${token.boxShadow};
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
.${token.prefixCls}-menu-item-selected {
|
|
14
|
+
.${token.prefixCls}-menu-title-content {
|
|
15
|
+
color: ${token.colorText};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
12
18
|
`;
|
|
@@ -13,12 +13,14 @@ export interface ServerModelProviderConfig {
|
|
|
13
13
|
serverModelCards?: ChatModelCard[];
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export type ServerLanguageModel = Partial<Record<GlobalLLMProviderKey, ServerModelProviderConfig>>;
|
|
17
|
+
|
|
16
18
|
export interface GlobalServerConfig {
|
|
17
19
|
defaultAgent?: DeepPartial<GlobalDefaultAgent>;
|
|
18
20
|
enableUploadFileToServer?: boolean;
|
|
19
21
|
enabledAccessCode?: boolean;
|
|
20
22
|
enabledOAuthSSO?: boolean;
|
|
21
|
-
languageModel?:
|
|
23
|
+
languageModel?: ServerLanguageModel;
|
|
22
24
|
telemetry: {
|
|
23
25
|
langfuse?: boolean;
|
|
24
26
|
};
|
package/src/types/user/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { DeepPartial } from 'utility-types';
|
|
2
|
+
|
|
3
|
+
import { GlobalSettings } from '@/types/settings';
|
|
4
|
+
|
|
1
5
|
export interface LobeUser {
|
|
2
6
|
avatar?: string;
|
|
3
7
|
email?: string | null;
|
|
@@ -27,3 +31,12 @@ export interface UserPreference {
|
|
|
27
31
|
*/
|
|
28
32
|
useCmdEnterToSend?: boolean;
|
|
29
33
|
}
|
|
34
|
+
|
|
35
|
+
export interface UserInitializationState {
|
|
36
|
+
avatar?: string;
|
|
37
|
+
canEnableTrace?: boolean;
|
|
38
|
+
isOnboard?: boolean;
|
|
39
|
+
preference: UserPreference;
|
|
40
|
+
settings: DeepPartial<GlobalSettings>;
|
|
41
|
+
userId: string;
|
|
42
|
+
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { LOBE_DEFAULT_MODEL_LIST, OpenAIProviderCard } from '@/config/modelProviders';
|
|
4
|
+
import { ChatModelCard } from '@/types/llm';
|
|
5
|
+
|
|
6
|
+
import { parseModelString, transformToChatModelCards } from './parseModels';
|
|
4
7
|
|
|
5
8
|
describe('parseModelString', () => {
|
|
6
9
|
it('custom deletion, addition, and renaming of models', () => {
|
|
@@ -67,6 +70,29 @@ describe('parseModelString', () => {
|
|
|
67
70
|
]);
|
|
68
71
|
});
|
|
69
72
|
|
|
73
|
+
it('should have file with builtin models like gpt-4-0125-preview', () => {
|
|
74
|
+
const result = parseModelString(
|
|
75
|
+
'-all,+gpt-4-0125-preview=ChatGPT-4<128000:fc:file>,+gpt-4-turbo-2024-04-09=ChatGPT-4 Vision<128000:fc:vision:file>',
|
|
76
|
+
);
|
|
77
|
+
expect(result.add).toEqual([
|
|
78
|
+
{
|
|
79
|
+
displayName: 'ChatGPT-4',
|
|
80
|
+
files: true,
|
|
81
|
+
functionCall: true,
|
|
82
|
+
id: 'gpt-4-0125-preview',
|
|
83
|
+
tokens: 128000,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
displayName: 'ChatGPT-4 Vision',
|
|
87
|
+
files: true,
|
|
88
|
+
functionCall: true,
|
|
89
|
+
id: 'gpt-4-turbo-2024-04-09',
|
|
90
|
+
tokens: 128000,
|
|
91
|
+
vision: true,
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
});
|
|
95
|
+
|
|
70
96
|
it('should handle empty extension capability value', () => {
|
|
71
97
|
const result = parseModelString('model1<1024:>');
|
|
72
98
|
expect(result.add[0]).toEqual({ id: 'model1', tokens: 1024 });
|
|
@@ -168,3 +194,97 @@ describe('parseModelString', () => {
|
|
|
168
194
|
});
|
|
169
195
|
});
|
|
170
196
|
});
|
|
197
|
+
|
|
198
|
+
describe('transformToChatModelCards', () => {
|
|
199
|
+
const defaultChatModels: ChatModelCard[] = [
|
|
200
|
+
{ id: 'model1', displayName: 'Model 1', enabled: true },
|
|
201
|
+
{ id: 'model2', displayName: 'Model 2', enabled: false },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
it('should return undefined when modelString is empty', () => {
|
|
205
|
+
const result = transformToChatModelCards({
|
|
206
|
+
modelString: '',
|
|
207
|
+
defaultChatModels,
|
|
208
|
+
});
|
|
209
|
+
expect(result).toBeUndefined();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should remove all models when removeAll is true', () => {
|
|
213
|
+
const result = transformToChatModelCards({
|
|
214
|
+
modelString: '-all',
|
|
215
|
+
defaultChatModels,
|
|
216
|
+
});
|
|
217
|
+
expect(result).toEqual([]);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should remove specified models', () => {
|
|
221
|
+
const result = transformToChatModelCards({
|
|
222
|
+
modelString: '-model1',
|
|
223
|
+
defaultChatModels,
|
|
224
|
+
});
|
|
225
|
+
expect(result).toEqual([{ id: 'model2', displayName: 'Model 2', enabled: false }]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should add a new known model', () => {
|
|
229
|
+
const knownModel = LOBE_DEFAULT_MODEL_LIST[0];
|
|
230
|
+
const result = transformToChatModelCards({
|
|
231
|
+
modelString: `${knownModel.id}`,
|
|
232
|
+
defaultChatModels,
|
|
233
|
+
});
|
|
234
|
+
expect(result).toContainEqual({
|
|
235
|
+
...knownModel,
|
|
236
|
+
displayName: knownModel.displayName || knownModel.id,
|
|
237
|
+
enabled: true,
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should update an existing known model', () => {
|
|
242
|
+
const knownModel = LOBE_DEFAULT_MODEL_LIST[0];
|
|
243
|
+
const result = transformToChatModelCards({
|
|
244
|
+
modelString: `+${knownModel.id}=Updated Model`,
|
|
245
|
+
defaultChatModels: [knownModel],
|
|
246
|
+
});
|
|
247
|
+
expect(result![0]).toEqual({ ...knownModel, displayName: 'Updated Model', enabled: true });
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should add a new custom model', () => {
|
|
251
|
+
const result = transformToChatModelCards({
|
|
252
|
+
modelString: '+custom_model=Custom Model',
|
|
253
|
+
defaultChatModels,
|
|
254
|
+
});
|
|
255
|
+
expect(result).toContainEqual({
|
|
256
|
+
id: 'custom_model',
|
|
257
|
+
displayName: 'Custom Model',
|
|
258
|
+
enabled: true,
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should have file with builtin models like gpt-4-0125-preview', () => {
|
|
263
|
+
const result = transformToChatModelCards({
|
|
264
|
+
modelString:
|
|
265
|
+
'-all,+gpt-4-0125-preview=ChatGPT-4<128000:fc:file>,+gpt-4-turbo-2024-04-09=ChatGPT-4 Vision<128000:fc:vision:file>',
|
|
266
|
+
defaultChatModels: OpenAIProviderCard.chatModels,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(result).toEqual([
|
|
270
|
+
{
|
|
271
|
+
displayName: 'ChatGPT-4',
|
|
272
|
+
files: true,
|
|
273
|
+
functionCall: true,
|
|
274
|
+
enabled: true,
|
|
275
|
+
id: 'gpt-4-0125-preview',
|
|
276
|
+
tokens: 128000,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
description: 'GPT-4 Turbo 视觉版 (240409)',
|
|
280
|
+
displayName: 'ChatGPT-4 Vision',
|
|
281
|
+
files: true,
|
|
282
|
+
functionCall: true,
|
|
283
|
+
enabled: true,
|
|
284
|
+
id: 'gpt-4-turbo-2024-04-09',
|
|
285
|
+
tokens: 128000,
|
|
286
|
+
vision: true,
|
|
287
|
+
},
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
});
|
package/src/utils/parseModels.ts
CHANGED
|
@@ -114,17 +114,22 @@ export const transformToChatModelCards = ({
|
|
|
114
114
|
|
|
115
115
|
// if the model is known, update it based on the known model
|
|
116
116
|
if (knownModel) {
|
|
117
|
-
const
|
|
117
|
+
const index = draft.findIndex((model) => model.id === toAddModel.id);
|
|
118
|
+
const modelInList = draft[index];
|
|
118
119
|
|
|
119
120
|
// if the model is already in chatModels, update it
|
|
120
121
|
if (modelInList) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
draft[index] = {
|
|
123
|
+
...modelInList,
|
|
124
|
+
...toAddModel,
|
|
125
|
+
displayName: toAddModel.displayName || modelInList.displayName || modelInList.id,
|
|
126
|
+
enabled: true,
|
|
127
|
+
};
|
|
124
128
|
} else {
|
|
125
129
|
// if the model is not in chatModels, add it
|
|
126
130
|
draft.push({
|
|
127
131
|
...knownModel,
|
|
132
|
+
...toAddModel,
|
|
128
133
|
displayName: toAddModel.displayName || knownModel.displayName || knownModel.id,
|
|
129
134
|
enabled: true,
|
|
130
135
|
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { StateCreator } from 'zustand/vanilla';
|
|
2
|
-
|
|
3
|
-
import type { UserStore } from '@/store/user';
|
|
4
|
-
|
|
5
|
-
import { GeneralSettingsAction, generalSettingsSlice } from './general';
|
|
6
|
-
import { LLMSettingsAction, llmSettingsSlice } from './llm';
|
|
7
|
-
|
|
8
|
-
export interface SettingsAction extends LLMSettingsAction, GeneralSettingsAction {}
|
|
9
|
-
|
|
10
|
-
export const createSettingsSlice: StateCreator<
|
|
11
|
-
UserStore,
|
|
12
|
-
[['zustand/devtools', never]],
|
|
13
|
-
[],
|
|
14
|
-
SettingsAction
|
|
15
|
-
> = (...params) => ({
|
|
16
|
-
...llmSettingsSlice(...params),
|
|
17
|
-
...generalSettingsSlice(...params),
|
|
18
|
-
});
|