@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.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/app/(main)/chat/(workspace)/@conversation/features/ChatList/Content.tsx +3 -9
- package/src/app/(main)/chat/(workspace)/@conversation/features/ThreadHydration.tsx +2 -4
- package/src/app/(main)/chat/@session/features/SessionListContent/DefaultMode.tsx +2 -5
- package/src/app/(main)/discover/(detail)/plugin/[slug]/features/InstallPlugin.tsx +10 -15
- package/src/database/server/models/plugin.ts +4 -0
- package/src/database/server/models/session.ts +2 -0
- package/src/features/AgentSetting/AgentPlugin/index.tsx +1 -1
- package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +4 -4
- package/src/features/Portal/Thread/Chat/ChatList.tsx +1 -2
- package/src/hooks/useCheckPluginsIsInstalled.ts +10 -0
- package/src/hooks/useFetchInstalledPlugins.ts +10 -0
- package/src/hooks/useFetchMessages.ts +15 -0
- package/src/hooks/useFetchSessions.ts +13 -0
- package/src/hooks/useFetchThreads.ts +11 -0
- package/src/hooks/useFetchTopics.ts +6 -6
- package/src/layout/GlobalProvider/StoreInitialization.tsx +3 -1
- package/src/services/user/client.ts +2 -2
- package/src/store/agent/slices/chat/action.test.ts +21 -10
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +10 -0
- package/src/store/chat/slices/builtinTool/action.ts +0 -1
- package/src/store/chat/slices/message/action.test.ts +3 -1
- package/src/store/chat/slices/message/action.ts +7 -3
- package/src/store/chat/slices/thread/action.ts +3 -3
- package/src/store/chat/slices/topic/action.test.ts +1 -1
- package/src/store/chat/slices/topic/action.ts +3 -3
- package/src/store/global/selectors.ts +6 -0
- package/src/store/session/slices/session/action.ts +6 -3
- package/src/store/session/slices/sessionGroup/action.test.ts +8 -6
- package/src/store/tool/slices/plugin/action.ts +5 -3
- package/src/store/tool/slices/store/action.ts +4 -3
- 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
|
+
[](#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
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@lobehub/chat",
|
3
|
-
"version": "1.36.
|
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 [
|
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
|
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
|
-
|
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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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;
|
@@ -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
|
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
|
-
|
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
|
10
|
-
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
|
@@ -466,7 +466,9 @@ describe('chatMessage actions', () => {
|
|
466
466
|
// 设置模拟返回值
|
467
467
|
(messageService.getMessages as Mock).mockResolvedValue(messages);
|
468
468
|
|
469
|
-
const { result } = renderHook(() =>
|
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: (
|
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: (
|
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
|
-
|
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(
|
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
|
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) =>
|
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
|
-
|
94
|
-
|
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
|
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);
|