@lobehub/chat 0.120.2 → 0.120.4
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 +47 -22
- package/README.zh-CN.md +53 -26
- package/docs/Deployment/Upstream-Sync.md +8 -2
- package/docs/Deployment/Upstream-Sync.zh-CN.md +9 -3
- package/docs/Development/Add-New-Locale.md +9 -1
- package/docs/Development/Add-New-Locale.zh-CN.md +9 -1
- package/docs/Development/Contributing-Guidelines.md +83 -0
- package/docs/Development/Contributing-Guidelines.zh-CN.md +13 -0
- package/docs/Development/Internationalization-Implementation.md +125 -0
- package/docs/Development/Internationalization-Implementation.zh-CN.md +9 -1
- package/docs/Development/State-Management/State-Management-Intro.md +224 -0
- package/docs/Development/State-Management/{index.zh-CN.md → State-Management-Intro.zh-CN.md} +25 -28
- package/docs/Home.md +9 -6
- package/docs/_Sidebar.md +10 -7
- package/locales/ar/common.json +1 -0
- package/locales/ar/setting.json +1 -3
- package/locales/de-DE/common.json +1 -0
- package/locales/de-DE/setting.json +1 -3
- package/locales/en-US/common.json +1 -0
- package/locales/en-US/setting.json +1 -3
- package/locales/es-ES/common.json +1 -0
- package/locales/es-ES/setting.json +1 -3
- package/locales/fr-FR/common.json +1 -0
- package/locales/fr-FR/setting.json +1 -3
- package/locales/it-IT/common.json +1 -0
- package/locales/it-IT/setting.json +1 -3
- package/locales/ja-JP/common.json +1 -0
- package/locales/ja-JP/setting.json +1 -3
- package/locales/ko-KR/common.json +1 -0
- package/locales/ko-KR/setting.json +1 -3
- package/locales/nl-NL/common.json +1 -0
- package/locales/nl-NL/setting.json +1 -3
- package/locales/pl-PL/common.json +1 -0
- package/locales/pl-PL/setting.json +1 -3
- package/locales/pt-BR/common.json +1 -0
- package/locales/pt-BR/setting.json +1 -3
- package/locales/ru-RU/common.json +1 -0
- package/locales/ru-RU/setting.json +1 -3
- package/locales/tr-TR/common.json +1 -0
- package/locales/tr-TR/setting.json +1 -3
- package/locales/vi-VN/common.json +1 -0
- package/locales/vi-VN/setting.json +1 -3
- package/locales/zh-CN/common.json +1 -0
- package/locales/zh-CN/setting.json +1 -3
- package/locales/zh-TW/common.json +1 -0
- package/locales/zh-TW/setting.json +1 -3
- package/next.config.mjs +2 -2
- package/package.json +8 -7
- package/scripts/docsWorkflow/const.ts +1 -1
- package/scripts/docsWorkflow/index.ts +9 -3
- package/scripts/readmeWorkflow/syncAgentIndex.ts +2 -2
- package/scripts/readmeWorkflow/syncPluginIndex.ts +2 -2
- 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/SessionListContent/DefaultMode.tsx +6 -1
- package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +0 -2
- 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/settings/common/Common.tsx +5 -6
- 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/locale.ts +2 -2
- 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/ActionBar/Clear.tsx +0 -2
- 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/PluginDetailModal/index.tsx +0 -2
- package/src/features/PluginDevModal/index.tsx +0 -1
- package/src/features/SideBar/TopActions.tsx +5 -5
- package/src/layout/GlobalLayout/Locale.tsx +37 -10
- package/src/layout/GlobalLayout/StoreHydration.tsx +12 -11
- package/src/layout/GlobalLayout/index.tsx +1 -1
- package/src/locales/default/setting.ts +0 -2
- package/src/locales/resources.ts +9 -7
- 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/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 +13 -17
- package/src/store/session/slices/session/selectors/list.ts +7 -0
- package/src/store/session/store.ts +5 -46
- 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/src/app/chat/{features → (desktop)/features/SideBar}/SystemRole/style.ts +0 -0
- /package/src/app/chat/{features → components}/SidebarHeader/index.tsx +0 -0
|
@@ -94,10 +94,8 @@ const Actions = memo<ActionProps>(({ id, setOpen }) => {
|
|
|
94
94
|
domEvent.stopPropagation();
|
|
95
95
|
|
|
96
96
|
modal.confirm({
|
|
97
|
-
cancelText: t('cancel'),
|
|
98
97
|
centered: true,
|
|
99
98
|
okButtonProps: { danger: true },
|
|
100
|
-
okText: t('ok'),
|
|
101
99
|
onOk: () => {
|
|
102
100
|
removeSession(id);
|
|
103
101
|
},
|
|
@@ -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: [
|
|
@@ -49,10 +49,8 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
|
49
49
|
|
|
50
50
|
const handleReset = useCallback(() => {
|
|
51
51
|
modal.confirm({
|
|
52
|
-
cancelText: t('cancel', { ns: 'common' }),
|
|
53
52
|
centered: true,
|
|
54
53
|
okButtonProps: { danger: true },
|
|
55
|
-
okText: t('ok', { ns: 'common' }),
|
|
56
54
|
onOk: () => {
|
|
57
55
|
resetSettings();
|
|
58
56
|
form.setFieldsValue(DEFAULT_SETTINGS);
|
|
@@ -63,12 +61,10 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
|
63
61
|
|
|
64
62
|
const handleClear = useCallback(() => {
|
|
65
63
|
modal.confirm({
|
|
66
|
-
cancelText: t('cancel', { ns: 'common' }),
|
|
67
64
|
centered: true,
|
|
68
65
|
okButtonProps: {
|
|
69
66
|
danger: true,
|
|
70
67
|
},
|
|
71
|
-
okText: t('ok', { ns: 'common' }),
|
|
72
68
|
onOk: async () => {
|
|
73
69
|
await clearSessions();
|
|
74
70
|
await removeAllPlugins();
|
|
@@ -136,20 +132,23 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
|
|
|
136
132
|
<SliderWithInput
|
|
137
133
|
marks={{
|
|
138
134
|
12: {
|
|
139
|
-
label:
|
|
135
|
+
label: 'A',
|
|
140
136
|
style: {
|
|
137
|
+
fontSize: 12,
|
|
141
138
|
marginTop: 4,
|
|
142
139
|
},
|
|
143
140
|
},
|
|
144
141
|
14: {
|
|
145
142
|
label: t('settingTheme.fontSize.marks.normal'),
|
|
146
143
|
style: {
|
|
144
|
+
fontSize: 14,
|
|
147
145
|
marginTop: 4,
|
|
148
146
|
},
|
|
149
147
|
},
|
|
150
148
|
18: {
|
|
151
|
-
label:
|
|
149
|
+
label: 'A',
|
|
152
150
|
style: {
|
|
151
|
+
fontSize: 18,
|
|
153
152
|
marginTop: 4,
|
|
154
153
|
},
|
|
155
154
|
},
|
|
@@ -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/locale.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeLocale, supportLocales } from '@/locales/resources';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_LANG = 'en-US';
|
|
4
4
|
export const LOBE_LOCALE_COOKIE = 'LOBE_LOCALE';
|
|
@@ -7,6 +7,6 @@ export const LOBE_LOCALE_COOKIE = 'LOBE_LOCALE';
|
|
|
7
7
|
* Check if the language is supported
|
|
8
8
|
* @param locale
|
|
9
9
|
*/
|
|
10
|
-
export const isLocaleNotSupport = (locale:
|
|
10
|
+
export const isLocaleNotSupport = (locale: string) => {
|
|
11
11
|
return normalizeLocale(locale) === DEFAULT_LANG || !supportLocales.includes(locale);
|
|
12
12
|
};
|
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
|
}}
|
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
import { ConfigProvider } from 'antd';
|
|
2
|
-
import { PropsWithChildren, memo, useState } from 'react';
|
|
2
|
+
import { PropsWithChildren, memo, useEffect, useState } from 'react';
|
|
3
3
|
import useSWR from 'swr';
|
|
4
4
|
|
|
5
5
|
import { createI18nNext } from '@/locales/create';
|
|
6
|
+
import { normalizeLocale } from '@/locales/resources';
|
|
6
7
|
import { useOnFinishHydrationGlobal } from '@/store/global';
|
|
7
8
|
import { isOnServerSide } from '@/utils/env';
|
|
8
9
|
import { switchLang } from '@/utils/switchLang';
|
|
9
10
|
|
|
11
|
+
const getAntdLocale = async (lang?: string) => {
|
|
12
|
+
let normalLang = normalizeLocale(lang);
|
|
13
|
+
|
|
14
|
+
// due to antd only have ar-EG locale, we need to convert ar to ar-EG
|
|
15
|
+
// refs: https://ant.design/docs/react/i18n
|
|
16
|
+
|
|
17
|
+
// And we don't want to handle it in `normalizeLocale` function
|
|
18
|
+
// because of other locale files are all `ar` not `ar-EG`
|
|
19
|
+
if (normalLang === 'ar') normalLang = 'ar-EG';
|
|
20
|
+
|
|
21
|
+
const { default: locale } = await import(`antd/locale/${normalLang.replace('-', '_')}.js`);
|
|
22
|
+
|
|
23
|
+
return locale;
|
|
24
|
+
};
|
|
25
|
+
|
|
10
26
|
interface LocaleLayoutProps extends PropsWithChildren {
|
|
11
|
-
|
|
27
|
+
defaultLang?: string;
|
|
12
28
|
}
|
|
13
29
|
|
|
14
|
-
const Locale = memo<LocaleLayoutProps>(({ children,
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
);
|
|
21
|
-
const [i18n] = useState(createI18nNext(lang));
|
|
30
|
+
const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
|
|
31
|
+
const [i18n] = useState(createI18nNext(defaultLang));
|
|
32
|
+
const [lang, setLang] = useState(defaultLang);
|
|
33
|
+
|
|
34
|
+
const { data: locale } = useSWR(['antd-locale', lang], ([, key]) => getAntdLocale(key), {
|
|
35
|
+
revalidateOnFocus: false,
|
|
36
|
+
});
|
|
22
37
|
|
|
23
38
|
// if run on server side, init i18n instance everytime
|
|
24
39
|
if (isOnServerSide) {
|
|
@@ -38,6 +53,18 @@ const Locale = memo<LocaleLayoutProps>(({ children, lang }) => {
|
|
|
38
53
|
}
|
|
39
54
|
}, []);
|
|
40
55
|
|
|
56
|
+
// handle i18n instance language change
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const handleLang = (e: string) => {
|
|
59
|
+
setLang(e);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
i18n.instance.on('languageChanged', handleLang);
|
|
63
|
+
return () => {
|
|
64
|
+
i18n.instance.off('languageChanged', handleLang);
|
|
65
|
+
};
|
|
66
|
+
}, [i18n]);
|
|
67
|
+
|
|
41
68
|
return <ConfigProvider locale={locale}>{children}</ConfigProvider>;
|
|
42
69
|
});
|
|
43
70
|
|
|
@@ -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');
|
|
@@ -48,7 +48,7 @@ interface GlobalLayoutProps extends AppThemeProps {
|
|
|
48
48
|
|
|
49
49
|
const GlobalLayout = ({ children, defaultLang, ...theme }: GlobalLayoutProps) => (
|
|
50
50
|
<AppTheme {...theme}>
|
|
51
|
-
<Locale
|
|
51
|
+
<Locale defaultLang={defaultLang}>
|
|
52
52
|
<StoreHydration />
|
|
53
53
|
<Container>{children}</Container>
|
|
54
54
|
<DebugUI />
|
package/src/locales/resources.ts
CHANGED
|
@@ -19,7 +19,9 @@ export const locales = [
|
|
|
19
19
|
export type DefaultResources = typeof resources;
|
|
20
20
|
export type Locales = (typeof locales)[number];
|
|
21
21
|
|
|
22
|
-
export const normalizeLocale = (locale
|
|
22
|
+
export const normalizeLocale = (locale?: string) => {
|
|
23
|
+
if (!locale) return 'en-US';
|
|
24
|
+
|
|
23
25
|
switch (locale) {
|
|
24
26
|
case 'zh-CN':
|
|
25
27
|
case 'zh': {
|
|
@@ -43,8 +45,8 @@ type LocaleOptions = {
|
|
|
43
45
|
|
|
44
46
|
export const localeOptions: LocaleOptions = [
|
|
45
47
|
{
|
|
46
|
-
label: '
|
|
47
|
-
value: '
|
|
48
|
+
label: 'English',
|
|
49
|
+
value: 'en-US',
|
|
48
50
|
},
|
|
49
51
|
{
|
|
50
52
|
label: '简体中文',
|
|
@@ -54,10 +56,6 @@ export const localeOptions: LocaleOptions = [
|
|
|
54
56
|
label: '繁體中文',
|
|
55
57
|
value: 'zh-TW',
|
|
56
58
|
},
|
|
57
|
-
{
|
|
58
|
-
label: 'English',
|
|
59
|
-
value: 'en-US',
|
|
60
|
-
},
|
|
61
59
|
{
|
|
62
60
|
label: '日本語',
|
|
63
61
|
value: 'ja-JP',
|
|
@@ -74,6 +72,10 @@ export const localeOptions: LocaleOptions = [
|
|
|
74
72
|
label: 'Español',
|
|
75
73
|
value: 'es-ES',
|
|
76
74
|
},
|
|
75
|
+
{
|
|
76
|
+
label: 'العربية',
|
|
77
|
+
value: 'ar',
|
|
78
|
+
},
|
|
77
79
|
{
|
|
78
80
|
label: 'Français',
|
|
79
81
|
value: 'fr-FR',
|