@lobehub/chat 0.148.3 → 0.148.4
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 +25 -0
- package/package.json +1 -1
- package/src/app/settings/llm/components/ProviderModelList/Option.tsx +23 -19
- package/src/app/settings/llm/components/ProviderModelList/index.tsx +1 -0
- package/src/layout/GlobalProvider/StoreInitialization.tsx +45 -0
- package/src/layout/GlobalProvider/index.tsx +2 -2
- package/src/store/global/slices/common/action.ts +3 -3
- package/src/store/global/slices/preference/action.ts +26 -12
- package/src/store/global/slices/preference/initialState.ts +5 -2
- package/src/store/global/slices/settings/actions/llm.ts +10 -7
- package/src/store/global/store.ts +5 -25
- package/src/utils/localStorage.ts +36 -0
- package/src/layout/GlobalProvider/StoreHydration.tsx +0 -61
- package/src/store/global/hooks/useEffectAfterHydrated.ts +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.148.4](https://github.com/lobehub/lobe-chat/compare/v0.148.3...v0.148.4)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2024-04-21**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **misc**: Fix model list menu not display correctly.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **misc**: Fix model list menu not display correctly, closes [#2133](https://github.com/lobehub/lobe-chat/issues/2133) ([98c844b](https://github.com/lobehub/lobe-chat/commit/98c844b))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
### [Version 0.148.3](https://github.com/lobehub/lobe-chat/compare/v0.148.2...v0.148.3)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2024-04-21**</sup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/chat",
|
|
3
|
-
"version": "0.148.
|
|
3
|
+
"version": "0.148.4",
|
|
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",
|
|
@@ -11,28 +11,32 @@ import { GlobalLLMProviderKey } from '@/types/settings';
|
|
|
11
11
|
|
|
12
12
|
import CustomModelOption from './CustomModelOption';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
interface OptionRenderProps {
|
|
15
|
+
displayName: string;
|
|
16
|
+
id: string;
|
|
17
|
+
isAzure?: boolean;
|
|
18
|
+
provider: GlobalLLMProviderKey;
|
|
19
|
+
}
|
|
20
|
+
const OptionRender = memo<OptionRenderProps>(({ displayName, id, provider, isAzure }) => {
|
|
21
|
+
const model = useGlobalStore((s) => modelProviderSelectors.getModelCardById(id)(s), isEqual);
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
// if there is isCustom, it means it is a user defined custom model
|
|
24
|
+
if (model?.isCustom || isAzure) return <CustomModelOption id={id} provider={provider} />;
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</Flexbox>
|
|
29
|
-
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
30
|
-
{id}
|
|
31
|
-
</Typography.Text>
|
|
26
|
+
return (
|
|
27
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
28
|
+
<ModelIcon model={id} size={32} />
|
|
29
|
+
<Flexbox>
|
|
30
|
+
<Flexbox align={'center'} gap={8} horizontal>
|
|
31
|
+
{displayName}
|
|
32
|
+
<ModelInfoTags directionReverse placement={'top'} {...model!} />
|
|
32
33
|
</Flexbox>
|
|
34
|
+
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
|
|
35
|
+
{id}
|
|
36
|
+
</Typography.Text>
|
|
33
37
|
</Flexbox>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
38
|
+
</Flexbox>
|
|
39
|
+
);
|
|
40
|
+
});
|
|
37
41
|
|
|
38
42
|
export default OptionRender;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useRouter } from 'next/navigation';
|
|
4
|
+
import { memo, useEffect } from 'react';
|
|
5
|
+
import { createStoreUpdater } from 'zustand-utils';
|
|
6
|
+
|
|
7
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
8
|
+
import { useEnabledDataSync } from '@/hooks/useSyncData';
|
|
9
|
+
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
|
|
11
|
+
const StoreInitialization = memo(() => {
|
|
12
|
+
const [useFetchServerConfig, useFetchUserConfig, useInitPreference] = useGlobalStore((s) => [
|
|
13
|
+
s.useFetchServerConfig,
|
|
14
|
+
s.useFetchUserConfig,
|
|
15
|
+
s.useInitPreference,
|
|
16
|
+
]);
|
|
17
|
+
// init the system preference
|
|
18
|
+
useInitPreference();
|
|
19
|
+
|
|
20
|
+
const { isLoading } = useFetchServerConfig();
|
|
21
|
+
useFetchUserConfig(!isLoading);
|
|
22
|
+
|
|
23
|
+
useEnabledDataSync();
|
|
24
|
+
|
|
25
|
+
const useStoreUpdater = createStoreUpdater(useGlobalStore);
|
|
26
|
+
|
|
27
|
+
const mobile = useIsMobile();
|
|
28
|
+
const router = useRouter();
|
|
29
|
+
|
|
30
|
+
useStoreUpdater('isMobile', mobile);
|
|
31
|
+
useStoreUpdater('router', router);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
router.prefetch('/chat');
|
|
35
|
+
router.prefetch('/chat/settings');
|
|
36
|
+
router.prefetch('/market');
|
|
37
|
+
router.prefetch('/settings/common');
|
|
38
|
+
router.prefetch('/settings/agent');
|
|
39
|
+
router.prefetch('/settings/sync');
|
|
40
|
+
}, [router]);
|
|
41
|
+
|
|
42
|
+
return null;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default StoreInitialization;
|
|
@@ -13,7 +13,7 @@ import { getAntdLocale } from '@/utils/locale';
|
|
|
13
13
|
|
|
14
14
|
import AppTheme from './AppTheme';
|
|
15
15
|
import Locale from './Locale';
|
|
16
|
-
import
|
|
16
|
+
import StoreInitialization from './StoreInitialization';
|
|
17
17
|
import StyleRegistry from './StyleRegistry';
|
|
18
18
|
|
|
19
19
|
let DebugUI: FC = () => null;
|
|
@@ -50,7 +50,7 @@ const GlobalLayout = async ({ children }: GlobalLayoutProps) => {
|
|
|
50
50
|
defaultNeutralColor={neutralColor?.value as any}
|
|
51
51
|
defaultPrimaryColor={primaryColor?.value as any}
|
|
52
52
|
>
|
|
53
|
-
<
|
|
53
|
+
<StoreInitialization />
|
|
54
54
|
{children}
|
|
55
55
|
<DebugUI />
|
|
56
56
|
</AppTheme>
|
|
@@ -147,7 +147,7 @@ export const createCommonSlice: StateCreator<
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
onSuccess: (syncEnabled) => {
|
|
150
|
-
set({ syncEnabled });
|
|
150
|
+
set({ syncEnabled }, false, n('useEnabledSync'));
|
|
151
151
|
},
|
|
152
152
|
revalidateOnFocus: false,
|
|
153
153
|
},
|
|
@@ -165,7 +165,7 @@ export const createCommonSlice: StateCreator<
|
|
|
165
165
|
|
|
166
166
|
set({ defaultSettings, serverConfig: data }, false, n('initGlobalConfig'));
|
|
167
167
|
|
|
168
|
-
get().refreshDefaultModelProviderList();
|
|
168
|
+
get().refreshDefaultModelProviderList({ trigger: 'fetchServerConfig' });
|
|
169
169
|
}
|
|
170
170
|
},
|
|
171
171
|
revalidateOnFocus: false,
|
|
@@ -188,7 +188,7 @@ export const createCommonSlice: StateCreator<
|
|
|
188
188
|
);
|
|
189
189
|
|
|
190
190
|
// when get the user config ,refresh the model provider list to the latest
|
|
191
|
-
get().
|
|
191
|
+
get().refreshDefaultModelProviderList({ trigger: 'fetchUserConfig' });
|
|
192
192
|
|
|
193
193
|
const { language } = settingsSelectors.currentSettings(get());
|
|
194
194
|
if (language === 'auto') {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { produce } from 'immer';
|
|
2
|
+
import { SWRResponse } from 'swr';
|
|
2
3
|
import type { StateCreator } from 'zustand/vanilla';
|
|
3
4
|
|
|
5
|
+
import { useClientDataSWR } from '@/libs/swr';
|
|
4
6
|
import type { GlobalStore } from '@/store/global';
|
|
5
7
|
import { merge } from '@/utils/merge';
|
|
6
8
|
import { setNamespace } from '@/utils/storeDebug';
|
|
7
9
|
|
|
8
|
-
import type { GlobalPreference,
|
|
10
|
+
import type { GlobalPreference, Guide } from './initialState';
|
|
9
11
|
|
|
10
12
|
const n = setNamespace('preference');
|
|
11
13
|
|
|
@@ -18,7 +20,8 @@ export interface PreferenceAction {
|
|
|
18
20
|
toggleMobileTopic: (visible?: boolean) => void;
|
|
19
21
|
toggleSystemRole: (visible?: boolean) => void;
|
|
20
22
|
updateGuideState: (guide: Partial<Guide>) => void;
|
|
21
|
-
updatePreference: (preference: Partial<GlobalPreference>, action?:
|
|
23
|
+
updatePreference: (preference: Partial<GlobalPreference>, action?: any) => void;
|
|
24
|
+
useInitPreference: () => SWRResponse;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export const createPreferenceSlice: StateCreator<
|
|
@@ -31,7 +34,7 @@ export const createPreferenceSlice: StateCreator<
|
|
|
31
34
|
const showChatSideBar =
|
|
32
35
|
typeof newValue === 'boolean' ? newValue : !get().preference.showChatSideBar;
|
|
33
36
|
|
|
34
|
-
get().updatePreference({ showChatSideBar }, n('toggleAgentPanel', newValue)
|
|
37
|
+
get().updatePreference({ showChatSideBar }, n('toggleAgentPanel', newValue));
|
|
35
38
|
},
|
|
36
39
|
toggleExpandSessionGroup: (id, expand) => {
|
|
37
40
|
const { preference } = get();
|
|
@@ -50,13 +53,13 @@ export const createPreferenceSlice: StateCreator<
|
|
|
50
53
|
const mobileShowTopic =
|
|
51
54
|
typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
|
|
52
55
|
|
|
53
|
-
get().updatePreference({ mobileShowTopic }, n('toggleMobileTopic', newValue)
|
|
56
|
+
get().updatePreference({ mobileShowTopic }, n('toggleMobileTopic', newValue));
|
|
54
57
|
},
|
|
55
58
|
toggleSystemRole: (newValue) => {
|
|
56
59
|
const showSystemRole =
|
|
57
60
|
typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
|
|
58
61
|
|
|
59
|
-
get().updatePreference({ showSystemRole }, n('toggleMobileTopic', newValue)
|
|
62
|
+
get().updatePreference({ showSystemRole }, n('toggleMobileTopic', newValue));
|
|
60
63
|
},
|
|
61
64
|
updateGuideState: (guide) => {
|
|
62
65
|
const { updatePreference } = get();
|
|
@@ -64,12 +67,23 @@ export const createPreferenceSlice: StateCreator<
|
|
|
64
67
|
updatePreference({ guide: nextGuide });
|
|
65
68
|
},
|
|
66
69
|
updatePreference: (preference, action) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
action,
|
|
73
|
-
);
|
|
70
|
+
const nextPreference = merge(get().preference, preference);
|
|
71
|
+
|
|
72
|
+
set({ preference: nextPreference }, false, action || n('updatePreference'));
|
|
73
|
+
|
|
74
|
+
get().preferenceStorage.saveToLocalStorage(nextPreference);
|
|
74
75
|
},
|
|
76
|
+
|
|
77
|
+
useInitPreference: () =>
|
|
78
|
+
useClientDataSWR<GlobalPreference>(
|
|
79
|
+
'preference',
|
|
80
|
+
() => get().preferenceStorage.getFromLocalStorage(),
|
|
81
|
+
{
|
|
82
|
+
onSuccess: (preference) => {
|
|
83
|
+
if (preference) {
|
|
84
|
+
set({ preference }, false, n('initPreference'));
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
),
|
|
75
89
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SessionDefaultGroup, SessionGroupId } from '@/types/session';
|
|
2
|
+
import { AsyncLocalStorage } from '@/utils/localStorage';
|
|
2
3
|
|
|
3
4
|
export interface Guide {
|
|
4
5
|
// Topic 引导
|
|
@@ -18,6 +19,7 @@ export interface GlobalPreference {
|
|
|
18
19
|
showSessionPanel?: boolean;
|
|
19
20
|
showSystemRole?: boolean;
|
|
20
21
|
telemetry: boolean | null;
|
|
22
|
+
|
|
21
23
|
/**
|
|
22
24
|
* whether to use cmd + enter to send message
|
|
23
25
|
*/
|
|
@@ -26,10 +28,10 @@ export interface GlobalPreference {
|
|
|
26
28
|
|
|
27
29
|
export interface GlobalPreferenceState {
|
|
28
30
|
/**
|
|
29
|
-
*
|
|
30
|
-
* @localStorage
|
|
31
|
+
* the user preference, which only store in local storage
|
|
31
32
|
*/
|
|
32
33
|
preference: GlobalPreference;
|
|
34
|
+
preferenceStorage: AsyncLocalStorage<GlobalPreference>;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
export const initialPreferenceState: GlobalPreferenceState = {
|
|
@@ -45,4 +47,5 @@ export const initialPreferenceState: GlobalPreferenceState = {
|
|
|
45
47
|
telemetry: null,
|
|
46
48
|
useCmdEnterToSend: false,
|
|
47
49
|
},
|
|
50
|
+
preferenceStorage: new AsyncLocalStorage('LOBE_PREFERENCE'),
|
|
48
51
|
};
|
|
@@ -20,11 +20,14 @@ import {
|
|
|
20
20
|
import { GlobalStore } from '@/store/global';
|
|
21
21
|
import { ChatModelCard } from '@/types/llm';
|
|
22
22
|
import { GlobalLLMConfig, GlobalLLMProviderKey } from '@/types/settings';
|
|
23
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
23
24
|
|
|
24
25
|
import { CustomModelCardDispatch, customModelCardsReducer } from '../reducers/customModelCard';
|
|
25
26
|
import { modelProviderSelectors } from '../selectors/modelProvider';
|
|
26
27
|
import { settingsSelectors } from '../selectors/settings';
|
|
27
28
|
|
|
29
|
+
const n = setNamespace('settings');
|
|
30
|
+
|
|
28
31
|
/**
|
|
29
32
|
* 设置操作
|
|
30
33
|
*/
|
|
@@ -36,8 +39,8 @@ export interface LLMSettingsAction {
|
|
|
36
39
|
/**
|
|
37
40
|
* make sure the default model provider list is sync to latest state
|
|
38
41
|
*/
|
|
39
|
-
refreshDefaultModelProviderList: () => void;
|
|
40
|
-
refreshModelProviderList: () => void;
|
|
42
|
+
refreshDefaultModelProviderList: (params?: { trigger?: string }) => void;
|
|
43
|
+
refreshModelProviderList: (params?: { trigger?: string }) => void;
|
|
41
44
|
removeEnabledModels: (provider: GlobalLLMProviderKey, model: string) => Promise<void>;
|
|
42
45
|
setModelProviderConfig: <T extends GlobalLLMProviderKey>(
|
|
43
46
|
provider: T,
|
|
@@ -69,7 +72,7 @@ export const llmSettingsSlice: StateCreator<
|
|
|
69
72
|
await get().setModelProviderConfig(provider, { customModelCards: nextState });
|
|
70
73
|
},
|
|
71
74
|
|
|
72
|
-
refreshDefaultModelProviderList: () => {
|
|
75
|
+
refreshDefaultModelProviderList: (params) => {
|
|
73
76
|
/**
|
|
74
77
|
* Because we have several model cards sources, we need to merge the model cards
|
|
75
78
|
* the priority is below:
|
|
@@ -113,12 +116,12 @@ export const llmSettingsSlice: StateCreator<
|
|
|
113
116
|
ZhiPuProviderCard,
|
|
114
117
|
];
|
|
115
118
|
|
|
116
|
-
set({ defaultModelProviderList }, false,
|
|
119
|
+
set({ defaultModelProviderList }, false, n(`refreshDefaultModelList - ${params?.trigger}`));
|
|
117
120
|
|
|
118
|
-
get().refreshModelProviderList();
|
|
121
|
+
get().refreshModelProviderList({ trigger: 'refreshDefaultModelList' });
|
|
119
122
|
},
|
|
120
123
|
|
|
121
|
-
refreshModelProviderList: () => {
|
|
124
|
+
refreshModelProviderList: (params) => {
|
|
122
125
|
const modelProviderList = get().defaultModelProviderList.map((list) => ({
|
|
123
126
|
...list,
|
|
124
127
|
chatModels: modelProviderSelectors
|
|
@@ -136,7 +139,7 @@ export const llmSettingsSlice: StateCreator<
|
|
|
136
139
|
enabled: modelProviderSelectors.isProviderEnabled(list.id as any)(get()),
|
|
137
140
|
}));
|
|
138
141
|
|
|
139
|
-
set({ modelProviderList }, false,
|
|
142
|
+
set({ modelProviderList }, false, n(`refreshModelList - ${params?.trigger}`));
|
|
140
143
|
},
|
|
141
144
|
|
|
142
145
|
removeEnabledModels: async (provider, model) => {
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { devtools, subscribeWithSelector } from 'zustand/middleware';
|
|
2
2
|
import { shallow } from 'zustand/shallow';
|
|
3
3
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
4
4
|
import { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
import { isDev } from '@/utils/env';
|
|
7
7
|
|
|
8
|
-
import { createHyperStorage } from '../middleware/createHyperStorage';
|
|
9
8
|
import { type GlobalState, initialState } from './initialState';
|
|
10
9
|
import { type CommonAction, createCommonSlice } from './slices/common/action';
|
|
11
10
|
import { type PreferenceAction, createPreferenceSlice } from './slices/preference/action';
|
|
@@ -22,32 +21,13 @@ const createStore: StateCreator<GlobalStore, [['zustand/devtools', never]]> = (.
|
|
|
22
21
|
...createPreferenceSlice(...parameters),
|
|
23
22
|
});
|
|
24
23
|
|
|
25
|
-
// =============== persist 本地缓存中间件配置 ============ //
|
|
26
|
-
type GlobalPersist = Pick<GlobalStore, 'preference' | 'settings'>;
|
|
27
|
-
|
|
28
|
-
const persistOptions: PersistOptions<GlobalStore, GlobalPersist> = {
|
|
29
|
-
name: 'LOBE_GLOBAL',
|
|
30
|
-
|
|
31
|
-
skipHydration: true,
|
|
32
|
-
|
|
33
|
-
storage: createHyperStorage({
|
|
34
|
-
localStorage: {
|
|
35
|
-
dbName: 'LobeHub',
|
|
36
|
-
selectors: ['preference'],
|
|
37
|
-
},
|
|
38
|
-
}),
|
|
39
|
-
};
|
|
40
|
-
|
|
41
24
|
// =============== 实装 useStore ============ //
|
|
42
25
|
|
|
43
26
|
export const useGlobalStore = createWithEqualityFn<GlobalStore>()(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}),
|
|
49
|
-
),
|
|
50
|
-
persistOptions,
|
|
27
|
+
subscribeWithSelector(
|
|
28
|
+
devtools(createStore, {
|
|
29
|
+
name: 'LobeChat_Global' + (isDev ? '_DEV' : ''),
|
|
30
|
+
}),
|
|
51
31
|
),
|
|
52
32
|
shallow,
|
|
53
33
|
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const PREV_KEY = 'LOBE_GLOBAL';
|
|
2
|
+
|
|
3
|
+
type StorageKey = 'LOBE_PREFERENCE';
|
|
4
|
+
|
|
5
|
+
export class AsyncLocalStorage<State> {
|
|
6
|
+
private storageKey: StorageKey;
|
|
7
|
+
|
|
8
|
+
constructor(storageKey: StorageKey) {
|
|
9
|
+
this.storageKey = storageKey;
|
|
10
|
+
|
|
11
|
+
// skip server side rendering
|
|
12
|
+
if (typeof window === 'undefined') return;
|
|
13
|
+
|
|
14
|
+
// migrate old data
|
|
15
|
+
if (localStorage.getItem(PREV_KEY)) {
|
|
16
|
+
const data = JSON.parse(localStorage.getItem(PREV_KEY) || '{}');
|
|
17
|
+
|
|
18
|
+
const preference = data.state.preference;
|
|
19
|
+
|
|
20
|
+
if (data.state?.preference) {
|
|
21
|
+
localStorage.setItem('LOBE_PREFERENCE', JSON.stringify(preference));
|
|
22
|
+
}
|
|
23
|
+
localStorage.removeItem(PREV_KEY);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async saveToLocalStorage(state: object) {
|
|
28
|
+
const data = await this.getFromLocalStorage();
|
|
29
|
+
|
|
30
|
+
localStorage.setItem(this.storageKey, JSON.stringify({ ...data, ...state }));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getFromLocalStorage(key: StorageKey = this.storageKey): Promise<State> {
|
|
34
|
+
return JSON.parse(localStorage.getItem(key) || '{}');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useResponsive } from 'antd-style';
|
|
4
|
-
import { useRouter } from 'next/navigation';
|
|
5
|
-
import { memo, useEffect } from 'react';
|
|
6
|
-
|
|
7
|
-
import { useEnabledDataSync } from '@/hooks/useSyncData';
|
|
8
|
-
import { useGlobalStore } from '@/store/global';
|
|
9
|
-
import { useEffectAfterGlobalHydrated } from '@/store/global/hooks/useEffectAfterHydrated';
|
|
10
|
-
|
|
11
|
-
const StoreHydration = memo(() => {
|
|
12
|
-
const [useFetchServerConfig, useFetchUserConfig] = useGlobalStore((s) => [
|
|
13
|
-
s.useFetchServerConfig,
|
|
14
|
-
s.useFetchUserConfig,
|
|
15
|
-
]);
|
|
16
|
-
|
|
17
|
-
const { isLoading } = useFetchServerConfig();
|
|
18
|
-
|
|
19
|
-
useFetchUserConfig(!isLoading);
|
|
20
|
-
|
|
21
|
-
useEnabledDataSync();
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
// refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
|
|
25
|
-
useGlobalStore.persist.rehydrate();
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
const { mobile } = useResponsive();
|
|
29
|
-
useEffectAfterGlobalHydrated(
|
|
30
|
-
(store) => {
|
|
31
|
-
const prevState = store.getState().isMobile;
|
|
32
|
-
|
|
33
|
-
if (prevState !== mobile) {
|
|
34
|
-
store.setState({ isMobile: mobile });
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
[mobile],
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const router = useRouter();
|
|
41
|
-
|
|
42
|
-
useEffectAfterGlobalHydrated(
|
|
43
|
-
(store) => {
|
|
44
|
-
store.setState({ router });
|
|
45
|
-
},
|
|
46
|
-
[router],
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
router.prefetch('/chat');
|
|
51
|
-
router.prefetch('/chat/settings');
|
|
52
|
-
router.prefetch('/market');
|
|
53
|
-
router.prefetch('/settings/common');
|
|
54
|
-
router.prefetch('/settings/agent');
|
|
55
|
-
router.prefetch('/settings/sync');
|
|
56
|
-
}, [router]);
|
|
57
|
-
|
|
58
|
-
return null;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
export default StoreHydration;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
import { useGlobalStore } from '../store';
|
|
4
|
-
|
|
5
|
-
export const useEffectAfterGlobalHydrated = (
|
|
6
|
-
fn: (store: typeof useGlobalStore) => void,
|
|
7
|
-
deps: any[] = [],
|
|
8
|
-
) => {
|
|
9
|
-
useEffect(() => {
|
|
10
|
-
const hasRehydrated = useGlobalStore.persist.hasHydrated();
|
|
11
|
-
|
|
12
|
-
if (hasRehydrated) {
|
|
13
|
-
// 等价 useEffect 多次触发
|
|
14
|
-
fn(useGlobalStore);
|
|
15
|
-
} else {
|
|
16
|
-
// 等价于 useEffect 第一次触发
|
|
17
|
-
useGlobalStore.persist.onFinishHydration(() => {
|
|
18
|
-
fn(useGlobalStore);
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
}, deps);
|
|
22
|
-
};
|