@lobehub/chat 0.41.0 → 0.41.2

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 (36) hide show
  1. package/.eslintrc.js +1 -0
  2. package/CHANGELOG.md +51 -0
  3. package/package.json +1 -1
  4. package/src/features/AvatarWithUpload/index.tsx +1 -2
  5. package/src/features/FolderPanel/index.tsx +4 -5
  6. package/src/features/SideBar/index.tsx +1 -2
  7. package/src/layout/index.tsx +6 -6
  8. package/src/pages/chat/SessionList/Header.tsx +2 -6
  9. package/src/pages/chat/SessionList/List/SessionItem.tsx +2 -2
  10. package/src/pages/chat/SessionList/List/index.tsx +1 -5
  11. package/src/pages/chat/[id]/Conversation/ChatList/Error/ApiKeyForm.tsx +1 -5
  12. package/src/pages/chat/[id]/Conversation/ChatList/Error/InvalidAccess.tsx +2 -6
  13. package/src/pages/chat/[id]/Conversation/ChatList/Error/OpenAiBizError.tsx +1 -5
  14. package/src/pages/chat/[id]/Conversation/ChatList/MessageExtra.tsx +1 -2
  15. package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +9 -19
  16. package/src/pages/chat/[id]/Conversation/Input/Action.tsx +1 -2
  17. package/src/pages/chat/[id]/Conversation/Input/ActionRight.tsx +1 -2
  18. package/src/pages/chat/[id]/Conversation/Input/Token.tsx +6 -10
  19. package/src/pages/chat/[id]/Conversation/Input/index.tsx +6 -6
  20. package/src/pages/chat/[id]/Header/index.tsx +13 -17
  21. package/src/pages/chat/[id]/Sidebar/Inner.tsx +4 -5
  22. package/src/pages/chat/[id]/Sidebar/Topic/TopicContent.tsx +5 -5
  23. package/src/pages/chat/[id]/Sidebar/Topic/TopicItem.tsx +1 -2
  24. package/src/pages/chat/[id]/Sidebar/Topic/index.tsx +2 -6
  25. package/src/pages/chat/[id]/Sidebar/index.tsx +4 -5
  26. package/src/pages/chat/[id]/index.page.tsx +4 -5
  27. package/src/pages/chat/[id]/setting/index.page.tsx +7 -11
  28. package/src/pages/chat/layout.tsx +1 -2
  29. package/src/pages/settings/Settings/Agent.tsx +5 -5
  30. package/src/pages/settings/Settings/Common.tsx +5 -9
  31. package/src/pages/settings/ThemeSwatches/ThemeSwatchesNeutral.tsx +1 -5
  32. package/src/pages/settings/ThemeSwatches/ThemeSwatchesPrimary.tsx +1 -5
  33. package/src/store/session/store.ts +8 -2
  34. package/src/store/settings/hooks/useHydrated.ts +20 -0
  35. package/src/store/settings/index.ts +1 -25
  36. package/src/store/settings/store.ts +29 -1
package/.eslintrc.js CHANGED
@@ -8,5 +8,6 @@ config.rules['unicorn/prefer-logical-operator-over-ternary'] = 0;
8
8
  config.rules['unicorn/no-null'] = 0;
9
9
  config.rules['unicorn/no-typeof-undefined'] = 0;
10
10
  config.rules['unicorn/explicit-length-check'] = 0;
11
+ config.rules['unicorn/prefer-code-point'] = 0;
11
12
 
