@lobehub/chat 1.36.30 → 1.36.31

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 (34) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +1 -1
  4. package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +3 -9
  5. package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +2 -4
  6. package/src/app/(main)/chat/@session/features/SessionListContent/DefaultMode.tsx +2 -5
  7. package/src/app/(main)/discover/(detail)/plugin/[slug]/features/InstallPlugin.tsx +10 -15
  8. package/src/database/server/models/plugin.ts +4 -0
  9. package/src/database/server/models/session.ts +2 -0
  10. package/src/features/AgentSetting/AgentPlugin/index.tsx +1 -1
  11. package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +4 -4
  12. package/src/features/Portal/Thread/Chat/ChatList.tsx +1 -2
  13. package/src/hooks/useCheckPluginsIsInstalled.ts +10 -0
  14. package/src/hooks/useFetchInstalledPlugins.ts +10 -0
  15. package/src/hooks/useFetchMessages.ts +15 -0
  16. package/src/hooks/useFetchSessions.ts +13 -0
  17. package/src/hooks/useFetchThreads.ts +11 -0
  18. package/src/hooks/useFetchTopics.ts +6 -6
  19. package/src/layout/GlobalProvider/StoreInitialization.tsx +3 -1
  20. package/src/services/user/client.ts +2 -2
  21. package/src/store/agent/slices/chat/action.test.ts +21 -10
  22. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +10 -0
  23. package/src/store/chat/slices/builtinTool/action.ts +0 -1
  24. package/src/store/chat/slices/message/action.test.ts +3 -1
  25. package/src/store/chat/slices/message/action.ts +7 -3
  26. package/src/store/chat/slices/thread/action.ts +3 -3
  27. package/src/store/chat/slices/topic/action.test.ts +1 -1
  28. package/src/store/chat/slices/topic/action.ts +3 -3
  29. package/src/store/global/selectors.ts +6 -0
  30. package/src/store/session/slices/session/action.ts +6 -3
  31. package/src/store/session/slices/sessionGroup/action.test.ts +8 -6
  32. package/src/store/tool/slices/plugin/action.ts +5 -3
  33. package/src/store/tool/slices/store/action.ts +4 -3
  34. package/src/store/user/slices/common/action.test.ts +3 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 1.36.31](https://github.com/lobehub/lobe-chat/compare/v1.36.30...v1.36.31)
6
+
7
+ <sup>Released on **2024-12-17**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the data fetch with clientDB init check.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the data fetch with clientDB init check, closes [#5049](https://github.com/lobehub/lobe-chat/issues/5049) ([e6d2e09](https://github.com/lobehub/lobe-chat/commit/e6d2e09))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 1.36.30](https://github.com/lobehub/lobe-chat/compare/v1.36.29...v1.36.30)
6
31
 
7
32
  <sup>Released on **2024-12-16**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor the data fetch with clientDB init check."
6
+ ]
7
+ },
8
+ "date": "2024-12-17",
9
+ "version": "1.36.31"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.36.30",
3
+ "version": "1.36.31",
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",
@@ -3,9 +3,9 @@
3
3
  import React, { memo, useCallback } from 'react';
4
4
 
5
5
  import { SkeletonList, VirtualizedList } from '@/features/Conversation';
6
+ import { useFetchMessages } from '@/hooks/useFetchMessages';
6
7
  import { useChatStore } from '@/store/chat';
7
8
  import { chatSelectors } from '@/store/chat/selectors';
8
- import { useSessionStore } from '@/store/session';
9
9
 
10
10
  import MainChatItem from './ChatItem';
11
11
  import Welcome from './WelcomeChatItem';
@@ -15,15 +15,9 @@ interface ListProps {
15
15
  }
16
16
 
