@lobehub/chat 0.159.6 → 0.159.8

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 +42 -0
  2. package/package.json +1 -1
  3. package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/index.tsx +24 -6
  4. package/src/app/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/Main.tsx +18 -2
  5. package/src/app/(main)/chat/(workspace)/_layout/Mobile/ChatHeader/ChatHeaderTitle.tsx +1 -1
  6. package/src/app/(main)/chat/(workspace)/_layout/Mobile/ChatHeader/index.tsx +2 -0
  7. package/src/app/(main)/chat/(workspace)/_layout/useInitAgentConfig.ts +30 -0
  8. package/src/config/client.ts +4 -0
  9. package/src/const/user.ts +10 -0
  10. package/src/const/version.ts +3 -0
  11. package/src/features/ChatInput/ActionBar/Tools/Dropdown.tsx +145 -0
  12. package/src/features/ChatInput/ActionBar/Tools/index.tsx +8 -120
  13. package/src/features/Conversation/components/VirtualizedList/index.tsx +31 -7
  14. package/src/features/Conversation/index.tsx +3 -2
  15. package/src/features/PluginStore/PluginItem/Action.tsx +12 -17
  16. package/src/services/message/type.ts +1 -0
  17. package/src/services/plugin/client.test.ts +5 -5
  18. package/src/services/plugin/client.ts +8 -10
  19. package/src/services/plugin/type.ts +21 -0
  20. package/src/services/topic/type.ts +1 -0
  21. package/src/store/agent/slices/chat/action.ts +2 -0
  22. package/src/store/chat/slices/topic/action.ts +2 -0
  23. package/src/store/tool/slices/plugin/action.test.ts +10 -2
  24. package/src/store/tool/slices/plugin/action.ts +8 -2
  25. package/src/store/tool/slices/plugin/initialState.ts +1 -0
  26. package/src/store/tool/slices/store/action.test.ts +10 -0
  27. package/src/store/tool/slices/store/action.ts +6 -1
  28. package/src/store/user/slices/preference/action.test.ts +3 -3
  29. package/src/store/user/slices/preference/action.ts +3 -3
  30. package/src/store/user/slices/preference/initialState.ts +2 -29
  31. package/src/types/tool/index.ts +2 -2
  32. package/src/types/user.ts +20 -0
  33. package/src/utils/fetch.ts +3 -1
  34. package/src/features/Conversation/hooks/useInitConversation.ts +0 -47