12
13
  module.exports = config;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.41.2](https://github.com/lobehub/lobe-chat/compare/v0.41.1...v0.41.2)
6
+
7
+ <sup>Released on **2023-08-10**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: 将 sessionStore 默认 equalFn 改为 shallow, 将 settingStore 默认 equalFn 改为 shallow.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: 将 sessionStore 默认 equalFn 改为 shallow ([5c1b8d7](https://github.com/lobehub/lobe-chat/commit/5c1b8d7))
21
+ - **misc**: 将 settingStore 默认 equalFn 改为 shallow ([1e72308](https://github.com/lobehub/lobe-chat/commit/1e72308))
22
+
23
+ </details>
24
+
25
+ <div align="right">
26
+
27
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
28
+
29
+ </div>
30
+
31
+ ### [Version 0.41.1](https://github.com/lobehub/lobe-chat/compare/v0.41.0...v0.41.1)
32
+
33
+ <sup>Released on **2023-08-10**</sup>
34
+
35
+ #### ♻ Code Refactoring
36
+
37
+ - **misc**: 重构 settings store 代码写法.
38
+
39
+ <br/>
40
+
41
+ <details>
42
+ <summary><kbd>Improvements and Fixes</kbd></summary>
43
+
44
+ #### Code refactoring
45
+
46
+ - **misc**: 重构 settings store 代码写法 ([4b6f917](https://github.com/lobehub/lobe-chat/commit/4b6f917))
47
+
48
+ </details>
49
+
50
+ <div align="right">
51
+
52
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
53
+
54
+ </div>
55
+
5
56
  ## [Version 0.41.0](https://github.com/lobehub/lobe-chat/compare/v0.40.7...v0.41.0)
6
57
 
7
58
  <sup>Released on **2023-08-10**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.41.0",
3
+ "version": "0.41.2",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -2,7 +2,6 @@ import { Avatar, Logo } from '@lobehub/ui';
2
2
  import { Upload } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
4
  import { memo } from 'react';
5
- import { shallow } from 'zustand/shallow';
6
5
 
7
6
  import { useSettings } from '@/store/settings';
8
7
  import { createUploadImageHandler } from '@/utils/uploadFIle';
@@ -28,7 +27,7 @@ interface AvatarWithUploadProps {
28
27
  }
29
28
 
30
29
  const AvatarWithUpload = memo<AvatarWithUploadProps>(({ size = 40 }) => {
31
- const [avatar, setSettings] = useSettings((st) => [st.settings.avatar, st.setSettings], shallow);
30
+ const [avatar, setSettings] = useSettings((st) => [st.settings.avatar, st.setSettings]);
32
31
  const { styles } = useStyle();
33
32
 
34
33
  const handleUploadAvatar = createUploadImageHandler((avatar) => {
@@ -2,7 +2,6 @@ import { DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { PropsWithChildren, memo, useState } from 'react';
5
- import { shallow } from 'zustand/shallow';
6
5
 
7
6
  import { FOLDER_WIDTH } from '@/const/layoutTokens';
8
7
  import { useSettings } from '@/store/settings';
@@ -17,10 +16,10 @@ export const useStyles = createStyles(({ css, token }) => ({
17
16
 
18
17
  const FolderPanel = memo<PropsWithChildren>(({ children }) => {
19
18
  const { styles } = useStyles();
20
- const [sessionsWidth, sessionExpandable] = useSettings(
21
- (s) => [s.sessionsWidth, s.sessionExpandable],
22
- shallow,
23
- );
19
+ const [sessionsWidth, sessionExpandable] = useSettings((s) => [
20
+ s.sessionsWidth,
21
+ s.sessionExpandable,
22
+ ]);
24
23
  const [tmpWidth, setWidth] = useState(sessionsWidth);
25
24
  if (tmpWidth !== sessionsWidth) setWidth(sessionsWidth);
26
25
 
@@ -1,6 +1,5 @@
1
1
  import { SideNav } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
- import { shallow } from 'zustand/shallow';
4
3
 
5
4
  import AvatarWithUpload from '@/features/AvatarWithUpload';
6
5
  import { useSettings } from '@/store/settings';
@@ -9,7 +8,7 @@ import BottomActions from './BottomActions';
9
8
  import TopActions from './TopActions';
10
9
 
11
10
  export default memo(() => {
12
- const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar], shallow);
11
+ const [tab, setTab] = useSettings((s) => [s.sidebarKey, s.switchSideBar]);
13
12
 
14
13
  return (
15
14
  <SideNav
@@ -4,7 +4,6 @@ import { useThemeMode } from 'antd-style';
4
4
  import 'antd/dist/reset.css';
5
5
  import Zh_CN from 'antd/locale/zh_CN';
6
6
  import { PropsWithChildren, useCallback, useEffect } from 'react';
7
- import { shallow } from 'zustand/shallow';
8
7
 
9
8
  import { useSessionStore } from '@/store/session';
10
9
  import { useSettings } from '@/store/settings';
@@ -29,11 +28,12 @@ const Layout = ({ children }: PropsWithChildren) => {
29
28
  };
30
29
 
31
30
  export default ({ children }: PropsWithChildren) => {
32
- const themeMode = useSettings((s) => s.settings.themeMode, shallow);
33
- const { primaryColor, neutralColor } = useSettings(
34
- (s) => ({ neutralColor: s.settings.neutralColor, primaryColor: s.settings.primaryColor }),
35
- shallow,
36
- );
31
+ const themeMode = useSettings((s) => s.settings.themeMode);
32
+ const [primaryColor, neutralColor] = useSettings((s) => [
33
+ s.settings.primaryColor,
34
+ s.settings.neutralColor,
35
+ ]);
36
+
37
37
  const { browserPrefers } = useThemeMode();
38
38
  const isDarkMode = themeMode === 'auto' ? browserPrefers === 'dark' : themeMode === 'dark';
39
39
 
@@ -1,11 +1,10 @@
1
1
  import { ActionIcon, Logo, SearchBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { MessageSquarePlus } from 'lucide-react';
4
- import { useTranslation } from 'react-i18next';
5
4
  import Link from 'next/link';
6
5
  import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
- import { shallow } from 'zustand/shallow';
9
8
 
10
9
  import { useSessionStore } from '@/store/session';
11
10
 
@@ -22,10 +21,7 @@ export const useStyles = createStyles(({ css, token }) => ({
22
21
  const Header = memo(() => {
23
22
  const { styles } = useStyles();
24
23
  const { t } = useTranslation('common');
25
- const [keywords, createSession] = useSessionStore(
26
- (s) => [s.searchKeywords, s.createSession],
27
- shallow,
28
- );
24
+ const [keywords, createSession] = useSessionStore((s) => [s.searchKeywords, s.createSession]);
29
25
 
30
26
  return (
31
27
  <Flexbox className={styles.top} gap={16} padding={16}>
@@ -29,7 +29,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
29
29
  const { t } = useTranslation('common');
30
30
 
31
31
  const { styles } = useStyles();
32
- const [defaultModel] = useSettings((s) => [s.settings.defaultAgent.config?.model], shallow);
32
+ const [defaultModel] = useSettings((s) => [s.settings.defaultAgent.config?.model]);
33
33
 
34
34
  const [
35
35
  pin,
@@ -60,7 +60,7 @@ const SessionItem: FC<SessionItemProps> = memo(({ id, active = true, loading })
60
60
  s.removeSession,
61
61
  s.pinSession,
62
62
  ];
63
- }, shallow);
63
+ });
64
64
 
65
65
  const { modal } = App.useApp();
66
66
  const items: MenuProps['items'] = useMemo(
@@ -1,7 +1,6 @@
1
1
  import isEqual from 'fast-deep-equal';
2
2
  import Link from 'next/link';
3
3
  import { memo } from 'react';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import { sessionSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
7
6
 
@@ -10,10 +9,7 @@ import SkeletonItem from './SkeletonItem';
10
9
 
11
10
  const SessionList = memo(() => {
12
11
  const list = useSessionStore((s) => sessionSelectors.sessionList(s), isEqual);
13
- const [activeId, loading] = useSessionStore(
14
- (s) => [s.activeId, s.autocompleteLoading.title],
15
- shallow,
16
- );
12
+ const [activeId, loading] = useSessionStore((s) => [s.activeId, s.autocompleteLoading.title]);
17
13
 
18
14
  const isInit = useSessionHydrated();
19
15
 
@@ -3,7 +3,6 @@ import { Button, Input } from 'antd';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Center, Flexbox } from 'react-layout-kit';
6
- import { shallow } from 'zustand/shallow';
7
6
 
8
7
  import { useSettings } from '@/store/settings';
9
8
 
@@ -12,10 +11,7 @@ import { useStyles } from './style';
12
11
  const APIKeyForm = memo<{ onConfirm?: () => void }>(({ onConfirm }) => {
13
12
  const { t } = useTranslation('error');
14
13
  const { styles, theme } = useStyles();
15
- const [apiKey, setSettings] = useSettings(
16
- (s) => [s.settings.OPENAI_API_KEY, s.setSettings],
17
- shallow,
18
- );
14
+ const [apiKey, setSettings] = useSettings((s) => [s.settings.OPENAI_API_KEY, s.setSettings]);
19
15
 
20
16
  return (
21
17
  <Center gap={16} style={{ maxWidth: 300 }}>
@@ -4,7 +4,6 @@ import { KeySquare, SquareAsterisk } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
- import { shallow } from 'zustand/shallow';
8
7
 
9
8
  import { useSessionStore } from '@/store/session';
10
9
  import { useSettings } from '@/store/settings';
@@ -16,11 +15,8 @@ import { ErrorActionContainer, FormAction } from './style';
16
15
  const InvalidAccess = memo<{ id: string }>(({ id }) => {
17
16
  const { t } = useTranslation('error');
18
17
  const [mode, setMode] = useState('password');
19
- const [password, setSettings] = useSettings((s) => [s.settings.password, s.setSettings], shallow);
20
- const [resend, deleteMessage] = useSessionStore(
21
- (s) => [s.resendMessage, s.deleteMessage],
22
- shallow,
23
- );
18
+ const [password, setSettings] = useSettings((s) => [s.settings.password, s.setSettings]);
19
+ const [resend, deleteMessage] = useSessionStore((s) => [s.resendMessage, s.deleteMessage]);
24
20
 
25
21
  return (
26
22
  <ErrorActionContainer>
@@ -1,7 +1,6 @@
1
1
  import { Highlighter } from '@lobehub/ui';
2
2
  import { memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import { useSessionStore } from '@/store/session';
7
6
 
@@ -22,10 +21,7 @@ interface OpenAIErrorResponse {
22
21
  const OpenAiBizError = memo<{ content: OpenAIErrorResponse; id: string }>(({ content, id }) => {
23
22
  const { styles } = useStyles();
24
23
 
25
- const [resend, deleteMessage] = useSessionStore(
26
- (s) => [s.resendMessage, s.deleteMessage],
27
- shallow,
28
- );
24
+ const [resend, deleteMessage] = useSessionStore((s) => [s.resendMessage, s.deleteMessage]);
29
25
 
30
26
  const errorCode = content.error?.code;
31
27
 
@@ -1,7 +1,6 @@
1
1
  import { createStyles } from 'antd-style';
2
2
  import { ReactNode } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import Tag from '@/components/Tag';
7
6
  import { agentSelectors, useSessionStore } from '@/store/session';
@@ -28,7 +27,7 @@ const MessageExtra = ({
28
27
  const { styles } = useStyles();
29
28
 
30
29
  // const { t } = useTranslation();
31
- const [model] = useSessionStore((s) => [agentSelectors.currentAgentModel(s)], shallow);
30
+ const [model] = useSessionStore((s) => [agentSelectors.currentAgentModel(s)]);
32
31
 
33
32
  // const plugin = PluginsMap[name || ''];
34
33
 
@@ -3,14 +3,8 @@ import isEqual from 'fast-deep-equal';
3
3
  import { memo, useCallback } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
- import { shallow } from 'zustand/shallow';
7
6
 
8
- import {
9
- agentSelectors,
10
- chatSelectors,
11
- useSessionHydrated,
12
- useSessionStore,
13
- } from '@/store/session';
7
+ import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
14
8
  import { ChatMessage } from '@/types/chatMessage';
15
9
  import { ErrorType } from '@/types/fetch';
16
10
  import { isFunctionMessage } from '@/utils/message';
@@ -34,20 +28,16 @@ const renderErrorMessage: RenderErrorMessage = (error, message) => {
34
28
 
35
29
  const List = () => {
36
30
  const { t } = useTranslation('common');
37
- const hydrated = useSessionHydrated();
31
+
38
32
  const data = useSessionStore(chatSelectors.currentChats, isEqual);
39
- console.log(hydrated, data);
40
33
  const [displayMode, chatLoadingId, deleteMessage, resendMessage, dispatchMessage] =
41
- useSessionStore(
42
- (s) => [
43
- agentSelectors.currentAgentConfig(s).displayMode,
44
- s.chatLoadingId,
45
- s.deleteMessage,
46
- s.resendMessage,
47
- s.dispatchMessage,
48
- ],
49
- shallow,
50
- );
34
+ useSessionStore((s) => [
35
+ agentSelectors.currentAgentConfig(s).displayMode,
36
+ s.chatLoadingId,
37
+ s.deleteMessage,
38
+ s.resendMessage,
39
+ s.dispatchMessage,
40
+ ]);
51
41
 
52
42
  const renderMessage: RenderMessage = useCallback(
53
43
  (content, message: ChatMessage) => {
@@ -4,7 +4,6 @@ import { BrainCog, Thermometer, Timer, TimerOff } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
- import { shallow } from 'zustand/shallow';
8
7
 
9
8
  import SliderWithInput from '@/components/SliderWithInput';
10
9
  import { agentSelectors, useSessionStore } from '@/store/session';
@@ -22,7 +21,7 @@ const InputActions = memo(() => {
22
21
  !config.enableHistoryCount,
23
22
  s.updateAgentConfig,
24
23
  ];
25
- }, shallow);
24
+ });
26
25
 
27
26
  return (
28
27
  <>
@@ -3,13 +3,12 @@ import { Popconfirm } from 'antd';
3
3
  import { Eraser } from 'lucide-react';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { shallow } from 'zustand/shallow';
7
6
 
8
7
  import { useSessionStore } from '@/store/session';
9
8
 
10
9
  const ActionsRight = memo(() => {
11
10
  const { t } = useTranslation('setting');
12
- const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig], shallow);
11
+ const [clearMessage] = useSessionStore((s) => [s.clearMessage, s.updateAgentConfig]);
13
12
 
14
13
  return (
15
14
  <Popconfirm
@@ -2,7 +2,6 @@ import { TokenTag, Tooltip } from '@lobehub/ui';
2
2
  import { encode } from 'gpt-tokenizer';
3
3
  import { memo, useMemo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
- import { shallow } from 'zustand/shallow';
6
5
 
7
6
  import { ModelTokens } from '@/const/modelTokens';
8
7
  import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
@@ -12,15 +11,12 @@ const Token = memo<{ input: string }>(({ input }) => {
12
11
 
13
12
  const inputTokenCount = useMemo(() => encode(input).length, [input]);
14
13
 
15
- const [totalToken, systemRoleToken, chatsToken, model] = useSessionStore(
16
- (s) => [
17
- chatSelectors.totalTokenCount(s),
18
- chatSelectors.systemRoleTokenCount(s),
19
- chatSelectors.chatsTokenCount(s),
20
- agentSelectors.currentAgentModel(s),
21
- ],
22
- shallow,
23
- );
14
+ const [totalToken, systemRoleToken, chatsToken, model] = useSessionStore((s) => [
15
+ chatSelectors.totalTokenCount(s),
16
+ chatSelectors.systemRoleTokenCount(s),
17
+ chatSelectors.chatsTokenCount(s),
18
+ agentSelectors.currentAgentModel(s),
19
+ ]);
24
20
 
25
21
  return (
26
22
  <Tooltip placement={'bottom'} title={t('tokenDetail', { chatsToken, systemRoleToken })}>
@@ -3,7 +3,6 @@ import { Button } from 'antd';
3
3
  import { LucideGalleryVerticalEnd } from 'lucide-react';
4
4
  import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
- import { shallow } from 'zustand/shallow';
7
6
 
8
7
  import { CHAT_TEXTAREA_HEIGHT, HEADER_HEIGHT } from '@/const/layoutTokens';
9
8
  import { useSessionStore } from '@/store/session';
@@ -18,11 +17,12 @@ const ChatInput = () => {
18
17
  const [expand, setExpand] = useState<boolean>(false);
19
18
  const [text, setText] = useState('');
20
19
 
21
- const [inputHeight] = useSettings((s) => [s.inputHeight], shallow);
22
- const [sendMessage, hasTopic, saveToTopic] = useSessionStore(
23
- (s) => [s.createOrSendMsg, !!s.activeTopicId, s.saveToTopic],
24
- shallow,
25
- );
20
+ const [inputHeight] = useSettings((s) => [s.inputHeight]);
21
+ const [sendMessage, hasTopic, saveToTopic] = useSessionStore((s) => [
22
+ s.createOrSendMsg,
23
+ !!s.activeTopicId,
24
+ s.saveToTopic,
25
+ ]);
26
26
 
27
27
  const footer = hasTopic ? null : (
28
28
  <Tooltip title={t('topic.saveCurrentMessages')}>
@@ -6,7 +6,6 @@ import Router from 'next/router';
6
6
  import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
- import { shallow } from 'zustand/shallow';
10
9
 
11
10
  import HeaderTitle from '@/components/HeaderTitle';
12
11
  import Tag from '@/components/Tag';
@@ -19,23 +18,20 @@ const Header = memo(() => {
19
18
  const init = useSessionHydrated();
20
19
  const { t } = useTranslation('common');
21
20
 
22
- const [title, description, avatar, backgroundColor, id, model, plugins] = useSessionStore(
23
- (s) => [
24
- agentSelectors.currentAgentTitle(s),
25
- agentSelectors.currentAgentDescription(s),
26
- agentSelectors.currentAgentAvatar(s),
27
- agentSelectors.currentAgentBackgroundColor(s),
28
- s.activeId,
29
- agentSelectors.currentAgentModel(s),
30
- agentSelectors.currentAgentPlugins(s),
31
- ],
32
- shallow,
33
- );
21
+ const [title, description, avatar, backgroundColor, id, model, plugins] = useSessionStore((s) => [
22
+ agentSelectors.currentAgentTitle(s),
23
+ agentSelectors.currentAgentDescription(s),
24
+ agentSelectors.currentAgentAvatar(s),
25
+ agentSelectors.currentAgentBackgroundColor(s),
26
+ s.activeId,
27
+ agentSelectors.currentAgentModel(s),
28
+ agentSelectors.currentAgentPlugins(s),
29
+ ]);
34
30
 
35
- const [showAgentSettings, toggleConfig] = useSettings(
36
- (s) => [s.showAgentConfig, s.toggleAgentPanel],
37
- shallow,
38
- );
31
+ const [showAgentSettings, toggleConfig] = useSettings((s) => [
32
+ s.showAgentConfig,
33
+ s.toggleAgentPanel,
34
+ ]);
39
35
 
40
36
  return (
41
37
  <ChatHeader
@@ -5,7 +5,6 @@ import { Maximize2Icon } from 'lucide-react';
5
5
  import { memo, useState } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
- import { shallow } from 'zustand/shallow';
9
8
 
10
9
  import { agentSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
11
10
 
@@ -41,10 +40,10 @@ const useStyles = createStyles(({ css, token }) => ({
41
40
  const Inner = memo(() => {
42
41
  const [openModal, setOpenModal] = useState(false);
43
42
  const { styles } = useStyles();
44
- const [systemRole, updateAgentConfig] = useSessionStore(
45
- (s) => [agentSelectors.currentAgentSystemRole(s), s.updateAgentConfig],
46
- shallow,
47
- );
43
+ const [systemRole, updateAgentConfig] = useSessionStore((s) => [
44
+ agentSelectors.currentAgentSystemRole(s),
45
+ s.updateAgentConfig,
46
+ ]);
48
47
 
49
48
  const hydrated = useSessionHydrated();
50
49
  const { t } = useTranslation('common');
@@ -5,7 +5,6 @@ import { MoreVertical, PencilLine, Star, Trash } from 'lucide-react';
5
5
  import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
- import { shallow } from 'zustand/shallow';
9
8
 
10
9
  import { useSessionStore } from '@/store/session';
11
10
 
@@ -33,10 +32,11 @@ interface TopicContentProps {
33
32
  const TopicContent = memo<TopicContentProps>(({ id, title, fav }) => {
34
33
  const { t } = useTranslation('common');
35
34
 
36
- const [editing, dispatchTopic, removeTopic] = useSessionStore(
37
- (s) => [s.renameTopicId === id, s.dispatchTopic, s.removeTopic],
38
- shallow,
39
- );
35
+ const [editing, dispatchTopic, removeTopic] = useSessionStore((s) => [
36
+ s.renameTopicId === id,
37
+ s.dispatchTopic,
38
+ s.removeTopic,
39
+ ]);
40
40
  const { styles, theme } = useStyles();
41
41
 
42
42
  const toggleEditing = (visible?: boolean) => {
@@ -1,7 +1,6 @@
1
1
  import { createStyles } from 'antd-style';
2
2
  import { memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import { useSessionStore } from '@/store/session';
7
6
 
@@ -50,7 +49,7 @@ export interface ConfigCellProps {
50
49
  const TopicItem = memo<ConfigCellProps>(({ title, active, id, fav }) => {
51
50
  const { styles, cx } = useStyles();
52
51
 
53
- const [toggleTopic] = useSessionStore((s) => [s.toggleTopic], shallow);
52
+ const [toggleTopic] = useSessionStore((s) => [s.toggleTopic]);
54
53
 
55
54
  return (
56
55
  <Flexbox
@@ -2,7 +2,6 @@ import { useThemeMode } from 'antd-style';
2
2
  import isEqual from 'fast-deep-equal';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
- import { shallow } from 'zustand/shallow';
6
5
 
7
6
  import Empty from '@/components/Empty';
8
7
  import { topicSelectors, useSessionStore } from '@/store/session';
@@ -13,13 +12,10 @@ import TopicItem from './TopicItem';
13
12
  export const Topic = () => {
14
13
  const topics = useSessionStore(topicSelectors.currentTopics, isEqual);
15
14
  const { isDarkMode } = useThemeMode();
16
- const [activeTopicId] = useSessionStore((s) => [s.activeTopicId], shallow);
15
+ const [activeTopicId] = useSessionStore((s) => [s.activeTopicId]);
17
16
  const { t } = useTranslation('empty');
18
17
 
19
- const [visible, updateGuideState] = useSettings(
20
- (s) => [s.guide?.topic, s.updateGuideState],
21
- shallow,
22
- );
18
+ const [visible, updateGuideState] = useSettings((s) => [s.guide?.topic, s.updateGuideState]);
23
19
 
24
20
  return (
25
21
  <Flexbox gap={2}>
@@ -1,7 +1,6 @@
1
1
  import { DraggablePanel, DraggablePanelContainer } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { rgba } from 'polished';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import HeaderSpacing from '@/components/HeaderSpacing';
7
6
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
@@ -23,10 +22,10 @@ const useStyles = createStyles(({ cx, css, token, stylish }) => ({
23
22
 
24
23
  const Config = () => {
25
24
  const { styles } = useStyles();
26
- const [showAgentSettings, toggleConfig] = useSettings(
27
- (s) => [s.showAgentConfig, s.toggleAgentPanel],
28
- shallow,
29
- );
25
+ const [showAgentSettings, toggleConfig] = useSettings((s) => [
26
+ s.showAgentConfig,
27
+ s.toggleAgentPanel,
28
+ ]);
30
29
 
31
30
  return (
32
31
  <DraggablePanel
@@ -1,4 +1,3 @@
1
- import isEqual from 'fast-deep-equal';
2
1
  import Head from 'next/head';
3
2
  import { memo } from 'react';
4
3
  import { Flexbox } from 'react-layout-kit';
@@ -12,10 +11,10 @@ import Header from './Header';
12
11
  import Config from './Sidebar';
13
12
 
14
13
  const Chat = memo(() => {
15
- const [avatar, title] = useSessionStore(
16
- (s) => [agentSelectors.currentAgentAvatar(s), agentSelectors.currentAgentTitle(s)],
17
- isEqual,
18
- );
14
+ const [avatar, title] = useSessionStore((s) => [
15
+ agentSelectors.currentAgentAvatar(s),
16
+ agentSelectors.currentAgentTitle(s),
17
+ ]);
19
18
 
20
19
  const pageTitle = genSiteHeadTitle([avatar, title].filter(Boolean).join(' '));
21
20
 
@@ -3,7 +3,6 @@ import Head from 'next/head';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
- import { shallow } from 'zustand/shallow';
7
6
 
8
7
  import HeaderSpacing from '@/components/HeaderSpacing';
9
8
  import { HEADER_HEIGHT } from '@/const/layoutTokens';
@@ -18,16 +17,13 @@ const EditPage = memo(() => {
18
17
  const { t } = useTranslation('setting');
19
18
  const config = useSessionStore(agentSelectors.currentAgentConfig, isEqual);
20
19
  const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
21
- const autocomplete = useSessionStore(agentSelectors.currentAutocomplete, shallow);
22
- const [updateAgentConfig, toggleAgentPlugin, updateAgentMeta, title] = useSessionStore(
23
- (s) => [
24
- s.updateAgentConfig,
25
- s.toggleAgentPlugin,
26
- s.updateAgentMeta,
27
- agentSelectors.currentAgentTitle(s),
28
- ],
29
- shallow,
30
- );
20
+ const autocomplete = useSessionStore(agentSelectors.currentAutocomplete);
21
+ const [updateAgentConfig, toggleAgentPlugin, updateAgentMeta, title] = useSessionStore((s) => [
22
+ s.updateAgentConfig,
23
+ s.toggleAgentPlugin,
24
+ s.updateAgentMeta,
25
+ agentSelectors.currentAgentTitle(s),
26
+ ]);
31
27
 
32
28
  const pageTitle = genSiteHeadTitle(t('header.sessionWithName', { name: title }));
33
29
 
@@ -1,7 +1,6 @@
1
1
  import { useRouter } from 'next/router';
2
2
  import { PropsWithChildren, memo, useEffect } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
- import { shallow } from 'zustand/shallow';
5
4
 
6
5
  import SideBar from '@/features/SideBar';
7
6
  import { createI18nNext } from '@/locales/create';
@@ -19,7 +18,7 @@ const ChatLayout = memo<PropsWithChildren>(({ children }) => {
19
18
 
20
19
  const [activeSession, toggleTopic] = useSessionStore((s) => {
21
20
  return [s.activeSession, s.toggleTopic];
22
- }, shallow);
21
+ });
23
22
 
24
23
  const router = useRouter();
25
24
  const { id } = router.query;
@@ -1,6 +1,5 @@
1
1
  import isEqual from 'fast-deep-equal';
2
2
  import { memo } from 'react';
3
- import { shallow } from 'zustand/shallow';
4
3
 
5
4
  import { AgentConfig, AgentMeta, AgentPlugin, AgentPrompt } from '@/features/AgentSetting';
6
5
  import { settingsSelectors, useSettings } from '@/store/settings';
@@ -8,10 +7,11 @@ import { settingsSelectors, useSettings } from '@/store/settings';
8
7
  const Agent = memo(() => {
9
8
  const config = useSettings(settingsSelectors.currentAgentConfig, isEqual);
10
9
  const meta = useSettings(settingsSelectors.currentAgentMeta, isEqual);
11
- const [setAgentConfig, setAgentMeta, toggleAgentPlugin] = useSettings(
12
- (s) => [s.setAgentConfig, s.setAgentMeta, s.toggleAgentPlugin],
13
- shallow,
14
- );
10
+ const [setAgentConfig, setAgentMeta, toggleAgentPlugin] = useSettings((s) => [
11
+ s.setAgentConfig,
12
+ s.setAgentMeta,
13
+ s.toggleAgentPlugin,
14
+ ]);
15
15
  return (
16
16
  <>
17
17
  <AgentPrompt config={config} updateConfig={setAgentConfig} />
@@ -6,7 +6,6 @@ import { AppWindow, Palette, Webhook } from 'lucide-react';
6
6
  import { memo, useCallback, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import SliderWithInput from 'src/components/SliderWithInput';
9
- import { shallow } from 'zustand/shallow';
10
9
 
11
10
  import { FORM_STYLE } from '@/const/layoutTokens';
12
11
  import { DEFAULT_SETTINGS } from '@/const/settings';
@@ -29,14 +28,11 @@ const Common = memo(() => {
29
28
  const [form] = AntForm.useForm();
30
29
  const clearSessions = useSessionStore((s) => s.clearSessions);
31
30
  const settings = useSettings(settingsSelectors.currentSettings, isEqual);
32
- const { setThemeMode, setSettings, resetSettings } = useSettings(
33
- (s) => ({
34
- resetSettings: s.resetSettings,
35
- setSettings: s.setSettings,
36
- setThemeMode: s.setThemeMode,
37
- }),
38
- shallow,
39
- );
31
+ const [setThemeMode, setSettings, resetSettings] = useSettings((s) => [
32
+ s.setThemeMode,
33
+ s.setSettings,
34
+ s.resetSettings,
35
+ ]);
40
36
 
41
37
  const { message, modal } = App.useApp();
42
38
  const handleReset = useCallback(() => {
@@ -6,15 +6,11 @@ import {
6
6
  neutralColorsSwatches,
7
7
  } from '@lobehub/ui';
8
8
  import { memo } from 'react';
9
- import { shallow } from 'zustand/shallow';
10
9
 
11
10
  import { useSettings } from '@/store/settings';
12
11
 
13
12
  const ThemeSwatchesNeutral = memo(() => {
14
- const { neutralColor, setSettings } = useSettings(
15
- (s) => ({ neutralColor: s.settings.neutralColor, setSettings: s.setSettings }),
16
- shallow,
17
- );
13
+ const [neutralColor, setSettings] = useSettings((s) => [s.settings.neutralColor, s.setSettings]);
18
14
 
19
15
  const handleSelect = (v: any) => {
20
16
  const name = findCustomThemeName('neutral', v) as NeutralColors;
@@ -6,15 +6,11 @@ import {
6
6
  primaryColorsSwatches,
7
7
  } from '@lobehub/ui';
8
8
  import { memo } from 'react';
9
- import { shallow } from 'zustand/shallow';
10
9
 
11
10
  import { useSettings } from '@/store/settings';
12
11
 
13
12
  const ThemeSwatchesPrimary = memo(() => {
14
- const { primaryColor, setSettings } = useSettings(
15
- (s) => ({ primaryColor: s.settings.primaryColor, setSettings: s.setSettings }),
16
- shallow,
17
- );
13
+ const [primaryColor, setSettings] = useSettings((s) => [s.settings.primaryColor, s.setSettings]);
18
14
 
19
15
  const handleSelect = (v: any) => {
20
16
  const name = findCustomThemeName('primary', v) as PrimaryColors;
@@ -1,5 +1,5 @@
1
- import equal from 'fast-deep-equal';
2
1
  import { PersistOptions, devtools, persist } from 'zustand/middleware';
2
+ import { shallow } from 'zustand/shallow';
3
3
  import { createWithEqualityFn } from 'zustand/traditional';
4
4
  import { StateCreator } from 'zustand/vanilla';
5
5
 
@@ -10,6 +10,8 @@ import { AgentAction, createAgentSlice } from './slices/agentConfig';
10
10
  import { ChatAction, createChatSlice } from './slices/chat';
11
11
  import { SessionAction, createSessionSlice } from './slices/session';
12
12
 
13
+ // =============== 聚合 createStoreFn ============ //
14
+
13
15
  export type SessionStore = SessionAction & AgentAction & ChatAction & SessionStoreState;
14
16
 
15
17
  const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (...parameters) => ({
@@ -19,6 +21,8 @@ const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (
19
21
  ...createChatSlice(...parameters),
20
22
  });
21
23
 
24
+ // =============== persist 本地缓存中间件配置 ============ //
25
+
22
26
  type SessionPersist = Pick<SessionStore, 'sessions'>;
23
27
 
24
28
  const LOBE_CHAT = 'LOBE_CHAT';
@@ -36,6 +40,8 @@ const persistOptions: PersistOptions<SessionStore, SessionPersist> = {
36
40
  // version: Migration.targetVersion,
37
41
  };
38
42
 
43
+ // =============== 实装 useStore ============ //
44
+
39
45
  export const useSessionStore = createWithEqualityFn<SessionStore>()(
40
46
  persist(
41
47
  devtools(createStore, {
@@ -43,5 +49,5 @@ export const useSessionStore = createWithEqualityFn<SessionStore>()(
43
49
  }),
44
50
  persistOptions,
45
51
  ),
46
- equal,
52
+ shallow,
47
53
  );
@@ -0,0 +1,20 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { useSettings } from '../store';
4
+
5
+ export const useSettingsHydrated = () => {
6
+ // 根据 sessions 是否有值来判断是否已经初始化
7
+ const hasInited = !!Object.values(useSettings.getState().settings).length;
8
+
9
+ const [isInit, setInit] = useState(hasInited);
10
+
11
+ useEffect(() => {
12
+ const hasRehydrated = useSettings.persist.hasHydrated();
13
+
14
+ if (hasRehydrated && !isInit) {
15
+ setInit(true);
16
+ }
17
+ }, []);
18
+
19
+ return isInit;
20
+ };
@@ -1,27 +1,3 @@
1
- import equal from 'fast-deep-equal';
2
- import { type PersistOptions, devtools, persist } from 'zustand/middleware';
3
- import { createWithEqualityFn } from 'zustand/traditional';
4
-
5
- import { isDev } from '@/utils/env';
6
-
7
- import { type SettingsStore, createStore } from './store';
8
-
9
- export const LOBE_SETTINGS = 'LOBE_SETTINGS';
10
-
11
- const persistOptions: PersistOptions<SettingsStore> = {
12
- name: LOBE_SETTINGS,
13
- skipHydration: true,
14
- };
15
-
16
- export const useSettings = createWithEqualityFn<SettingsStore>()(
17
- persist(
18
- devtools(createStore, {
19
- name: LOBE_SETTINGS + (isDev ? '_DEV' : ''),
20
- }),
21
- persistOptions,
22
- ),
23
- equal,
24
- );
25
-
26
1
  export * from './selectors';
27
2
  export type { SettingsStore } from './store';
3
+ export { useSettings } from './store';
@@ -1,15 +1,43 @@
1
+ import { PersistOptions, devtools, persist } from 'zustand/middleware';
2
+ import { shallow } from 'zustand/shallow';
3
+ import { createWithEqualityFn } from 'zustand/traditional';
1
4
  import { StateCreator } from 'zustand/vanilla';
2
5
 
6
+ import { isDev } from '@/utils/env';
7
+
3
8
  import { type AppSettingsState, initialState } from './initialState';
4
9
  import { type AgentAction, createAgentSlice } from './slices/agent';
5
10
  import { type CommonAction, createCommonSlice } from './slices/common';
6
11
 
12
+ // =============== 聚合 createStoreFn ============ //
13
+
7
14
  export type SettingsStore = CommonAction & AppSettingsState & AgentAction;
8
15
 
9
- export const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
16
+ const createStore: StateCreator<SettingsStore, [['zustand/devtools', never]]> = (
10
17
  ...parameters
11
18
  ) => ({
12
19
  ...initialState,
13
20
  ...createCommonSlice(...parameters),
14
21
  ...createAgentSlice(...parameters),
15
22
  });
23
+
24
+ // =============== persist 本地缓存中间件配置 ============ //
25
+
26
+ const LOBE_SETTINGS = 'LOBE_SETTINGS';
27
+
28
+ const persistOptions: PersistOptions<SettingsStore> = {
29
+ name: LOBE_SETTINGS,
30
+ skipHydration: true,
31
+ };
32
+
33
+ // =============== 实装 useStore ============ //
34
+
35
+ export const useSettings = createWithEqualityFn<SettingsStore>()(
36
+ persist(
37
+ devtools(createStore, {
38
+ name: LOBE_SETTINGS + (isDev ? '_DEV' : ''),
39
+ }),
40
+ persistOptions,
41
+ ),
42
+ shallow,
43
+ );