17
17
  const Content = memo<ListProps>(({ mobile }) => {
18
- const [activeTopicId, useFetchMessages, isCurrentChatLoaded] = useChatStore((s) => [
19
- s.activeTopicId,
20
- s.useFetchMessages,
21
- chatSelectors.isCurrentChatLoaded(s),
22
- ]);
23
-
24
- const [sessionId] = useSessionStore((s) => [s.activeId]);
25
- useFetchMessages(sessionId, activeTopicId);
18
+ const [isCurrentChatLoaded] = useChatStore((s) => [chatSelectors.isCurrentChatLoaded(s)]);
26
19
 
20
+ useFetchMessages();
27
21
  const data = useChatStore(chatSelectors.mainDisplayChatIDs);
28
22
 
29
23
  const itemContent = useCallback(
@@ -4,6 +4,7 @@ import { useQueryState } from 'nuqs';
4
4
  import { memo, useEffect, useLayoutEffect } from 'react';
5
5
  import { createStoreUpdater } from 'zustand-utils';
6
6
 
7
+ import { useFetchThreads } from '@/hooks/useFetchThreads';
7
8
  import { useChatStore } from '@/store/chat';
8
9
 
9
10
  // sync outside state to useChatStore
@@ -34,10 +35,7 @@ const ThreadHydration = memo(() => {
34
35
  }
35
36
  }, [portalThread]);
36
37
 
37
- const [activeTopicId, useFetchThreads] = useChatStore((s) => [
38
- s.activeTopicId,
39
- s.useFetchThreads,
40
- ]);
38
+ const activeTopicId = useChatStore((s) => s.activeTopicId);
41
39
 
42
40
  useFetchThreads(activeTopicId);
43
41
 
@@ -3,12 +3,11 @@ import isEqual from 'fast-deep-equal';
3
3
  import { memo, useMemo, useState } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
+ import { useFetchSessions } from '@/hooks/useFetchSessions';
6
7
  import { useGlobalStore } from '@/store/global';
7
8
  import { systemStatusSelectors } from '@/store/global/selectors';
8
9
  import { useSessionStore } from '@/store/session';
9
10
  import { sessionSelectors } from '@/store/session/selectors';
10
- import { useUserStore } from '@/store/user';
11
- import { authSelectors } from '@/store/user/selectors';
12
11
  import { SessionDefaultGroup } from '@/types/session';
13
12
 
14
13
  import Actions from '../SessionListContent/CollapseGroup/Actions';
@@ -25,9 +24,7 @@ const DefaultMode = memo(() => {
25
24
  const [renameGroupModalOpen, setRenameGroupModalOpen] = useState(false);
26
25
  const [configGroupModalOpen, setConfigGroupModalOpen] = useState(false);
27
26
 
28
- const isLogin = useUserStore(authSelectors.isLogin);
29
- const [useFetchSessions] = useSessionStore((s) => [s.useFetchSessions]);
30
- useFetchSessions(isLogin);
27
+ useFetchSessions();
31
28
 
32
29
  const defaultSessions = useSessionStore(sessionSelectors.defaultSessions, isEqual);
33
30
  const customSessionGroups = useSessionStore(sessionSelectors.customSessionGroups, isEqual);
@@ -5,6 +5,7 @@ import { createStyles } from 'antd-style';
5
5
  import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
8
9
  import { useToolStore } from '@/store/tool';
9
10
  import { pluginSelectors, pluginStoreSelectors } from '@/store/tool/selectors';
10
11
 
@@ -21,21 +22,15 @@ const InstallPlugin = memo<{ identifier: string }>(({ identifier }) => {
21
22
  const { t } = useTranslation(['discover', 'plugin']);
22
23
  const [loading, setLoading] = useState(false);
23
24
  const { modal } = App.useApp();
24
- const [
25
- useFetchPluginStore,
26
- useFetchInstalledPlugins,
27
- installed,
28
- installing,
29
- installPlugin,
30
- unInstallPlugin,
31
- ] = useToolStore((s) => [
32
- s.useFetchPluginStore,
33
- s.useFetchInstalledPlugins,
34
- pluginSelectors.isPluginInstalled(identifier)(s),
35
- pluginStoreSelectors.isPluginInstallLoading(identifier)(s),
36
- s.installPlugin,
37
- s.uninstallPlugin,
38
- ]);
25
+ const [useFetchPluginStore, installed, installing, installPlugin, unInstallPlugin] = useToolStore(
26
+ (s) => [
27
+ s.useFetchPluginStore,
28
+ pluginSelectors.isPluginInstalled(identifier)(s),
29
+ pluginStoreSelectors.isPluginInstallLoading(identifier)(s),
30
+ s.installPlugin,
31
+ s.uninstallPlugin,
32
+ ],
33
+ );
39
34
 
40
35
  const { isLoading } = useFetchPluginStore();
41
36
  const { isLoading: installedPluginLoading } = useFetchInstalledPlugins();
@@ -19,6 +19,10 @@ export class PluginModel {
19
19
  const [result] = await this.db
20
20
  .insert(installedPlugins)
21
21
  .values({ ...params, createdAt: new Date(), updatedAt: new Date(), userId: this.userId })
22
+ .onConflictDoUpdate({
23
+ set: { ...params, updatedAt: new Date() },
24
+ target: [installedPlugins.identifier, installedPlugins.userId],
25
+ })
22
26
  .returning();
23
27
 
24
28
  return result;
@@ -229,6 +229,8 @@ export class SessionModel {
229
229
  }
230
230
 
231
231
  async updateConfig(id: string, data: Partial<AgentItem>) {
232
+ if (Object.keys(data).length === 0) return;
233
+
232
234
  return this.db
233
235
  .update(agents)
234
236
  .set(data)
@@ -11,6 +11,7 @@ import { Center, Flexbox } from 'react-layout-kit';
11
11
  import { FORM_STYLE } from '@/const/layoutTokens';
12
12
  import PluginStore from '@/features/PluginStore';
13
13
  import PluginTag from '@/features/PluginStore/PluginItem/PluginTag';
14
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
14
15
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
15
16
  import { pluginHelpers, useToolStore } from '@/store/tool';
16
17
  import { toolSelectors } from '@/store/tool/selectors';
@@ -33,7 +34,6 @@ const AgentPlugin = memo(() => {
33
34
 
34
35
  const { showDalle } = useServerConfigStore(featureFlagsSelectors);
35
36
  const installedPlugins = useToolStore(toolSelectors.metaList(showDalle), isEqual);
36
- const useFetchInstalledPlugins = useToolStore((s) => s.useFetchInstalledPlugins);
37
37
 
38
38
  const { isLoading } = useFetchInstalledPlugins();
39
39
 
@@ -10,6 +10,8 @@ import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import { useWorkspaceModal } from '@/app/(main)/chat/(workspace)/features/useWorkspaceModal';
12
12
  import PluginStore from '@/features/PluginStore';
13
+ import { useCheckPluginsIsInstalled } from '@/hooks/useCheckPluginsIsInstalled';
14
+ import { useFetchInstalledPlugins } from '@/hooks/useFetchInstalledPlugins';
13
15
  import { useAgentStore } from '@/store/agent';
14
16
  import { agentSelectors } from '@/store/agent/selectors';
15
17
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
@@ -109,13 +111,11 @@ const DropdownMenu = memo<PropsWithChildren>(({ children }) => {
109
111
 
110
112
  const plugins = useAgentStore((s) => agentSelectors.currentAgentPlugins(s));
111
113
 
112
- const [useFetchPluginStore, useFetchInstalledPlugins, checkPluginsIsInstalled] = useToolStore(
113
- (s) => [s.useFetchPluginStore, s.useFetchInstalledPlugins, s.useCheckPluginsIsInstalled],
114
- );
114
+ const [useFetchPluginStore] = useToolStore((s) => [s.useFetchPluginStore]);
115
115
 
116
116
  useFetchPluginStore();
117
117
  useFetchInstalledPlugins();
118
- checkPluginsIsInstalled(plugins);
118
+ useCheckPluginsIsInstalled(plugins);
119
119
 
120
120
  return (
121
121
  <>
@@ -2,6 +2,7 @@ import React, { memo, useCallback } from 'react';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import { SkeletonList, VirtualizedList } from '@/features/Conversation';
5
+ import { useFetchThreads } from '@/hooks/useFetchThreads';
5
6
  import { useChatStore } from '@/store/chat';
6
7
  import { threadSelectors } from '@/store/chat/selectors';
7
8
 
@@ -15,8 +16,6 @@ const ChatList = memo(({ mobile }: ChatListProps) => {
15
16
  const data = useChatStore(threadSelectors.portalDisplayChatIDs);
16
17
  const isInit = useChatStore((s) => s.threadsInit);
17
18
 
18
- const useFetchThreads = useChatStore((s) => s.useFetchThreads);
19
-
20
19
  useFetchThreads();
21
20
 
22
21
  const itemContent = useCallback(
@@ -0,0 +1,10 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useToolStore } from '@/store/tool';
4
+
5
+ export const useCheckPluginsIsInstalled = (plugins: string[]) => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+ const checkPluginsIsInstalled = useToolStore((s) => s.useCheckPluginsIsInstalled);
8
+
9
+ checkPluginsIsInstalled(isPgliteInited, plugins);
10
+ };
@@ -0,0 +1,10 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useToolStore } from '@/store/tool';
4
+
5
+ export const useFetchInstalledPlugins = () => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+ const [useFetchInstalledPlugins] = useToolStore((s) => [s.useFetchInstalledPlugins]);
8
+
9
+ return useFetchInstalledPlugins(isPgliteInited);
10
+ };
@@ -0,0 +1,15 @@
1
+ import { useChatStore } from '@/store/chat';
2
+ import { useGlobalStore } from '@/store/global';
3
+ import { systemStatusSelectors } from '@/store/global/selectors';
4
+ import { useSessionStore } from '@/store/session';
5
+
6
+ export const useFetchMessages = () => {
7
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
8
+ const [sessionId] = useSessionStore((s) => [s.activeId]);
9
+ const [activeTopicId, useFetchMessages] = useChatStore((s) => [
10
+ s.activeTopicId,
11
+ s.useFetchMessages,
12
+ ]);
13
+
14
+ useFetchMessages(isPgliteInited, sessionId, activeTopicId);
15
+ };
@@ -0,0 +1,13 @@
1
+ import { useGlobalStore } from '@/store/global';
2
+ import { systemStatusSelectors } from '@/store/global/selectors';
3
+ import { useSessionStore } from '@/store/session';
4
+ import { useUserStore } from '@/store/user';
5
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
6
+
7
+ export const useFetchSessions = () => {
8
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
9
+ const isLogin = useUserStore(authSelectors.isLogin);
10
+ const useFetchSessions = useSessionStore((s) => s.useFetchSessions);
11
+
12
+ useFetchSessions(isPgliteInited, isLogin);
13
+ };
@@ -0,0 +1,11 @@
1
+ import { useChatStore } from '@/store/chat';
2
+ import { useGlobalStore } from '@/store/global';
3
+ import { systemStatusSelectors } from '@/store/global/selectors';
4
+
5
+ export const useFetchThreads = (activeTopicId?: string) => {
6
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
7
+
8
+ const [useFetchThreads] = useChatStore((s) => [s.useFetchThreads]);
9
+
10
+ useFetchThreads(isPgliteInited, activeTopicId);
11
+ };
@@ -1,4 +1,7 @@
1
+ import { useFetchThreads } from '@/hooks/useFetchThreads';
1
2
  import { useChatStore } from '@/store/chat';
3
+ import { useGlobalStore } from '@/store/global';
4
+ import { systemStatusSelectors } from '@/store/global/selectors';
2
5
  import { useSessionStore } from '@/store/session';
3
6
 
4
7
  /**
@@ -6,12 +9,9 @@ import { useSessionStore } from '@/store/session';
6
9
  */
7
10
  export const useFetchTopics = () => {
8
11
  const [sessionId] = useSessionStore((s) => [s.activeId]);
9
- const [activeTopicId, useFetchTopics, useFetchThreads] = useChatStore((s) => [
10
- s.activeTopicId,
11
- s.useFetchTopics,
12
- s.useFetchThreads,
13
- ]);
14
- useFetchTopics(sessionId);
12
+ const [activeTopicId, useFetchTopics] = useChatStore((s) => [s.activeTopicId, s.useFetchTopics]);
13
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
15
14
 
15
+ useFetchTopics(isPgliteInited, sessionId);
16
16
  useFetchThreads(activeTopicId);
17
17
  };
@@ -10,6 +10,7 @@ import { useIsMobile } from '@/hooks/useIsMobile';
10
10
  import { useEnabledDataSync } from '@/hooks/useSyncData';
11
11
  import { useAgentStore } from '@/store/agent';
12
12
  import { useGlobalStore } from '@/store/global';
13
+ import { systemStatusSelectors } from '@/store/global/selectors';
13
14
  import { useServerConfigStore } from '@/store/serverConfig';
14
15
  import { serverConfigSelectors } from '@/store/serverConfig/selectors';
15
16
  import { useUserStore } from '@/store/user';
@@ -50,7 +51,8 @@ const StoreInitialization = memo(() => {
50
51
  * But during initialization, the value of `enableAuth` might be incorrect cause of the async fetch.
51
52
  * So we need to use `isSignedIn` only to determine whether request for the default agent config and user state.
52
53
  */
53
- const isLoginOnInit = enableNextAuth ? isSignedIn : isLogin;
54
+ const isPgliteInited = useGlobalStore(systemStatusSelectors.isPgliteInited);
55
+ const isLoginOnInit = isPgliteInited && (enableNextAuth ? isSignedIn : isLogin);
54
56
 
55
57
  // init inbox agent and default agent config
56
58
  useInitAgentStore(isLoginOnInit, serverConfig.defaultAgent?.config);
@@ -42,8 +42,8 @@ export class ClientService implements IUserService {
42
42
  return UserModel.resetSettings();
43
43
  };
44
44
 
45
- updateAvatar(avatar: string) {
46
- return UserModel.updateAvatar(avatar);
45
+ async updateAvatar(avatar: string) {
46
+ await UserModel.updateAvatar(avatar);
47
47
  }
48
48
 
49
49
  async updatePreference(preference: Partial<UserPreference>) {
@@ -3,7 +3,6 @@ import { mutate } from 'swr';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
4
 
5
5
  import { INBOX_SESSION_ID } from '@/const/session';
6
- import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
7
6
  import { globalService } from '@/services/global';
8
7
  import { sessionService } from '@/services/session';
9
8
  import { useAgentStore } from '@/store/agent';
@@ -24,7 +23,9 @@ describe('AgentSlice', () => {
24
23
  it('should call togglePlugin with the provided id and false', async () => {
25
24
  const { result } = renderHook(() => useAgentStore());
26
25
  const pluginId = 'plugin-id';
27
- const togglePluginMock = vi.spyOn(result.current, 'togglePlugin');
26
+ const togglePluginMock = vi
27
+ .spyOn(result.current, 'togglePlugin')
28
+ .mockResolvedValue(undefined);
28
29
 
29
30
  await act(async () => {
30
31
  await result.current.removePlugin(pluginId);
@@ -39,8 +40,9 @@ describe('AgentSlice', () => {
39
40
  it('should add plugin id to plugins array if not present and open is true or undefined', async () => {
40
41
  const { result } = renderHook(() => useAgentStore());
41
42
  const pluginId = 'plugin-id';
42
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
43
-
43
+ const updateAgentConfigMock = vi
44
+ .spyOn(result.current, 'updateAgentConfig')
45
+ .mockResolvedValue(undefined);
44
46
  // 模拟当前配置不包含插件 ID
45
47
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any);
46
48
 
@@ -57,8 +59,9 @@ describe('AgentSlice', () => {
57
59
  it('should remove plugin id from plugins array if present and open is false', async () => {
58
60
  const { result } = renderHook(() => useAgentStore());
59
61
  const pluginId = 'plugin-id';
60
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
61
-
62
+ const updateAgentConfigMock = vi
63
+ .spyOn(result.current, 'updateAgentConfig')
64
+ .mockResolvedValue(undefined);
62
65
  // 模拟当前配置包含插件 ID
63
66
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({
64
67
  plugins: [pluginId],
@@ -75,7 +78,9 @@ describe('AgentSlice', () => {
75
78
  it('should not modify plugins array if plugin id is not present and open is false', async () => {
76
79
  const { result } = renderHook(() => useAgentStore());
77
80
  const pluginId = 'plugin-id';
78
- const updateAgentConfigMock = vi.spyOn(result.current, 'updateAgentConfig');
81
+ const updateAgentConfigMock = vi
82
+ .spyOn(result.current, 'updateAgentConfig')
83
+ .mockResolvedValue(undefined);
79
84
 
80
85
  // 模拟当前配置不包含插件 ID
81
86
  vi.spyOn(agentSelectors, 'currentAgentConfig').mockReturnValue({ plugins: [] } as any);
@@ -93,7 +98,9 @@ describe('AgentSlice', () => {
93
98
  it('should update global config if current session is inbox session', async () => {
94
99
  const { result } = renderHook(() => useAgentStore());
95
100
  const config = { model: 'gpt-3.5-turbo' };
96
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
101
+ const updateSessionConfigMock = vi
102
+ .spyOn(sessionService, 'updateSessionConfig')
103
+ .mockResolvedValue(undefined);
97
104
  const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig');
98
105
 
99
106
  await act(async () => {
@@ -113,7 +120,9 @@ describe('AgentSlice', () => {
113
120
  it('should update session config if current session is not inbox session', async () => {
114
121
  const { result } = renderHook(() => useAgentStore());
115
122
  const config = { model: 'gpt-3.5-turbo' };
116
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
123
+ const updateSessionConfigMock = vi
124
+ .spyOn(sessionService, 'updateSessionConfig')
125
+ .mockResolvedValue(undefined);
117
126
  const refreshMock = vi.spyOn(result.current, 'internal_refreshAgentConfig');
118
127
 
119
128
  // 模拟当前会话不是收件箱会话
@@ -247,7 +256,9 @@ describe('AgentSlice', () => {
247
256
  it('should call sessionService.updateSessionConfig', async () => {
248
257
  const { result } = renderHook(() => useAgentStore());
249
258
 
250
- const updateSessionConfigMock = vi.spyOn(sessionService, 'updateSessionConfig');
259
+ const updateSessionConfigMock = vi
260
+ .spyOn(sessionService, 'updateSessionConfig')
261
+ .mockResolvedValue(undefined);
251
262
 
252
263
  await act(async () => {
253
264
  await result.current.internal_updateAgentConfig('test-session-id', { foo: 'bar' } as any);
@@ -5,6 +5,7 @@ import { LOADING_FLAT } from '@/const/message';
5
5
  import { DEFAULT_AGENT_CHAT_CONFIG, DEFAULT_AGENT_CONFIG } from '@/const/settings';
6
6
  import { chatService } from '@/services/chat';
7
7
  import { messageService } from '@/services/message';
8
+ import { sessionService } from '@/services/session';
8
9
  import { topicService } from '@/services/topic';
9
10
  import { useAgentStore } from '@/store/agent';
10
11
  import { agentSelectors } from '@/store/agent/selectors';
@@ -51,6 +52,15 @@ vi.mock('@/services/chat', async (importOriginal) => {
51
52
  },
52
53
  };
53
54
  });
55
+ vi.mock('@/services/session', async (importOriginal) => {
56
+ const module = await importOriginal();
57
+
58
+ return {
59
+ sessionService: {
60
+ updateSession: vi.fn(),
61
+ },
62
+ };
63
+ });
54
64
 
55
65
  const realCoreProcessMessage = useChatStore.getState().internal_coreProcessMessage;
56
66
 
@@ -72,7 +72,6 @@ export const chatToolSlice: StateCreator<
72
72
 
73
73
  const data = await useFileStore.getState().uploadWithProgress({
74
74
  file: imageFile,
75
- onStatusUpdate: () => {},
76
75
  });
77
76
 
78
77
  if (!data) return;
@@ -466,7 +466,9 @@ describe('chatMessage actions', () => {
466
466
  // 设置模拟返回值
467
467
  (messageService.getMessages as Mock).mockResolvedValue(messages);
468
468
 
469
- const { result } = renderHook(() => useChatStore().useFetchMessages(sessionId, topicId));
469
+ const { result } = renderHook(() =>
470
+ useChatStore().useFetchMessages(true, sessionId, topicId),
471
+ );
470
472
 
471
473
  // 等待异步操作完成
472
474
  await waitFor(() => {
@@ -47,7 +47,11 @@ export interface ChatMessageAction {
47
47
  modifyMessageContent: (id: string, content: string) => Promise<void>;
48
48
  toggleMessageEditing: (id: string, editing: boolean) => void;
49
49
  // query
50
- useFetchMessages: (sessionId: string, topicId?: string) => SWRResponse<ChatMessage[]>;
50
+ useFetchMessages: (
51
+ enable: boolean,
52
+ sessionId: string,
53
+ topicId?: string,
54
+ ) => SWRResponse<ChatMessage[]>;
51
55
  copyMessage: (id: string, content: string) => Promise<void>;
52
56
  refreshMessages: () => Promise<void>;
53
57
 
@@ -220,9 +224,9 @@ export const chatMessage: StateCreator<
220
224
 
221
225
  await get().internal_updateMessageContent(id, content);
222
226
  },
223
- useFetchMessages: (sessionId, activeTopicId) =>
227
+ useFetchMessages: (enable, sessionId, activeTopicId) =>
224
228
  useClientDataSWR<ChatMessage[]>(
225
- [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId],
229
+ enable ? [SWR_USE_FETCH_MESSAGES, sessionId, activeTopicId] : null,
226
230
  async ([, sessionId, topicId]: [string, string, string | undefined]) =>
227
231
  messageService.getMessages(sessionId, topicId),
228
232
  {
@@ -44,7 +44,7 @@ export interface ChatThreadAction {
44
44
  openThreadCreator: (messageId: string) => void;
45
45
  openThreadInPortal: (threadId: string, sourceMessageId: string) => void;
46
46
  closeThreadPortal: () => void;
47
- useFetchThreads: (topicId?: string) => SWRResponse<ThreadItem[]>;
47
+ useFetchThreads: (enable: boolean, topicId?: string) => SWRResponse<ThreadItem[]>;
48
48
  summaryThreadTitle: (threadId: string, messages: ChatMessage[]) => Promise<void>;
49
49
  updateThreadTitle: (id: string, title: string) => Promise<void>;
50
50
  removeThread: (id: string) => Promise<void>;
@@ -209,9 +209,9 @@ export const chatThreadMessage: StateCreator<
209
209
  return data;
210
210
  },
211
211
 
212
- useFetchThreads: (topicId) =>
212
+ useFetchThreads: (enable, topicId) =>
213
213
  useClientDataSWR<ThreadItem[]>(
214
- !!topicId && isServerMode ? [SWR_USE_FETCH_THREADS, topicId] : null,
214
+ enable && !!topicId && isServerMode ? [SWR_USE_FETCH_THREADS, topicId] : null,
215
215
  async ([, topicId]: [string, string]) => threadService.getThreads(topicId),
216
216
  {
217
217
  suspense: true,
@@ -232,7 +232,7 @@ describe('topic action', () => {
232
232
  (topicService.getTopics as Mock).mockResolvedValue(topics);
233
233
 
234
234
  // Use the hook with the session id
235
- const { result } = renderHook(() => useChatStore().useFetchTopics(sessionId));
235
+ const { result } = renderHook(() => useChatStore().useFetchTopics(true, sessionId));
236
236
 
237
237
  // Wait for the hook to resolve and update the state
238
238
  await waitFor(() => {
@@ -48,7 +48,7 @@ export interface ChatTopicAction {
48
48
  summaryTopicTitle: (topicId: string, messages: ChatMessage[]) => Promise<void>;
49
49
  switchTopic: (id?: string, skipRefreshMessage?: boolean) => Promise<void>;
50
50
  updateTopicTitle: (id: string, title: string) => Promise<void>;
51
- useFetchTopics: (sessionId: string) => SWRResponse<ChatTopic[]>;
51
+ useFetchTopics: (enable: boolean, sessionId: string) => SWRResponse<ChatTopic[]>;
52
52
  useSearchTopics: (keywords?: string, sessionId?: string) => SWRResponse<ChatTopic[]>;
53
53
 
54
54
  internal_updateTopicTitleInSummary: (id: string, title: string) => void;
@@ -190,9 +190,9 @@ export const chatTopic: StateCreator<
190
190
  },
191
191
 
192
192
  // query
193
- useFetchTopics: (sessionId) =>
193
+ useFetchTopics: (enable, sessionId) =>
194
194
  useClientDataSWR<ChatTopic[]>(
195
- [SWR_USE_FETCH_TOPIC, sessionId],
195
+ enable ? [SWR_USE_FETCH_TOPIC, sessionId] : null,
196
196
  async ([, sessionId]: [string, string]) => topicService.getTopics({ sessionId }),
197
197
  {
198
198
  suspense: true,
@@ -24,12 +24,18 @@ const threadInputHeight = (s: GlobalStore) => s.status.threadInputHeight;
24
24
 
25
25
  const isPgliteNotEnabled = () => false;
26
26
 
27
+ const isPgliteNotInited = () => false;
28
+
29
+ const isPgliteInited = (): boolean => true;
30
+
27
31
  export const systemStatusSelectors = {
28
32
  filePanelWidth,
29
33
  hidePWAInstaller,
30
34
  inZenMode,
31
35
  inputHeight,
36
+ isPgliteInited,
32
37
  isPgliteNotEnabled,
38
+ isPgliteNotInited,
33
39
  mobileShowPortal,
34
40
  mobileShowTopic,
35
41
  sessionGroupKeys,
@@ -73,7 +73,10 @@ export interface SessionAction {
73
73
 
74
74
  updateSearchKeywords: (keywords: string) => void;
75
75
 
76
- useFetchSessions: (isLogin: boolean | undefined) => SWRResponse<ChatSessionList>;
76
+ useFetchSessions: (
77
+ enabled: boolean,
78
+ isLogin: boolean | undefined,
79
+ ) => SWRResponse<ChatSessionList>;
77
80
  useSearchSessions: (keyword?: string) => SWRResponse<any>;
78
81
 
79
82
  internal_dispatchSessions: (payload: SessionDispatch) => void;
@@ -197,9 +200,9 @@ export const createSessionSlice: StateCreator<
197
200
  await refreshSessions();
198
201
  },
199
202
 
200
- useFetchSessions: (isLogin) =>
203
+ useFetchSessions: (enabled, isLogin) =>
201
204
  useClientDataSWR<ChatSessionList>(
202
- [FETCH_SESSIONS_KEY, isLogin],
205
+ enabled ? [FETCH_SESSIONS_KEY, isLogin] : null,
203
206
  () => sessionService.getGroupedSessions(),
204
207
  {
205
208
  fallbackData: {
@@ -40,7 +40,9 @@ describe('createSessionGroupSlice', () => {
40
40
 
41
41
  describe('clearSessionGroups', () => {
42
42
  it('should clear session groups and refresh sessions', async () => {
43
- vi.spyOn(sessionService, 'removeSessionGroups');
43
+ const spyOn = vi
44
+ .spyOn(sessionService, 'removeSessionGroups')
45
+ .mockResolvedValueOnce(undefined);
44
46
  const spyOnRefreshSessions = vi.spyOn(useSessionStore.getState(), 'refreshSessions');
45
47
 
46
48
  const { result } = renderHook(() => useSessionStore());
@@ -49,7 +51,7 @@ describe('createSessionGroupSlice', () => {
49
51
  await result.current.clearSessionGroups();
50
52
  });
51
53
 
52
- expect(sessionService.removeSessionGroups).toHaveBeenCalled();
54
+ expect(spyOn).toHaveBeenCalled();
53
55
  expect(spyOnRefreshSessions).toHaveBeenCalled();
54
56
  });
55
57
  });
@@ -57,7 +59,7 @@ describe('createSessionGroupSlice', () => {
57
59
  describe('removeSessionGroup', () => {
58
60
  it('should remove a session group and refresh sessions', async () => {
59
61
  const mockId = 'mock-id';
60
- vi.spyOn(sessionService, 'removeSessionGroup');
62
+ vi.spyOn(sessionService, 'removeSessionGroup').mockResolvedValueOnce(undefined);
61
63
  const spyOnRefreshSessions = vi.spyOn(useSessionStore.getState(), 'refreshSessions');
62
64
 
63
65
  const { result } = renderHook(() => useSessionStore());
@@ -75,7 +77,7 @@ describe('createSessionGroupSlice', () => {
75
77
  it('should update a session group id and refresh sessions', async () => {
76
78
  const mockSessionId = 'session-id';
77
79
  const mockGroupId = 'group-id';
78
- vi.spyOn(sessionService, 'updateSession');
80
+ vi.spyOn(sessionService, 'updateSession').mockResolvedValueOnce(undefined);
79
81
  const spyOnRefreshSessions = vi.spyOn(useSessionStore.getState(), 'refreshSessions');
80
82
 
81
83
  const { result } = renderHook(() => useSessionStore());
@@ -96,7 +98,7 @@ describe('createSessionGroupSlice', () => {
96
98
  const mockId = 'mock-id';
97
99
  const mockName = 'New Name';
98
100
  const spyOnRefreshSessions = vi.spyOn(useSessionStore.getState(), 'refreshSessions');
99
- vi.spyOn(sessionService, 'updateSessionGroup');
101
+ vi.spyOn(sessionService, 'updateSessionGroup').mockResolvedValueOnce(undefined);
100
102
 
101
103
  const { result } = renderHook(() => useSessionStore());
102
104
 
@@ -115,7 +117,7 @@ describe('createSessionGroupSlice', () => {
115
117
  { id: 'id1', sort: 0 },
116
118
  { id: 'id2', sort: 1 },
117
119
  ];
118
- vi.spyOn(sessionService, 'updateSessionGroupOrder');
120
+ vi.spyOn(sessionService, 'updateSessionGroupOrder').mockResolvedValueOnce(undefined);
119
121
  const spyOnRefreshSessions = vi.spyOn(useSessionStore.getState(), 'refreshSessions');
120
122
 
121
123
  const { result } = renderHook(() => useSessionStore());
@@ -1,8 +1,9 @@
1
1
  import { Schema, ValidationResult } from '@cfworker/json-schema';
2
- import useSWR, { SWRResponse } from 'swr';
2
+ import { SWRResponse } from 'swr';
3
3
  import { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { MESSAGE_CANCEL_FLAT } from '@/const/message';
6
+ import { useClientDataSWR } from '@/libs/swr';
6
7
  import { pluginService } from '@/services/plugin';
7
8
  import { merge } from '@/utils/merge';
8
9
 
@@ -17,7 +18,7 @@ export interface PluginAction {
17
18
  checkPluginsIsInstalled: (plugins: string[]) => Promise<void>;
18
19
  removeAllPlugins: () => Promise<void>;
19
20
  updatePluginSettings: <T>(id: string, settings: Partial<T>) => Promise<void>;
20
- useCheckPluginsIsInstalled: (plugins: string[]) => SWRResponse;
21
+ useCheckPluginsIsInstalled: (enable: boolean, plugins: string[]) => SWRResponse;
21
22
  validatePluginSettings: (identifier: string) => Promise<ValidationResult | undefined>;
22
23
  }
23
24
 
@@ -59,7 +60,8 @@ export const createPluginSlice: StateCreator<
59
60
 
60
61
  await get().refreshPlugins();
61
62
  },
62
- useCheckPluginsIsInstalled: (plugins) => useSWR(plugins, get().checkPluginsIsInstalled),
63
+ useCheckPluginsIsInstalled: (enable, plugins) =>
64
+ useClientDataSWR(enable ? plugins : null, get().checkPluginsIsInstalled),
63
65
  validatePluginSettings: async (identifier) => {
64
66
  const manifest = pluginSelectors.getToolManifestById(identifier)(get());
65
67
  if (!manifest || !manifest.settings) return;
@@ -27,7 +27,7 @@ export interface PluginStoreAction {
27
27
  uninstallPlugin: (identifier: string) => Promise<void>;
28
28
 
29
29
  updateInstallLoadingState: (key: string, value: boolean | undefined) => void;
30
- useFetchInstalledPlugins: () => SWRResponse<LobeTool[]>;
30
+ useFetchInstalledPlugins: (enabled: boolean) => SWRResponse<LobeTool[]>;
31
31
  useFetchPluginStore: () => SWRResponse<LobeChatPluginMeta[]>;
32
32
  }
33
33
 
@@ -90,8 +90,9 @@ export const createPluginStoreSlice: StateCreator<
90
90
  n('updateInstallLoadingState'),
91
91
  );
92
92
  },
93
- useFetchInstalledPlugins: () =>
94
- useSWR<LobeTool[]>(INSTALLED_PLUGINS, pluginService.getInstalledPlugins, {
93
+
94
+ useFetchInstalledPlugins: (enabled: boolean) =>
95
+ useSWR<LobeTool[]>(enabled ? INSTALLED_PLUGINS : null, pluginService.getInstalledPlugins, {
95
96
  fallbackData: [],
96
97
  onSuccess: (data) => {
97
98
  set(
@@ -37,7 +37,9 @@ describe('createCommonSlice', () => {
37
37
  const avatar = 'new-avatar';
38
38
 
39
39
  const spyOn = vi.spyOn(result.current, 'refreshUserState');
40
- const updateAvatarSpy = vi.spyOn(ClientService.prototype, 'updateAvatar');
40
+ const updateAvatarSpy = vi
41
+ .spyOn(ClientService.prototype, 'updateAvatar')
42
+ .mockResolvedValue(undefined);
41
43
 
42
44
  await act(async () => {
43
45
  await result.current.updateAvatar(avatar);