package/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.159.8](https://github.com/lobehub/lobe-chat/compare/v0.159.7...v0.159.8)
6
+
7
+ <sup>Released on **2024-05-14**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix retry issue when hide page.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix retry issue when hide page, closes [#2503](https://github.com/lobehub/lobe-chat/issues/2503) ([24489bc](https://github.com/lobehub/lobe-chat/commit/24489bc))
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
+
30
+ ### [Version 0.159.7](https://github.com/lobehub/lobe-chat/compare/v0.159.6...v0.159.7)
31
+
32
+ <sup>Released on **2024-05-14**</sup>
33
+
34
+ <br/>
35
+
36
+ <details>
37
+ <summary><kbd>Improvements and Fixes</kbd></summary>
38
+
39
+ </details>
40
+
41
+ <div align="right">
42
+
43
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
44
+
45
+ </div>
46
+
5
47
  ### [Version 0.159.6](https://github.com/lobehub/lobe-chat/compare/v0.159.5...v0.159.6)
6
48
 
7
49
  <sup>Released on **2024-05-14**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.159.6",
3
+ "version": "0.159.8",
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,14 +3,16 @@
3
3
  import { EmptyCard } from '@lobehub/ui';
4
4
  import { useThemeMode } from 'antd-style';
5
5
  import isEqual from 'fast-deep-equal';
6
- import React, { memo, useCallback, useRef } from 'react';
6
+ import React, { Suspense, memo, useCallback, useRef } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
  import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
10
10
 
11
11
  import { imageUrl } from '@/const/url';
12
+ import { isServerMode } from '@/const/version';
12
13
  import { useChatStore } from '@/store/chat';
13
14
  import { topicSelectors } from '@/store/chat/selectors';
15
+ import { useSessionStore } from '@/store/session';
14
16
  import { useUserStore } from '@/store/user';
15
17
  import { ChatTopic } from '@/types/topic';
16
18
 
@@ -21,10 +23,11 @@ const TopicListContent = memo(() => {
21
23
  const { t } = useTranslation('chat');
22
24
  const virtuosoRef = useRef<VirtuosoHandle>(null);
23
25
  const { isDarkMode } = useThemeMode();
24
- const [topicsInit, activeTopicId, topicLength] = useChatStore((s) => [
26
+ const [topicsInit, activeTopicId, topicLength, useFetchTopics] = useChatStore((s) => [
25
27
  s.topicsInit,
26
28
  s.activeTopicId,
27
29
  topicSelectors.currentTopicLength(s),
30
+ s.useFetchTopics,
28
31
  ]);
29
32
  const [visible, updateGuideState] = useUserStore((s) => [
30
33
  s.preference.guide?.topic,
@@ -43,6 +46,10 @@ const TopicListContent = memo(() => {
43
46
  isEqual,
44
47
  );
45
48
 
49
+ const [sessionId] = useSessionStore((s) => [s.activeId]);
50
+
51
+ const { isLoading } = useFetchTopics(sessionId);
52
+
46
53
  const itemContent = useCallback(
47
54
  (index: number, { id, favorite, title }: ChatTopic) =>
48
55
  index === 0 ? (
@@ -55,9 +62,14 @@ const TopicListContent = memo(() => {
55
62
 
56
63
  const activeIndex = topics.findIndex((topic) => topic.id === activeTopicId);
57
64
 
58
- return !topicsInit ? (
59
- <SkeletonList />
60
- ) : (
65
+ // first time loading
66
+ if (!topicsInit) return <SkeletonList />;
67
+
68
+ // in server mode and re-loading
69
+ if (isServerMode && isLoading) return <SkeletonList />;
70
+
71
+ // in client mode no need the loading state for better UX
72
+ return (
61
73
  <>
62
74
  {topicLength === 0 && visible && (
63
75
  <Flexbox paddingInline={8}>
@@ -97,4 +109,10 @@ const TopicListContent = memo(() => {
97
109
  );
98
110
  });
99
111
 
100
- export default TopicListContent;
112
+ TopicListContent.displayName = 'TopicListContent';
113
+
114
+ export default memo(() => (
115
+ <Suspense fallback={<SkeletonList />}>
116
+ <TopicListContent />;
117
+ </Suspense>
118
+ ));
@@ -3,10 +3,11 @@
3
3
  import { ActionIcon, Avatar, ChatHeaderTitle } from '@lobehub/ui';
4
4
  import { Skeleton } from 'antd';
5
5
  import { PanelLeftClose, PanelLeftOpen } from 'lucide-react';
6
- import { memo } from 'react';
6
+ import { Suspense, memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
+ import { useInitAgentConfig } from '@/app/(main)/chat/(workspace)/_layout/useInitAgentConfig';
10
11
  import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
11
12
  import { useGlobalStore } from '@/store/global';
12
13
  import { useSessionStore } from '@/store/session';
@@ -17,6 +18,8 @@ import Tags from './Tags';
17
18
  const Main = memo(() => {
18
19
  const { t } = useTranslation('chat');
19
20
 
21
+ useInitAgentConfig();
22
+
20
23
  const [init, isInbox, title, description, avatar, backgroundColor] = useSessionStore((s) => [
21
24
  sessionSelectors.isSomeSessionActive(s),
22
25
  sessionSelectors.isInboxSession(s),
@@ -70,4 +73,17 @@ const Main = memo(() => {
70
73
  );
71
74
  });
72
75
 
73
- export default Main;
76
+ export default () => (
77
+ <Suspense
78
+ fallback={
79
+ <Skeleton
80
+ active
81
+ avatar={{ shape: 'circle', size: 'default' }}
82
+ paragraph={false}
83
+ title={{ style: { margin: 0, marginTop: 8 }, width: 200 }}
84
+ />
85
+ }
86
+ >
87
+ <Main />
88
+ </Suspense>
89
+ );
@@ -30,6 +30,7 @@ const ChatHeaderTitle = memo(() => {
30
30
  <MobileNavBarTitle
31
31
  desc={
32
32
  <Flexbox align={'center'} gap={4} horizontal onClick={() => toggleConfig()}>
33
+ <span>{topic?.title || t('topic.title')}</span>
33
34
  <ActionIcon
34
35
  active
35
36
  icon={ChevronDown}
@@ -39,7 +40,6 @@ const ChatHeaderTitle = memo(() => {
39
40
  color: theme.colorTextDescription,
40
41
  }}
41
42
  />
42
- <span>{topic?.title || t('topic.title')}</span>
43
43
  </Flexbox>
44
44
  }
45
45
  title={
@@ -3,6 +3,7 @@
3
3
  import { MobileNavBar } from '@lobehub/ui';
4
4
  import { memo, useState } from 'react';
5
5
 
6
+ import { useInitAgentConfig } from '@/app/(main)/chat/(workspace)/_layout/useInitAgentConfig';
6
7
  import { useQueryRoute } from '@/hooks/useQueryRoute';
7
8
  import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
8
9
 
@@ -15,6 +16,7 @@ const MobileHeader = memo(() => {
15
16
  const [open, setOpen] = useState(false);
16
17
 
17
18
  const { isAgentEditable } = useServerConfigStore(featureFlagsSelectors);
19
+ useInitAgentConfig();
18
20
 
19
21
  return (
20
22
  <MobileNavBar
@@ -0,0 +1,30 @@
1
+ import { useEffect } from 'react';
2
+
3
+ import { useAgentStore } from '@/store/agent';
4
+ import { useChatStore } from '@/store/chat';
5
+ import { useSessionStore } from '@/store/session';
6
+
7
+ export const useInitAgentConfig = () => {
8
+ const [useFetchAgentConfig] = useAgentStore((s) => [s.useFetchAgentConfig]);
9
+
10
+ const [switchTopic] = useChatStore((s) => [s.switchTopic]);
11
+ const [sessionId] = useSessionStore((s) => [s.activeId]);
12
+
13
+ useEffect(() => {
14
+ // // when activeId changed, switch topic to undefined
15
+ const unsubscribe = useSessionStore.subscribe(
16
+ (s) => s.activeId,
17
+ (activeId) => {
18
+ switchTopic();
19
+
20
+ useAgentStore.setState({ activeId }, false, 'updateActiveId');
21
+ },
22
+ );
23
+
24
+ return () => {
25
+ unsubscribe();
26
+ };
27
+ }, []);
28
+
29
+ return useFetchAgentConfig(sessionId);
30
+ };
@@ -30,11 +30,15 @@ declare global {
30
30
  NEXT_PUBLIC_I18N_DEBUG_SERVER: string;
31
31
 
32
32
  NEXT_PUBLIC_DEVELOPER_DEBUG: string;
33
+
34
+ NEXT_PUBLIC_SERVICE_MODE?: 'server' | 'browser';
33
35
  }
34
36
  }
35
37
  }
36
38
 
37
39
  export const getClientConfig = () => ({
40
+ ENABLED_SERVER_SERVICE: process.env.NEXT_PUBLIC_SERVICE_MODE === 'server',
41
+
38
42
  BASE_PATH: process.env.NEXT_PUBLIC_BASE_PATH || '',
39
43
 
40
44
  // Plausible Analytics
@@ -0,0 +1,10 @@
1
+ import { UserPreference } from '@/types/user';
2
+
3
+ export const DEFAULT_PREFERENCE: UserPreference = {
4
+ guide: {
5
+ moveSettingsToAvatar: true,
6
+ topic: true,
7
+ },
8
+ telemetry: null,
9
+ useCmdEnterToSend: false,
10
+ };
@@ -1,3 +1,6 @@
1
1
  import pkg from '@/../package.json';
2
+ import { getClientConfig } from '@/config/client';
2
3
 
3
4
  export const CURRENT_VERSION = pkg.version;
5
+
6
+ export const isServerMode = getClientConfig().ENABLED_SERVER_SERVICE;
@@ -0,0 +1,145 @@
1
+ import { Avatar, Icon } from '@lobehub/ui';
2
+ import { Dropdown } from 'antd';
3
+ import { createStyles } from 'antd-style';
4
+ import type { ItemType } from 'antd/es/menu/hooks/useItems';
5
+ import isEqual from 'fast-deep-equal';
6
+ import { ArrowRight, Store, ToyBrick } from 'lucide-react';
7
+ import { PropsWithChildren, memo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Flexbox } from 'react-layout-kit';
10
+
11
+ import { useWorkspaceModal } from '@/app/(main)/chat/(workspace)/features/useWorkspaceModal';
12
+ import PluginStore from '@/features/PluginStore';
13
+ import { useAgentStore } from '@/store/agent';
14
+ import { agentSelectors } from '@/store/agent/selectors';
15
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
16
+ import { pluginHelpers, useToolStore } from '@/store/tool';
17
+ import { builtinToolSelectors, pluginSelectors } from '@/store/tool/selectors';
18
+
19
+ import ToolItem from './ToolItem';
20
+
21
+ const useStyles = createStyles(({ css, prefixCls }) => ({
22
+ menu: css`
23
+ &.${prefixCls}-dropdown-menu {
24
+ padding-block: 8px;
25
+ }
26
+
27
+ .${prefixCls}-dropdown-menu-item-group-list .${prefixCls}-dropdown-menu-item {
28
+ padding: 0;
29
+ border-radius: 4px;
30
+ }
31
+ `,
32
+ }));
33
+
34
+ const DropdownMenu = memo<PropsWithChildren>(({ children }) => {
35
+ const { t } = useTranslation('setting');
36
+ const list = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
37
+ const { showDalle } = useServerConfigStore(featureFlagsSelectors);
38
+ const builtinList = useToolStore(builtinToolSelectors.metaList(showDalle), isEqual);
39
+
40
+ const enablePluginCount = useAgentStore(
41
+ (s) =>
42
+ agentSelectors
43
+ .currentAgentPlugins(s)
44
+ .filter((i) => !builtinList.some((b) => b.identifier === i)).length,
45
+ );
46
+
47
+ const [open, setOpen] = useWorkspaceModal();
48
+ const { styles } = useStyles();
49
+
50
+ const items: ItemType[] = [
51
+ (builtinList.length !== 0 && {
52
+ children: builtinList.map((item) => ({
53
+ icon: <Avatar avatar={item.meta.avatar} size={24} />,
54
+ key: item.identifier,
55
+ label: (
56
+ <ToolItem identifier={item.identifier} label={item.meta?.title || item.identifier} />
57
+ ),
58
+ })),
59
+
60
+ key: 'builtins',
61
+ label: t('tools.builtins.groupName'),
62
+ type: 'group',
63
+ }) as ItemType,
64
+ {
65
+ children: [
66
+ ...list.map((item) => ({
67
+ icon: item.meta?.avatar ? (
68
+ <Avatar avatar={pluginHelpers.getPluginAvatar(item.meta)} size={24} />
69
+ ) : (
70
+ <Icon icon={ToyBrick} size={{ fontSize: 16 }} style={{ padding: 4 }} />
71
+ ),
72
+ key: item.identifier,
73
+ label: (
74
+ <ToolItem
75
+ identifier={item.identifier}
76
+ label={pluginHelpers.getPluginTitle(item?.meta) || item.identifier}
77
+ />
78
+ ),
79
+ })),
80
+ {
81
+ icon: <Icon icon={Store} size={{ fontSize: 16 }} style={{ padding: 4 }} />,
82
+
83
+ key: 'plugin-store',
84
+ label: (
85
+ <Flexbox gap={40} horizontal justify={'space-between'} padding={'8px 12px'}>
86
+ {t('tools.plugins.store')} <Icon icon={ArrowRight} />
87
+ </Flexbox>
88
+ ),
89
+ onClick: (e) => {
90
+ e.domEvent.stopPropagation();
91
+ setOpen(true);
92
+ },
93
+ },
94
+ ],
95
+ key: 'plugins',
96
+ label: (
97
+ <Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
98
+ {t('tools.plugins.groupName')}
99
+ {enablePluginCount === 0 ? null : (
100
+ <div style={{ fontSize: 12, marginInlineEnd: 4 }}>
101
+ {t('tools.plugins.enabled', { num: enablePluginCount })}
102
+ </div>
103
+ )}
104
+ </Flexbox>
105
+ ),
106
+ type: 'group',
107
+ } as ItemType,
108
+ ].filter(Boolean);
109
+
110
+ const plugins = useAgentStore((s) => agentSelectors.currentAgentPlugins(s));
111
+
112
+ const [useFetchPluginStore, useFetchInstalledPlugins, checkPluginsIsInstalled] = useToolStore(
113
+ (s) => [s.useFetchPluginStore, s.useFetchInstalledPlugins, s.useCheckPluginsIsInstalled],
114
+ );
115
+
116
+ useFetchPluginStore();
117
+ useFetchInstalledPlugins();
118
+ checkPluginsIsInstalled(plugins);
119
+
120
+ return (
121
+ <>
122
+ <Dropdown
123
+ arrow={false}
124
+ menu={{
125
+ className: styles.menu,
126
+ items,
127
+ onClick: (e) => {
128
+ e.domEvent.preventDefault();
129
+ },
130
+ style: {
131
+ maxHeight: 500,
132
+ overflowY: 'scroll',
133
+ },
134
+ }}
135
+ placement={'top'}
136
+ trigger={['click']}
137
+ >
138
+ {children}
139
+ </Dropdown>
140
+ <PluginStore open={open} setOpen={setOpen} />
141
+ </>
142
+ );
143
+ });
144
+
145
+ export default DropdownMenu;
@@ -1,144 +1,32 @@
1
- import { ActionIcon, Avatar, Icon } from '@lobehub/ui';
2
- import { Dropdown } from 'antd';
3
- import { createStyles } from 'antd-style';
4
- import type { ItemType } from 'antd/es/menu/hooks/useItems';
5
- import isEqual from 'fast-deep-equal';
6
- import { ArrowRight, Blocks, Store, ToyBrick } from 'lucide-react';
7
- import { memo } from 'react';
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Blocks, LucideLoader2 } from 'lucide-react';
3
+ import { Suspense, memo } from 'react';
8
4
  import { useTranslation } from 'react-i18next';
9
- import { Flexbox } from 'react-layout-kit';
10
5
 
11
- import { useWorkspaceModal } from '@/app/(main)/chat/(workspace)/features/useWorkspaceModal';
12
- import PluginStore from '@/features/PluginStore';
13
6
  import { useAgentStore } from '@/store/agent';
14
7
  import { agentSelectors } from '@/store/agent/selectors';
15
- import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
16
- import { pluginHelpers, useToolStore } from '@/store/tool';
17
- import { builtinToolSelectors, pluginSelectors } from '@/store/tool/selectors';
18
8
  import { useUserStore } from '@/store/user';
19
9
  import { modelProviderSelectors } from '@/store/user/selectors';
20
10
 
21
- import ToolItem from './ToolItem';
22
-
23
- const useStyles = createStyles(({ css, prefixCls }) => ({
24
- menu: css`
25
- &.${prefixCls}-dropdown-menu {
26
- padding-block: 8px;
27
- }
28
-
29
- .${prefixCls}-dropdown-menu-item-group-list .${prefixCls}-dropdown-menu-item {
30
- padding: 0;
31
- border-radius: 4px;
32
- }
33
- `,
34
- }));
11
+ import DropdownMenu from './Dropdown';
35
12
 
36
13
  const Tools = memo(() => {
37
14
  const { t } = useTranslation('setting');
38
- const list = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
39
- const { showDalle } = useServerConfigStore(featureFlagsSelectors);
40
- const builtinList = useToolStore(builtinToolSelectors.metaList(showDalle), isEqual);
41
-
42
- const enablePluginCount = useAgentStore(
43
- (s) =>
44
- agentSelectors
45
- .currentAgentPlugins(s)
46
- .filter((i) => !builtinList.some((b) => b.identifier === i)).length,
47
- );
48
-
49
- const [open, setOpen] = useWorkspaceModal();
50
- const { styles } = useStyles();
51
15
 
52
16
  const model = useAgentStore(agentSelectors.currentAgentModel);
53
17
  const enableFC = useUserStore(modelProviderSelectors.isModelEnabledFunctionCall(model));
54
18
 
55
- const items: ItemType[] = [
56
- (builtinList.length !== 0 && {
57
- children: builtinList.map((item) => ({
58
- icon: <Avatar avatar={item.meta.avatar} size={24} />,
59
- key: item.identifier,
60
- label: (
61
- <ToolItem identifier={item.identifier} label={item.meta?.title || item.identifier} />
62
- ),
63
- })),
64
-
65
- key: 'builtins',
66
- label: t('tools.builtins.groupName'),
67
- type: 'group',
68
- }) as ItemType,
69
- {
70
- children: [
71
- ...list.map((item) => ({
72
- icon: item.meta?.avatar ? (
73
- <Avatar avatar={pluginHelpers.getPluginAvatar(item.meta)} size={24} />
74
- ) : (
75
- <Icon icon={ToyBrick} size={{ fontSize: 16 }} style={{ padding: 4 }} />
76
- ),
77
- key: item.identifier,
78
- label: (
79
- <ToolItem
80
- identifier={item.identifier}
81
- label={pluginHelpers.getPluginTitle(item?.meta) || item.identifier}
82
- />
83
- ),
84
- })),
85
- {
86
- icon: <Icon icon={Store} size={{ fontSize: 16 }} style={{ padding: 4 }} />,
87
-
88
- key: 'plugin-store',
89
- label: (
90
- <Flexbox gap={40} horizontal justify={'space-between'} padding={'8px 12px'}>
91
- {t('tools.plugins.store')} <Icon icon={ArrowRight} />
92
- </Flexbox>
93
- ),
94
- onClick: (e) => {
95
- e.domEvent.stopPropagation();
96
- setOpen(true);
97
- },
98
- },
99
- ],
100
- key: 'plugins',
101
- label: (
102
- <Flexbox align={'center'} gap={40} horizontal justify={'space-between'}>
103
- {t('tools.plugins.groupName')}
104
- {enablePluginCount === 0 ? null : (
105
- <div style={{ fontSize: 12, marginInlineEnd: 4 }}>
106
- {t('tools.plugins.enabled', { num: enablePluginCount })}
107
- </div>
108
- )}
109
- </Flexbox>
110
- ),
111
- type: 'group',
112
- } as ItemType,
113
- ].filter(Boolean);
114
-
115
19
  return (
116
- <>
117
- <Dropdown
118
- arrow={false}
119
- menu={{
120
- className: styles.menu,
121
- items,
122
- onClick: (e) => {
123
- e.domEvent.preventDefault();
124
- },
125
- style: {
126
- maxHeight: 500,
127
- overflowY: 'scroll',
128
- },
129
- }}
130
- placement={'top'}
131
- trigger={['click']}
132
- >
20
+ <Suspense fallback={<ActionIcon icon={LucideLoader2} />}>
21
+ <DropdownMenu>
133
22
  <ActionIcon
134
23
  disable={!enableFC}
135
24
  icon={Blocks}
136
25
  placement={'bottom'}
137
26
  title={t(enableFC ? 'tools.title' : 'tools.disabled')}
138
27
  />
139
- </Dropdown>
140
- <PluginStore open={open} setOpen={setOpen} />
141
- </>
28
+ </DropdownMenu>
29
+ </Suspense>
142
30
  );
143
31
  });
144
32
 
@@ -1,15 +1,19 @@
1
1
  'use client';
2
2
 
3
+ import { Icon } from '@lobehub/ui';
4
+ import { useTheme } from 'antd-style';
3
5
  import isEqual from 'fast-deep-equal';
6
+ import { Loader2Icon } from 'lucide-react';
4
7
  import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
8
+ import { Center, Flexbox } from 'react-layout-kit';
6
9
  import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
7
10
 
8
11
  import { WELCOME_GUIDE_CHAT_ID } from '@/const/session';
12
+ import { isServerMode } from '@/const/version';
9
13
  import { useChatStore } from '@/store/chat';
10
14
  import { chatSelectors } from '@/store/chat/selectors';
15
+ import { useSessionStore } from '@/store/session';
11
16
 
12
- import { useInitConversation } from '../../hooks/useInitConversation';
13
17
  import AutoScroll from '../AutoScroll';
14
18
  import Item from '../ChatItem';
15
19
  import InboxWelcome from '../InboxWelcome';
@@ -19,16 +23,21 @@ interface VirtualizedListProps {
19
23
  mobile?: boolean;
20
24
  }
21
25
  const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
22
- useInitConversation();
23
26
  const virtuosoRef = useRef<VirtuosoHandle>(null);
24
27
  const [atBottom, setAtBottom] = useState(true);
25
28
  const [isScrolling, setIsScrolling] = useState(false);
26
29
 
27
- const [id, chatLoading] = useChatStore((s) => [
28
- chatSelectors.currentChatKey(s),
30
+ const [id] = useChatStore((s) => [chatSelectors.currentChatKey(s)]);
31
+
32
+ const [activeTopicId, useFetchMessages, isFirstLoading] = useChatStore((s) => [
33
+ s.activeTopicId,
34
+ s.useFetchMessages,
29
35
  chatSelectors.currentChatLoadingState(s),
30
36
  ]);
31
37
 
38
+ const [sessionId] = useSessionStore((s) => [s.activeId]);
39
+ const { isLoading } = useFetchMessages(sessionId, activeTopicId);
40
+
32
41
  const data = useChatStore((s) => {
33
42
  const showInboxWelcome = chatSelectors.showInboxWelcome(s);
34
43
  const ids = showInboxWelcome
@@ -51,6 +60,7 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
51
60
  return newFollowOutput;
52
61
  }, [data.length]);
53
62
 
63
+ const theme = useTheme();
54
64
  // overscan should be 1.5 times the height of the window
55
65
  const overscan = typeof window !== 'undefined' ? window.innerHeight * 1.5 : 0;
56
66
 
@@ -67,8 +77,22 @@ const VirtualizedList = memo<VirtualizedListProps>(({ mobile }) => {
67
77
  [mobile],
68
78
  );
69
79
 
70
- return chatLoading ? (
71
- <SkeletonList mobile={mobile} />
80
+ // first time loading
81
+ if (isFirstLoading) return <SkeletonList mobile={mobile} />;
82
+
83
+ // in server mode and switch page
84
+ if (isServerMode && isLoading) return <SkeletonList mobile={mobile} />;
85
+
86
+ // in client mode using the center loading for more
87
+ return isLoading ? (
88
+ <Center height={'100%'} width={'100%'}>
89
+ <Icon
90
+ icon={Loader2Icon}
91
+ size={{ fontSize: 32 }}
92
+ spin
93
+ style={{ color: theme.colorTextTertiary }}
94
+ />
95
+ </Center>
72
96
  ) : (
73
97
  <Flexbox height={'100%'}>
74
98
  <Virtuoso
@@ -1,8 +1,9 @@
1
- import { Suspense } from 'react';
1
+ import { Suspense, lazy } from 'react';
2
2
  import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import SkeletonList from './components/SkeletonList';
5
- import ChatList from './components/VirtualizedList';
5
+
6
+ const ChatList = lazy(() => import('./components/VirtualizedList'));
6
7
 
7
8
  interface ConversationProps {
8
9
  mobile?: boolean;
@@ -1,5 +1,5 @@
1
1
  import { ActionIcon, Icon } from '@lobehub/ui';
2
- import { Button, Dropdown, Popconfirm } from 'antd';
2
+ import { App, Button, Dropdown } from 'antd';
3
3
  import { InfoIcon, MoreVerticalIcon, Settings, Trash2 } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
@@ -31,7 +31,7 @@ const Actions = memo<ActionsProps>(({ identifier, type }) => {
31
31
  const { t } = useTranslation('plugin');
32
32
  const [open, setOpen] = useState(false);
33
33
  const plugin = useToolStore(pluginSelectors.getPluginManifestById(identifier));
34
-
34
+ const { modal } = App.useApp();
35
35
  const [tab, setTab] = useState('info');
36
36
  const hasSettings = pluginHelpers.isSettingSchemaNonEmpty(plugin?.settings);
37
37
 
@@ -67,21 +67,16 @@ const Actions = memo<ActionsProps>(({ identifier, type }) => {
67
67
  danger: true,
68
68
  icon: <Icon icon={Trash2} />,
69
69
  key: 'uninstall',
70
- label: (
71
- <Popconfirm
72
- arrow={false}
73
- cancelText={t('cancel', { ns: 'common' })}
74
- okButtonProps={{ danger: true }}
75
- okText={t('ok', { ns: 'common' })}
76
- onConfirm={() => {
77
- unInstallPlugin(identifier);
78
- }}
79
- placement={'topRight'}
80
- title={t('store.actions.confirmUninstall')}
81
- >
82
- {t('store.actions.uninstall')}
83
- </Popconfirm>
84
- ),
70
+ label: t('store.actions.uninstall'),
71
+ onClick: () => {
72
+ modal.confirm({
73
+ centered: true,
74
+ okButtonProps: { danger: true },
75
+ onOk: async () => unInstallPlugin(identifier),
76
+ title: t('store.actions.confirmUninstall'),
77
+ type: 'error',
78
+ });
79
+ },
85
80
  },
86
81
  ],
87
82
  }}
@@ -20,6 +20,7 @@ export interface IMessageService {
20
20
  getAllMessages(): Promise<ChatMessage[]>;
21
21
  getAllMessagesInSession(sessionId: string): Promise<ChatMessage[]>;
22
22
  countMessages(): Promise<number>;
23
+ countTodayMessages(): Promise<number>;
23
24
 
24
25
  updateMessageError(id: string, error: ChatMessageError): Promise<any>;
25
26
  updateMessage(id: string, message: Partial<DB_Message>): Promise<any>;
@@ -6,7 +6,8 @@ import { DB_Plugin } from '@/database/client/schemas/plugin';
6
6
  import { LobeTool } from '@/types/tool';
7
7
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
8
8
 
9
- import { ClientService, InstallPluginParams } from './client';
9
+ import { ClientService } from './client';
10
+ import { InstallPluginParams } from './type';
10
11
 
11
12
  const pluginService = new ClientService();
12
13
 
@@ -110,7 +111,7 @@ describe('PluginService', () => {
110
111
 
111
112
  // Assert
112
113
  expect(PluginModel.update).toHaveBeenCalledWith(id, value);
113
- expect(result).toEqual(1);
114
+ expect(result).toEqual(undefined);
114
115
  });
115
116
  });
116
117
 
@@ -126,7 +127,7 @@ describe('PluginService', () => {
126
127
 
127
128
  // Assert
128
129
  expect(PluginModel.update).toHaveBeenCalledWith(id, { manifest });
129
- expect(result).toEqual(1);
130
+ expect(result).toEqual(undefined);
130
131
  });
131
132
  });
132
133
 
@@ -149,14 +150,13 @@ describe('PluginService', () => {
149
150
  // Arrange
150
151
  const id = 'plugin-id';
151
152
  const settings = { color: 'blue' };
152
- vi.mocked(PluginModel.update).mockResolvedValue(1);
153
153
 
154
154
  // Act
155
155
  const result = await pluginService.updatePluginSettings(id, settings);
156
156
 
157
157
  // Assert
158
158
  expect(PluginModel.update).toHaveBeenCalledWith(id, { settings });
159
- expect(result).toEqual(1);
159
+ expect(result).toEqual(undefined);
160
160
  });
161
161
  });
162
162
  });
@@ -4,13 +4,9 @@ import { PluginModel } from '@/database/client/models/plugin';
4
4
  import { LobeTool } from '@/types/tool';
5
5
  import { LobeToolCustomPlugin } from '@/types/tool/plugin';
6
6
 
7
- export interface InstallPluginParams {
8
- identifier: string;
9
- manifest: LobeChatPluginManifest;
10
- type: 'plugin' | 'customPlugin';
11
- }
7
+ import { IPluginService, InstallPluginParams } from './type';
12
8
 
13
- export class ClientService {
9
+ export class ClientService implements IPluginService {
14
10
  installPlugin = async (plugin: InstallPluginParams) => {
15
11
  return PluginModel.create(plugin);
16
12
  };
@@ -28,17 +24,19 @@ export class ClientService {
28
24
  }
29
25
 
30
26
  async updatePlugin(id: string, value: LobeToolCustomPlugin) {
31
- return PluginModel.update(id, value);
27
+ await PluginModel.update(id, value);
28
+ return;
32
29
  }
33
30
  async updatePluginManifest(id: string, manifest: LobeChatPluginManifest) {
34
- return PluginModel.update(id, { manifest });
31
+ await PluginModel.update(id, { manifest });
35
32
  }
36
33
 
37
34
  async removeAllPlugins() {
38
35
  return PluginModel.clear();
39
36
  }
40
37
 
41
- async updatePluginSettings(id: string, settings: any) {
42
- return PluginModel.update(id, { settings });
38
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
39
+ async updatePluginSettings(id: string, settings: any, _?: AbortSignal) {
40
+ await PluginModel.update(id, { settings });
43
41
  }
44
42
  }
@@ -0,0 +1,21 @@
1
+ import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
2
+
3
+ import { LobeTool } from '@/types/tool';
4
+ import { LobeToolCustomPlugin } from '@/types/tool/plugin';
5
+
6
+ export interface InstallPluginParams {
7
+ identifier: string;
8
+ manifest: LobeChatPluginManifest;
9
+ type: 'plugin' | 'customPlugin';
10
+ }
11
+
12
+ export interface IPluginService {
13
+ createCustomPlugin: (customPlugin: LobeToolCustomPlugin) => Promise<void>;
14
+ getInstalledPlugins: () => Promise<LobeTool[]>;
15
+ installPlugin: (plugin: InstallPluginParams) => Promise<void>;
16
+ removeAllPlugins: () => Promise<void>;
17
+ uninstallPlugin: (identifier: string) => Promise<void>;
18
+ updatePlugin: (id: string, value: LobeToolCustomPlugin) => Promise<void>;
19
+ updatePluginManifest: (id: string, manifest: LobeChatPluginManifest) => Promise<void>;
20
+ updatePluginSettings: (id: string, settings: any, signal?: AbortSignal) => Promise<void>;
21
+ }
@@ -22,6 +22,7 @@ export interface ITopicService {
22
22
 
23
23
  getTopics(params: QueryTopicParams): Promise<ChatTopic[]>;
24
24
  getAllTopics(): Promise<ChatTopic[]>;
25
+ countTopics(): Promise<number>;
25
26
  searchTopics(keyword: string, sessionId?: string): Promise<ChatTopic[]>;
26
27
 
27
28
  updateTopic(id: string, data: Partial<ChatTopic>): Promise<any>;
@@ -81,11 +81,13 @@ export const createChatSlice: StateCreator<
81
81
  [FETCH_AGENT_CONFIG_KEY, sessionId],
82
82
  ([, id]: string[]) => sessionService.getSessionConfig(id),
83
83
  {
84
+ fallbackData: DEFAULT_AGENT_CONFIG,
84
85
  onSuccess: (data) => {
85
86
  if (get().isAgentConfigInit && isEqual(get().agentConfig, data)) return;
86
87
 
87
88
  set({ agentConfig: data, isAgentConfigInit: true }, false, 'fetchAgentConfig');
88
89
  },
90
+ suspense: true,
89
91
  },
90
92
  ),
91
93
  useFetchDefaultAgentConfig: () =>
@@ -171,6 +171,8 @@ export const chatTopic: StateCreator<
171
171
  [SWR_USE_FETCH_TOPIC, sessionId],
172
172
  async ([, sessionId]: [string, string]) => topicService.getTopics({ sessionId }),
173
173
  {
174
+ suspense: true,
175
+ fallbackData: [],
174
176
  onSuccess: (topics) => {
175
177
  set({ topics, topicsInit: true }, false, n('useFetchTopics(success)', { sessionId }));
176
178
  },
@@ -90,7 +90,11 @@ describe('useToolStore:plugin', () => {
90
90
  await result.current.updatePluginSettings(pluginId, newSettings);
91
91
  });
92
92
 
93
- expect(pluginService.updatePluginSettings).toBeCalledWith(pluginId, newSettings);
93
+ expect(pluginService.updatePluginSettings).toBeCalledWith(
94
+ pluginId,
95
+ newSettings,
96
+ expect.any(AbortSignal),
97
+ );
94
98
  });
95
99
 
96
100
  it('should merge settings for a plugin with existing settings', async () => {
@@ -108,7 +112,11 @@ describe('useToolStore:plugin', () => {
108
112
  await result.current.updatePluginSettings(pluginId, newSettings);
109
113
  });
110
114
 
111
- expect(pluginService.updatePluginSettings).toBeCalledWith(pluginId, mergedSettings);
115
+ expect(pluginService.updatePluginSettings).toBeCalledWith(
116
+ pluginId,
117
+ mergedSettings,
118
+ expect.any(AbortSignal),
119
+ );
112
120
  });
113
121
  });
114
122
 
@@ -45,10 +45,16 @@ export const createPluginSlice: StateCreator<
45
45
  await get().refreshPlugins();
46
46
  },
47
47
  updatePluginSettings: async (id, settings) => {
48
- const previousSettings = pluginSelectors.getPluginSettingsById(id)(get());
48
+ const signal = get().updatePluginSettingsSignal;
49
+ if (signal) signal.abort('canceled');
50
+
51
+ const newSignal = new AbortController();
49
52
 
53
+ const previousSettings = pluginSelectors.getPluginSettingsById(id)(get());
50
54
  const nextSettings = merge(previousSettings, settings);
51
- await pluginService.updatePluginSettings(id, nextSettings);
55
+
56
+ set({ updatePluginSettingsSignal: newSignal });
57
+ await pluginService.updatePluginSettings(id, nextSettings, newSignal.signal);
52
58
 
53
59
  await get().refreshPlugins();
54
60
  },
@@ -6,6 +6,7 @@ export interface PluginState {
6
6
  installedPlugins: LobeTool[];
7
7
  loadingInstallPlugins: boolean;
8
8
  pluginsSettings: PluginsSettings;
9
+ updatePluginSettingsSignal?: AbortController;
9
10
  }
10
11
 
11
12
  export const initialPluginState: PluginState = {
@@ -157,7 +157,12 @@ describe('useToolStore:pluginStore', () => {
157
157
 
158
158
  // Then
159
159
  expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function), {
160
+ fallbackData: {
161
+ plugins: [],
162
+ schemaVersion: 1,
163
+ },
160
164
  revalidateOnFocus: false,
165
+ suspense: true,
161
166
  });
162
167
  expect(result.current.data).toEqual(pluginListMock);
163
168
  expect(result.current.error).toBeNull();
@@ -178,7 +183,12 @@ describe('useToolStore:pluginStore', () => {
178
183
 
179
184
  // Then
180
185
  expect(useSWR).toHaveBeenCalledWith('loadPluginStore', expect.any(Function), {
186
+ fallbackData: {
187
+ plugins: [],
188
+ schemaVersion: 1,
189
+ },
181
190
  revalidateOnFocus: false,
191
+ suspense: true,
182
192
  });
183
193
  expect(result.current.data).toBeNull();
184
194
  expect(result.current.error).toEqual(error);
@@ -45,11 +45,12 @@ export const createPluginStoreSlice: StateCreator<
45
45
  try {
46
46
  updateInstallLoadingState(name, true);
47
47
  const data = await toolService.getPluginManifest(plugin.manifest);
48
- updateInstallLoadingState(name, undefined);
49
48
 
50
49
  // 4. 存储 manifest 信息
51
50
  await pluginService.installPlugin({ identifier: plugin.identifier, manifest: data, type });
52
51
  await refreshPlugins();
52
+
53
+ updateInstallLoadingState(name, undefined);
53
54
  } catch (error) {
54
55
  console.error(error);
55
56
  updateInstallLoadingState(name, undefined);
@@ -91,6 +92,7 @@ export const createPluginStoreSlice: StateCreator<
91
92
  },
92
93
  useFetchInstalledPlugins: () =>
93
94
  useSWR<LobeTool[]>(INSTALLED_PLUGINS, pluginService.getInstalledPlugins, {
95
+ fallbackData: [],
94
96
  onSuccess: (data) => {
95
97
  set(
96
98
  { installedPlugins: data, loadingInstallPlugins: false },
@@ -99,9 +101,12 @@ export const createPluginStoreSlice: StateCreator<
99
101
  );
100
102
  },
101
103
  revalidateOnFocus: false,
104
+ suspense: true,
102
105
  }),
103
106
  useFetchPluginStore: () =>
104
107
  useSWR<LobeChatPluginsMarketIndex>('loadPluginStore', get().loadPluginStore, {
108
+ fallbackData: { plugins: [], schemaVersion: 1 },
105
109
  revalidateOnFocus: false,
110
+ suspense: true,
106
111
  }),
107
112
  });
@@ -2,9 +2,9 @@ import { act, renderHook, waitFor } from '@testing-library/react';
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { withSWR } from '~test-utils';
4
4
 
5
+ import { DEFAULT_PREFERENCE } from '@/const/user';
5
6
  import { useUserStore } from '@/store/user';
6
-
7
- import { DEFAULT_PREFERENCE, type Guide, UserPreference } from './initialState';
7
+ import { UserGuide, UserPreference } from '@/types/user';
8
8
 
9
9
  beforeEach(() => {
10
10
  vi.clearAllMocks();
@@ -18,7 +18,7 @@ describe('createPreferenceSlice', () => {
18
18
  describe('updateGuideState', () => {
19
19
  it('should update guide state', () => {
20
20
  const { result } = renderHook(() => useUserStore());
21
- const guide: Guide = { topic: true };
21
+ const guide: UserGuide = { topic: true };
22
22
 
23
23
  act(() => {
24
24
  result.current.updateGuideState(guide);
@@ -1,17 +1,17 @@
1
1
  import { SWRResponse } from 'swr';
2
2
  import type { StateCreator } from 'zustand/vanilla';
3
3
 
4
+ import { DEFAULT_PREFERENCE } from '@/const/user';
4
5
  import { useClientDataSWR } from '@/libs/swr';
5
6
  import type { UserStore } from '@/store/user';
7
+ import { UserGuide, UserPreference } from '@/types/user';
6
8
  import { merge } from '@/utils/merge';
7
9
  import { setNamespace } from '@/utils/storeDebug';
8
10
 
9
- import { DEFAULT_PREFERENCE, Guide, UserPreference } from './initialState';
10
-
11
11
  const n = setNamespace('preference');
12
12
 
13
13
  export interface PreferenceAction {
14
- updateGuideState: (guide: Partial<Guide>) => void;
14
+ updateGuideState: (guide: Partial<UserGuide>) => void;
15
15
  updatePreference: (preference: Partial<UserPreference>, action?: any) => void;
16
16
  useInitPreference: () => SWRResponse;
17
17
  }
@@ -1,25 +1,7 @@
1
+ import { DEFAULT_PREFERENCE } from '@/const/user';
2
+ import { UserPreference } from '@/types/user';
1
3
  import { AsyncLocalStorage } from '@/utils/localStorage';
2
4
 
3
- export interface Guide {
4
- /**
5
- * Move the settings button to the avatar dropdown
6
- */
7
- moveSettingsToAvatar?: boolean;
8
-
9
- // Topic 引导
10
- topic?: boolean;
11
- }
12
-
13
- export interface UserPreference {
14
- guide?: Guide;
15
- hideSyncAlert?: boolean;
16
- telemetry: boolean | null;
17
- /**
18
- * whether to use cmd + enter to send message
19
- */
20
- useCmdEnterToSend?: boolean;
21
- }
22
-
23
5
  export interface UserPreferenceState {
24
6
  isPreferenceInit: boolean;
25
7
  /**
@@ -29,15 +11,6 @@ export interface UserPreferenceState {
29
11
  preferenceStorage: AsyncLocalStorage<UserPreference>;
30
12
  }
31
13
 
32
- export const DEFAULT_PREFERENCE: UserPreference = {
33
- guide: {
34
- moveSettingsToAvatar: true,
35
- topic: true,
36
- },
37
- telemetry: null,
38
- useCmdEnterToSend: false,
39
- };
40
-
41
14
  export const initialPreferenceState: UserPreferenceState = {
42
15
  isPreferenceInit: false,
43
16
  preference: DEFAULT_PREFERENCE,
@@ -4,9 +4,9 @@ import { CustomPluginParams } from './plugin';
4
4
  import { LobeToolType } from './tool';
5
5
 
6
6
  export interface LobeTool {
7
- customParams?: CustomPluginParams;
7
+ customParams?: CustomPluginParams | null;
8
8
  identifier: string;
9
- manifest?: LobeChatPluginManifest;
9
+ manifest?: LobeChatPluginManifest | null;
10
10
  settings?: any;
11
11
  type: LobeToolType;
12
12
  }
package/src/types/user.ts CHANGED
@@ -7,3 +7,23 @@ export interface LobeUser {
7
7
  latestName?: string | null;
8
8
  username?: string | null;
9
9
  }
10
+
11
+ export interface UserGuide {
12
+ /**
13
+ * Move the settings button to the avatar dropdown
14
+ */
15
+ moveSettingsToAvatar?: boolean;
16
+
17
+ // Topic 引导
18
+ topic?: boolean;
19
+ }
20
+
21
+ export interface UserPreference {
22
+ guide?: UserGuide;
23
+ hideSyncAlert?: boolean;
24
+ telemetry: boolean | null;
25
+ /**
26
+ * whether to use cmd + enter to send message
27
+ */
28
+ useCmdEnterToSend?: boolean;
29
+ }
@@ -363,7 +363,9 @@ export const fetchSSE = async (url: string, options: RequestInit & FetchSSEOptio
363
363
  return;
364
364
  }
365
365
  },
366
-
366
+ // we should keep open when page hidden, or it will case lots of token cost
367
+ // refs: https://github.com/lobehub/lobe-chat/issues/2501
368
+ openWhenHidden: true,
367
369
  signal: options.signal,
368
370
  });
369
371
  } catch {}
@@ -1,47 +0,0 @@
1
- import { useEffect } from 'react';
2
-
3
- import { useAgentStore } from '@/store/agent';
4
- import { agentSelectors } from '@/store/agent/selectors';
5
- import { useChatStore } from '@/store/chat';
6
- import { useSessionStore } from '@/store/session';
7
- import { useToolStore } from '@/store/tool';
8
-
9
- export const useInitConversation = () => {
10
- const [sessionId] = useSessionStore((s) => [s.activeId]);
11
- const [useFetchAgentConfig] = useAgentStore((s) => [s.useFetchAgentConfig]);
12
- const plugins = useAgentStore((s) => agentSelectors.currentAgentPlugins(s));
13
- const [activeTopicId, switchTopic, useFetchMessages, useFetchTopics] = useChatStore((s) => [
14
- s.activeTopicId,
15
- s.switchTopic,
16
- s.useFetchMessages,
17
- s.useFetchTopics,
18
- ]);
19
-
20
- useFetchMessages(sessionId, activeTopicId);
21
- useFetchTopics(sessionId);
22
- useFetchAgentConfig(sessionId);
23
-
24
- const [useFetchPluginStore, useFetchInstalledPlugins, checkPluginsIsInstalled] = useToolStore(
25
- (s) => [s.useFetchPluginStore, s.useFetchInstalledPlugins, s.useCheckPluginsIsInstalled],
26
- );
27
-
28
- useFetchPluginStore();
29
- useFetchInstalledPlugins();
30
- checkPluginsIsInstalled(plugins);
31
-
32
- useEffect(() => {
33
- // // when activeId changed, switch topic to undefined
34
- const unsubscribe = useSessionStore.subscribe(
35
- (s) => s.activeId,
36
- (activeId) => {
37
- switchTopic();
38
-
39
- useAgentStore.setState({ activeId }, false, 'updateActiveId');
40
- },
41
- );
42
-
43
- return () => {
44
- unsubscribe();
45
- };
46
- }, []);
47
- };