@lobehub/chat 0.40.4 → 0.40.6
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 +58 -0
- package/package.json +1 -1
- package/src/pages/chat/SessionList/List/SkeletonItem.tsx +47 -0
- package/src/pages/chat/SessionList/List/index.tsx +7 -2
- package/src/pages/chat/[id]/Conversation/ChatList/index.tsx +8 -1
- package/src/pages/chat/[id]/Header/index.tsx +40 -26
- package/src/pages/chat/[id]/Sidebar/Inner.tsx +30 -25
- package/src/store/session/hooks/useSessionHydrated.ts +17 -0
- package/src/store/session/index.ts +2 -35
- package/src/store/session/store.ts +33 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.40.6](https://github.com/lobehub/lobe-chat/compare/v0.40.5...v0.40.6)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-08-10**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: 重构优化 hydrated 的判断逻辑.
|
|
12
|
+
|
|
13
|
+
#### 💄 Styles
|
|
14
|
+
|
|
15
|
+
- **misc**: 优化水合前的加载效果.
|
|
16
|
+
|
|
17
|
+
<br/>
|
|
18
|
+
|
|
19
|
+
<details>
|
|
20
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
21
|
+
|
|
22
|
+
#### Code refactoring
|
|
23
|
+
|
|
24
|
+
- **misc**: 重构优化 hydrated 的判断逻辑 ([1781119](https://github.com/lobehub/lobe-chat/commit/1781119))
|
|
25
|
+
|
|
26
|
+
#### Styles
|
|
27
|
+
|
|
28
|
+
- **misc**: 优化水合前的加载效果 ([6bbd978](https://github.com/lobehub/lobe-chat/commit/6bbd978))
|
|
29
|
+
|
|
30
|
+
</details>
|
|
31
|
+
|
|
32
|
+
<div align="right">
|
|
33
|
+
|
|
34
|
+
[](#readme-top)
|
|
35
|
+
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
### [Version 0.40.5](https://github.com/lobehub/lobe-chat/compare/v0.40.4...v0.40.5)
|
|
39
|
+
|
|
40
|
+
<sup>Released on **2023-08-10**</sup>
|
|
41
|
+
|
|
42
|
+
#### 💄 Styles
|
|
43
|
+
|
|
44
|
+
- **misc**: 增加未初始化的 loading 态.
|
|
45
|
+
|
|
46
|
+
<br/>
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
50
|
+
|
|
51
|
+
#### Styles
|
|
52
|
+
|
|
53
|
+
- **misc**: 增加未初始化的 loading 态 ([dcb7c07](https://github.com/lobehub/lobe-chat/commit/dcb7c07))
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
57
|
+
<div align="right">
|
|
58
|
+
|
|
59
|
+
[](#readme-top)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
5
63
|
### [Version 0.40.4](https://github.com/lobehub/lobe-chat/compare/v0.40.3...v0.40.4)
|
|
6
64
|
|
|
7
65
|
<sup>Released on **2023-08-10**</sup>
|
package/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Skeleton } from 'antd';
|
|
2
|
+
import { createStyles } from 'antd-style';
|
|
3
|
+
import { Flexbox } from 'react-layout-kit';
|
|
4
|
+
|
|
5
|
+
const useStyles = createStyles(({ css }) => ({
|
|
6
|
+
avatar: css``,
|
|
7
|
+
paragraph: css`
|
|
8
|
+
height: 12px !important;
|
|
9
|
+
margin-top: 12px !important;
|
|
10
|
+
|
|
11
|
+
> li {
|
|
12
|
+
height: 12px !important;
|
|
13
|
+
}
|
|
14
|
+
`,
|
|
15
|
+
title: css`
|
|
16
|
+
height: 14px !important;
|
|
17
|
+
margin-top: 4px !important;
|
|
18
|
+
margin-bottom: 12px !important;
|
|
19
|
+
|
|
20
|
+
> li {
|
|
21
|
+
height: 14px !important;
|
|
22
|
+
}
|
|
23
|
+
`,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// 从 3~10 随机取一个整数
|
|
27
|
+
|
|
28
|
+
const SkeletonItem = () => {
|
|
29
|
+
const { styles } = useStyles();
|
|
30
|
+
|
|
31
|
+
const list = Array.from({ length: 4 }).fill('');
|
|
32
|
+
return (
|
|
33
|
+
<Flexbox gap={8} paddingInline={16}>
|
|
34
|
+
{list.map((_, index) => (
|
|
35
|
+
<Skeleton
|
|
36
|
+
active
|
|
37
|
+
avatar
|
|
38
|
+
key={index}
|
|
39
|
+
paragraph={{ className: styles.paragraph, rows: 1 }}
|
|
40
|
+
round
|
|
41
|
+
title={{ className: styles.title }}
|
|
42
|
+
/>
|
|
43
|
+
))}
|
|
44
|
+
</Flexbox>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
export default SkeletonItem;
|
|
@@ -3,9 +3,10 @@ import Link from 'next/link';
|
|
|
3
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
|
+
import SkeletonItem from './SkeletonItem';
|
|
9
10
|
|
|
10
11
|
const SessionList = memo(() => {
|
|
11
12
|
const list = useSessionStore((s) => sessionSelectors.sessionList(s), isEqual);
|
|
@@ -14,7 +15,11 @@ const SessionList = memo(() => {
|
|
|
14
15
|
shallow,
|
|
15
16
|
);
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
const isInit = useSessionHydrated();
|
|
19
|
+
|
|
20
|
+
return !isInit ? (
|
|
21
|
+
<SkeletonItem />
|
|
22
|
+
) : (
|
|
18
23
|
<>
|
|
19
24
|
{list.map(({ id }) => (
|
|
20
25
|
<Link href={`/chat/${id}`} key={id}>
|
|
@@ -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 {
|
|
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
|
-
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,24 +61,30 @@ const Inner = memo(() => {
|
|
|
62
61
|
}
|
|
63
62
|
title={t('settingAgent.prompt.title', { ns: 'setting' })}
|
|
64
63
|
/>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
89
|
<Topic />
|
|
85
90
|
</Flexbox>
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
);
|