@lobehub/chat 0.120.3 → 0.120.5

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 (74) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +14 -14
  3. package/README.zh-CN.md +14 -14
  4. package/docs/Home.md +2 -2
  5. package/docs/_Sidebar.md +3 -3
  6. package/locales/ar/common.json +1 -0
  7. package/locales/de-DE/common.json +1 -0
  8. package/locales/en-US/common.json +1 -0
  9. package/locales/es-ES/common.json +1 -0
  10. package/locales/fr-FR/common.json +1 -0
  11. package/locales/it-IT/common.json +1 -0
  12. package/locales/ja-JP/common.json +1 -0
  13. package/locales/ko-KR/common.json +1 -0
  14. package/locales/nl-NL/common.json +1 -0
  15. package/locales/pl-PL/common.json +1 -0
  16. package/locales/pt-BR/common.json +1 -0
  17. package/locales/ru-RU/common.json +1 -0
  18. package/locales/tr-TR/common.json +1 -0
  19. package/locales/vi-VN/common.json +1 -0
  20. package/locales/zh-CN/common.json +1 -0
  21. package/locales/zh-TW/common.json +1 -0
  22. package/package.json +18 -17
  23. package/src/app/chat/(desktop)/features/ChatHeader.tsx +6 -6
  24. package/src/app/chat/{features → (desktop)/features/SideBar}/SystemRole/index.tsx +8 -5
  25. package/src/app/chat/(desktop)/features/{SideBar.tsx → SideBar/index.tsx} +2 -3
  26. package/src/app/chat/(mobile)/index.tsx +17 -4
  27. package/src/app/chat/(mobile)/mobile/page.tsx +30 -2
  28. package/src/app/chat/components/SessionHydration/index.tsx +44 -0
  29. package/src/app/chat/features/ChatHeader/ShareButton/style.ts +1 -1
  30. package/src/app/chat/features/SessionListContent/DefaultMode.tsx +6 -1
  31. package/src/app/chat/features/SessionListContent/List/index.tsx +7 -3
  32. package/src/app/chat/features/TopicListContent/Header.tsx +1 -1
  33. package/src/app/chat/page.tsx +7 -3
  34. package/src/app/home/Redirect.tsx +2 -2
  35. package/src/app/metadata.ts +6 -0
  36. package/src/app/welcome/features/Banner/index.tsx +6 -7
  37. package/src/components/StoreHydration/ChatHydration/index.tsx +33 -0
  38. package/src/config/server.ts +4 -0
  39. package/src/const/url.ts +1 -1
  40. package/src/database/models/__tests__/message.test.ts +3 -2
  41. package/src/database/models/message.ts +13 -9
  42. package/src/database/schemas/message.ts +1 -1
  43. package/src/features/ChatInput/useChatInput.ts +2 -6
  44. package/src/features/Conversation/hooks/useInitConversation.ts +0 -2
  45. package/src/features/Conversation/index.tsx +7 -2
  46. package/src/features/SideBar/TopActions.tsx +5 -5
  47. package/src/layout/GlobalLayout/StoreHydration.tsx +12 -11
  48. package/src/locales/default/common.ts +1 -0
  49. package/src/store/chat/slices/message/action.ts +1 -1
  50. package/src/store/chat/store.ts +2 -34
  51. package/src/store/global/slices/common/action.ts +6 -0
  52. package/src/store/global/slices/common/initialState.ts +5 -0
  53. package/src/store/global/slices/settings/action.test.ts +117 -0
  54. package/src/store/session/index.ts +0 -1
  55. package/src/store/session/initialState.ts +6 -2
  56. package/src/store/session/slices/agent/selectors.ts +3 -1
  57. package/src/store/session/slices/session/action.ts +14 -18
  58. package/src/store/session/slices/session/selectors/list.ts +7 -0
  59. package/src/store/session/store.ts +5 -46
  60. package/src/styles/global.ts +1 -1
  61. package/src/app/chat/(mobile)/layout.mobile.tsx +0 -17
  62. package/src/app/chat/(mobile)/mobile/index.tsx +0 -30
  63. package/src/app/chat/(mobile)/mobile/layout.mobile.tsx +0 -11
  64. package/src/store/session/hooks/index.ts +0 -4
  65. package/src/store/session/hooks/useEffectAfterHydrated.ts +0 -27
  66. package/src/store/session/hooks/useOnFinishHydrationSession.ts +0 -19
  67. package/src/store/session/hooks/useSessionChatInit.ts +0 -12
  68. package/src/store/session/hooks/useSessionHydrated.ts +0 -13
  69. /package/docs/Development/{index.md → Intro.md} +0 -0
  70. /package/docs/Development/{index.zh-CN.md → Intro.zh-CN.md} +0 -0
  71. /package/docs/Development/State-Management/{index.md → State-Management-Intro.md} +0 -0
  72. /package/docs/Development/State-Management/{index.zh-CN.md → State-Management-Intro.zh-CN.md} +0 -0
  73. /package/src/app/chat/{features → (desktop)/features/SideBar}/SystemRole/style.ts +0 -0
  74. /package/src/app/chat/{features → components}/SidebarHeader/index.tsx +0 -0
