@lobehub/chat 0.40.5 → 0.40.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.40.7](https://github.com/lobehub/lobe-chat/compare/v0.40.6...v0.40.7)
6
+
7
+ <sup>Released on **2023-08-10**</sup>
8
+
9
+ #### 💄 Styles
10
+
11
+ - **misc**: 优化 Topic 的水合加载效果.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Styles
19
+
20
+ - **misc**: 优化 Topic 的水合加载效果 ([0cd0088](https://github.com/lobehub/lobe-chat/commit/0cd0088))
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.40.6](https://github.com/lobehub/lobe-chat/compare/v0.40.5...v0.40.6)
31
+
32
+ <sup>Released on **2023-08-10**</sup>
33
+
34
+ #### ♻ Code Refactoring
35
+
36
+ - **misc**: 重构优化 hydrated 的判断逻辑.
37
+
38
+ #### 💄 Styles
39
+
40
+ - **misc**: 优化水合前的加载效果.
41
+
42
+ <br/>
43
+
44
+ <details>
45
+ <summary><kbd>Improvements and Fixes</kbd></summary>
46
+
47
+ #### Code refactoring
48
+
49
+ - **misc**: 重构优化 hydrated 的判断逻辑 ([1781119](https://github.com/lobehub/lobe-chat/commit/1781119))
50
+
51
+ #### Styles
52
+
53
+ - **misc**: 优化水合前的加载效果 ([6bbd978](https://github.com/lobehub/lobe-chat/commit/6bbd978))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ### [Version 0.40.5](https://github.com/lobehub/lobe-chat/compare/v0.40.4...v0.40.5)
6
64
 
7
65
  <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.40.5",
3
+ "version": "0.40.7",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -1,28 +1,21 @@
1
1
  import isEqual from 'fast-deep-equal';
2
2
  import Link from 'next/link';
3
- import { memo, useEffect, useState } from 'react';
3
+ import { memo } from 'react';
4
4
  import { shallow } from 'zustand/shallow';
5
5
 
6
- import { sessionSelectors, useSessionStore } from '@/store/session';
6
+ import { sessionSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
7
7
 
8
8
  import SessionItem from './SessionItem';
9
9
  import SkeletonItem from './SkeletonItem';
10
10
 
11
11
  const SessionList = memo(() => {
12
- const [isInit, setInit] = useState(false);
13
12
  const list = useSessionStore((s) => sessionSelectors.sessionList(s), isEqual);
14
13
  const [activeId, loading] = useSessionStore(
15
14
  (s) => [s.activeId, s.autocompleteLoading.title],
16
15
  shallow,
17
16
  );
18
17
 
19
- useEffect(() => {
20
- const hasRehydrated = useSessionStore.persist.hasHydrated();
21
-
22
- if (hasRehydrated) {
23
- setInit(true);
24
- }
25
- }, []);
18
+ const isInit = useSessionHydrated();
26
19
 
27
20
  return !isInit ? (
28
21
  <SkeletonItem />
@@ -5,7 +5,12 @@ import { useTranslation } from 'react-i18next';
5
5
  import { Flexbox } from 'react-layout-kit';
6
6
  import { shallow } from 'zustand/shallow';
7
7
 
8
- import { agentSelectors, chatSelectors, useSessionStore } from '@/store/session';
8
+ import {
9
+ agentSelectors,
10
+ chatSelectors,
11
+ useSessionHydrated,
12
+ useSessionStore,
13
+ } from '@/store/session';
9
14
  import { ChatMessage } from '@/types/chatMessage';
10
15
  import { ErrorType } from '@/types/fetch';
11
16
  import { isFunctionMessage } from '@/utils/message';
@@ -29,7 +34,9 @@ const renderErrorMessage: RenderErrorMessage = (error, message) => {
29
34
 
30
35
  const List = () => {
31
36
  const { t } = useTranslation('common');
37
+ const hydrated = useSessionHydrated();
32
38
  const data = useSessionStore(chatSelectors.currentChats, isEqual);
39
+ console.log(hydrated, data);
33
40
  const [displayMode, chatLoadingId, deleteMessage, resendMessage, dispatchMessage] =
34
41
  useSessionStore(
35
42
  (s) => [
@@ -1,5 +1,6 @@
1
1
  import { SiOpenai } from '@icons-pack/react-simple-icons';
2
2
  import { ActionIcon, Avatar, ChatHeader } from '@lobehub/ui';
3
+ import { Skeleton } from 'antd';
3
4
  import { PanelRightClose, PanelRightOpen, Settings } from 'lucide-react';
4
5
  import Router from 'next/router';
5
6
  import { memo } from 'react';
@@ -9,12 +10,13 @@ import { shallow } from 'zustand/shallow';
9
10
 
10
11
  import HeaderTitle from '@/components/HeaderTitle';
11
12
  import Tag from '@/components/Tag';
12
- import { agentSelectors, useSessionStore } from '@/store/session';
13
+ import { agentSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
13
14
  import { useSettings } from '@/store/settings';
14
15
 
15
16
  import PluginTag from './PluginTag';
16
17
 
17
18
  const Header = memo(() => {
19
+ const init = useSessionHydrated();
18
20
  const { t } = useTranslation('common');
19
21
 
20
22
  const [title, description, avatar, backgroundColor, id, model, plugins] = useSessionStore(
@@ -38,31 +40,43 @@ const Header = memo(() => {
38
40
  return (
39
41
  <ChatHeader
40
42
  left={
41
- <Flexbox align={'flex-start'} horizontal>
42
- <Avatar
43
- avatar={avatar}
44
- background={backgroundColor}
45
- onClick={() => {
46
- Router.push(`/chat/${id}/setting`);
47
- }}
48
- size={40}
49
- style={{ cursor: 'pointer', flex: 'none' }}
50
- title={title}
51
- />
52
- <HeaderTitle
53
- desc={description}
54
- tag={
55
- <>
56
- <Tag>
57
- <SiOpenai size={'11px'} style={{ marginTop: 3 }} />
58
- {model}
59
- </Tag>
60
- {plugins?.length > 0 && <PluginTag plugins={plugins} />}
61
- </>
62
- }
63
- title={title}
64
- />
65
- </Flexbox>
43
+ !init ? (
44
+ <Flexbox horizontal>
45
+ <Skeleton
46
+ active
47
+ avatar={{ shape: 'circle', size: 'default' }}
48
+ paragraph={false}
49
+ round
50
+ title={{ style: { margin: 0, marginTop: 8 }, width: 200 }}
51
+ />
52
+ </Flexbox>
53
+ ) : (
54
+ <Flexbox align={'flex-start'} gap={12} horizontal>
55
+ <Avatar
56
+ avatar={avatar}
57
+ background={backgroundColor}
58
+ onClick={() => {
59
+ Router.push(`/chat/${id}/setting`);
60
+ }}
61
+ size={40}
62
+ style={{ cursor: 'pointer', flex: 'none' }}
63
+ title={title}
64
+ />
65
+ <HeaderTitle
66
+ desc={description}
67
+ tag={
68
+ <>
69
+ <Tag>
70
+ <SiOpenai size={'11px'} style={{ marginTop: 3 }} />
71
+ {model}
72
+ </Tag>
73
+ {plugins?.length > 0 && <PluginTag plugins={plugins} />}
74
+ </>
75
+ }
76
+ title={title}
77
+ />
78
+ </Flexbox>
79
+ )
66
80
  }
67
81
  right={
68
82
  id && (
@@ -1,4 +1,5 @@
1
1
  import { ActionIcon, DraggablePanelBody, EditableMessage, SearchBar } from '@lobehub/ui';
2
+ import { Skeleton } from 'antd';
2
3
  import { createStyles } from 'antd-style';
3
4
  import { Maximize2Icon } from 'lucide-react';
4
5
  import { memo, useState } from 'react';
@@ -6,7 +7,7 @@ import { useTranslation } from 'react-i18next';
6
7
  import { Flexbox } from 'react-layout-kit';
7
8
  import { shallow } from 'zustand/shallow';
8
9
 
9
- import { agentSelectors, useSessionStore } from '@/store/session';
10
+ import { agentSelectors, useSessionHydrated, useSessionStore } from '@/store/session';
10
11
 
11
12
  import Header from './Header';
12
13
  import { Topic } from './Topic';
@@ -21,13 +22,7 @@ const useStyles = createStyles(({ css, token }) => ({
21
22
  prompt: css`
22
23
  overflow-x: hidden;
23
24
  overflow-y: auto;
24
-
25
- height: 200px;
26
- padding: 0 16px 16px;
27
-
28
25
  opacity: 0.75;
29
- border-bottom: 1px solid ${token.colorBorder};
30
-
31
26
  transition: opacity 200ms ${token.motionEaseOut};
32
27
 
33
28
  &:hover {
@@ -38,6 +33,9 @@ const useStyles = createStyles(({ css, token }) => ({
38
33
  font-size: ${token.fontSizeHeading4}px;
39
34
  font-weight: bold;
40
35
  `,
36
+ topic: css`
37
+ border-top: 1px solid ${token.colorBorder};
38
+ `,
41
39
  }));
42
40
 
43
41
  const Inner = memo(() => {
@@ -48,6 +46,7 @@ const Inner = memo(() => {
48
46
  shallow,
49
47
  );
50
48
 
49
+ const hydrated = useSessionHydrated();
51
50
  const { t } = useTranslation('common');
52
51
  return (
53
52
  <DraggablePanelBody style={{ padding: 0 }}>
@@ -62,26 +61,47 @@ const Inner = memo(() => {
62
61
  }
63
62
  title={t('settingAgent.prompt.title', { ns: 'setting' })}
64
63
  />
65
- <EditableMessage
66
- classNames={{ markdown: styles.prompt }}
67
- onChange={(e) => {
68
- updateAgentConfig({ systemRole: e });
69
- }}
70
- onOpenChange={setOpenModal}
71
- openModal={openModal}
72
- placeholder={`${t('settingAgent.prompt.placeholder', { ns: 'setting' })}...`}
73
- styles={{ markdown: systemRole ? {} : { opacity: 0.5 } }}
74
- text={{
75
- cancel: t('cancel'),
76
- confirm: t('ok'),
77
- edit: t('edit'),
78
- title: t('settingAgent.prompt.title', { ns: 'setting' }),
79
- }}
80
- value={systemRole}
81
- />
82
- <Flexbox gap={16} style={{ padding: 16 }}>
64
+ <Flexbox height={200} padding={'0 16px 16px'}>
65
+ {hydrated ? (
66
+ <EditableMessage
67
+ classNames={{ markdown: styles.prompt }}
68
+ onChange={(e) => {
69
+ updateAgentConfig({ systemRole: e });
70
+ }}
71
+ onOpenChange={setOpenModal}
72
+ openModal={openModal}
73
+ placeholder={`${t('settingAgent.prompt.placeholder', { ns: 'setting' })}...`}
74
+ styles={{ markdown: systemRole ? {} : { opacity: 0.5 } }}
75
+ text={{
76
+ cancel: t('cancel'),
77
+ confirm: t('ok'),
78
+ edit: t('edit'),
79
+ title: t('settingAgent.prompt.title', { ns: 'setting' }),
80
+ }}
81
+ value={systemRole}
82
+ />
83
+ ) : (
84
+ <Skeleton active avatar={false} style={{ marginTop: 12 }} title={false} />
85
+ )}
86
+ </Flexbox>
87
+ <Flexbox className={styles.topic} gap={16} padding={16}>
83
88
  <SearchBar placeholder={t('topic.searchPlaceholder')} spotlight type={'ghost'} />
84
- <Topic />
89
+ {!hydrated ? (
90
+ <Flexbox gap={8} style={{ marginTop: 12 }}>
91
+ {Array.from({ length: 8 }).map((_, i) => (
92
+ <Skeleton
93
+ active
94
+ avatar={false}
95
+ key={i}
96
+ paragraph={{ rows: 1, width: '100%' }}
97
+ round
98
+ title={false}
99
+ />
100
+ ))}
101
+ </Flexbox>
102
+ ) : (
103
+ <Topic />
104
+ )}
85
105
  </Flexbox>
86
106
  </DraggablePanelBody>
87
107
  );
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { useSessionStore } from '@/store/session';
4
+
5
+ export const useSessionHydrated = () => {
6
+ const [isInit, setInit] = useState(false);
7
+
8
+ useEffect(() => {
9
+ const hasRehydrated = useSessionStore.persist.hasHydrated();
10
+
11
+ if (hasRehydrated) {
12
+ setInit(true);
13
+ }
14
+ }, []);
15
+
16
+ return isInit;
17
+ };
@@ -1,39 +1,6 @@
1
- import equal from 'fast-deep-equal';
2
- import { PersistOptions, devtools, persist } from 'zustand/middleware';
3
- import { createWithEqualityFn } from 'zustand/traditional';
4
-
5
- import { isDev } from '@/utils/env';
6
-
7
- import { SessionStore, createStore } from './store';
8
-
9
- type SessionPersist = Pick<SessionStore, 'sessions'>;
10
-
11
- export const LOBE_CHAT = 'LOBE_CHAT';
12
-
13
- const persistOptions: PersistOptions<SessionStore, SessionPersist> = {
14
- name: LOBE_CHAT,
15
-
16
- partialize: (s) => ({
17
- sessions: s.sessions,
18
- }),
19
-
20
- // 手动控制 Hydration ,避免 ssr 报错
21
- skipHydration: true,
22
- version: 0,
23
- // version: Migration.targetVersion,
24
- };
25
-
26
- export const useSessionStore = createWithEqualityFn<SessionStore>()(
27
- persist(
28
- devtools(createStore, {
29
- name: LOBE_CHAT + (isDev ? '_DEV' : ''),
30
- }),
31
- persistOptions,
32
- ),
33
- equal,
34
- );
35
-
1
+ export * from './hooks/useSessionHydrated';
36
2
  export * from './selectors';
37
3
  export { initialLobeAgentConfig } from './slices/agentConfig';
38
4
  export { initLobeSession } from './slices/session';
39
5
  export type { SessionStore } from './store';
6
+ export { useSessionStore } from './store';
@@ -1,5 +1,10 @@
1
+ import equal from 'fast-deep-equal';
2
+ import { PersistOptions, devtools, persist } from 'zustand/middleware';
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 { SessionStoreState, initialState } from './initialState';
4
9
  import { AgentAction, createAgentSlice } from './slices/agentConfig';
5
10
  import { ChatAction, createChatSlice } from './slices/chat';
@@ -7,11 +12,36 @@ import { SessionAction, createSessionSlice } from './slices/session';
7
12
 
8
13
  export type SessionStore = SessionAction & AgentAction & ChatAction & SessionStoreState;
9
14
 
10
- export const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (
11
- ...parameters
12
- ) => ({
15
+ const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (...parameters) => ({
13
16
  ...initialState,
14
17
  ...createAgentSlice(...parameters),
15
18
  ...createSessionSlice(...parameters),
16
19
  ...createChatSlice(...parameters),
17
20
  });
21
+
22
+ type SessionPersist = Pick<SessionStore, 'sessions'>;
23
+
24
+ const LOBE_CHAT = 'LOBE_CHAT';
25
+
26
+ const persistOptions: PersistOptions<SessionStore, SessionPersist> = {
27
+ name: LOBE_CHAT,
28
+
29
+ partialize: (s) => ({
30
+ sessions: s.sessions,
31
+ }),
32
+
33
+ // 手动控制 Hydration ,避免 ssr 报错
34
+ skipHydration: true,
35
+ version: 0,
36
+ // version: Migration.targetVersion,
37
+ };
38
+
39
+ export const useSessionStore = createWithEqualityFn<SessionStore>()(
40
+ persist(
41
+ devtools(createStore, {
42
+ name: LOBE_CHAT + (isDev ? '_DEV' : ''),
43
+ }),
44
+ persistOptions,
45
+ ),
46
+ equal,
47
+ );