@lobehub/chat 0.69.1 → 0.70.1
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/.github/workflows/contributor-help.yml +2 -4
- package/CHANGELOG.md +50 -0
- package/package.json +1 -1
- package/src/const/url.ts +3 -0
- package/src/features/MobileTabBar/index.tsx +3 -1
- package/src/features/SideBar/TopActions.tsx +4 -3
- package/src/helpers/export.ts +2 -2
- package/src/pages/chat/features/Header/Mobile.tsx +6 -8
- package/src/pages/chat/features/Header/PC.tsx +15 -16
- package/src/pages/chat/features/Header/index.tsx +3 -3
- package/src/pages/chat/{SessionList → features/SessionList}/Inbox.tsx +9 -2
- package/src/pages/chat/{SessionList → features/SessionList}/List/Item/index.tsx +2 -2
- package/src/pages/chat/{SessionList → features/SessionList}/List/index.tsx +10 -1
- package/src/pages/chat/features/Sidebar/index.tsx +6 -2
- package/src/pages/chat/index.page.tsx +9 -11
- package/src/pages/chat/layout.tsx +6 -1
- package/src/pages/chat/{[id] → mobile}/index.page.tsx +7 -17
- package/src/pages/chat/mobile.tsx +2 -2
- package/src/pages/chat/{[id]/setting → setting}/Header.tsx +3 -4
- package/src/pages/index.page.tsx +10 -4
- package/src/pages/settings/Header.tsx +4 -2
- package/src/pages/settings/Settings/Agent.tsx +3 -3
- package/src/pages/settings/Settings/Common.tsx +2 -2
- package/src/pages/welcome/Banner/index.tsx +11 -3
- package/src/store/global/selectors.ts +9 -11
- package/src/store/middleware/createHyperStorage/index.ts +95 -0
- package/src/store/middleware/createHyperStorage/keyMapper.ts +53 -0
- package/src/store/middleware/createHyperStorage/type.ts +14 -0
- package/src/store/middleware/createHyperStorage/urlSearch.ts +17 -0
- package/src/store/session/slices/chat/selectors/token.ts +8 -9
- package/src/store/session/slices/session/action.ts +18 -9
- package/src/store/session/slices/session/initialState.ts +2 -3
- package/src/store/session/slices/session/selectors/index.ts +6 -0
- package/src/store/session/store.ts +16 -8
- package/src/pages/chat/[id]/layout.tsx +0 -34
- package/src/pages/chat/[id]/mobile.tsx +0 -31
- package/src/store/middleware/createHashStorage.ts +0 -49
- /package/src/pages/chat/{SessionList → features/SessionList}/CollapseGroup.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/Header.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/AddButton.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/Item/Actions.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/List/SkeletonList.tsx +0 -0
- /package/src/pages/chat/{SessionList → features/SessionList}/index.tsx +0 -0
- /package/src/pages/chat/{[id]/setting → setting}/index.page.tsx +0 -0
- /package/src/pages/chat/{[id]/setting → setting}/mobile.tsx +0 -0
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
name: Contributor Helper
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
# 🌏 Think about the planet! No need to update stats too frequently
|
|
5
4
|
schedule: [{ cron: '0 18 * * *' }]
|
|
6
|
-
# 💡 The following line lets you run workflow manually from the action tab!
|
|
7
5
|
workflow_dispatch:
|
|
8
6
|
jobs:
|
|
9
7
|
contributor:
|
|
@@ -21,8 +19,8 @@ jobs:
|
|
|
21
19
|
- name: Commit and push if changed
|
|
22
20
|
run: |-
|
|
23
21
|
git diff
|
|
24
|
-
git config --global user.
|
|
25
|
-
git config --global user.
|
|
22
|
+
git config --global user.name "lobehubbot"
|
|
23
|
+
git config --global user.email "i@lobehub.com"
|
|
26
24
|
git pull
|
|
27
25
|
git add -A
|
|
28
26
|
git commit -m "🤖 docs: Auto update contributors" || exit 0
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
### [Version 0.70.1](https://github.com/lobehub/lobe-chat/compare/v0.70.0...v0.70.1)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2023-09-08**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Refactor settingsSelectors to globalSelectors.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Refactor settingsSelectors to globalSelectors ([38917e8](https://github.com/lobehub/lobe-chat/commit/38917e8))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 0.70.0](https://github.com/lobehub/lobe-chat/compare/v0.69.1...v0.70.0)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2023-09-08**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **misc**: Refactor to url state.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### What's improved
|
|
44
|
+
|
|
45
|
+
- **misc**: Refactor to url state, closes [#157](https://github.com/lobehub/lobe-chat/issues/157) ([2efac2b](https://github.com/lobehub/lobe-chat/commit/2efac2b))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
### [Version 0.69.1](https://github.com/lobehub/lobe-chat/compare/v0.69.0...v0.69.1)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2023-09-06**</sup>
|
package/package.json
CHANGED
package/src/const/url.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pkg from '../../package.json';
|
|
2
|
+
import { INBOX_SESSION_ID } from './session';
|
|
2
3
|
|
|
3
4
|
export const GITHUB = pkg.homepage;
|
|
4
5
|
export const CHANGELOG = `${pkg.homepage}/blob/master/CHANGELOG.md`;
|
|
@@ -8,3 +9,5 @@ export const DISCORD = 'https://discord.gg/AYFPHvv2jT';
|
|
|
8
9
|
|
|
9
10
|
export const PLUGINS_INDEX_URL =
|
|
10
11
|
process.env.PLUGINS_INDEX_URL ?? 'https://chat-plugins.lobehub.com';
|
|
12
|
+
|
|
13
|
+
export const SESSION_CHAT_URL = (id: string = INBOX_SESSION_ID) => `/chat#session=${id}`;
|
|
@@ -7,6 +7,7 @@ import { memo, useMemo } from 'react';
|
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
import { useGlobalStore } from '@/store/global';
|
|
10
|
+
import { useSessionStore } from '@/store/session';
|
|
10
11
|
|
|
11
12
|
const useStyles = createStyles(({ css, token }) => ({
|
|
12
13
|
active: css`
|
|
@@ -20,6 +21,7 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
20
21
|
const [tab, setTab] = useGlobalStore((s) => [s.sidebarKey, s.switchSideBar]);
|
|
21
22
|
const { t } = useTranslation('common');
|
|
22
23
|
const { styles } = useStyles();
|
|
24
|
+
const switchSession = useSessionStore((s) => s.switchSession);
|
|
23
25
|
const items: MobileTabBarProps['items'] = useMemo(
|
|
24
26
|
() => [
|
|
25
27
|
{
|
|
@@ -28,7 +30,7 @@ export default memo<{ className?: string }>(({ className }) => {
|
|
|
28
30
|
),
|
|
29
31
|
key: 'chat',
|
|
30
32
|
onClick: () => {
|
|
31
|
-
|
|
33
|
+
switchSession();
|
|
32
34
|
},
|
|
33
35
|
title: t('tab.chat'),
|
|
34
36
|
},
|
|
@@ -5,6 +5,7 @@ import { memo } from 'react';
|
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
7
|
import { GlobalStore } from '@/store/global';
|
|
8
|
+
import { useSessionStore } from '@/store/session';
|
|
8
9
|
|
|
9
10
|
export interface TopActionProps {
|
|
10
11
|
setTab: GlobalStore['switchSideBar'];
|
|
@@ -13,7 +14,7 @@ export interface TopActionProps {
|
|
|
13
14
|
|
|
14
15
|
const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|
15
16
|
const { t } = useTranslation('common');
|
|
16
|
-
|
|
17
|
+
const switchBackToChat = useSessionStore((s) => s.switchBackToChat);
|
|
17
18
|
return (
|
|
18
19
|
<>
|
|
19
20
|
<ActionIcon
|
|
@@ -22,7 +23,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|
|
22
23
|
onClick={() => {
|
|
23
24
|
// 如果已经在 chat 路径下了,那么就不用再跳转了
|
|
24
25
|
if (Router.asPath.startsWith('/chat')) return;
|
|
25
|
-
|
|
26
|
+
switchBackToChat();
|
|
26
27
|
setTab('chat');
|
|
27
28
|
}}
|
|
28
29
|
size="large"
|
|
@@ -31,7 +32,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|
|
31
32
|
<ActionIcon
|
|
32
33
|
active={tab === 'market'}
|
|
33
34
|
icon={Sticker}
|
|
34
|
-
onClick={() => setTab('market')}
|
|
35
|
+
// onClick={() => setTab('market')}
|
|
35
36
|
size="large"
|
|
36
37
|
title={t('tab.market')}
|
|
37
38
|
/>
|
package/src/helpers/export.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { globalSelectors, useGlobalStore } from '@/store/global';
|
|
2
2
|
import { useSessionStore } from '@/store/session';
|
|
3
3
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
4
4
|
import { createConfigFile, exportConfigFile } from '@/utils/config';
|
|
@@ -6,7 +6,7 @@ import { createConfigFile, exportConfigFile } from '@/utils/config';
|
|
|
6
6
|
const getSessions = () => sessionSelectors.exportSessions(useSessionStore.getState());
|
|
7
7
|
const getSession = (id: string) => sessionSelectors.getSessionById(id)(useSessionStore.getState());
|
|
8
8
|
|
|
9
|
-
const getSettings = () =>
|
|
9
|
+
const getSettings = () => globalSelectors.exportSettings(useGlobalStore.getState());
|
|
10
10
|
|
|
11
11
|
// ============= 导出所有角色 ============= //
|
|
12
12
|
|
|
@@ -4,21 +4,19 @@ import Router from 'next/router';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
import { useTranslation } from 'react-i18next';
|
|
6
6
|
|
|
7
|
-
import { INBOX_SESSION_ID } from '@/const/session';
|
|
8
7
|
import { useGlobalStore } from '@/store/global';
|
|
9
8
|
import { useSessionStore } from '@/store/session';
|
|
10
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
9
|
+
import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
|
|
11
10
|
|
|
12
|
-
const MobileHeader = memo
|
|
11
|
+
const MobileHeader = memo(() => {
|
|
13
12
|
const { t } = useTranslation('common');
|
|
14
13
|
|
|
15
|
-
const [
|
|
16
|
-
s
|
|
14
|
+
const [isInbox, title, model] = useSessionStore((s) => [
|
|
15
|
+
sessionSelectors.isInboxSession(s),
|
|
17
16
|
agentSelectors.currentAgentTitle(s),
|
|
18
17
|
agentSelectors.currentAgentModel(s),
|
|
19
18
|
]);
|
|
20
19
|
|
|
21
|
-
const isInbox = id === INBOX_SESSION_ID;
|
|
22
20
|
const [toggleConfig] = useGlobalStore((s) => [s.toggleMobileTopic]);
|
|
23
21
|
|
|
24
22
|
const displayTitle = isInbox ? t('inbox.title') : title;
|
|
@@ -30,11 +28,11 @@ const MobileHeader = memo<{ settings?: boolean }>(({ settings = true }) => {
|
|
|
30
28
|
right={
|
|
31
29
|
<>
|
|
32
30
|
<ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
|
|
33
|
-
{
|
|
31
|
+
{!isInbox && (
|
|
34
32
|
<ActionIcon
|
|
35
33
|
icon={Settings}
|
|
36
34
|
onClick={() => {
|
|
37
|
-
Router.push(`/chat
|
|
35
|
+
Router.push({ hash: location.hash, pathname: `/chat/setting` });
|
|
38
36
|
}}
|
|
39
37
|
/>
|
|
40
38
|
)}
|
|
@@ -7,31 +7,30 @@ import { memo } from 'react';
|
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
import { Flexbox } from 'react-layout-kit';
|
|
9
9
|
|
|
10
|
-
import { INBOX_SESSION_ID } from '@/const/session';
|
|
11
10
|
import { useGlobalStore } from '@/store/global';
|
|
12
11
|
import { useSessionChatInit, useSessionStore } from '@/store/session';
|
|
13
|
-
import { agentSelectors } from '@/store/session/selectors';
|
|
12
|
+
import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
|
|
14
13
|
|
|
15
14
|
import PluginTag from './PluginTag';
|
|
16
15
|
import ShareButton from './ShareButton';
|
|
17
16
|
|
|
18
|
-
const Header = memo
|
|
17
|
+
const Header = memo(() => {
|
|
19
18
|
const init = useSessionChatInit();
|
|
20
19
|
|
|
21
20
|
const { t } = useTranslation('common');
|
|
22
21
|
|
|
23
|
-
const [
|
|
24
|
-
s
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
const [isInbox, title, description, avatar, backgroundColor, model, plugins] = useSessionStore(
|
|
23
|
+
(s) => [
|
|
24
|
+
sessionSelectors.isInboxSession(s),
|
|
25
|
+
agentSelectors.currentAgentTitle(s),
|
|
26
|
+
agentSelectors.currentAgentDescription(s),
|
|
27
|
+
agentSelectors.currentAgentAvatar(s),
|
|
28
|
+
agentSelectors.currentAgentBackgroundColor(s),
|
|
29
|
+
agentSelectors.currentAgentModel(s),
|
|
30
|
+
agentSelectors.currentAgentPlugins(s),
|
|
31
|
+
],
|
|
32
|
+
);
|
|
33
33
|
|
|
34
|
-
const isInbox = id === INBOX_SESSION_ID;
|
|
35
34
|
const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
|
|
36
35
|
s.preference.showChatSideBar,
|
|
37
36
|
s.toggleChatSideBar,
|
|
@@ -77,11 +76,11 @@ const Header = memo<{ settings?: boolean }>(({ settings = true }) => {
|
|
|
77
76
|
size={{ fontSize: 24 }}
|
|
78
77
|
title={t('roleAndArchive')}
|
|
79
78
|
/>
|
|
80
|
-
{
|
|
79
|
+
{!isInbox && (
|
|
81
80
|
<ActionIcon
|
|
82
81
|
icon={Settings}
|
|
83
82
|
onClick={() => {
|
|
84
|
-
Router.push(`/chat
|
|
83
|
+
Router.push({ hash: location.hash, pathname: `/chat/setting` });
|
|
85
84
|
}}
|
|
86
85
|
size={{ fontSize: 24 }}
|
|
87
86
|
title={t('header.session', { ns: 'setting' })}
|
|
@@ -4,12 +4,12 @@ import { memo } from 'react';
|
|
|
4
4
|
import Mobile from './Mobile';
|
|
5
5
|
import PC from './PC';
|
|
6
6
|
|
|
7
|
-
const Header = memo
|
|
7
|
+
const Header = memo(() => {
|
|
8
8
|
const { mobile } = useResponsive();
|
|
9
9
|
|
|
10
|
-
if (mobile) return <Mobile
|
|
10
|
+
if (mobile) return <Mobile />;
|
|
11
11
|
|
|
12
|
-
return <PC
|
|
12
|
+
return <PC />;
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
export default Header;
|
|
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
6
6
|
|
|
7
7
|
import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
|
|
8
8
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
9
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
9
10
|
import { useSessionStore } from '@/store/session';
|
|
10
11
|
|
|
11
12
|
const { Item } = List;
|
|
@@ -14,7 +15,7 @@ const Inbox = memo(() => {
|
|
|
14
15
|
const ref = useRef(null);
|
|
15
16
|
const isHovering = useHover(ref);
|
|
16
17
|
const { t } = useTranslation('common');
|
|
17
|
-
const [activeId] = useSessionStore((s) => [s.activeId]);
|
|
18
|
+
const [activeId, switchSession] = useSessionStore((s) => [s.activeId, s.switchSession]);
|
|
18
19
|
|
|
19
20
|
const avatarRender = useMemo(
|
|
20
21
|
() => (
|
|
@@ -29,7 +30,13 @@ const Inbox = memo(() => {
|
|
|
29
30
|
);
|
|
30
31
|
|
|
31
32
|
return (
|
|
32
|
-
<Link
|
|
33
|
+
<Link
|
|
34
|
+
href={SESSION_CHAT_URL()}
|
|
35
|
+
onClick={(e) => {
|
|
36
|
+
e.preventDefault();
|
|
37
|
+
switchSession(INBOX_SESSION_ID);
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
33
40
|
<Item
|
|
34
41
|
active={activeId === INBOX_SESSION_ID}
|
|
35
42
|
avatar={avatarRender}
|
|
@@ -6,7 +6,7 @@ import { memo, useMemo, useRef, useState } from 'react';
|
|
|
6
6
|
import { Flexbox } from 'react-layout-kit';
|
|
7
7
|
import { shallow } from 'zustand/shallow';
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { globalSelectors, useGlobalStore } from '@/store/global';
|
|
10
10
|
import { useSessionStore } from '@/store/session';
|
|
11
11
|
import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
|
|
12
12
|
|
|
@@ -36,7 +36,7 @@ const SessionItem = memo<SessionItemProps>(({ id }) => {
|
|
|
36
36
|
const isHovering = useHover(ref);
|
|
37
37
|
|
|
38
38
|
const { styles } = useStyles();
|
|
39
|
-
const [defaultModel] = useGlobalStore((s) => [
|
|
39
|
+
const [defaultModel] = useGlobalStore((s) => [globalSelectors.defaultAgentConfig(s).model]);
|
|
40
40
|
|
|
41
41
|
const [
|
|
42
42
|
active,
|
|
@@ -2,6 +2,7 @@ import isEqual from 'fast-deep-equal';
|
|
|
2
2
|
import Link from 'next/link';
|
|
3
3
|
import { memo } from 'react';
|
|
4
4
|
|
|
5
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
5
6
|
import { useSessionHydrated, useSessionStore } from '@/store/session';
|
|
6
7
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ import SkeletonList from './SkeletonList';
|
|
|
11
12
|
|
|
12
13
|
const SessionList = memo(() => {
|
|
13
14
|
const list = useSessionStore(sessionSelectors.sessionList, isEqual);
|
|
15
|
+
const activeSession = useSessionStore((s) => s.activeSession);
|
|
14
16
|
|
|
15
17
|
const isInit = useSessionHydrated();
|
|
16
18
|
|
|
@@ -18,7 +20,14 @@ const SessionList = memo(() => {
|
|
|
18
20
|
<SkeletonList />
|
|
19
21
|
) : list.length > 0 ? (
|
|
20
22
|
list.map(({ id }) => (
|
|
21
|
-
<Link
|
|
23
|
+
<Link
|
|
24
|
+
href={SESSION_CHAT_URL(id)}
|
|
25
|
+
key={id}
|
|
26
|
+
onClick={(e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
activeSession(id);
|
|
29
|
+
}}
|
|
30
|
+
>
|
|
22
31
|
<SessionItem id={id} />
|
|
23
32
|
</Link>
|
|
24
33
|
))
|
|
@@ -8,6 +8,8 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
8
8
|
import SafeSpacing from '@/components/SafeSpacing';
|
|
9
9
|
import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
|
|
10
10
|
import { useGlobalStore } from '@/store/global';
|
|
11
|
+
import { useSessionStore } from '@/store/session';
|
|
12
|
+
import { sessionSelectors } from '@/store/session/slices/session/selectors';
|
|
11
13
|
|
|
12
14
|
import Mobile from './Mobile';
|
|
13
15
|
import SystemRole from './SystemRole';
|
|
@@ -37,13 +39,15 @@ interface SideBarProps {
|
|
|
37
39
|
mobile?: boolean;
|
|
38
40
|
systemRole?: boolean;
|
|
39
41
|
}
|
|
40
|
-
const SideBar = memo<SideBarProps>(({
|
|
42
|
+
const SideBar = memo<SideBarProps>(({ mobile }) => {
|
|
41
43
|
const { styles } = useStyles(mobile);
|
|
42
44
|
const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
|
|
43
45
|
s.preference.showChatSideBar,
|
|
44
46
|
s.toggleChatSideBar,
|
|
45
47
|
]);
|
|
46
48
|
|
|
49
|
+
const isInbox = useSessionStore(sessionSelectors.isInboxSession);
|
|
50
|
+
|
|
47
51
|
const { t } = useTranslation('common');
|
|
48
52
|
|
|
49
53
|
const topic = (
|
|
@@ -79,7 +83,7 @@ const SideBar = memo<SideBarProps>(({ systemRole = true, mobile }) => {
|
|
|
79
83
|
style={{ flex: 'none', height: '100%', minWidth: CHAT_SIDEBAR_WIDTH }}
|
|
80
84
|
>
|
|
81
85
|
<SafeSpacing />
|
|
82
|
-
{
|
|
86
|
+
{!isInbox && <SystemRole mobile={mobile} />}
|
|
83
87
|
|
|
84
88
|
{topic}
|
|
85
89
|
</DraggablePanelContainer>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useResponsive } from 'antd-style';
|
|
2
2
|
import Head from 'next/head';
|
|
3
3
|
import { memo } from 'react';
|
|
4
|
-
import { useTranslation } from 'react-i18next';
|
|
5
4
|
import { Flexbox } from 'react-layout-kit';
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { useSessionStore } from '@/store/session';
|
|
7
|
+
import { agentSelectors } from '@/store/session/selectors';
|
|
9
8
|
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
|
|
10
9
|
|
|
11
10
|
import Conversation from './features/Conversation';
|
|
@@ -15,13 +14,12 @@ import Layout from './layout';
|
|
|
15
14
|
import Mobile from './mobile';
|
|
16
15
|
|
|
17
16
|
const Chat = memo(() => {
|
|
18
|
-
const { t } = useTranslation('common');
|
|
19
17
|
const { mobile } = useResponsive();
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const [avatar, title] = useSessionStore((s) => [
|
|
19
|
+
agentSelectors.currentAgentAvatar(s),
|
|
20
|
+
agentSelectors.currentAgentTitle(s),
|
|
21
|
+
]);
|
|
22
|
+
const pageTitle = genSiteHeadTitle([avatar, title].filter(Boolean).join(' '));
|
|
25
23
|
|
|
26
24
|
return (
|
|
27
25
|
<>
|
|
@@ -32,10 +30,10 @@ const Chat = memo(() => {
|
|
|
32
30
|
<Mobile />
|
|
33
31
|
) : (
|
|
34
32
|
<Layout>
|
|
35
|
-
<Header
|
|
33
|
+
<Header />
|
|
36
34
|
<Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
|
|
37
35
|
<Conversation />
|
|
38
|
-
<SideBar
|
|
36
|
+
<SideBar />
|
|
39
37
|
</Flexbox>
|
|
40
38
|
</Layout>
|
|
41
39
|
)}
|
|
@@ -4,13 +4,18 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
4
4
|
|
|
5
5
|
import AppLayout from '@/layout/AppLayout';
|
|
6
6
|
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
7
|
+
import { usePluginStore } from '@/store/plugin';
|
|
7
8
|
|
|
8
|
-
import { Sessions } from './SessionList';
|
|
9
|
+
import { Sessions } from './features/SessionList';
|
|
9
10
|
|
|
10
11
|
const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
|
11
12
|
const { mobile } = useResponsive();
|
|
13
|
+
|
|
12
14
|
useSwitchSideBarOnInit('chat');
|
|
13
15
|
|
|
16
|
+
const useFetchPluginList = usePluginStore((s) => s.useFetchPluginList);
|
|
17
|
+
useFetchPluginList();
|
|
18
|
+
|
|
14
19
|
return (
|
|
15
20
|
<AppLayout>
|
|
16
21
|
<Sessions />
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { useResponsive } from 'antd-style';
|
|
2
1
|
import Head from 'next/head';
|
|
3
2
|
import { memo } from 'react';
|
|
4
3
|
import { Flexbox } from 'react-layout-kit';
|
|
5
4
|
|
|
5
|
+
import AppMobileLayout from '@/layout/AppMobileLayout';
|
|
6
|
+
import Header from '@/pages/chat/features/Header';
|
|
6
7
|
import { useSessionStore } from '@/store/session';
|
|
7
8
|
import { agentSelectors } from '@/store/session/selectors';
|
|
8
9
|
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
|
|
9
10
|
|
|
10
11
|
import Conversation from '../features/Conversation';
|
|
11
|
-
import Header from '../features/Header';
|
|
12
12
|
import SideBar from '../features/Sidebar';
|
|
13
|
-
import
|
|
14
|
-
import Mobile from './mobile';
|
|
13
|
+
import ChatLayout from '../layout';
|
|
15
14
|
|
|
16
15
|
const Chat = memo(() => {
|
|
17
|
-
const { mobile } = useResponsive();
|
|
18
16
|
const [avatar, title] = useSessionStore((s) => [
|
|
19
17
|
agentSelectors.currentAgentAvatar(s),
|
|
20
18
|
agentSelectors.currentAgentTitle(s),
|
|
@@ -27,22 +25,14 @@ const Chat = memo(() => {
|
|
|
27
25
|
<Head>
|
|
28
26
|
<title>{pageTitle}</title>
|
|
29
27
|
</Head>
|
|
30
|
-
{
|
|
31
|
-
<
|
|
28
|
+
<AppMobileLayout navBar={<Header />}>
|
|
29
|
+
<ChatLayout>
|
|
32
30
|
<Flexbox height={'calc(100vh - 44px)'} horizontal>
|
|
33
31
|
<Conversation mobile />
|
|
34
32
|
<SideBar mobile />
|
|
35
33
|
</Flexbox>
|
|
36
|
-
</
|
|
37
|
-
|
|
38
|
-
<Layout>
|
|
39
|
-
<Header />
|
|
40
|
-
<Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
|
|
41
|
-
<Conversation />
|
|
42
|
-
<SideBar />
|
|
43
|
-
</Flexbox>
|
|
44
|
-
</Layout>
|
|
45
|
-
)}
|
|
34
|
+
</ChatLayout>
|
|
35
|
+
</AppMobileLayout>
|
|
46
36
|
</>
|
|
47
37
|
);
|
|
48
38
|
});
|
|
@@ -2,10 +2,10 @@ import { useTheme } from 'antd-style';
|
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
|
|
4
4
|
import AppMobileLayout from '@/layout/AppMobileLayout';
|
|
5
|
-
import Header from '@/pages/chat/SessionList/Header';
|
|
6
5
|
import { useSwitchSideBarOnInit } from '@/store/global';
|
|
7
6
|
|
|
8
|
-
import { Sessions } from './SessionList';
|
|
7
|
+
import { Sessions } from './features/SessionList';
|
|
8
|
+
import Header from './features/SessionList/Header';
|
|
9
9
|
|
|
10
10
|
const ChatMobileLayout = memo(() => {
|
|
11
11
|
useSwitchSideBarOnInit('chat');
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
} from '@lobehub/ui';
|
|
8
8
|
import { Dropdown, MenuProps } from 'antd';
|
|
9
9
|
import { HardDriveDownload, Share2 } from 'lucide-react';
|
|
10
|
-
import Router from 'next/router';
|
|
11
10
|
import { memo, useMemo } from 'react';
|
|
12
11
|
import { useTranslation } from 'react-i18next';
|
|
13
12
|
|
|
@@ -16,7 +15,7 @@ import { useSessionStore } from '@/store/session';
|
|
|
16
15
|
|
|
17
16
|
const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
18
17
|
const { t } = useTranslation('setting');
|
|
19
|
-
const id = useSessionStore((s) => s.activeId);
|
|
18
|
+
const [id, onBack] = useSessionStore((s) => [s.activeId, s.switchBackToChat]);
|
|
20
19
|
|
|
21
20
|
const items = useMemo<MenuProps['items']>(
|
|
22
21
|
() => [
|
|
@@ -46,7 +45,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
46
45
|
return (
|
|
47
46
|
<MobileNavBar
|
|
48
47
|
center={<MobileNavBarTitle title={t('header.session')} />}
|
|
49
|
-
onBackClick={
|
|
48
|
+
onBackClick={onBack}
|
|
50
49
|
right={
|
|
51
50
|
<>
|
|
52
51
|
<ActionIcon icon={Share2} title={t('share', { ns: 'common' })} />
|
|
@@ -62,7 +61,7 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
|
62
61
|
return (
|
|
63
62
|
<ChatHeader
|
|
64
63
|
left={<ChatHeaderTitle title={t('header.session')} />}
|
|
65
|
-
onBackClick={
|
|
64
|
+
onBackClick={onBack}
|
|
66
65
|
right={
|
|
67
66
|
<>
|
|
68
67
|
<ActionIcon icon={Share2} size={{ fontSize: 24 }} title={t('share', { ns: 'common' })} />
|
package/src/pages/index.page.tsx
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
1
|
+
import { memo, useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useSessionHydrated, useSessionStore } from '@/store/session';
|
|
4
4
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
5
5
|
|
|
6
|
-
import Chat from './chat/index.page';
|
|
7
6
|
import Loading from './index/Loading';
|
|
8
7
|
import Welcome from './welcome/index.page';
|
|
9
8
|
|
|
10
9
|
const Home = memo(() => {
|
|
11
10
|
const hydrated = useSessionHydrated();
|
|
12
|
-
const hasSession = useSessionStore(
|
|
11
|
+
const [hasSession, switchSession] = useSessionStore((s) => [
|
|
12
|
+
sessionSelectors.hasSessionList(s),
|
|
13
|
+
s.switchSession,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (hasSession) switchSession();
|
|
18
|
+
}, [hasSession]);
|
|
13
19
|
|
|
14
20
|
if (!hydrated) return <Loading />;
|
|
15
21
|
|
|
16
|
-
return !hasSession ? <Welcome /> : <
|
|
22
|
+
return !hasSession ? <Welcome /> : <Loading />;
|
|
17
23
|
});
|
|
18
24
|
|
|
19
25
|
export default Home;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
|
|
2
|
-
import Router from 'next/router';
|
|
3
2
|
import { memo } from 'react';
|
|
4
3
|
import { useTranslation } from 'react-i18next';
|
|
5
4
|
|
|
5
|
+
import { useSessionStore } from '@/store/session';
|
|
6
|
+
|
|
6
7
|
const Header = memo(() => {
|
|
7
8
|
const { t } = useTranslation('setting');
|
|
9
|
+
const [switchBackToChat] = useSessionStore((s) => [s.switchBackToChat]);
|
|
8
10
|
|
|
9
11
|
return (
|
|
10
12
|
<ChatHeader
|
|
11
13
|
left={<ChatHeaderTitle title={t('header.global')} />}
|
|
12
|
-
onBackClick={
|
|
14
|
+
onBackClick={switchBackToChat}
|
|
13
15
|
showBackButton
|
|
14
16
|
/>
|
|
15
17
|
);
|
|
@@ -2,11 +2,11 @@ import isEqual from 'fast-deep-equal';
|
|
|
2
2
|
import { memo } from 'react';
|
|
3
3
|
|
|
4
4
|
import AgentSetting from '@/features/AgentSetting';
|
|
5
|
-
import {
|
|
5
|
+
import { globalSelectors, useGlobalStore } from '@/store/global';
|
|
6
6
|
|
|
7
7
|
const Agent = memo(() => {
|
|
8
|
-
const config = useGlobalStore(
|
|
9
|
-
const meta = useGlobalStore(
|
|
8
|
+
const config = useGlobalStore(globalSelectors.defaultAgentConfig, isEqual);
|
|
9
|
+
const meta = useGlobalStore(globalSelectors.defaultAgentMeta, isEqual);
|
|
10
10
|
const [updateAgent] = useGlobalStore((s) => [s.updateDefaultAgent]);
|
|
11
11
|
|
|
12
12
|
return (
|
|
@@ -12,7 +12,7 @@ import { FORM_STYLE } from '@/const/layoutTokens';
|
|
|
12
12
|
import { DEFAULT_SETTINGS } from '@/const/settings';
|
|
13
13
|
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
|
14
14
|
import { options } from '@/locales/options';
|
|
15
|
-
import {
|
|
15
|
+
import { globalSelectors, useGlobalStore } from '@/store/global';
|
|
16
16
|
import { useSessionStore } from '@/store/session';
|
|
17
17
|
import { ConfigKeys } from '@/types/settings';
|
|
18
18
|
|
|
@@ -29,7 +29,7 @@ const Common = memo(() => {
|
|
|
29
29
|
const [form] = AntForm.useForm();
|
|
30
30
|
const clearSessions = useSessionStore((s) => s.clearSessions);
|
|
31
31
|
|
|
32
|
-
const settings = useGlobalStore(
|
|
32
|
+
const settings = useGlobalStore(globalSelectors.currentSettings, isEqual);
|
|
33
33
|
const [setThemeMode, setSettings, resetSettings] = useGlobalStore((s) => [
|
|
34
34
|
s.switchThemeMode,
|
|
35
35
|
s.setSettings,
|
|
@@ -3,12 +3,13 @@ import { useSize } from 'ahooks';
|
|
|
3
3
|
import { Button, Upload } from 'antd';
|
|
4
4
|
import { SendHorizonal } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
|
-
import Router from 'next/router';
|
|
7
6
|
import { memo, useCallback, useRef } from 'react';
|
|
8
7
|
import { useTranslation } from 'react-i18next';
|
|
9
8
|
import { Flexbox } from 'react-layout-kit';
|
|
10
9
|
|
|
10
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
11
11
|
import { useImportConfig } from '@/hooks/useImportConfig';
|
|
12
|
+
import { useSessionStore } from '@/store/session';
|
|
12
13
|
|
|
13
14
|
import { useStyles } from '../style';
|
|
14
15
|
import Hero from './Hero';
|
|
@@ -22,9 +23,10 @@ const Banner = memo<{ mobile?: boolean }>(() => {
|
|
|
22
23
|
|
|
23
24
|
const { styles } = useStyles();
|
|
24
25
|
|
|
26
|
+
const switchSession = useSessionStore((s) => s.switchSession);
|
|
25
27
|
const handleImport = useCallback((e: any) => {
|
|
26
28
|
importConfig(e);
|
|
27
|
-
|
|
29
|
+
switchSession();
|
|
28
30
|
}, []);
|
|
29
31
|
|
|
30
32
|
return (
|
|
@@ -37,7 +39,13 @@ const Banner = memo<{ mobile?: boolean }>(() => {
|
|
|
37
39
|
<Upload maxCount={1} onChange={handleImport} showUploadList={false}>
|
|
38
40
|
<Button size={'large'}>{t('button.import')}</Button>
|
|
39
41
|
</Upload>
|
|
40
|
-
<Link
|
|
42
|
+
<Link
|
|
43
|
+
href={SESSION_CHAT_URL()}
|
|
44
|
+
onClick={(e) => {
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
switchSession();
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
41
49
|
<Button size={'large'} type={'primary'}>
|
|
42
50
|
<Flexbox align={'center'} gap={4} horizontal>
|
|
43
51
|
{t('button.start')}
|
|
@@ -1,20 +1,18 @@
|
|
|
1
|
-
import { merge } from 'lodash-es';
|
|
2
|
-
|
|
3
1
|
import { DEFAULT_AGENT_META } from '@/const/meta';
|
|
4
2
|
import { DEFAULT_AGENT, DEFAULT_AGENT_CONFIG, DEFAULT_SETTINGS } from '@/const/settings';
|
|
5
3
|
import { GlobalSettings } from '@/types/settings';
|
|
4
|
+
import { merge } from '@/utils/merge';
|
|
6
5
|
|
|
7
6
|
import { GlobalStore } from './store';
|
|
8
7
|
|
|
9
8
|
const currentSettings = (s: GlobalStore) => merge({}, DEFAULT_SETTINGS, s.settings);
|
|
10
9
|
|
|
11
|
-
const
|
|
10
|
+
const defaultAgent = (s: GlobalStore) => merge({}, DEFAULT_AGENT, s.settings.defaultAgent);
|
|
12
11
|
|
|
13
|
-
const
|
|
14
|
-
merge({}, DEFAULT_AGENT_CONFIG,
|
|
12
|
+
const defaultAgentConfig = (s: GlobalStore) =>
|
|
13
|
+
merge({}, DEFAULT_AGENT_CONFIG, defaultAgent(s).config);
|
|
15
14
|
|
|
16
|
-
const
|
|
17
|
-
merge({}, DEFAULT_AGENT_META, currentDefaultAgent(s).meta);
|
|
15
|
+
const defaultAgentMeta = (s: GlobalStore) => merge({}, DEFAULT_AGENT_META, defaultAgent(s).meta);
|
|
18
16
|
|
|
19
17
|
export const exportSettings = (s: GlobalStore) => {
|
|
20
18
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -23,10 +21,10 @@ export const exportSettings = (s: GlobalStore) => {
|
|
|
23
21
|
return settings as GlobalSettings;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
|
-
export const
|
|
27
|
-
currentAgentConfig,
|
|
28
|
-
currentAgentMeta,
|
|
29
|
-
currentDefaultAgent,
|
|
24
|
+
export const globalSelectors = {
|
|
30
25
|
currentSettings,
|
|
26
|
+
defaultAgent,
|
|
27
|
+
defaultAgentConfig,
|
|
28
|
+
defaultAgentMeta,
|
|
31
29
|
exportSettings,
|
|
32
30
|
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { isEmpty } from 'lodash-es';
|
|
2
|
+
import { PersistStorage } from 'zustand/middleware';
|
|
3
|
+
import { StorageValue } from 'zustand/middleware/persist';
|
|
4
|
+
|
|
5
|
+
import { createKeyMapper } from './keyMapper';
|
|
6
|
+
import { HyperStorageOptions } from './type';
|
|
7
|
+
import { createUrlSearch } from './urlSearch';
|
|
8
|
+
|
|
9
|
+
export const createHyperStorage = <T extends object>(
|
|
10
|
+
options: HyperStorageOptions,
|
|
11
|
+
): PersistStorage<T> => {
|
|
12
|
+
const hasUrl = !!options.url;
|
|
13
|
+
|
|
14
|
+
const { setUrlSearch, getUrlSearch } = createUrlSearch(options.url?.mode);
|
|
15
|
+
|
|
16
|
+
const { mapStateKeyToStorageKey, getStateKeyFromStorageKey } = createKeyMapper(options);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
getItem: (name): StorageValue<T> => {
|
|
20
|
+
const state: any = {};
|
|
21
|
+
let version: number | undefined;
|
|
22
|
+
// ============== 处理 Local Storage ============== //
|
|
23
|
+
const string = localStorage.getItem(name);
|
|
24
|
+
if (string) {
|
|
25
|
+
const localState: StorageValue<T> = JSON.parse(string);
|
|
26
|
+
version = localState.version;
|
|
27
|
+
for (const [k, v] of Object.entries(localState.state)) {
|
|
28
|
+
const key = getStateKeyFromStorageKey(k, 'localStorage');
|
|
29
|
+
if (key) state[key] = v;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============== 处理 URL Storage ============== //
|
|
34
|
+
// 不从 URL 中获取 version,由于 url 状态是临时态 不作为版本控制的依据
|
|
35
|
+
if (hasUrl) {
|
|
36
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
37
|
+
|
|
38
|
+
for (const [k, v] of searchParameters.entries()) {
|
|
39
|
+
const key = getStateKeyFromStorageKey(k, 'url');
|
|
40
|
+
// 当存在 UrlSelector 逻辑,且 key 有效时,才将状态加入终态 state
|
|
41
|
+
if (hasUrl && key) {
|
|
42
|
+
state[key] = v;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { state, version };
|
|
48
|
+
},
|
|
49
|
+
removeItem: (key): void => {
|
|
50
|
+
// ============== 处理 URL Storage ============== //
|
|
51
|
+
if (hasUrl) {
|
|
52
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
53
|
+
|
|
54
|
+
const storageKey = getStateKeyFromStorageKey(key, 'url');
|
|
55
|
+
if (storageKey) searchParameters.delete(storageKey);
|
|
56
|
+
|
|
57
|
+
setUrlSearch(searchParameters);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
setItem: (name, newValue) => {
|
|
61
|
+
// ============== 处理 Local Storage ============== //
|
|
62
|
+
const localState: Record<string, any> = {};
|
|
63
|
+
for (const [k, v] of Object.entries(newValue.state)) {
|
|
64
|
+
const localKey = mapStateKeyToStorageKey(k, 'localStorage');
|
|
65
|
+
if (localKey) localState[localKey] = v;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
localStorage.setItem(name, JSON.stringify({ ...newValue, state: localState }));
|
|
69
|
+
|
|
70
|
+
// ============== 处理 URL Storage ============== //
|
|
71
|
+
if (hasUrl) {
|
|
72
|
+
const searchParameters = new URLSearchParams(getUrlSearch());
|
|
73
|
+
|
|
74
|
+
for (const [k, v] of Object.entries(newValue.state)) {
|
|
75
|
+
const urlKey = mapStateKeyToStorageKey(k, 'url');
|
|
76
|
+
|
|
77
|
+
if (!urlKey) continue;
|
|
78
|
+
|
|
79
|
+
if (isEmpty(v)) {
|
|
80
|
+
searchParameters.delete(urlKey);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (typeof v === 'string') {
|
|
85
|
+
searchParameters.set(urlKey, v);
|
|
86
|
+
} else {
|
|
87
|
+
searchParameters.set(urlKey, JSON.stringify(v));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setUrlSearch(searchParameters);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { HyperStorageOptions } from './type';
|
|
2
|
+
|
|
3
|
+
export const createKeyMapper = (options: HyperStorageOptions) => {
|
|
4
|
+
const mapStateKeyToStorageKey = (
|
|
5
|
+
key: string,
|
|
6
|
+
mode: keyof HyperStorageOptions = 'localStorage',
|
|
7
|
+
) => {
|
|
8
|
+
const selectors = options[mode]?.selectors;
|
|
9
|
+
if (!selectors) return key;
|
|
10
|
+
|
|
11
|
+
let storageKey: string | undefined;
|
|
12
|
+
|
|
13
|
+
if (!selectors) return key;
|
|
14
|
+
|
|
15
|
+
for (const selector of selectors) {
|
|
16
|
+
if (typeof selector === 'string') {
|
|
17
|
+
if (selector === key) storageKey = key;
|
|
18
|
+
} else {
|
|
19
|
+
if (selector[key]) storageKey = selector[key];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return storageKey;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getStateKeyFromStorageKey = (
|
|
27
|
+
key: string,
|
|
28
|
+
mode: keyof HyperStorageOptions = 'localStorage',
|
|
29
|
+
) => {
|
|
30
|
+
const selectors = options[mode]?.selectors;
|
|
31
|
+
if (!selectors) return key;
|
|
32
|
+
|
|
33
|
+
let stateKey: string | undefined;
|
|
34
|
+
|
|
35
|
+
for (const item of selectors) {
|
|
36
|
+
// 对象如果是 字符串,直接返回该 item key
|
|
37
|
+
if (typeof item === 'string') {
|
|
38
|
+
if (item === key) stateKey = key;
|
|
39
|
+
} else {
|
|
40
|
+
for (const [k, v] of Object.entries(item)) {
|
|
41
|
+
if (v === key) stateKey = k;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return stateKey;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
getStateKeyFromStorageKey,
|
|
51
|
+
mapStateKeyToStorageKey,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type StorageSelector = string | Record<string, string>;
|
|
2
|
+
|
|
3
|
+
export interface HyperStorageOptions {
|
|
4
|
+
localStorage?: {
|
|
5
|
+
selectors: StorageSelector[];
|
|
6
|
+
};
|
|
7
|
+
url?: {
|
|
8
|
+
/**
|
|
9
|
+
* @default 'hash'
|
|
10
|
+
*/
|
|
11
|
+
mode?: 'hash' | 'search';
|
|
12
|
+
selectors: StorageSelector[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface UrlSearchHelper {
|
|
2
|
+
getUrlSearch: () => string;
|
|
3
|
+
setUrlSearch: (params: URLSearchParams) => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const createUrlSearch = (mode: 'search' | 'hash' = 'hash'): UrlSearchHelper => {
|
|
7
|
+
if (mode === 'hash')
|
|
8
|
+
return {
|
|
9
|
+
getUrlSearch: () => location.hash.slice(1),
|
|
10
|
+
setUrlSearch: (params: URLSearchParams) => (location.hash = params.toString()),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
getUrlSearch: () => location.search.slice(1),
|
|
15
|
+
setUrlSearch: (params: URLSearchParams) => (location.search = params.toString()),
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -5,25 +5,24 @@ import { agentSelectors } from '@/store/session/slices/agentConfig';
|
|
|
5
5
|
import type { SessionStore } from '../../../store';
|
|
6
6
|
import { currentChatsWithHistoryConfig } from './chat';
|
|
7
7
|
|
|
8
|
+
// TODO: We should find a smaller package to do this. gpt-tokenizer has 2M Size
|
|
9
|
+
const count = (text: string) => encode(text).length;
|
|
10
|
+
|
|
8
11
|
export const systemRoleSel = (s: SessionStore): string => {
|
|
9
12
|
const config = agentSelectors.currentAgentConfig(s);
|
|
10
13
|
|
|
11
14
|
return config.systemRole;
|
|
12
15
|
};
|
|
13
16
|
|
|
14
|
-
const
|
|
17
|
+
export const systemRoleTokenCount = (s: SessionStore): number => {
|
|
15
18
|
const systemRole = systemRoleSel(s);
|
|
16
19
|
|
|
17
|
-
return
|
|
20
|
+
return count(systemRole || '');
|
|
18
21
|
};
|
|
19
22
|
|
|
20
|
-
const
|
|
23
|
+
export const chatsTokenCount = (s: SessionStore): number => {
|
|
21
24
|
const chats = currentChatsWithHistoryConfig(s);
|
|
22
|
-
return
|
|
25
|
+
return count(chats.map((m) => m.content).join(''));
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
export const
|
|
26
|
-
|
|
27
|
-
export const systemRoleTokenCount = (s: SessionStore) => systemRoleTokens(s).length;
|
|
28
|
-
|
|
29
|
-
export const totalTokenCount = (s: SessionStore) => chatsTokens(s).length + systemRoleTokenCount(s);
|
|
28
|
+
export const totalTokenCount = (s: SessionStore) => chatsTokenCount(s) + systemRoleTokenCount(s);
|
|
@@ -4,6 +4,7 @@ import Router from 'next/router';
|
|
|
4
4
|
import { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
|
6
6
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
7
|
+
import { SESSION_CHAT_URL } from '@/const/url';
|
|
7
8
|
import { useGlobalStore } from '@/store/global';
|
|
8
9
|
import { SessionStore } from '@/store/session';
|
|
9
10
|
import { LobeAgentSession, LobeSessions } from '@/types/session';
|
|
@@ -54,12 +55,14 @@ export interface SessionAction {
|
|
|
54
55
|
*/
|
|
55
56
|
removeSession: (sessionId: string) => void;
|
|
56
57
|
|
|
58
|
+
switchBackToChat: () => void;
|
|
59
|
+
|
|
57
60
|
/**
|
|
58
61
|
* @title 切换会话
|
|
59
62
|
* @param sessionId - 会话索引
|
|
60
63
|
* @returns void
|
|
61
64
|
*/
|
|
62
|
-
switchSession: (sessionId?: string) =>
|
|
65
|
+
switchSession: (sessionId?: string) => void;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export const createSessionSlice: StateCreator<
|
|
@@ -69,7 +72,7 @@ export const createSessionSlice: StateCreator<
|
|
|
69
72
|
SessionAction
|
|
70
73
|
> = (set, get) => ({
|
|
71
74
|
activeSession: (sessionId) => {
|
|
72
|
-
set({ activeId: sessionId }, false, t('activeSession'));
|
|
75
|
+
set({ activeId: sessionId, activeTopicId: undefined }, false, t('activeSession'));
|
|
73
76
|
},
|
|
74
77
|
|
|
75
78
|
clearSessions: () => {
|
|
@@ -144,18 +147,24 @@ export const createSessionSlice: StateCreator<
|
|
|
144
147
|
get().dispatchSession({ id: sessionId, type: 'removeSession' });
|
|
145
148
|
|
|
146
149
|
if (sessionId === get().activeId) {
|
|
147
|
-
|
|
150
|
+
get().switchSession();
|
|
148
151
|
}
|
|
149
152
|
},
|
|
150
153
|
|
|
151
|
-
|
|
154
|
+
switchBackToChat: () => {
|
|
155
|
+
const { activeId } = get();
|
|
156
|
+
|
|
157
|
+
const id = activeId || INBOX_SESSION_ID;
|
|
158
|
+
|
|
159
|
+
get().activeSession(id);
|
|
160
|
+
|
|
161
|
+
Router.push(SESSION_CHAT_URL(id));
|
|
162
|
+
},
|
|
163
|
+
switchSession: (sessionId = INBOX_SESSION_ID) => {
|
|
152
164
|
if (get().activeId === sessionId) return;
|
|
153
165
|
|
|
154
|
-
|
|
155
|
-
get().activeSession(sessionId);
|
|
156
|
-
}
|
|
166
|
+
get().activeSession(sessionId);
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
await Router.push(`/chat/${sessionId}`);
|
|
168
|
+
Router.push(SESSION_CHAT_URL(sessionId));
|
|
160
169
|
},
|
|
161
170
|
});
|
|
@@ -9,9 +9,8 @@ export interface SessionState {
|
|
|
9
9
|
/**
|
|
10
10
|
* @title 当前活动的会话
|
|
11
11
|
* @description 当前正在编辑或查看的会话
|
|
12
|
-
* @default null
|
|
13
12
|
*/
|
|
14
|
-
activeId: string |
|
|
13
|
+
activeId: string | undefined;
|
|
15
14
|
// 默认会话
|
|
16
15
|
inbox: LobeAgentSession;
|
|
17
16
|
searchKeywords: string;
|
|
@@ -39,7 +38,7 @@ export const initInbox = merge({}, initLobeSession, {
|
|
|
39
38
|
} as Partial<LobeAgentSession>);
|
|
40
39
|
|
|
41
40
|
export const initialSessionState: SessionState = {
|
|
42
|
-
activeId:
|
|
41
|
+
activeId: undefined,
|
|
43
42
|
inbox: initInbox,
|
|
44
43
|
searchKeywords: '',
|
|
45
44
|
sessions: {},
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
2
|
+
import { SessionStore } from '@/store/session';
|
|
3
|
+
|
|
1
4
|
import { exportAgents, exportSessions, getExportAgent } from './export';
|
|
2
5
|
import {
|
|
3
6
|
currentSession,
|
|
@@ -8,6 +11,8 @@ import {
|
|
|
8
11
|
sessionList,
|
|
9
12
|
} from './list';
|
|
10
13
|
|
|
14
|
+
const isInboxSession = (s: SessionStore) => s.activeId === INBOX_SESSION_ID;
|
|
15
|
+
|
|
11
16
|
export const sessionSelectors = {
|
|
12
17
|
currentSession,
|
|
13
18
|
currentSessionSafe,
|
|
@@ -17,5 +22,6 @@ export const sessionSelectors = {
|
|
|
17
22
|
getSessionById,
|
|
18
23
|
getSessionMetaById,
|
|
19
24
|
hasSessionList,
|
|
25
|
+
isInboxSession,
|
|
20
26
|
sessionList,
|
|
21
27
|
};
|
|
@@ -5,6 +5,7 @@ import { StateCreator } from 'zustand/vanilla';
|
|
|
5
5
|
|
|
6
6
|
import { isDev } from '@/utils/env';
|
|
7
7
|
|
|
8
|
+
import { createHyperStorage } from '../middleware/createHyperStorage';
|
|
8
9
|
import { SessionStoreState, initialState } from './initialState';
|
|
9
10
|
import { AgentAction, createAgentSlice } from './slices/agentConfig/action';
|
|
10
11
|
import { ChatAction, createChatSlice } from './slices/chat/actions';
|
|
@@ -22,20 +23,27 @@ const createStore: StateCreator<SessionStore, [['zustand/devtools', never]]> = (
|
|
|
22
23
|
|
|
23
24
|
// =============== persist 本地缓存中间件配置 ============ //
|
|
24
25
|
|
|
25
|
-
type SessionPersist = Pick<SessionStore, 'sessions' | 'inbox'>;
|
|
26
|
-
|
|
27
26
|
const LOBE_CHAT = 'LOBE_CHAT';
|
|
28
27
|
|
|
29
|
-
const persistOptions: PersistOptions<SessionStore
|
|
28
|
+
const persistOptions: PersistOptions<SessionStore> = {
|
|
30
29
|
name: LOBE_CHAT,
|
|
31
30
|
|
|
32
|
-
partialize: (s) => ({
|
|
33
|
-
inbox: s.inbox,
|
|
34
|
-
sessions: s.sessions,
|
|
35
|
-
}),
|
|
36
|
-
|
|
37
31
|
// 手动控制 Hydration ,避免 ssr 报错
|
|
38
32
|
skipHydration: true,
|
|
33
|
+
|
|
34
|
+
storage: createHyperStorage({
|
|
35
|
+
localStorage: {
|
|
36
|
+
selectors: ['inbox', 'sessions'],
|
|
37
|
+
},
|
|
38
|
+
url: {
|
|
39
|
+
mode: 'hash',
|
|
40
|
+
selectors: [
|
|
41
|
+
// map state key to storage key
|
|
42
|
+
{ activeId: 'session' },
|
|
43
|
+
{ activeTopicId: 'topic' },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
39
47
|
version: 0,
|
|
40
48
|
};
|
|
41
49
|
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/router';
|
|
2
|
-
import { PropsWithChildren, memo } from 'react';
|
|
3
|
-
import { shallow } from 'zustand/shallow';
|
|
4
|
-
|
|
5
|
-
import { usePluginStore } from '@/store/plugin';
|
|
6
|
-
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
|
|
7
|
-
|
|
8
|
-
import ChatLayout from '../layout';
|
|
9
|
-
|
|
10
|
-
const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
11
|
-
const useFetchPluginList = usePluginStore((s) => s.useFetchPluginList);
|
|
12
|
-
const [activeSession, toggleTopic] = useSessionStore((s) => {
|
|
13
|
-
return [s.activeSession, s.toggleTopic];
|
|
14
|
-
}, shallow);
|
|
15
|
-
|
|
16
|
-
const router = useRouter();
|
|
17
|
-
const { id } = router.query;
|
|
18
|
-
|
|
19
|
-
useOnFinishHydrationSession(() => {
|
|
20
|
-
// 1. 正常激活会话
|
|
21
|
-
if (typeof id === 'string') {
|
|
22
|
-
activeSession(id);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 将话题重置为默认值
|
|
26
|
-
toggleTopic();
|
|
27
|
-
}, [id]);
|
|
28
|
-
|
|
29
|
-
useFetchPluginList();
|
|
30
|
-
|
|
31
|
-
return <ChatLayout>{children}</ChatLayout>;
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default Layout;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/router';
|
|
2
|
-
import { PropsWithChildren, memo } from 'react';
|
|
3
|
-
import { shallow } from 'zustand/shallow';
|
|
4
|
-
|
|
5
|
-
import AppMobileLayout from '@/layout/AppMobileLayout';
|
|
6
|
-
import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
|
|
7
|
-
|
|
8
|
-
import Header from '../features/Header';
|
|
9
|
-
|
|
10
|
-
const MobileLayout = memo<PropsWithChildren>(({ children }) => {
|
|
11
|
-
const [activeSession, toggleTopic] = useSessionStore((s) => {
|
|
12
|
-
return [s.activeSession, s.toggleTopic];
|
|
13
|
-
}, shallow);
|
|
14
|
-
|
|
15
|
-
const router = useRouter();
|
|
16
|
-
const { id } = router.query;
|
|
17
|
-
|
|
18
|
-
useOnFinishHydrationSession(() => {
|
|
19
|
-
// 1. 正常激活会话
|
|
20
|
-
if (typeof id === 'string') {
|
|
21
|
-
activeSession(id);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// 将话题重置为默认值
|
|
25
|
-
toggleTopic();
|
|
26
|
-
}, [id]);
|
|
27
|
-
|
|
28
|
-
return <AppMobileLayout navBar={<Header />}>{children}</AppMobileLayout>;
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export default MobileLayout;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { isEmpty } from 'lodash-es';
|
|
2
|
-
import { PersistStorage } from 'zustand/middleware';
|
|
3
|
-
|
|
4
|
-
import { Compressor } from '@/utils/compass';
|
|
5
|
-
|
|
6
|
-
export const createHashStorage = <T extends object>(): PersistStorage<T> => {
|
|
7
|
-
return {
|
|
8
|
-
getItem: async () => {
|
|
9
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
10
|
-
const state: any = {};
|
|
11
|
-
const pool = [...searchParameters.entries()].map(async ([k, v]) => {
|
|
12
|
-
const string_ = await Compressor.decompressAsync(v);
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
state[k] = JSON.parse(string_);
|
|
16
|
-
} catch {
|
|
17
|
-
state[k] = string_;
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await Promise.all(pool);
|
|
22
|
-
|
|
23
|
-
return { state } as any;
|
|
24
|
-
},
|
|
25
|
-
removeItem: (key): void => {
|
|
26
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
27
|
-
searchParameters.delete(key);
|
|
28
|
-
location.hash = searchParameters.toString();
|
|
29
|
-
},
|
|
30
|
-
setItem: async (_, newValue) => {
|
|
31
|
-
const searchParameters = new URLSearchParams(location.hash.slice(1));
|
|
32
|
-
|
|
33
|
-
const pool = Object.entries(newValue.state).map(async ([k, v]) => {
|
|
34
|
-
if (isEmpty(v)) {
|
|
35
|
-
searchParameters.delete(k);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (typeof v === 'string') {
|
|
39
|
-
searchParameters.set(k, await Compressor.compressAsync(v));
|
|
40
|
-
} else {
|
|
41
|
-
searchParameters.set(k, await Compressor.compressAsync(JSON.stringify(v)));
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
await Promise.all(pool);
|
|
45
|
-
|
|
46
|
-
location.hash = searchParameters.toString();
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|