@lobehub/chat 0.152.11 → 0.153.0
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 +8 -8
- package/README.zh-CN.md +8 -8
- package/locales/ar/common.json +1 -0
- package/locales/bg-BG/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 +1 -1
- package/src/app/(main)/chat/(desktop)/features/ChatHeader/Main.tsx +4 -9
- package/src/app/(main)/chat/(desktop)/features/SideBar/SystemRole/index.tsx +5 -6
- package/src/app/(main)/chat/features/SettingButton.tsx +3 -17
- package/src/app/(main)/chat/features/TopicListContent/Header.tsx +1 -1
- package/src/app/(main)/chat/settings/_layout/Desktop/Header.tsx +2 -1
- package/src/app/(main)/chat/settings/_layout/Mobile/Header.tsx +2 -1
- package/src/app/(main)/chat/settings/modal/page.tsx +23 -0
- package/src/app/(main)/settings/@category/features/CategoryContent.tsx +5 -1
- package/src/app/(main)/settings/modal/page.tsx +27 -0
- package/src/app/@modal/(.)settings/modal/index.tsx +40 -0
- package/src/app/@modal/(.)settings/modal/layout.tsx +32 -0
- package/src/app/@modal/(.)settings/modal/loading.tsx +5 -0
- package/src/app/@modal/(.)settings/modal/page.tsx +19 -0
- package/src/app/@modal/_layout/SettingModalLayout.tsx +59 -0
- package/src/app/@modal/chat/(.)settings/modal/features/CategoryContent.tsx +37 -0
- package/src/app/@modal/chat/(.)settings/modal/features/useCategory.tsx +54 -0
- package/src/app/@modal/chat/(.)settings/modal/layout.tsx +55 -0
- package/src/app/@modal/chat/(.)settings/modal/loading.tsx +5 -0
- package/src/app/@modal/chat/(.)settings/modal/page.tsx +55 -0
- package/src/app/@modal/default.tsx +3 -0
- package/src/app/@modal/error.tsx +5 -0
- package/src/app/@modal/layout.tsx +30 -0
- package/src/app/@modal/loading.tsx +5 -0
- package/src/app/layout.tsx +6 -2
- package/src/features/AgentSetting/AgentMeta/AutoGenerateInput.tsx +31 -26
- package/src/features/AgentSetting/AgentMeta/AutoGenerateSelect.tsx +3 -1
- package/src/features/AgentSetting/AgentMeta/index.tsx +2 -0
- package/src/features/Conversation/Messages/index.ts +9 -12
- package/src/features/MobileTabBar/index.tsx +3 -3
- package/src/features/User/UserPanel/useMenu.tsx +31 -16
- package/src/hooks/useInterceptingRoutes.test.ts +70 -0
- package/src/hooks/useInterceptingRoutes.ts +46 -0
- package/src/hooks/useQuery.test.ts +0 -1
- package/src/hooks/useQuery.ts +2 -1
- package/src/layout/GlobalProvider/StoreInitialization.tsx +12 -5
- package/src/locales/default/common.ts +2 -1
- package/src/store/global/initialState.ts +9 -0
- package/src/features/User/UserPanel/UserInfo.tsx +0 -35
- /package/src/{app/(main)/chat/components → components}/SidebarHeader/index.tsx +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useResponsive, useTheme, useThemeMode } from 'antd-style';
|
|
4
|
+
import { ReactNode, memo, useRef } from 'react';
|
|
5
|
+
import { Flexbox } from 'react-layout-kit';
|
|
6
|
+
|
|
7
|
+
import Header from '@/app/(main)/settings/_layout/Desktop/Header';
|
|
8
|
+
import SideBar from '@/app/(main)/settings/_layout/Desktop/SideBar';
|
|
9
|
+
|
|
10
|
+
interface SettingLayoutProps {
|
|
11
|
+
category: ReactNode;
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
desc?: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const SettingModalLayout = memo<SettingLayoutProps>(({ children, category, desc, title }) => {
|
|
18
|
+
const ref = useRef<any>(null);
|
|
19
|
+
const theme = useTheme();
|
|
20
|
+
const { isDarkMode } = useThemeMode();
|
|
21
|
+
const { md = true } = useResponsive();
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{md ? (
|
|
26
|
+
<SideBar
|
|
27
|
+
desc={desc}
|
|
28
|
+
style={{
|
|
29
|
+
background: isDarkMode ? theme.colorBgContainer : theme.colorFillTertiary,
|
|
30
|
+
borderColor: theme.colorFillTertiary,
|
|
31
|
+
}}
|
|
32
|
+
title={title}
|
|
33
|
+
>
|
|
34
|
+
{category}
|
|
35
|
+
</SideBar>
|
|
36
|
+
) : (
|
|
37
|
+
<Header getContainer={() => ref.current}>{category}</Header>
|
|
38
|
+
)}
|
|
39
|
+
<Flexbox
|
|
40
|
+
align={'center'}
|
|
41
|
+
gap={64}
|
|
42
|
+
style={{
|
|
43
|
+
background: isDarkMode ? theme.colorFillQuaternary : theme.colorBgElevated,
|
|
44
|
+
overflowX: 'hidden',
|
|
45
|
+
overflowY: 'auto',
|
|
46
|
+
paddingBlock: 40,
|
|
47
|
+
paddingInline: 56,
|
|
48
|
+
}}
|
|
49
|
+
width={'100%'}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</Flexbox>
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
SettingModalLayout.displayName = 'SettingModalLayout';
|
|
58
|
+
|
|
59
|
+
export default SettingModalLayout;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
import { Flexbox } from 'react-layout-kit';
|
|
5
|
+
|
|
6
|
+
import HeaderContent from '@/app/(main)/chat/settings/features/HeaderContent';
|
|
7
|
+
import Menu from '@/components/Menu';
|
|
8
|
+
import { useQuery } from '@/hooks/useQuery';
|
|
9
|
+
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
|
10
|
+
import { ChatSettingsTabs } from '@/store/global/initialState';
|
|
11
|
+
|
|
12
|
+
import { useCategory } from './useCategory';
|
|
13
|
+
|
|
14
|
+
const CategoryContent = memo(() => {
|
|
15
|
+
const cateItems = useCategory();
|
|
16
|
+
const router = useQueryRoute();
|
|
17
|
+
const { tab = ChatSettingsTabs.Meta } = useQuery();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<Menu
|
|
22
|
+
items={cateItems}
|
|
23
|
+
onClick={({ key }) => {
|
|
24
|
+
router.replace('/chat/settings/modal', { query: { tab: key } });
|
|
25
|
+
}}
|
|
26
|
+
selectable
|
|
27
|
+
selectedKeys={[tab as any]}
|
|
28
|
+
variant={'compact'}
|
|
29
|
+
/>
|
|
30
|
+
<Flexbox align={'center'} gap={8} paddingInline={8} width={'100%'}>
|
|
31
|
+
<HeaderContent modal />
|
|
32
|
+
</Flexbox>
|
|
33
|
+
</>
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default CategoryContent;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
|
2
|
+
import { Blocks, Bot, BrainCog, MessagesSquare, Mic2, UserCircle } from 'lucide-react';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
|
|
6
|
+
import type { MenuProps } from '@/components/Menu';
|
|
7
|
+
import { ChatSettingsTabs } from '@/store/global/initialState';
|
|
8
|
+
|
|
9
|
+
interface UseCategoryOptions {
|
|
10
|
+
mobile?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useCategory = ({ mobile }: UseCategoryOptions = {}) => {
|
|
14
|
+
const { t } = useTranslation('setting');
|
|
15
|
+
const iconSize = mobile ? { fontSize: 20 } : undefined;
|
|
16
|
+
|
|
17
|
+
const cateItems: MenuProps['items'] = useMemo(
|
|
18
|
+
() => [
|
|
19
|
+
{
|
|
20
|
+
icon: <Icon icon={UserCircle} size={iconSize} />,
|
|
21
|
+
key: ChatSettingsTabs.Meta,
|
|
22
|
+
label: t('settingAgent.title'),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
icon: <Icon icon={Bot} size={iconSize} />,
|
|
26
|
+
key: ChatSettingsTabs.Prompt,
|
|
27
|
+
label: t('settingAgent.prompt.title'),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
icon: <Icon icon={MessagesSquare} size={iconSize} />,
|
|
31
|
+
key: ChatSettingsTabs.Chat,
|
|
32
|
+
label: t('settingChat.title'),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
icon: <Icon icon={BrainCog} size={iconSize} />,
|
|
36
|
+
key: ChatSettingsTabs.Modal,
|
|
37
|
+
label: t('settingModel.title'),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
icon: <Icon icon={Mic2} size={iconSize} />,
|
|
41
|
+
key: ChatSettingsTabs.TTS,
|
|
42
|
+
label: t('settingTTS.title'),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
icon: <Icon icon={Blocks} size={iconSize} />,
|
|
46
|
+
key: ChatSettingsTabs.Plugin,
|
|
47
|
+
label: t('settingPlugin.title'),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
[t],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return cateItems;
|
|
54
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Skeleton } from 'antd';
|
|
4
|
+
import isEqual from 'fast-deep-equal';
|
|
5
|
+
import dynamic from 'next/dynamic';
|
|
6
|
+
import { PropsWithChildren, memo } from 'react';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import StoreUpdater from '@/features/AgentSetting/StoreUpdater';
|
|
10
|
+
import { Provider, createStore } from '@/features/AgentSetting/store';
|
|
11
|
+
import { useAgentStore } from '@/store/agent';
|
|
12
|
+
import { agentSelectors } from '@/store/agent/slices/chat';
|
|
13
|
+
import { useSessionStore } from '@/store/session';
|
|
14
|
+
import { sessionMetaSelectors } from '@/store/session/selectors';
|
|
15
|
+
|
|
16
|
+
import SettingModalLayout from '../../../_layout/SettingModalLayout';
|
|
17
|
+
|
|
18
|
+
const CategoryContent = dynamic(() => import('./features/CategoryContent'), {
|
|
19
|
+
loading: () => <Skeleton paragraph={{ rows: 6 }} title={false} />,
|
|
20
|
+
ssr: false,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const Layout = memo<PropsWithChildren>(({ children }) => {
|
|
24
|
+
const { t } = useTranslation('setting');
|
|
25
|
+
const id = useSessionStore((s) => s.activeId);
|
|
26
|
+
const config = useAgentStore(agentSelectors.currentAgentConfig, isEqual);
|
|
27
|
+
const meta = useSessionStore(sessionMetaSelectors.currentAgentMeta, isEqual);
|
|
28
|
+
const [updateAgentConfig] = useAgentStore((s) => [s.updateAgentConfig]);
|
|
29
|
+
|
|
30
|
+
const [updateAgentMeta] = useSessionStore((s) => [
|
|
31
|
+
s.updateSessionMeta,
|
|
32
|
+
sessionMetaSelectors.currentAgentTitle(s),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<SettingModalLayout
|
|
37
|
+
category={<CategoryContent />}
|
|
38
|
+
desc={t('header.sessionDesc')}
|
|
39
|
+
title={t('header.session')}
|
|
40
|
+
>
|
|
41
|
+
<Provider createStore={createStore}>
|
|
42
|
+
<StoreUpdater
|
|
43
|
+
config={config}
|
|
44
|
+
id={id}
|
|
45
|
+
meta={meta}
|
|
46
|
+
onConfigChange={updateAgentConfig}
|
|
47
|
+
onMetaChange={updateAgentMeta}
|
|
48
|
+
/>
|
|
49
|
+
{children}
|
|
50
|
+
</Provider>
|
|
51
|
+
</SettingModalLayout>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export default Layout;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import dynamic from 'next/dynamic';
|
|
4
|
+
|
|
5
|
+
import { useQuery } from '@/hooks/useQuery';
|
|
6
|
+
import { ChatSettingsTabs } from '@/store/global/initialState';
|
|
7
|
+
|
|
8
|
+
import Skeleton from './loading';
|
|
9
|
+
|
|
10
|
+
const loading = () => <Skeleton />;
|
|
11
|
+
|
|
12
|
+
const AgentMeta = dynamic(() => import('@/features/AgentSetting/AgentMeta'), {
|
|
13
|
+
loading,
|
|
14
|
+
ssr: false,
|
|
15
|
+
});
|
|
16
|
+
const AgentChat = dynamic(() => import('@/features/AgentSetting/AgentChat'), {
|
|
17
|
+
loading,
|
|
18
|
+
ssr: false,
|
|
19
|
+
});
|
|
20
|
+
const AgentPrompt = dynamic(() => import('@/features/AgentSetting/AgentPrompt'), {
|
|
21
|
+
loading,
|
|
22
|
+
ssr: false,
|
|
23
|
+
});
|
|
24
|
+
const AgentPlugin = dynamic(() => import('@/features/AgentSetting/AgentPlugin'), {
|
|
25
|
+
loading,
|
|
26
|
+
ssr: false,
|
|
27
|
+
});
|
|
28
|
+
const AgentModal = dynamic(() => import('@/features/AgentSetting/AgentModal'), {
|
|
29
|
+
loading,
|
|
30
|
+
ssr: false,
|
|
31
|
+
});
|
|
32
|
+
const AgentTTS = dynamic(() => import('@/features/AgentSetting/AgentTTS'), { loading, ssr: false });
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @description: Agent Settings Modal (intercepting route: /chat/settings/modal )
|
|
36
|
+
* @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
const Page = () => {
|
|
40
|
+
const { tab = ChatSettingsTabs.Meta } = useQuery();
|
|
41
|
+
return (
|
|
42
|
+
<>
|
|
43
|
+
{tab === ChatSettingsTabs.Meta && <AgentMeta />}
|
|
44
|
+
{tab === ChatSettingsTabs.Prompt && <AgentPrompt modal />}
|
|
45
|
+
{tab === ChatSettingsTabs.Chat && <AgentChat />}
|
|
46
|
+
{tab === ChatSettingsTabs.Modal && <AgentModal />}
|
|
47
|
+
{tab === ChatSettingsTabs.TTS && <AgentTTS />}
|
|
48
|
+
{tab === ChatSettingsTabs.Plugin && <AgentPlugin />}
|
|
49
|
+
</>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
Page.displayName = 'AgentSettingModal';
|
|
54
|
+
|
|
55
|
+
export default Page;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Modal } from '@lobehub/ui';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { PropsWithChildren, memo, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
const SessionSettingsModal = memo<PropsWithChildren>(({ children }) => {
|
|
8
|
+
const [open, setOpen] = useState(true);
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Modal
|
|
13
|
+
afterClose={() => {
|
|
14
|
+
router.back();
|
|
15
|
+
}}
|
|
16
|
+
footer={null}
|
|
17
|
+
onCancel={() => setOpen(false)}
|
|
18
|
+
open={open}
|
|
19
|
+
styles={{
|
|
20
|
+
body: { display: 'flex', minHeight: 'min(75vh, 750px)', overflow: 'hidden', padding: 0 },
|
|
21
|
+
}}
|
|
22
|
+
title={false}
|
|
23
|
+
width={1024}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</Modal>
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default SessionSettingsModal;
|
package/src/app/layout.tsx
CHANGED
|
@@ -12,9 +12,10 @@ import { isMobileDevice } from '@/utils/responsive';
|
|
|
12
12
|
|
|
13
13
|
type RootLayoutProps = {
|
|
14
14
|
children: ReactNode;
|
|
15
|
+
modal: ReactNode;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
const RootLayout = async ({ children }: RootLayoutProps) => {
|
|
18
|
+
const RootLayout = async ({ children, modal }: RootLayoutProps) => {
|
|
18
19
|
const cookieStore = cookies();
|
|
19
20
|
|
|
20
21
|
const lang = cookieStore.get(LOBE_LOCALE_COOKIE);
|
|
@@ -24,7 +25,10 @@ const RootLayout = async ({ children }: RootLayoutProps) => {
|
|
|
24
25
|
<html dir={direction} lang={lang?.value || DEFAULT_LANG} suppressHydrationWarning>
|
|
25
26
|
<body>
|
|
26
27
|
<GlobalProvider>
|
|
27
|
-
<AuthProvider>
|
|
28
|
+
<AuthProvider>
|
|
29
|
+
{children}
|
|
30
|
+
{modal}
|
|
31
|
+
</AuthProvider>
|
|
28
32
|
</GlobalProvider>
|
|
29
33
|
<Analytics />
|
|
30
34
|
<SpeedInsights />
|
|
@@ -5,37 +5,42 @@ import { Wand2 } from 'lucide-react';
|
|
|
5
5
|
import { memo } from 'react';
|
|
6
6
|
import { useTranslation } from 'react-i18next';
|
|
7
7
|
|
|
8
|
+
|
|
8
9
|
export interface AutoGenerateInputProps extends InputProps {
|
|
10
|
+
canAutoGenerate?: boolean;
|
|
9
11
|
loading?: boolean;
|
|
10
12
|
onGenerate?: () => void;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const AutoGenerateInput = memo<AutoGenerateInputProps>(
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
const AutoGenerateInput = memo<AutoGenerateInputProps>(
|
|
16
|
+
({ loading, onGenerate, canAutoGenerate, ...props }) => {
|
|
17
|
+
const { t } = useTranslation('common');
|
|
18
|
+
const theme = useTheme();
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
return (
|
|
21
|
+
<Input
|
|
22
|
+
suffix={
|
|
23
|
+
onGenerate && (
|
|
24
|
+
<ActionIcon
|
|
25
|
+
active
|
|
26
|
+
disable={!canAutoGenerate}
|
|
27
|
+
icon={Wand2}
|
|
28
|
+
loading={loading}
|
|
29
|
+
onClick={onGenerate}
|
|
30
|
+
size="small"
|
|
31
|
+
style={{
|
|
32
|
+
color: theme.colorInfo,
|
|
33
|
+
marginRight: -4,
|
|
34
|
+
}}
|
|
35
|
+
title={t('autoGenerate')}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
type="block"
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
);
|
|
40
45
|
|
|
41
46
|
export default AutoGenerateInput;
|
|
@@ -7,12 +7,13 @@ import { memo } from 'react';
|
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
9
|
export interface AutoGenerateInputProps extends SelectProps {
|
|
10
|
+
canAutoGenerate?: boolean;
|
|
10
11
|
loading?: boolean;
|
|
11
12
|
onGenerate?: () => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const AutoGenerateSelect = memo<AutoGenerateInputProps>(
|
|
15
|
-
({ loading, onGenerate, value, ...props }) => {
|
|
16
|
+
({ loading, onGenerate, value, canAutoGenerate, ...props }) => {
|
|
16
17
|
const { t } = useTranslation('common');
|
|
17
18
|
const theme = useTheme();
|
|
18
19
|
|
|
@@ -25,6 +26,7 @@ const AutoGenerateSelect = memo<AutoGenerateInputProps>(
|
|
|
25
26
|
onGenerate && (
|
|
26
27
|
<ActionIcon
|
|
27
28
|
active
|
|
29
|
+
disable={!canAutoGenerate}
|
|
28
30
|
icon={Wand2}
|
|
29
31
|
loading={loading}
|
|
30
32
|
onClick={onGenerate}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRouter } from 'next/navigation';
|
|
3
|
-
|
|
1
|
+
import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
|
|
4
2
|
import { useGlobalStore } from '@/store/global';
|
|
5
3
|
import { useSessionStore } from '@/store/session';
|
|
6
4
|
import { sessionSelectors } from '@/store/session/selectors';
|
|
7
|
-
import { pathString } from '@/utils/url';
|
|
8
5
|
|
|
9
6
|
import { OnAvatarsClick, RenderMessage } from '../types';
|
|
10
7
|
import { AssistantMessage } from './Assistant';
|
|
@@ -22,18 +19,18 @@ export const renderMessages: Record<string, RenderMessage> = {
|
|
|
22
19
|
export const useAvatarsClick = (): OnAvatarsClick => {
|
|
23
20
|
const [isInbox] = useSessionStore((s) => [sessionSelectors.isInboxSession(s)]);
|
|
24
21
|
const [toggleSystemRole] = useGlobalStore((s) => [s.toggleSystemRole]);
|
|
25
|
-
const
|
|
26
|
-
const router = useRouter();
|
|
22
|
+
const openChatSettings = useOpenChatSettings();
|
|
27
23
|
|
|
28
24
|
return (role) => {
|
|
29
25
|
switch (role) {
|
|
30
26
|
case 'assistant': {
|
|
31
|
-
return () =>
|
|
32
|
-
isInbox
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
return () => {
|
|
28
|
+
if (!isInbox) {
|
|
29
|
+
toggleSystemRole(true);
|
|
30
|
+
} else {
|
|
31
|
+
openChatSettings();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
37
34
|
}
|
|
38
35
|
}
|
|
39
36
|
};
|
|
@@ -6,6 +6,7 @@ import { rgba } from 'polished';
|
|
|
6
6
|
import { memo, useMemo } from 'react';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
8
|
|
|
9
|
+
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
|
9
10
|
import { SidebarTabKey } from '@/store/global/initialState';
|
|
10
11
|
|
|
11
12
|
const useStyles = createStyles(({ css, token }) => ({
|
|
@@ -24,6 +25,7 @@ interface Props {
|
|
|
24
25
|
export default memo<Props>(({ className, tabBarKey }) => {
|
|
25
26
|
const { t } = useTranslation('common');
|
|
26
27
|
const { styles } = useStyles();
|
|
28
|
+
const openSettings = useOpenSettings();
|
|
27
29
|
const router = useRouter();
|
|
28
30
|
const items: MobileTabBarProps['items'] = useMemo(
|
|
29
31
|
() => [
|
|
@@ -48,9 +50,7 @@ export default memo<Props>(({ className, tabBarKey }) => {
|
|
|
48
50
|
{
|
|
49
51
|
icon: (active) => <Icon className={active ? styles.active : undefined} icon={User} />,
|
|
50
52
|
key: SidebarTabKey.Setting,
|
|
51
|
-
onClick:
|
|
52
|
-
router.push('/settings');
|
|
53
|
-
},
|
|
53
|
+
onClick: openSettings,
|
|
54
54
|
title: t('tab.setting'),
|
|
55
55
|
},
|
|
56
56
|
],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiscordIcon, Icon } from '@lobehub/ui';
|
|
1
|
+
import { ActionIcon, DiscordIcon, Icon } from '@lobehub/ui';
|
|
2
2
|
import { Badge } from 'antd';
|
|
3
3
|
import {
|
|
4
4
|
Book,
|
|
@@ -7,22 +7,29 @@ import {
|
|
|
7
7
|
HardDriveUpload,
|
|
8
8
|
LifeBuoy,
|
|
9
9
|
Mail,
|
|
10
|
+
Maximize,
|
|
10
11
|
Settings2,
|
|
11
12
|
} from 'lucide-react';
|
|
12
13
|
import Link from 'next/link';
|
|
13
14
|
import { PropsWithChildren, useCallback } from 'react';
|
|
14
15
|
import { useTranslation } from 'react-i18next';
|
|
15
16
|
import { Flexbox } from 'react-layout-kit';
|
|
17
|
+
import urlJoin from 'url-join';
|
|
16
18
|
|
|
17
19
|
import { type MenuProps } from '@/components/Menu';
|
|
18
20
|
import { DISCORD, DOCUMENTS, EMAIL_SUPPORT, GITHUB_ISSUES } from '@/const/url';
|
|
19
21
|
import DataImporter from '@/features/DataImporter';
|
|
22
|
+
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
|
|
23
|
+
import { useQueryRoute } from '@/hooks/useQueryRoute';
|
|
20
24
|
import { configService } from '@/services/config';
|
|
25
|
+
import { SettingsTabs } from '@/store/global/initialState';
|
|
21
26
|
|
|
22
27
|
import { useNewVersion } from './useNewVersion';
|
|
23
28
|
|
|
24
29
|
export const useMenu = () => {
|
|
30
|
+
const router = useQueryRoute();
|
|
25
31
|
const hasNewVersion = useNewVersion();
|
|
32
|
+
const openSettings = useOpenSettings();
|
|
26
33
|
const { t } = useTranslation(['common', 'setting']);
|
|
27
34
|
|
|
28
35
|
const NewVersionBadge = useCallback(
|
|
@@ -38,6 +45,29 @@ export const useMenu = () => {
|
|
|
38
45
|
[t],
|
|
39
46
|
);
|
|
40
47
|
|
|
48
|
+
const settings: MenuProps['items'] = [
|
|
49
|
+
{
|
|
50
|
+
icon: <Icon icon={Settings2} />,
|
|
51
|
+
key: 'setting',
|
|
52
|
+
label: (
|
|
53
|
+
<Flexbox align={'center'} horizontal>
|
|
54
|
+
<Flexbox flex={1} horizontal onClick={openSettings}>
|
|
55
|
+
<NewVersionBadge showBadge={hasNewVersion}>{t('userPanel.setting')}</NewVersionBadge>
|
|
56
|
+
</Flexbox>
|
|
57
|
+
<ActionIcon
|
|
58
|
+
icon={Maximize}
|
|
59
|
+
onClick={() => router.push(urlJoin('/settings', SettingsTabs.Common))}
|
|
60
|
+
size={'small'}
|
|
61
|
+
title={t('fullscreen')}
|
|
62
|
+
/>
|
|
63
|
+
</Flexbox>
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'divider',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
41
71
|
const exports: MenuProps['items'] = [
|
|
42
72
|
{
|
|
43
73
|
icon: <Icon icon={HardDriveUpload} />,
|
|
@@ -79,21 +109,6 @@ export const useMenu = () => {
|
|
|
79
109
|
},
|
|
80
110
|
];
|
|
81
111
|
|
|
82
|
-
const settings: MenuProps['items'] = [
|
|
83
|
-
{
|
|
84
|
-
icon: <Icon icon={Settings2} />,
|
|
85
|
-
key: 'setting',
|
|
86
|
-
label: (
|
|
87
|
-
<Link href={'/settings'}>
|
|
88
|
-
<NewVersionBadge showBadge={hasNewVersion}>{t('userPanel.setting')}</NewVersionBadge>
|
|
89
|
-
</Link>
|
|
90
|
-
),
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
type: 'divider',
|
|
94
|
-
},
|
|
95
|
-
];
|
|
96
|
-
|
|
97
112
|
const helps: MenuProps['items'] = [
|
|
98
113
|
{
|
|
99
114
|
icon: <Icon icon={DiscordIcon} />,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import urlJoin from 'url-join';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
|
6
|
+
import { useIsMobile } from '@/hooks/useIsMobile';
|
|
7
|
+
import { useGlobalStore } from '@/store/global';
|
|
8
|
+
import { ChatSettingsTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
|
9
|
+
import { useSessionStore } from '@/store/session';
|
|
10
|
+
|
|
11
|
+
import { useOpenChatSettings, useOpenSettings } from './useInterceptingRoutes';
|
|
12
|
+
|
|
13
|
+
// Mocks
|
|
14
|
+
vi.mock('next/navigation', () => ({
|
|
15
|
+
useRouter: vi.fn(() => ({
|
|
16
|
+
push: vi.fn((href) => href),
|
|
17
|
+
replace: vi.fn((href) => href),
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('@/hooks/useQuery', () => ({
|
|
21
|
+
useQuery: vi.fn(() => ({})),
|
|
22
|
+
}));
|
|
23
|
+
vi.mock('@/hooks/useIsMobile', () => ({
|
|
24
|
+
useIsMobile: vi.fn(),
|
|
25
|
+
}));
|
|
26
|
+
vi.mock('@/store/session', () => ({
|
|
27
|
+
useSessionStore: vi.fn(),
|
|
28
|
+
}));
|
|
29
|
+
vi.mock('@/store/global', () => ({
|
|
30
|
+
useGlobalStore: {
|
|
31
|
+
setState: vi.fn(),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
describe('useOpenSettings', () => {
|
|
36
|
+
it('should handle mobile route correctly', () => {
|
|
37
|
+
vi.mocked(useIsMobile).mockReturnValue(true);
|
|
38
|
+
const { result } = renderHook(() => useOpenSettings(SettingsTabs.Common));
|
|
39
|
+
expect(result.current()).toBe('/settings/common');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle desktop route correctly', () => {
|
|
43
|
+
vi.mocked(useIsMobile).mockReturnValue(false);
|
|
44
|
+
const { result } = renderHook(() => useOpenSettings(SettingsTabs.Agent));
|
|
45
|
+
expect(result.current()).toBe('/settings/modal?tab=agent');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('useOpenChatSettings', () => {
|
|
50
|
+
it('should handle inbox session id correctly', () => {
|
|
51
|
+
vi.mocked(useSessionStore).mockReturnValue(INBOX_SESSION_ID);
|
|
52
|
+
const { result } = renderHook(() => useOpenChatSettings());
|
|
53
|
+
|
|
54
|
+
expect(result.current()).toBe('/settings/modal?session=inbox&tab=agent'); // Assuming openSettings returns a function
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should handle mobile route for chat settings', () => {
|
|
58
|
+
vi.mocked(useSessionStore).mockReturnValue('123');
|
|
59
|
+
vi.mocked(useIsMobile).mockReturnValue(true);
|
|
60
|
+
const { result } = renderHook(() => useOpenChatSettings(ChatSettingsTabs.Meta));
|
|
61
|
+
expect(result.current()).toBe('/chat/settings');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should handle desktop route for chat settings with session and tab', () => {
|
|
65
|
+
vi.mocked(useSessionStore).mockReturnValue('456');
|
|
66
|
+
vi.mocked(useIsMobile).mockReturnValue(false);
|
|
67
|
+
const { result } = renderHook(() => useOpenChatSettings(ChatSettingsTabs.Meta));
|
|
68
|
+
expect(result.current()).toBe('/chat/settings/modal?session=456&tab=meta');
|
|
69
|
+
});
|
|
70
|
+
});
|