@lobehub/chat 0.150.8 → 0.150.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/upstream-sync.mdx +74 -0
- package/docs/self-hosting/advanced/upstream-sync.zh-CN.mdx +71 -0
- package/docs/usage/providers/ollama.mdx +1 -1
- package/docs/usage/providers/ollama.zh-CN.mdx +1 -1
- package/package.json +1 -1
- package/src/app/chat/(desktop)/features/ChatHeader/HeaderAction.tsx +2 -2
- package/src/app/chat/(desktop)/features/ChatHeader/Tags.tsx +3 -3
- package/src/app/chat/(desktop)/features/ChatInput/Footer/DragUpload.tsx +3 -3
- package/src/app/chat/(desktop)/features/ChatInput/Footer/SendMore.tsx +3 -3
- package/src/app/chat/(desktop)/features/ChatInput/Footer/index.tsx +3 -3
- package/src/app/chat/(desktop)/features/ChatInput/TextArea.test.tsx +5 -5
- package/src/app/chat/(desktop)/features/ChatInput/TextArea.tsx +3 -3
- package/src/app/chat/(desktop)/features/SideBar/index.tsx +2 -2
- package/src/app/chat/(mobile)/features/SessionHeader.tsx +5 -5
- package/src/app/chat/(mobile)/mobile/ChatHeader/index.tsx +2 -2
- package/src/app/chat/_layout/Desktop/SessionHeader.tsx +2 -2
- package/src/app/chat/features/PluginTag/index.tsx +2 -2
- package/src/app/chat/features/SessionListContent/List/index.tsx +2 -2
- package/src/app/chat/features/ShareButton/ShareModal.tsx +3 -3
- package/src/app/chat/features/TelemetryNotification/index.tsx +6 -4
- package/src/app/chat/features/TopicListContent/Topic/index.tsx +2 -2
- package/src/app/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +3 -3
- package/src/app/settings/(mobile)/index.tsx +3 -3
- package/src/app/settings/about/Analytics.tsx +4 -4
- package/src/app/settings/about/page.tsx +3 -3
- package/src/app/settings/agent/Agent.tsx +4 -4
- package/src/app/settings/common/Common.tsx +4 -4
- package/src/app/settings/common/Theme.tsx +4 -4
- package/src/app/settings/features/SettingList/index.tsx +2 -2
- package/src/app/settings/features/ThemeSwatches/ThemeSwatchesNeutral.tsx +3 -3
- package/src/app/settings/features/ThemeSwatches/ThemeSwatchesPrimary.tsx +3 -3
- package/src/app/settings/hooks/useSyncSettings.ts +3 -3
- package/src/app/settings/llm/Azure/index.tsx +3 -3
- package/src/app/settings/llm/OpenAI/index.tsx +2 -2
- package/src/app/settings/llm/components/ProviderConfig/index.tsx +3 -3
- package/src/app/settings/llm/components/ProviderModelList/CustomModelOption.tsx +4 -4
- package/src/app/settings/llm/components/ProviderModelList/ModelConfigModal.tsx +4 -4
- package/src/app/settings/llm/components/ProviderModelList/ModelFetcher.tsx +6 -6
- package/src/app/settings/llm/components/ProviderModelList/Option.tsx +3 -3
- package/src/app/settings/llm/components/ProviderModelList/index.tsx +6 -6
- package/src/app/settings/sync/Alert.tsx +3 -3
- package/src/app/settings/sync/DeviceInfo/DeviceName.tsx +3 -3
- package/src/app/settings/sync/WebRTC/index.tsx +2 -2
- package/src/app/settings/tts/TTS/index.tsx +4 -4
- package/src/chains/__tests__/summaryAgentName.test.ts +2 -2
- package/src/chains/__tests__/summaryDescription.test.ts +2 -2
- package/src/chains/__tests__/summaryTags.test.ts +2 -2
- package/src/chains/__tests__/summaryTitle.test.ts +2 -2
- package/src/chains/summaryAgentName.ts +1 -1
- package/src/chains/summaryDescription.ts +1 -1
- package/src/chains/summaryTags.ts +1 -1
- package/src/chains/summaryTitle.ts +1 -1
- package/src/features/AgentSetting/AgentConfig/ModelSelect.tsx +3 -6
- package/src/features/AgentSetting/AgentMeta/index.tsx +3 -3
- package/src/features/AgentSetting/AgentPlugin/index.tsx +2 -2
- package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +4 -4
- package/src/features/AgentSetting/AgentTTS/index.tsx +3 -3
- package/src/features/AvatarWithUpload/index.tsx +3 -3
- package/src/features/ChatInput/ActionBar/FileUpload.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Token/TokenTag.tsx +4 -4
- package/src/features/ChatInput/ActionBar/Token/index.tsx +3 -3
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +5 -5
- package/src/features/ChatInput/STT/browser.tsx +4 -4
- package/src/features/ChatInput/STT/index.tsx +3 -3
- package/src/features/ChatInput/STT/openai.tsx +4 -4
- package/src/features/ChatInput/useChatInput.ts +3 -3
- package/src/features/Conversation/Error/APIKeyForm/Bedrock.tsx +3 -3
- package/src/features/Conversation/Error/APIKeyForm/ProviderApiKeyForm.tsx +3 -3
- package/src/features/Conversation/Error/AccessCodeForm.tsx +3 -3
- package/src/features/Conversation/Error/InvalidAccessCode.tsx +3 -3
- package/src/features/Conversation/Extras/TTS/index.tsx +3 -3
- package/src/features/Conversation/Plugins/Render/MarkdownType/index.tsx +3 -3
- package/src/features/Conversation/components/ChatItem/index.tsx +3 -3
- package/src/features/ModelSwitchPanel/index.tsx +3 -6
- package/src/features/PluginDevModal/LocalForm.tsx +3 -3
- package/src/features/SyncStatusInspector/DisableSync.tsx +3 -3
- package/src/features/SyncStatusInspector/EnableSync.tsx +4 -4
- package/src/features/SyncStatusInspector/index.tsx +2 -2
- package/src/hooks/_header.ts +4 -4
- package/src/hooks/useSyncData.ts +3 -3
- package/src/hooks/useTTS.ts +4 -4
- package/src/layout/DefaultLayout/Desktop/SideBar/BottomActions.tsx +2 -2
- package/src/layout/DefaultLayout/Desktop/SideBar/TopActions.tsx +2 -2
- package/src/layout/DefaultLayout/Mobile/index.tsx +1 -1
- package/src/layout/GlobalProvider/AppTheme.tsx +4 -4
- package/src/layout/GlobalProvider/StoreInitialization.tsx +6 -1
- package/src/layout/GlobalProvider/index.tsx +5 -3
- package/src/server/globalConfig/index.ts +119 -0
- package/src/server/routers/config/index.ts +3 -112
- package/src/services/__tests__/chat.test.ts +17 -20
- package/src/services/__tests__/tool.test.ts +2 -2
- package/src/services/_auth.test.ts +2 -2
- package/src/services/_auth.ts +7 -7
- package/src/services/_header.ts +4 -4
- package/src/services/chat.ts +13 -13
- package/src/services/config.ts +4 -4
- package/src/services/models.ts +3 -3
- package/src/services/ollama.ts +3 -3
- package/src/services/session/client.ts +2 -2
- package/src/services/tool.ts +1 -1
- package/src/services/trace.ts +3 -3
- package/src/store/agent/slices/chat/selectors.test.ts +2 -2
- package/src/store/chat/slices/message/selectors.test.ts +1 -1
- package/src/store/chat/slices/message/selectors.ts +3 -3
- package/src/store/global/{slices/preference/action.test.ts → action.test.ts} +65 -13
- package/src/store/global/{slices/preference/action.ts → action.ts} +30 -16
- package/src/store/global/initialState.ts +58 -8
- package/src/store/global/selectors.ts +9 -8
- package/src/store/global/store.ts +3 -7
- package/src/store/market/action.ts +1 -1
- package/src/store/serverConfig/Provider.tsx +22 -0
- package/src/store/serverConfig/index.ts +3 -0
- package/src/store/serverConfig/selectors.test.ts +72 -0
- package/src/store/serverConfig/selectors.ts +11 -0
- package/src/store/serverConfig/store.test.ts +53 -0
- package/src/store/serverConfig/store.ts +61 -0
- package/src/store/session/slices/session/action.ts +3 -3
- package/src/store/{global → user}/helpers.ts +2 -2
- package/src/store/user/index.ts +1 -0
- package/src/store/user/initialState.ts +11 -0
- package/src/store/user/selectors.ts +8 -0
- package/src/store/{global → user}/slices/common/action.test.ts +29 -81
- package/src/store/{global → user}/slices/common/action.ts +2 -20
- package/src/store/user/slices/common/initialState.ts +18 -0
- package/src/store/user/slices/common/selectors.ts +6 -0
- package/src/store/user/slices/preference/action.test.ts +41 -0
- package/src/store/user/slices/preference/action.ts +50 -0
- package/src/store/user/slices/preference/initialState.ts +33 -0
- package/src/store/user/slices/preference/selectors.ts +13 -0
- package/src/store/{global → user}/slices/settings/actions/general.test.ts +6 -6
- package/src/store/{global → user}/slices/settings/actions/general.ts +2 -2
- package/src/store/{global → user}/slices/settings/actions/index.ts +2 -2
- package/src/store/{global → user}/slices/settings/actions/llm.test.ts +11 -14
- package/src/store/{global → user}/slices/settings/actions/llm.ts +2 -2
- package/src/store/{global → user}/slices/settings/initialState.ts +2 -2
- package/src/store/{global → user}/slices/settings/selectors/modelConfig.test.ts +8 -8
- package/src/store/{global → user}/slices/settings/selectors/modelConfig.ts +12 -12
- package/src/store/{global → user}/slices/settings/selectors/modelProvider.test.ts +17 -17
- package/src/store/{global → user}/slices/settings/selectors/modelProvider.ts +19 -20
- package/src/store/{global → user}/slices/settings/selectors/selectors.test.ts +8 -8
- package/src/store/{global → user}/slices/settings/selectors/settings.ts +12 -12
- package/src/store/user/slices/settings/selectors/sync.ts +14 -0
- package/src/store/user/store.ts +33 -0
- package/src/tools/dalle/Render/ToolBar.tsx +3 -3
- package/src/utils/localStorage.ts +3 -1
- package/src/store/featureFlags/Provider.tsx +0 -18
- package/src/store/featureFlags/index.ts +0 -3
- package/src/store/featureFlags/selectors.ts +0 -5
- package/src/store/featureFlags/store.ts +0 -42
- package/src/store/global/slices/common/initialState.ts +0 -42
- package/src/store/global/slices/common/selectors.ts +0 -8
- package/src/store/global/slices/preference/initialState.ts +0 -51
- package/src/store/global/slices/preference/selectors.ts +0 -18
- package/src/store/global/slices/settings/selectors/sync.ts +0 -14
- /package/src/server/{routers/config → globalConfig}/parseDefaultAgent.test.ts +0 -0
- /package/src/server/{routers/config → globalConfig}/parseDefaultAgent.ts +0 -0
- /package/src/store/{global → user}/slices/settings/reducers/customModelCard.test.ts +0 -0
- /package/src/store/{global → user}/slices/settings/reducers/customModelCard.ts +0 -0
- /package/src/store/{global → user}/slices/settings/selectors/__snapshots__/selectors.test.ts.snap +0 -0
- /package/src/store/{global → user}/slices/settings/selectors/index.ts +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { StoreApi } from 'zustand';
|
|
2
|
+
import { createContext } from 'zustand-utils';
|
|
3
|
+
import { devtools } from 'zustand/middleware';
|
|
4
|
+
import { shallow } from 'zustand/shallow';
|
|
5
|
+
import { createWithEqualityFn } from 'zustand/traditional';
|
|
6
|
+
import { StateCreator } from 'zustand/vanilla';
|
|
7
|
+
|
|
8
|
+
import { DEFAULT_FEATURE_FLAGS, IFeatureFlags } from '@/config/featureFlags';
|
|
9
|
+
import { GlobalServerConfig } from '@/types/serverConfig';
|
|
10
|
+
import { isDev } from '@/utils/env';
|
|
11
|
+
import { merge } from '@/utils/merge';
|
|
12
|
+
import { StoreApiWithSelector } from '@/utils/zustand';
|
|
13
|
+
|
|
14
|
+
const initialState: ServerConfigStore = {
|
|
15
|
+
featureFlags: DEFAULT_FEATURE_FLAGS,
|
|
16
|
+
serverConfig: { telemetry: {} },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// =============== 聚合 createStoreFn ============ //
|
|
20
|
+
|
|
21
|
+
export interface ServerConfigStore {
|
|
22
|
+
featureFlags: IFeatureFlags;
|
|
23
|
+
serverConfig: GlobalServerConfig;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type CreateStore = (
|
|
27
|
+
initState: Partial<ServerConfigStore>,
|
|
28
|
+
) => StateCreator<ServerConfigStore, [['zustand/devtools', never]]>;
|
|
29
|
+
|
|
30
|
+
const createStore: CreateStore = (runtimeState) => () => ({
|
|
31
|
+
...merge(initialState, runtimeState),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// =============== 实装 useStore ============ //
|
|
35
|
+
|
|
36
|
+
let store: StoreApi<ServerConfigStore>;
|
|
37
|
+
|
|
38
|
+
export const initServerConfigStore = (initState: Partial<ServerConfigStore>) =>
|
|
39
|
+
createWithEqualityFn<ServerConfigStore>()(
|
|
40
|
+
devtools(createStore(initState || {}), {
|
|
41
|
+
name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
|
|
42
|
+
}),
|
|
43
|
+
shallow,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
export const createServerConfigStore = (initState?: Partial<ServerConfigStore>) => {
|
|
47
|
+
// make sure there is only one store
|
|
48
|
+
if (!store) {
|
|
49
|
+
store = createWithEqualityFn<ServerConfigStore>()(
|
|
50
|
+
devtools(createStore(initState || {}), {
|
|
51
|
+
name: 'LobeChat_ServerConfig' + (isDev ? '_DEV' : ''),
|
|
52
|
+
}),
|
|
53
|
+
shallow,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return store;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const { useStore: useServerConfigStore, Provider } =
|
|
61
|
+
createContext<StoreApiWithSelector<ServerConfigStore>>();
|
|
@@ -8,9 +8,9 @@ import { message } from '@/components/AntdStaticMethods';
|
|
|
8
8
|
import { DEFAULT_AGENT_LOBE_SESSION, INBOX_SESSION_ID } from '@/const/session';
|
|
9
9
|
import { useClientDataSWR } from '@/libs/swr';
|
|
10
10
|
import { sessionService } from '@/services/session';
|
|
11
|
-
import { useGlobalStore } from '@/store/global';
|
|
12
|
-
import { settingsSelectors } from '@/store/global/selectors';
|
|
13
11
|
import { SessionStore } from '@/store/session';
|
|
12
|
+
import { useUserStore } from '@/store/user';
|
|
13
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
|
14
14
|
import { MetaData } from '@/types/meta';
|
|
15
15
|
import {
|
|
16
16
|
ChatSessionList,
|
|
@@ -111,7 +111,7 @@ export const createSessionSlice: StateCreator<
|
|
|
111
111
|
// merge the defaultAgent in settings
|
|
112
112
|
const defaultAgent = merge(
|
|
113
113
|
DEFAULT_AGENT_LOBE_SESSION,
|
|
114
|
-
settingsSelectors.defaultAgent(
|
|
114
|
+
settingsSelectors.defaultAgent(useUserStore.getState()),
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
const newSession: LobeAgentSession = merge(defaultAgent, agent);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { settingsSelectors } from './slices/settings/selectors';
|
|
2
|
-
import {
|
|
2
|
+
import { useUserStore } from './store';
|
|
3
3
|
|
|
4
|
-
const getCurrentLanguage = () => settingsSelectors.currentLanguage(
|
|
4
|
+
const getCurrentLanguage = () => settingsSelectors.currentLanguage(useUserStore.getState());
|
|
5
5
|
|
|
6
6
|
export const globalHelpers = {
|
|
7
7
|
getCurrentLanguage,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './store';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { UserCommonState, initialCommonState } from './slices/common/initialState';
|
|
2
|
+
import { UserPreferenceState, initialPreferenceState } from './slices/preference/initialState';
|
|
3
|
+
import { UserSettingsState, initialSettingsState } from './slices/settings/initialState';
|
|
4
|
+
|
|
5
|
+
export type UserState = UserCommonState & UserSettingsState & UserPreferenceState;
|
|
6
|
+
|
|
7
|
+
export const initialState: UserState = {
|
|
8
|
+
...initialCommonState,
|
|
9
|
+
...initialSettingsState,
|
|
10
|
+
...initialPreferenceState,
|
|
11
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { commonSelectors } from './slices/common/selectors';
|
|
2
|
+
export { preferenceSelectors } from './slices/preference/selectors';
|
|
3
|
+
export {
|
|
4
|
+
modelConfigSelectors,
|
|
5
|
+
modelProviderSelectors,
|
|
6
|
+
settingsSelectors,
|
|
7
|
+
syncSettingsSelectors,
|
|
8
|
+
} from './slices/settings/selectors';
|
|
@@ -6,10 +6,10 @@ import { withSWR } from '~test-utils';
|
|
|
6
6
|
import { globalService } from '@/services/global';
|
|
7
7
|
import { messageService } from '@/services/message';
|
|
8
8
|
import { userService } from '@/services/user';
|
|
9
|
-
import {
|
|
10
|
-
import { commonSelectors } from '@/store/
|
|
11
|
-
import { preferenceSelectors } from '@/store/
|
|
12
|
-
import { syncSettingsSelectors } from '@/store/
|
|
9
|
+
import { useUserStore } from '@/store/user';
|
|
10
|
+
import { commonSelectors } from '@/store/user/slices/common/selectors';
|
|
11
|
+
import { preferenceSelectors } from '@/store/user/slices/preference/selectors';
|
|
12
|
+
import { syncSettingsSelectors } from '@/store/user/slices/settings/selectors';
|
|
13
13
|
import { GlobalServerConfig } from '@/types/serverConfig';
|
|
14
14
|
import { switchLang } from '@/utils/client/switchLang';
|
|
15
15
|
|
|
@@ -32,24 +32,9 @@ afterEach(() => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
describe('createCommonSlice', () => {
|
|
35
|
-
describe('switchBackToChat', () => {
|
|
36
|
-
it('should switch back to chat', () => {
|
|
37
|
-
const { result } = renderHook(() => useGlobalStore());
|
|
38
|
-
const sessionId = 'session-id';
|
|
39
|
-
const router = { push: vi.fn() } as any;
|
|
40
|
-
|
|
41
|
-
act(() => {
|
|
42
|
-
useGlobalStore.setState({ router });
|
|
43
|
-
result.current.switchBackToChat(sessionId);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
expect(router.push).toHaveBeenCalledWith('/chat?session=session-id');
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
35
|
describe('refreshUserConfig', () => {
|
|
51
36
|
it('should refresh user config', async () => {
|
|
52
|
-
const { result } = renderHook(() =>
|
|
37
|
+
const { result } = renderHook(() => useUserStore());
|
|
53
38
|
|
|
54
39
|
await act(async () => {
|
|
55
40
|
await result.current.refreshUserConfig();
|
|
@@ -61,7 +46,7 @@ describe('createCommonSlice', () => {
|
|
|
61
46
|
|
|
62
47
|
describe('updateAvatar', () => {
|
|
63
48
|
it('should update avatar', async () => {
|
|
64
|
-
const { result } = renderHook(() =>
|
|
49
|
+
const { result } = renderHook(() => useUserStore());
|
|
65
50
|
const avatar = 'new-avatar';
|
|
66
51
|
|
|
67
52
|
const spyOn = vi.spyOn(result.current, 'refreshUserConfig');
|
|
@@ -76,42 +61,6 @@ describe('createCommonSlice', () => {
|
|
|
76
61
|
});
|
|
77
62
|
});
|
|
78
63
|
|
|
79
|
-
describe('useCheckLatestVersion', () => {
|
|
80
|
-
it('should set hasNewVersion to false if there is no new version', async () => {
|
|
81
|
-
const latestVersion = '0.0.1';
|
|
82
|
-
|
|
83
|
-
vi.spyOn(globalService, 'getLatestVersion').mockResolvedValueOnce(latestVersion);
|
|
84
|
-
|
|
85
|
-
const { result } = renderHook(() => useGlobalStore().useCheckLatestVersion(), {
|
|
86
|
-
wrapper: withSWR,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
await waitFor(() => {
|
|
90
|
-
expect(result.current.data).toBe(latestVersion);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
expect(useGlobalStore.getState().hasNewVersion).toBeUndefined();
|
|
94
|
-
expect(useGlobalStore.getState().latestVersion).toBeUndefined();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should set hasNewVersion to true if there is a new version', async () => {
|
|
98
|
-
const latestVersion = '10000000.0.0';
|
|
99
|
-
|
|
100
|
-
vi.spyOn(globalService, 'getLatestVersion').mockResolvedValueOnce(latestVersion);
|
|
101
|
-
|
|
102
|
-
const { result } = renderHook(() => useGlobalStore().useCheckLatestVersion(), {
|
|
103
|
-
wrapper: withSWR,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
await waitFor(() => {
|
|
107
|
-
expect(result.current.data).toBe(latestVersion);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(useGlobalStore.getState().hasNewVersion).toBe(true);
|
|
111
|
-
expect(useGlobalStore.getState().latestVersion).toBe(latestVersion);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
64
|
describe('useFetchServerConfig', () => {
|
|
116
65
|
it('should fetch server config correctly', async () => {
|
|
117
66
|
const mockServerConfig = {
|
|
@@ -121,7 +70,7 @@ describe('createCommonSlice', () => {
|
|
|
121
70
|
} as GlobalServerConfig;
|
|
122
71
|
vi.spyOn(globalService, 'getGlobalConfig').mockResolvedValueOnce(mockServerConfig);
|
|
123
72
|
|
|
124
|
-
const { result } = renderHook(() =>
|
|
73
|
+
const { result } = renderHook(() => useUserStore().useFetchServerConfig());
|
|
125
74
|
|
|
126
75
|
await waitFor(() => expect(result.current.data).toEqual(mockServerConfig));
|
|
127
76
|
});
|
|
@@ -132,7 +81,7 @@ describe('createCommonSlice', () => {
|
|
|
132
81
|
const mockUserConfig: any = undefined; // 模拟未初始化服务器的情况
|
|
133
82
|
vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig);
|
|
134
83
|
|
|
135
|
-
const { result } = renderHook(() =>
|
|
84
|
+
const { result } = renderHook(() => useUserStore().useFetchUserConfig(false), {
|
|
136
85
|
wrapper: withSWR,
|
|
137
86
|
});
|
|
138
87
|
|
|
@@ -151,7 +100,7 @@ describe('createCommonSlice', () => {
|
|
|
151
100
|
};
|
|
152
101
|
vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig);
|
|
153
102
|
|
|
154
|
-
const { result } = renderHook(() =>
|
|
103
|
+
const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), {
|
|
155
104
|
wrapper: withSWR,
|
|
156
105
|
});
|
|
157
106
|
|
|
@@ -159,8 +108,8 @@ describe('createCommonSlice', () => {
|
|
|
159
108
|
await waitFor(() => expect(result.current.data).toEqual(mockUserConfig));
|
|
160
109
|
|
|
161
110
|
// 验证状态是否正确更新
|
|
162
|
-
expect(
|
|
163
|
-
expect(
|
|
111
|
+
expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar);
|
|
112
|
+
expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings);
|
|
164
113
|
|
|
165
114
|
// 验证是否正确处理了语言设置
|
|
166
115
|
expect(switchLang).not.toHaveBeenCalledWith('auto');
|
|
@@ -174,7 +123,7 @@ describe('createCommonSlice', () => {
|
|
|
174
123
|
};
|
|
175
124
|
vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(mockUserConfig);
|
|
176
125
|
|
|
177
|
-
const { result } = renderHook(() =>
|
|
126
|
+
const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), {
|
|
178
127
|
wrapper: withSWR,
|
|
179
128
|
});
|
|
180
129
|
|
|
@@ -182,8 +131,8 @@ describe('createCommonSlice', () => {
|
|
|
182
131
|
await waitFor(() => expect(result.current.data).toEqual(mockUserConfig));
|
|
183
132
|
|
|
184
133
|
// 验证状态是否正确更新
|
|
185
|
-
expect(
|
|
186
|
-
expect(
|
|
134
|
+
expect(useUserStore.getState().avatar).toBe(mockUserConfig.avatar);
|
|
135
|
+
expect(useUserStore.getState().settings).toEqual(mockUserConfig.settings);
|
|
187
136
|
|
|
188
137
|
// 验证是否正确处理了语言设置
|
|
189
138
|
expect(switchLang).toHaveBeenCalledWith('auto');
|
|
@@ -192,7 +141,7 @@ describe('createCommonSlice', () => {
|
|
|
192
141
|
it('should handle the case when user config is null', async () => {
|
|
193
142
|
vi.spyOn(userService, 'getUserConfig').mockResolvedValueOnce(null as any);
|
|
194
143
|
|
|
195
|
-
const { result } = renderHook(() =>
|
|
144
|
+
const { result } = renderHook(() => useUserStore().useFetchUserConfig(true), {
|
|
196
145
|
wrapper: withSWR,
|
|
197
146
|
});
|
|
198
147
|
|
|
@@ -200,14 +149,14 @@ describe('createCommonSlice', () => {
|
|
|
200
149
|
await waitFor(() => expect(result.current.data).toBeNull());
|
|
201
150
|
|
|
202
151
|
// 验证状态未被错误更新
|
|
203
|
-
expect(
|
|
204
|
-
expect(
|
|
152
|
+
expect(useUserStore.getState().avatar).toBeUndefined();
|
|
153
|
+
expect(useUserStore.getState().settings).toEqual({});
|
|
205
154
|
});
|
|
206
155
|
});
|
|
207
156
|
|
|
208
157
|
describe('refreshConnection', () => {
|
|
209
158
|
it('should not call triggerEnableSync when userId is empty', async () => {
|
|
210
|
-
const { result } = renderHook(() =>
|
|
159
|
+
const { result } = renderHook(() => useUserStore());
|
|
211
160
|
const onEvent = vi.fn();
|
|
212
161
|
|
|
213
162
|
vi.spyOn(commonSelectors, 'userId').mockReturnValueOnce(undefined);
|
|
@@ -221,7 +170,7 @@ describe('createCommonSlice', () => {
|
|
|
221
170
|
});
|
|
222
171
|
|
|
223
172
|
it('should call triggerEnableSync when userId exists', async () => {
|
|
224
|
-
const { result } = renderHook(() =>
|
|
173
|
+
const { result } = renderHook(() => useUserStore());
|
|
225
174
|
const onEvent = vi.fn();
|
|
226
175
|
const userId = 'user-id';
|
|
227
176
|
|
|
@@ -238,7 +187,7 @@ describe('createCommonSlice', () => {
|
|
|
238
187
|
|
|
239
188
|
describe('triggerEnableSync', () => {
|
|
240
189
|
it('should return false when sync.channelName is empty', async () => {
|
|
241
|
-
const { result } = renderHook(() =>
|
|
190
|
+
const { result } = renderHook(() => useUserStore());
|
|
242
191
|
const userId = 'user-id';
|
|
243
192
|
const onEvent = vi.fn();
|
|
244
193
|
|
|
@@ -270,7 +219,7 @@ describe('createCommonSlice', () => {
|
|
|
270
219
|
});
|
|
271
220
|
vi.spyOn(syncSettingsSelectors, 'deviceName').mockReturnValueOnce(deviceName);
|
|
272
221
|
const enabledSyncSpy = vi.spyOn(globalService, 'enabledSync').mockResolvedValueOnce(true);
|
|
273
|
-
const { result } = renderHook(() =>
|
|
222
|
+
const { result } = renderHook(() => useUserStore());
|
|
274
223
|
|
|
275
224
|
const data = await act(async () => {
|
|
276
225
|
return result.current.triggerEnableSync(userId, onEvent);
|
|
@@ -290,7 +239,7 @@ describe('createCommonSlice', () => {
|
|
|
290
239
|
|
|
291
240
|
describe('useCheckTrace', () => {
|
|
292
241
|
it('should return false when shouldFetch is false', async () => {
|
|
293
|
-
const { result } = renderHook(() =>
|
|
242
|
+
const { result } = renderHook(() => useUserStore().useCheckTrace(false), {
|
|
294
243
|
wrapper: withSWR,
|
|
295
244
|
});
|
|
296
245
|
|
|
@@ -300,7 +249,7 @@ describe('createCommonSlice', () => {
|
|
|
300
249
|
it('should return false when userAllowTrace is already set', async () => {
|
|
301
250
|
vi.spyOn(preferenceSelectors, 'userAllowTrace').mockReturnValueOnce(true);
|
|
302
251
|
|
|
303
|
-
const { result } = renderHook(() =>
|
|
252
|
+
const { result } = renderHook(() => useUserStore().useCheckTrace(true), {
|
|
304
253
|
wrapper: withSWR,
|
|
305
254
|
});
|
|
306
255
|
|
|
@@ -313,7 +262,7 @@ describe('createCommonSlice', () => {
|
|
|
313
262
|
.spyOn(messageService, 'messageCountToCheckTrace')
|
|
314
263
|
.mockResolvedValueOnce(true);
|
|
315
264
|
|
|
316
|
-
const { result } = renderHook(() =>
|
|
265
|
+
const { result } = renderHook(() => useUserStore().useCheckTrace(true), {
|
|
317
266
|
wrapper: withSWR,
|
|
318
267
|
});
|
|
319
268
|
|
|
@@ -324,10 +273,9 @@ describe('createCommonSlice', () => {
|
|
|
324
273
|
|
|
325
274
|
describe('useEnabledSync', () => {
|
|
326
275
|
it('should return false when userId is empty', async () => {
|
|
327
|
-
const { result } = renderHook(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
);
|
|
276
|
+
const { result } = renderHook(() => useUserStore().useEnabledSync(true, undefined, vi.fn()), {
|
|
277
|
+
wrapper: withSWR,
|
|
278
|
+
});
|
|
331
279
|
|
|
332
280
|
await waitFor(() => expect(result.current.data).toBe(false));
|
|
333
281
|
});
|
|
@@ -336,7 +284,7 @@ describe('createCommonSlice', () => {
|
|
|
336
284
|
const disableSyncSpy = vi.spyOn(globalService, 'disableSync').mockResolvedValueOnce(false);
|
|
337
285
|
|
|
338
286
|
const { result } = renderHook(
|
|
339
|
-
() =>
|
|
287
|
+
() => useUserStore().useEnabledSync(false, 'user-id', vi.fn()),
|
|
340
288
|
{ wrapper: withSWR },
|
|
341
289
|
);
|
|
342
290
|
|
|
@@ -349,7 +297,7 @@ describe('createCommonSlice', () => {
|
|
|
349
297
|
const onEvent = vi.fn();
|
|
350
298
|
const triggerEnableSyncSpy = vi.fn().mockResolvedValueOnce(true);
|
|
351
299
|
|
|
352
|
-
const { result } = renderHook(() =>
|
|
300
|
+
const { result } = renderHook(() => useUserStore());
|
|
353
301
|
|
|
354
302
|
// replace triggerEnableSync as a mock
|
|
355
303
|
result.current.triggerEnableSync = triggerEnableSyncSpy;
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import { gt } from 'semver';
|
|
2
1
|
import useSWR, { SWRResponse, mutate } from 'swr';
|
|
3
2
|
import { DeepPartial } from 'utility-types';
|
|
4
3
|
import type { StateCreator } from 'zustand/vanilla';
|
|
5
4
|
|
|
6
|
-
import { INBOX_SESSION_ID } from '@/const/session';
|
|
7
|
-
import { SESSION_CHAT_URL } from '@/const/url';
|
|
8
|
-
import { CURRENT_VERSION } from '@/const/version';
|
|
9
5
|
import { globalService } from '@/services/global';
|
|
10
6
|
import { messageService } from '@/services/message';
|
|
11
7
|
import { UserConfig, userService } from '@/services/user';
|
|
12
|
-
import type {
|
|
8
|
+
import type { UserStore } from '@/store/user';
|
|
13
9
|
import type { GlobalServerConfig } from '@/types/serverConfig';
|
|
14
10
|
import type { GlobalSettings } from '@/types/settings';
|
|
15
11
|
import { OnSyncEvent, PeerSyncStatus } from '@/types/sync';
|
|
@@ -30,10 +26,8 @@ const n = setNamespace('common');
|
|
|
30
26
|
export interface CommonAction {
|
|
31
27
|
refreshConnection: (onEvent: OnSyncEvent) => Promise<void>;
|
|
32
28
|
refreshUserConfig: () => Promise<void>;
|
|
33
|
-
switchBackToChat: (sessionId?: string) => void;
|
|
34
29
|
triggerEnableSync: (userId: string, onEvent: OnSyncEvent) => Promise<boolean>;
|
|
35
30
|
updateAvatar: (avatar: string) => Promise<void>;
|
|
36
|
-
useCheckLatestVersion: () => SWRResponse<string>;
|
|
37
31
|
useCheckTrace: (shouldFetch: boolean) => SWRResponse;
|
|
38
32
|
useEnabledSync: (
|
|
39
33
|
userEnableSync: boolean,
|
|
@@ -47,7 +41,7 @@ export interface CommonAction {
|
|
|
47
41
|
const USER_CONFIG_FETCH_KEY = 'fetchUserConfig';
|
|
48
42
|
|
|
49
43
|
export const createCommonSlice: StateCreator<
|
|
50
|
-
|
|
44
|
+
UserStore,
|
|
51
45
|
[['zustand/devtools', never]],
|
|
52
46
|
[],
|
|
53
47
|
CommonAction
|
|
@@ -67,9 +61,6 @@ export const createCommonSlice: StateCreator<
|
|
|
67
61
|
get().refreshModelProviderList();
|
|
68
62
|
},
|
|
69
63
|
|
|
70
|
-
switchBackToChat: (sessionId) => {
|
|
71
|
-
get().router?.push(SESSION_CHAT_URL(sessionId || INBOX_SESSION_ID, get().isMobile));
|
|
72
|
-
},
|
|
73
64
|
triggerEnableSync: async (userId: string, onEvent: OnSyncEvent) => {
|
|
74
65
|
// double-check the sync ability
|
|
75
66
|
// if there is no channelName, don't start sync
|
|
@@ -106,15 +97,6 @@ export const createCommonSlice: StateCreator<
|
|
|
106
97
|
await userService.updateAvatar(avatar);
|
|
107
98
|
await get().refreshUserConfig();
|
|
108
99
|
},
|
|
109
|
-
useCheckLatestVersion: () =>
|
|
110
|
-
useSWR('checkLatestVersion', globalService.getLatestVersion, {
|
|
111
|
-
// check latest version every 30 minutes
|
|
112
|
-
focusThrottleInterval: 1000 * 60 * 30,
|
|
113
|
-
onSuccess: (data: string) => {
|
|
114
|
-
if (gt(data, CURRENT_VERSION))
|
|
115
|
-
set({ hasNewVersion: true, latestVersion: data }, false, n('checkLatestVersion'));
|
|
116
|
-
},
|
|
117
|
-
}),
|
|
118
100
|
useCheckTrace: (shouldFetch) =>
|
|
119
101
|
useSWR<boolean>(
|
|
120
102
|
['checkTrace', shouldFetch],
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PeerSyncStatus, SyncAwarenessState } from '@/types/sync';
|
|
2
|
+
|
|
3
|
+
export interface Guide {
|
|
4
|
+
// Topic 引导
|
|
5
|
+
topic?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface UserCommonState {
|
|
9
|
+
syncAwareness: SyncAwarenessState[];
|
|
10
|
+
syncEnabled: boolean;
|
|
11
|
+
syncStatus: PeerSyncStatus;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const initialCommonState: UserCommonState = {
|
|
15
|
+
syncAwareness: [],
|
|
16
|
+
syncEnabled: false,
|
|
17
|
+
syncStatus: PeerSyncStatus.Disabled,
|
|
18
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { useUserStore } from '@/store/user';
|
|
5
|
+
|
|
6
|
+
import { type Guide } from './initialState';
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
vi.restoreAllMocks();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('createPreferenceSlice', () => {
|
|
17
|
+
describe('updateGuideState', () => {
|
|
18
|
+
it('should update guide state', () => {
|
|
19
|
+
const { result } = renderHook(() => useUserStore());
|
|
20
|
+
const guide: Guide = { topic: true };
|
|
21
|
+
|
|
22
|
+
act(() => {
|
|
23
|
+
result.current.updateGuideState(guide);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(result.current.preference.guide).toEqual(guide);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('updatePreference', () => {
|
|
31
|
+
it('should update preference', () => {
|
|
32
|
+
const { result } = renderHook(() => useUserStore());
|
|
33
|
+
|
|
34
|
+
act(() => {
|
|
35
|
+
result.current.updatePreference({ hideSyncAlert: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(result.current.preference.hideSyncAlert).toEqual(true);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SWRResponse } from 'swr';
|
|
2
|
+
import type { StateCreator } from 'zustand/vanilla';
|
|
3
|
+
|
|
4
|
+
import { useClientDataSWR } from '@/libs/swr';
|
|
5
|
+
import type { UserStore } from '@/store/user';
|
|
6
|
+
import { merge } from '@/utils/merge';
|
|
7
|
+
import { setNamespace } from '@/utils/storeDebug';
|
|
8
|
+
|
|
9
|
+
import type { Guide, UserPreference } from './initialState';
|
|
10
|
+
|
|
11
|
+
const n = setNamespace('preference');
|
|
12
|
+
|
|
13
|
+
export interface PreferenceAction {
|
|
14
|
+
updateGuideState: (guide: Partial<Guide>) => void;
|
|
15
|
+
updatePreference: (preference: Partial<UserPreference>, action?: any) => void;
|
|
16
|
+
useInitPreference: () => SWRResponse;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const createPreferenceSlice: StateCreator<
|
|
20
|
+
UserStore,
|
|
21
|
+
[['zustand/devtools', never]],
|
|
22
|
+
[],
|
|
23
|
+
PreferenceAction
|
|
24
|
+
> = (set, get) => ({
|
|
25
|
+
updateGuideState: (guide) => {
|
|
26
|
+
const { updatePreference } = get();
|
|
27
|
+
const nextGuide = merge(get().preference.guide, guide);
|
|
28
|
+
updatePreference({ guide: nextGuide });
|
|
29
|
+
},
|
|
30
|
+
updatePreference: (preference, action) => {
|
|
31
|
+
const nextPreference = merge(get().preference, preference);
|
|
32
|
+
|
|
33
|
+
set({ preference: nextPreference }, false, action || n('updatePreference'));
|
|
34
|
+
|
|
35
|
+
get().preferenceStorage.saveToLocalStorage(nextPreference);
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
useInitPreference: () =>
|
|
39
|
+
useClientDataSWR<UserPreference>(
|
|
40
|
+
'initUserPreference',
|
|
41
|
+
() => get().preferenceStorage.getFromLocalStorage(),
|
|
42
|
+
{
|
|
43
|
+
onSuccess: (preference) => {
|
|
44
|
+
if (preference) {
|
|
45
|
+
set({ preference }, false, n('initPreference'));
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from '@/utils/localStorage';
|
|
2
|
+
|
|
3
|
+
export interface Guide {
|
|
4
|
+
// Topic 引导
|
|
5
|
+
topic?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface UserPreference {
|
|
9
|
+
guide?: Guide;
|
|
10
|
+
hideSyncAlert?: boolean;
|
|
11
|
+
telemetry: boolean | null;
|
|
12
|
+
/**
|
|
13
|
+
* whether to use cmd + enter to send message
|
|
14
|
+
*/
|
|
15
|
+
useCmdEnterToSend?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UserPreferenceState {
|
|
19
|
+
/**
|
|
20
|
+
* the user preference, which only store in local storage
|
|
21
|
+
*/
|
|
22
|
+
preference: UserPreference;
|
|
23
|
+
preferenceStorage: AsyncLocalStorage<UserPreference>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const initialPreferenceState: UserPreferenceState = {
|
|
27
|
+
preference: {
|
|
28
|
+
guide: {},
|
|
29
|
+
telemetry: null,
|
|
30
|
+
useCmdEnterToSend: false,
|
|
31
|
+
},
|
|
32
|
+
preferenceStorage: new AsyncLocalStorage('LOBE_PREFERENCE'),
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UserStore } from '@/store/user';
|
|
2
|
+
|
|
3
|
+
const useCmdEnterToSend = (s: UserStore): boolean => s.preference.useCmdEnterToSend || false;
|
|
4
|
+
|
|
5
|
+
const userAllowTrace = (s: UserStore) => s.preference.telemetry;
|
|
6
|
+
|
|
7
|
+
const hideSyncAlert = (s: UserStore) => s.preference.hideSyncAlert;
|
|
8
|
+
|
|
9
|
+
export const preferenceSelectors = {
|
|
10
|
+
hideSyncAlert,
|
|
11
|
+
useCmdEnterToSend,
|
|
12
|
+
userAllowTrace,
|
|
13
|
+
};
|
|
@@ -5,7 +5,7 @@ import { withSWR } from '~test-utils';
|
|
|
5
5
|
|
|
6
6
|
import { DEFAULT_AGENT, DEFAULT_SETTINGS } from '@/const/settings';
|
|
7
7
|
import { userService } from '@/services/user';
|
|
8
|
-
import {
|
|
8
|
+
import { useUserStore } from '@/store/user';
|
|
9
9
|
import { LobeAgentSettings } from '@/types/session';
|
|
10
10
|
import { GlobalSettings } from '@/types/settings';
|
|
11
11
|
|
|
@@ -20,7 +20,7 @@ vi.mock('@/services/user', () => ({
|
|
|
20
20
|
describe('SettingsAction', () => {
|
|
21
21
|
describe('importAppSettings', () => {
|
|
22
22
|
it('should import app settings', async () => {
|
|
23
|
-
const { result } = renderHook(() =>
|
|
23
|
+
const { result } = renderHook(() => useUserStore());
|
|
24
24
|
const newSettings: GlobalSettings = {
|
|
25
25
|
...DEFAULT_SETTINGS,
|
|
26
26
|
themeMode: 'dark',
|
|
@@ -51,7 +51,7 @@ describe('SettingsAction', () => {
|
|
|
51
51
|
|
|
52
52
|
describe('resetSettings', () => {
|
|
53
53
|
it('should reset settings to default', async () => {
|
|
54
|
-
const { result } = renderHook(() =>
|
|
54
|
+
const { result } = renderHook(() => useUserStore());
|
|
55
55
|
|
|
56
56
|
// Perform the action
|
|
57
57
|
await act(async () => {
|
|
@@ -68,7 +68,7 @@ describe('SettingsAction', () => {
|
|
|
68
68
|
|
|
69
69
|
describe('setSettings', () => {
|
|
70
70
|
it('should set partial settings', async () => {
|
|
71
|
-
const { result } = renderHook(() =>
|
|
71
|
+
const { result } = renderHook(() => useUserStore());
|
|
72
72
|
const partialSettings: Partial<GlobalSettings> = { themeMode: 'dark' };
|
|
73
73
|
|
|
74
74
|
// Perform the action
|
|
@@ -83,7 +83,7 @@ describe('SettingsAction', () => {
|
|
|
83
83
|
|
|
84
84
|
describe('switchThemeMode', () => {
|
|
85
85
|
it('should switch theme mode', async () => {
|
|
86
|
-
const { result } = renderHook(() =>
|
|
86
|
+
const { result } = renderHook(() => useUserStore());
|
|
87
87
|
const themeMode = 'light';
|
|
88
88
|
|
|
89
89
|
// Perform the action
|
|
@@ -98,7 +98,7 @@ describe('SettingsAction', () => {
|
|
|
98
98
|
|
|
99
99
|
describe('updateDefaultAgent', () => {
|
|
100
100
|
it('should update default agent settings', async () => {
|
|
101
|
-
const { result } = renderHook(() =>
|
|
101
|
+
const { result } = renderHook(() => useUserStore());
|
|
102
102
|
const updatedAgent: Partial<LobeAgentSettings> = {
|
|
103
103
|
meta: { title: 'docs' },
|
|
104
104
|
};
|