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