@@ -1,17 +1,30 @@
1
1
  'use client';
2
2
 
3
- import { memo } from 'react';
3
+ import { useRouter } from 'next/navigation';
4
+ import { memo, useEffect } from 'react';
5
+
6
+ import AppLayoutMobile from '@/layout/AppLayout.mobile';
7
+ import { useSwitchSideBarOnInit } from '@/store/global';
8
+ import { SidebarTabKey } from '@/store/global/slices/common/initialState';
4
9
 
5
10
  import PageTitle from '../features/PageTitle';
11
+ import SessionHeader from './features/SessionHeader';
6
12
  import SessionList from './features/SessionList';
7
- import Layout from './layout.mobile';
8
13
 
9
14
  const ChatMobilePage = memo(() => {
15
+ useSwitchSideBarOnInit(SidebarTabKey.Chat);
16
+
17
+ const router = useRouter();
18
+ useEffect(() => {
19
+ router.prefetch('/chat/mobile');
20
+ router.prefetch('/settings');
21
+ }, []);
22
+
10
23
  return (
11
- <Layout>
24
+ <AppLayoutMobile navBar={<SessionHeader />} showTabBar>
12
25
  <PageTitle />
13
26
  <SessionList />
14
- </Layout>
27
+ </AppLayoutMobile>
15
28
  );
16
29
  });
17
30
 
@@ -1,3 +1,31 @@
1
- import Index from './index';
1
+ 'use client';
2
2
 
3
- export default () => <Index />;
3
+ import dynamic from 'next/dynamic';
4
+ import { memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import ChatHeader from '@/app/chat/(mobile)/features/ChatHeader';
8
+ import Conversation from '@/features/Conversation';
9
+ import AppLayoutMobile from '@/layout/AppLayout.mobile';
10
+ import { useSessionStore } from '@/store/session';
11
+
12
+ import ChatInput from '../features/ChatInput';
13
+
14
+ const TopicList = dynamic(() => import('../features/TopicList'));
15
+
16
+ const Chat = memo(() => {
17
+ // due to mobile side don't have sessionList, so we need to fetch sessions here
18
+ // refs: https://github.com/lobehub/lobe-chat/pull/541
19
+ const useFetchSessions = useSessionStore((s) => s.useFetchSessions);
20
+ useFetchSessions();
21
+
22
+ return (
23
+ <AppLayoutMobile navBar={<ChatHeader />}>
24
+ <Flexbox height={'calc(100% - 44px)'} horizontal>
25
+ <Conversation chatInput={<ChatInput />} mobile />
26
+ <TopicList />
27
+ </Flexbox>
28
+ </AppLayoutMobile>
29
+ );
30
+ });
31
+ export default Chat;
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { useResponsive } from 'antd-style';
4
+ import { useRouter } from 'next/navigation';
5
+ import { useQueryState } from 'nuqs';
6
+ import { parseAsString } from 'nuqs/parsers';
7
+ import { memo, useEffect } from 'react';
8
+ import { createStoreUpdater } from 'zustand-utils';
9
+
10
+ import { useSessionStore } from '@/store/session';
11
+
12
+ // sync outside state to useSessionStore
13
+ const SessionHydration = memo(() => {
14
+ const useStoreUpdater = createStoreUpdater(useSessionStore);
15
+
16
+ const { mobile } = useResponsive();
17
+ useStoreUpdater('isMobile', mobile);
18
+
19
+ const router = useRouter();
20
+ // TODO: 后续可以把 router 从 useSessionStore 移除
21
+ useStoreUpdater('router', router);
22
+
23
+ // two-way bindings the url and session store
24
+ const [session, setSession] = useQueryState(
25
+ 'session',
26
+ parseAsString.withDefault('inbox').withOptions({ history: 'replace', throttleMs: 500 }),
27
+ );
28
+ useStoreUpdater('activeId', session);
29
+
30
+ useEffect(() => {
31
+ const unsubscribe = useSessionStore.subscribe(
32
+ (s) => s.activeId,
33
+ (state) => setSession(state),
34
+ );
35
+
36
+ return () => {
37
+ unsubscribe();
38
+ };
39
+ }, []);
40
+
41
+ return null;
42
+ });
43
+
44
+ export default SessionHydration;
@@ -38,7 +38,7 @@ export const useStyles = createStyles(({ css, token, stylish, cx }, withBackgrou
38
38
  overflow: hidden scroll;
39
39
 
40
40
  width: 100%;
41
- max-height: 40vh;
41
+ max-height: 40dvh;
42
42
 
43
43
  background: ${token.colorBgLayout};
44
44
  border: 1px solid ${token.colorBorder};
@@ -16,13 +16,18 @@ const SessionListContent = memo(() => {
16
16
  const { t } = useTranslation('chat');
17
17
  const unpinnedSessionList = useSessionStore(sessionSelectors.unpinnedSessionList, isEqual);
18
18
  const pinnedList = useSessionStore(sessionSelectors.pinnedSessionList, isEqual);
19
- const hasPinnedSessionList = useSessionStore(sessionSelectors.hasPinnedSessionList);
19
+ const [hasPinnedSessionList, useFetchSessions] = useSessionStore((s) => [
20
+ sessionSelectors.hasPinnedSessionList(s),
21
+ s.useFetchSessions,
22
+ ]);
20
23
 
21
24
  const [sessionGroupKeys, updatePreference] = useGlobalStore((s) => [
22
25
  preferenceSelectors.sessionGroupKeys(s),
23
26
  s.updatePreference,
24
27
  ]);
25
28
 
29
+ useFetchSessions();
30
+
26
31
  const items = [
27
32
  hasPinnedSessionList && {
28
33
  children: <SessionList dataSource={pinnedList} />,
@@ -4,7 +4,8 @@ import { memo } from 'react';
4
4
  import LazyLoad from 'react-lazy-load';
5
5
 
6
6
  import { SESSION_CHAT_URL } from '@/const/url';
7
- import { useSessionHydrated, useSessionStore } from '@/store/session';
7
+ import { useSessionStore } from '@/store/session';
8
+ import { sessionSelectors } from '@/store/session/selectors';
8
9
  import { LobeAgentSession } from '@/types/session';
9
10
 
10
11
  import AddButton from './AddButton';
@@ -21,9 +22,12 @@ interface SessionListProps {
21
22
  dataSource: LobeAgentSession[];
22
23
  }
23
24
  const SessionList = memo<SessionListProps>(({ dataSource }) => {
24
- const [activeSession, switchSession] = useSessionStore((s) => [s.activeSession, s.switchSession]);
25
+ const [activeSession, switchSession, isInit] = useSessionStore((s) => [
26
+ s.activeSession,
27
+ s.switchSession,
28
+ sessionSelectors.isSessionListInit(s),
29
+ ]);
25
30
  const { styles } = useStyles();
26
- const isInit = useSessionHydrated();
27
31
 
28
32
  const { mobile } = useResponsive();
29
33
 
@@ -8,7 +8,7 @@ import { Flexbox } from 'react-layout-kit';
8
8
  import { useChatStore } from '@/store/chat';
9
9
  import { topicSelectors } from '@/store/chat/selectors';
10
10
 
11
- import SidebarHeader from '../SidebarHeader';
11
+ import SidebarHeader from '../../components/SidebarHeader';
12
12
  import TopicSearchBar from './TopicSearchBar';
13
13
 
14
14
  const Header = memo(() => {
@@ -2,6 +2,7 @@ import { isMobileDevice } from '@/utils/responsive';
2
2
 
3
3
  import DesktopPage from './(desktop)';
4
4
  import MobilePage from './(mobile)';
5
+ import SessionHydration from './components/SessionHydration';
5
6
  import Migration from './features/Migration';
6
7
 
7
8
  const Page = () => {
@@ -10,9 +11,12 @@ const Page = () => {
10
11
  const Page = mobile ? MobilePage : DesktopPage;
11
12
 
12
13
  return (
13
- <Migration>
14
- <Page />
15
- </Migration>
14
+ <>
15
+ <Migration>
16
+ <Page />
17
+ </Migration>
18
+ <SessionHydration />
19
+ </>
16
20
  );
17
21
  };
18
22
 
@@ -24,11 +24,11 @@ const Redirect = memo(() => {
24
24
  useGlobalStore.getState().switchSideBar(SidebarTabKey.Chat);
25
25
 
26
26
  if (hasData) {
27
- router.push('/chat');
27
+ router.replace('/chat');
28
28
 
29
29
  switchSession();
30
30
  } else {
31
- router.push('/welcome');
31
+ router.replace('/welcome');
32
32
  }
33
33
  });
34
34
  }, []);
@@ -1,9 +1,14 @@
1
1
  import { Metadata } from 'next';
2
2
 
3
+ import { getServerConfig } from '@/config/server';
4
+
3
5
  import pkg from '../../package.json';
4
6
 
5
7
  const title = 'LobeChat';
6
8
  const { description, homepage } = pkg;
9
+
10
+ const { METADATA_BASE_URL = 'https://chat-preview.lobehub.com/' } = getServerConfig();
11
+
7
12
  const metadata: Metadata = {
8
13
  appleWebApp: {
9
14
  statusBarStyle: 'black-translucent',
@@ -18,6 +23,7 @@ const metadata: Metadata = {
18
23
  'https://registry.npmmirror.com/@lobehub/assets-favicons/latest/files/assets/favicon.ico',
19
24
  },
20
25
  manifest: '/manifest.json',
26
+ metadataBase: new URL(METADATA_BASE_URL),
21
27
  openGraph: {
22
28
  description: description,
23
29
  images: [
@@ -3,11 +3,13 @@
3
3
  import { Icon } from '@lobehub/ui';
4
4
  import { Button } from 'antd';
5
5
  import { SendHorizonal } from 'lucide-react';
6
+ import { useRouter } from 'next/navigation';
6
7
  import { memo } from 'react';
7
8
  import { useTranslation } from 'react-i18next';
8
9
  import { Flexbox } from 'react-layout-kit';
9
10
 
10
11
  import DataImporter from '@/features/DataImporter';
12
+ import { useGlobalStore } from '@/store/global';
11
13
  import { useSessionStore } from '@/store/session';
12
14
 
13
15
  import Hero from './Hero';
@@ -15,13 +17,10 @@ import { useStyles } from './style';
15
17
 
16
18
  const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
17
19
  const { t } = useTranslation('welcome');
20
+ const router = useRouter();
18
21
  const { styles } = useStyles();
19
- const [switchSession, switchBackToChat, router, isMobile] = useSessionStore((s) => [
20
- s.switchSession,
21
- s.switchBackToChat,
22
- s.router,
23
- s.isMobile,
24
- ]);
22
+ const [switchSession] = useSessionStore((s) => [s.switchSession]);
23
+ const [switchBackToChat, isMobile] = useGlobalStore((s) => [s.switchBackToChat, s.isMobile]);
25
24
 
26
25
  return (
27
26
  <>
@@ -46,7 +45,7 @@ const Banner = memo<{ mobile?: boolean }>(({ mobile }) => {
46
45
  </DataImporter>
47
46
  <Button
48
47
  block={mobile}
49
- onClick={() => (isMobile ? router?.push('/chat') : switchBackToChat())}
48
+ onClick={() => (isMobile ? router.push('/chat') : switchBackToChat())}
50
49
  size={'large'}
51
50
  type={'primary'}
52
51
  >
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { useQueryState } from 'nuqs';
4
+ import { memo, useEffect } from 'react';
5
+ import { createStoreUpdater } from 'zustand-utils';
6
+
7
+ import { useChatStore } from '@/store/chat';
8
+
9
+ // sync outside state to useChatStore
10
+ const ChatHydration = memo(() => {
11
+ const useStoreUpdater = createStoreUpdater(useChatStore);
12
+
13
+ // two-way bindings the topic params to chat store
14
+ const [topic, setTopic] = useQueryState('topic', { history: 'replace', throttleMs: 500 });
15
+ useStoreUpdater('activeTopicId', topic);
16
+
17
+ useEffect(() => {
18
+ const unsubscribe = useChatStore.subscribe(
19
+ (s) => s.activeTopicId,
20
+ (state) => {
21
+ setTopic(!state ? null : state);
22
+ },
23
+ );
24
+
25
+ return () => {
26
+ unsubscribe();
27
+ };
28
+ }, []);
29
+
30
+ return null;
31
+ });
32
+
33
+ export default ChatHydration;
@@ -16,6 +16,8 @@ declare global {
16
16
 
17
17
  IMGUR_CLIENT_ID?: string;
18
18
 
19
+ METADATA_BASE_URL?: string;
20
+
19
21
  AGENTS_INDEX_URL?: string;
20
22
 
21
23
  PLUGINS_INDEX_URL?: string;
@@ -51,6 +53,8 @@ export const getServerConfig = () => {
51
53
  OPENAI_PROXY_URL: process.env.OPENAI_PROXY_URL,
52
54
  OPENAI_FUNCTION_REGIONS: regions,
53
55
 
56
+ METADATA_BASE_URL: process.env.METADATA_BASE_URL,
57
+
54
58
  AZURE_API_KEY: process.env.AZURE_API_KEY,
55
59
  AZURE_API_VERSION: process.env.AZURE_API_VERSION,
56
60
  USE_AZURE_OPENAI: process.env.USE_AZURE_OPENAI === '1',
package/src/const/url.ts CHANGED
@@ -17,5 +17,5 @@ export const AGENTS_INDEX_GITHUB = 'https://github.com/lobehub/lobe-chat-agents'
17
17
  export const AGENTS_INDEX_GITHUB_ISSUE = urlJoin(AGENTS_INDEX_GITHUB, 'issues/new');
18
18
 
19
19
  export const SESSION_CHAT_URL = (id: string = INBOX_SESSION_ID, mobile?: boolean) =>
20
- mobile ? `/chat/mobile#session=${id}` : `/chat#session=${id}`;
20
+ mobile ? `/chat/mobile?session=${id}` : `/chat?session=${id}`;
21
21
  export const MANUAL_UPGRADE_URL = 'https://github.com/lobehub/lobe-chat/wiki/Upstream-Sync';
@@ -329,14 +329,15 @@ describe('MessageModel', () => {
329
329
  describe('duplicateMessages', () => {
330
330
  it('should duplicate messages and update parentId for copied messages', async () => {
331
331
  // 创建原始消息和父消息
332
- const parentMessageData: DB_Message = {
332
+ const parentMessageData: CreateMessageParams = {
333
333
  content: 'Parent message content',
334
334
  role: 'user',
335
335
  sessionId: 'session1',
336
+ topicId: undefined,
336
337
  };
337
338
  const parentMessage = await MessageModel.create(parentMessageData);
338
339
 
339
- const childMessageData: DB_Message = {
340
+ const childMessageData: CreateMessageParams = {
340
341
  content: 'Child message content',
341
342
  role: 'user',
342
343
  sessionId: 'session1',
@@ -46,14 +46,13 @@ class _MessageModel extends BaseModel {
46
46
  }: QueryMessageParams): Promise<ChatMessage[]> {
47
47
  const offset = current * pageSize;
48
48
 
49
- const query =
50
- topicId !== undefined
51
- ? // TODO: The query {"sessionId":"xxx","topicId":"xxx"} on messages would benefit of a compound index [sessionId+topicId]
52
- this.table.where({ sessionId, topicId }) // Use a compound index
53
- : this.table
54
- .where('sessionId')
55
- .equals(sessionId)
56
- .and((message) => !message.topicId);
49
+ const query = !!topicId
50
+ ? // TODO: The query {"sessionId":"xxx","topicId":"xxx"} on messages would benefit of a compound index [sessionId+topicId]
51
+ this.table.where({ sessionId, topicId }) // Use a compound index
52
+ : this.table
53
+ .where('sessionId')
54
+ .equals(sessionId)
55
+ .and((message) => !message.topicId);
57
56
 
58
57
  const dbMessages: DBModel<DB_Message>[] = await query
59
58
  .sortBy('createdAt')
@@ -221,7 +220,12 @@ class _MessageModel extends BaseModel {
221
220
  tts,
222
221
  ...item
223
222
  }: DBModel<DB_Message>): ChatMessage => {
224
- return { ...item, extra: { fromModel, translate, tts }, meta: {} };
223
+ return {
224
+ ...item,
225
+ extra: { fromModel, translate, tts },
226
+ meta: {},
227
+ topicId: item.topicId ?? undefined,
228
+ };
225
229
  };
226
230
  }
227
231
 
@@ -31,7 +31,7 @@ export const DB_MessageSchema = z.object({
31
31
  parentId: z.string().optional(),
32
32
  quotaId: z.string().optional(),
33
33
  sessionId: z.string(),
34
- topicId: z.string().optional(),
34
+ topicId: z.string().nullable().optional(),
35
35
  });
36
36
 
37
37
  export type DB_Message = z.infer<typeof DB_MessageSchema>;
@@ -1,7 +1,6 @@
1
1
  import { TextAreaRef } from 'antd/es/input/TextArea';
2
2
  import { useCallback, useRef, useState } from 'react';
3
3
 
4
- import { useIsMobile } from '@/hooks/useIsMobile';
5
4
  import { useChatStore } from '@/store/chat';
6
5
  import { useSessionStore } from '@/store/session';
7
6
  import { agentSelectors } from '@/store/session/selectors';
@@ -20,15 +19,12 @@ export const useChatInput = () => {
20
19
  s.updateInputMessage,
21
20
  s.stopGenerateMessage,
22
21
  ]);
23
- const mobile = useIsMobile();
24
22
 
25
23
  const handleSend = useCallback(() => {
26
24
  setExpand(false);
27
- if (mobile) {
28
- ref?.current?.blur();
29
- }
25
+
30
26
  onSend();
31
- }, [onSend, mobile]);
27
+ }, [onSend]);
32
28
 
33
29
  return {
34
30
  canUpload,
@@ -28,8 +28,6 @@ export const useInitConversation = () => {
28
28
  checkPluginsIsInstalled(plugins);
29
29
 
30
30
  useEffect(() => {
31
- useChatStore.persist.rehydrate();
32
-
33
31
  // // when activeId changed, switch topic to undefined
34
32
  const unsubscribe = useSessionStore.subscribe(
35
33
  (s) => s.activeId,
@@ -2,6 +2,7 @@ import { createStyles } from 'antd-style';
2
2
  import { ReactNode, memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
+ import ChatHydration from '@/components/StoreHydration/ChatHydration';
5
6
  import { useGlobalStore } from '@/store/global';
6
7
  import { settingsSelectors } from '@/store/global/selectors';
7
8
 
@@ -50,12 +51,16 @@ const Conversation = memo<ConversationProps>(({ chatInput, mobile }) => {
50
51
  const init = useInitConversation();
51
52
 
52
53
  return (
53
- // relative is required, ChatInput's absolute position needs it
54
- <Flexbox flex={1} style={{ position: 'relative' }}>
54
+ <Flexbox
55
+ flex={1}
56
+ // position: 'relative' is required, ChatInput's absolute position needs it
57
+ style={{ position: 'relative' }}
58
+ >
55
59
  <div className={styles}>
56
60
  {init ? <ChatList mobile={mobile} /> : <SkeletonList mobile={mobile} />}
57
61
  </div>
58
62
  {chatInput}
63
+ <ChatHydration />
59
64
  </Flexbox>
60
65
  );
61
66
  });
@@ -1,10 +1,10 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
- import { Bot, MessageSquare } from 'lucide-react';
2
+ import { Compass, MessageSquare } from 'lucide-react';
3
3
  import Link from 'next/link';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
- import { GlobalStore } from '@/store/global';
7
+ import { GlobalStore, useGlobalStore } from '@/store/global';
8
8
  import { SidebarTabKey } from '@/store/global/initialState';
9
9
  import { useSessionStore } from '@/store/session';
10
10
 
@@ -15,7 +15,7 @@ export interface TopActionProps {
15
15
 
16
16
  const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
17
17
  const { t } = useTranslation('common');
18
- const switchBackToChat = useSessionStore((s) => s.switchBackToChat);
18
+ const switchBackToChat = useGlobalStore((s) => s.switchBackToChat);
19
19
 
20
20
  return (
21
21
  <>
@@ -23,7 +23,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
23
23
  href={'/chat'}
24
24
  onClick={(e) => {
25
25
  e.preventDefault();
26
- switchBackToChat();
26
+ switchBackToChat(useSessionStore.getState().activeId);
27
27
  setTab(SidebarTabKey.Chat);
28
28
  }}
29
29
  >
@@ -38,7 +38,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
38
38
  <Link href={'/market'}>
39
39
  <ActionIcon
40
40
  active={tab === SidebarTabKey.Market}
41
- icon={Bot}
41
+ icon={Compass}
42
42
  onClick={() => {
43
43
  setTab(SidebarTabKey.Market);
44
44
  }}
@@ -2,32 +2,32 @@ import { useResponsive } from 'antd-style';
2
2
  import { useRouter } from 'next/navigation';
3
3
  import { memo, useEffect } from 'react';
4
4
 
5
- import { useGlobalStore } from '@/store/global';
6
- import { useEffectAfterSessionHydrated, useSessionStore } from '@/store/session';
5
+ import { useEffectAfterGlobalHydrated, useGlobalStore } from '@/store/global';
7
6
 
8
7
  const StoreHydration = memo(() => {
9
- const router = useRouter();
10
-
11
8
  const useFetchGlobalConfig = useGlobalStore((s) => s.useFetchGlobalConfig);
9
+ useFetchGlobalConfig();
12
10
 
13
11
  useEffect(() => {
14
12
  // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
15
- useSessionStore.persist.rehydrate();
16
13
  useGlobalStore.persist.rehydrate();
17
14
  }, []);
18
15
 
19
- useFetchGlobalConfig();
20
-
21
16
  const { mobile } = useResponsive();
22
-
23
- useEffectAfterSessionHydrated(
17
+ useEffectAfterGlobalHydrated(
24
18
  (store) => {
25
- store.setState({ isMobile: mobile });
19
+ const prevState = store.getState().isMobile;
20
+
21
+ if (prevState !== mobile) {
22
+ store.setState({ isMobile: mobile });
23
+ }
26
24
  },
27
25
  [mobile],
28
26
  );
29
27
 
30
- useEffectAfterSessionHydrated(
28
+ const router = useRouter();
29
+
30
+ useEffectAfterGlobalHydrated(
31
31
  (store) => {
32
32
  store.setState({ router });
33
33
  },
@@ -36,6 +36,7 @@ const StoreHydration = memo(() => {
36
36
 
37
37
  useEffect(() => {
38
38
  router.prefetch('/chat');
39
+ router.prefetch('/chat/settings');
39
40
  router.prefetch('/market');
40
41
  router.prefetch('/settings/common');
41
42
  router.prefetch('/settings/agent');
@@ -13,6 +13,7 @@ export default {
13
13
  close: '关闭',
14
14
  confirmRemoveSessionItemAlert: '即将删除该助手,删除后该将无法找回,请确认你的操作',
15
15
  copy: '复制',
16
+ copyFail: '复制失败',
16
17
  copySuccess: '复制成功',
17
18
  defaultAgent: '自定义助手',
18
19
  defaultSession: '自定义助手',
@@ -168,7 +168,7 @@ export const chatMessage: StateCreator<
168
168
  // if message is empty and no files, then stop
169
169
  if (!message && (!fileIdList || fileIdList?.length === 0)) return;
170
170
 
171
- let newMessage: CreateMessageParams = {
171
+ const newMessage: CreateMessageParams = {
172
172
  content: message,
173
173
  // if message has attached with files, then add files to message and the agent
174
174
  files: fileIdList,
@@ -1,12 +1,9 @@
1
- import { PersistOptions, devtools, persist } from 'zustand/middleware';
1
+ import { devtools, subscribeWithSelector } from 'zustand/middleware';
2
2
  import { shallow } from 'zustand/shallow';
3
3
  import { createWithEqualityFn } from 'zustand/traditional';
4
4
  import { StateCreator } from 'zustand/vanilla';
5
5
 
6
- import { createHyperStorage } from '@/store/middleware/createHyperStorage';
7
6
  import { isDev } from '@/utils/env';
8
- import { isInStandaloneMode } from '@/utils/matchMedia';
9
- import { isMobileScreen } from '@/utils/screen';
10
7
 
11
8
  import { ChatStoreState, initialState } from './initialState';
12
9
  import { ChatEnhanceAction, chatEnhance } from './slices/enchance/action';
@@ -36,42 +33,13 @@ const createStore: StateCreator<ChatStore, [['zustand/devtools', never]]> = (...
36
33
  ...chatPlugin(...params),
37
34
  });
38
35
 
39
- const persistOptions: PersistOptions<ChatStore> = {
40
- name: 'LobeChat_Chat',
41
-
42
- // 手动控制 Hydration ,避免 ssr 报错
43
- skipHydration: true,
44
-
45
- storage: createHyperStorage(() => {
46
- const isMobileAndPWA = isMobileScreen() && isInStandaloneMode();
47
- const selectors = [{ activeTopicId: 'topic' }];
48
-
49
- if (isMobileAndPWA)
50
- return {
51
- localStorage: {
52
- selectors,
53
- },
54
- };
55
-
56
- return {
57
- localStorage: false,
58
- url: {
59
- mode: 'hash',
60
- selectors,
61
- },
62
- };
63
- }),
64
- version: 0,
65
- };
66
-
67
36
  // =============== 实装 useStore ============ //
68
37
 
69
38
  export const useChatStore = createWithEqualityFn<ChatStore>()(
70
- persist(
39
+ subscribeWithSelector(
71
40
  devtools(createStore, {
72
41
  name: 'LobeChat_Chat' + (isDev ? '_DEV' : ''),
73
42
  }),
74
- persistOptions,
75
43
  ),
76
44
  shallow,
77
45
  );