mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
♻️ refactor: Refactor settings page and mobile ux
This commit is contained in:
parent
05f568a5fe
commit
89c5648544
65 changed files with 582 additions and 381 deletions
|
|
@ -36,7 +36,7 @@
|
|||
"historyRange": "History Range",
|
||||
"import": "Import Configuration",
|
||||
"inbox": {
|
||||
"defaultMessage": "Hello, I'm your intelligent agent. You can ask me any questions and I will do my best to answer you. If you need a more professional or customized agent, you can click <kbd>+</kbd> to create a custom agent.",
|
||||
"defaultMessage": "Hello, I'm your intelligent agent. You can ask me any questions and I will do my best to answer you. If you need a more professional or customized agent, you can click `+` to create a custom agent.",
|
||||
"desc": "Activate the brain cluster and spark your thinking. Your intelligent agent is here to communicate with you about everything.",
|
||||
"title": "Chat Freely"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"historyRange": "Исторический диапазон",
|
||||
"import": "Импорт конфига",
|
||||
"inbox": {
|
||||
"defaultMessage": "Hello, I'm your intelligent assistant. You can ask me any questions and I will do my best to answer you. If you need a more professional or customized assistant, you can click <kbd>+</kbd> to create a custom assistant.",
|
||||
"defaultMessage": "Hello, I'm your intelligent assistant. You can ask me any questions and I will do my best to answer you. If you need a more professional or customized assistant, you can click `+` to create a custom assistant.",
|
||||
"desc": "Activate the brain cluster and spark your thinking. Your intelligent assistant is here to communicate with you about everything.",
|
||||
"title": "Chat Freely"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"historyRange": "历史范围",
|
||||
"import": "导入配置",
|
||||
"inbox": {
|
||||
"defaultMessage": "你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击<kbd>+</kbd>创建自定义助手",
|
||||
"defaultMessage": "你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击`+`创建自定义助手",
|
||||
"desc": "开启大脑集群,激发思维火花。你的智能助理,在这里与你交流一切",
|
||||
"title": "随便聊聊"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"historyRange": "歷史範圍",
|
||||
"import": "匯入設定",
|
||||
"inbox": {
|
||||
"defaultMessage": "您好,我是您的智慧助理。您可以問我任何問題,我會盡力回答您。如果您需要更專業或自訂的助理,您可以點選 <kbd>+</kbd> 來建立一個自訂助理。",
|
||||
"defaultMessage": "您好,我是您的智慧助理。您可以問我任何問題,我會盡力回答您。如果您需要更專業或自訂的助理,您可以點選 `+` 來建立一個自訂助理。",
|
||||
"desc": "啟動思維並激發你的創意。你的智慧助理已經準備好與你討論所有事情。",
|
||||
"title": "隨便聊聊"
|
||||
},
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5 KiB |
|
|
@ -4,6 +4,8 @@ import { useRouter } from 'next/navigation';
|
|||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import ShareButton from '@/app/chat/features/Header/ShareButton';
|
||||
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
|
||||
|
|
@ -13,10 +15,9 @@ const MobileHeader = memo(() => {
|
|||
const { t } = useTranslation('common');
|
||||
const router = useRouter();
|
||||
|
||||
const [isInbox, title, model] = useSessionStore((s) => [
|
||||
const [isInbox, title] = useSessionStore((s) => [
|
||||
sessionSelectors.isInboxSession(s),
|
||||
agentSelectors.currentAgentTitle(s),
|
||||
agentSelectors.currentAgentModel(s),
|
||||
]);
|
||||
|
||||
const [toggleConfig] = useGlobalStore((s) => [s.toggleMobileTopic]);
|
||||
|
|
@ -25,17 +26,21 @@ const MobileHeader = memo(() => {
|
|||
|
||||
return (
|
||||
<MobileNavBar
|
||||
center={<MobileNavBarTitle desc={model} title={displayTitle} />}
|
||||
center={<MobileNavBarTitle title={displayTitle} />}
|
||||
onBackClick={() => router.push('/chat')}
|
||||
right={
|
||||
<>
|
||||
<ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
|
||||
<ShareButton />
|
||||
<ActionIcon
|
||||
icon={LayoutList}
|
||||
onClick={() => toggleConfig()}
|
||||
size={MOBILE_HEADER_ICON_SIZE}
|
||||
/>
|
||||
{!isInbox && (
|
||||
<ActionIcon
|
||||
icon={Settings}
|
||||
onClick={() => {
|
||||
router.push(pathString('/chat/settings', { hash: location.hash }));
|
||||
}}
|
||||
onClick={() => router.push(pathString('/chat/settings', { hash: location.hash }))}
|
||||
size={MOBILE_HEADER_ICON_SIZE}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { memo, useMemo, useState } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import MobilePadding from '@/components/MobilePadding';
|
||||
import { FORM_STYLE } from '@/const/layoutTokens';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
|
@ -89,33 +90,37 @@ const Inner = memo(() => {
|
|||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Segmented
|
||||
block
|
||||
onChange={(value) => setTab(value as Tab)}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
value={tab}
|
||||
/>
|
||||
<Form items={[settings]} {...FORM_STYLE} />
|
||||
{tab === Tab.Screenshot && (
|
||||
<Preview
|
||||
imageType={imageType}
|
||||
withBackground={withBackground}
|
||||
withFooter={withFooter}
|
||||
withSystemRole={withSystemRole}
|
||||
/>
|
||||
)}
|
||||
{tab === Tab.ShareGPT && (
|
||||
<Button
|
||||
<MobilePadding bottom={0}>
|
||||
<Segmented
|
||||
block
|
||||
loading={shareLoading}
|
||||
onClick={() => shareToShareGPT({ avatar, withPluginInfo, withSystemRole })}
|
||||
size={'large'}
|
||||
type={'primary'}
|
||||
>
|
||||
{t('shareModal.shareToShareGPT')}
|
||||
</Button>
|
||||
)}
|
||||
onChange={(value) => setTab(value as Tab)}
|
||||
options={options}
|
||||
style={{ width: '100%' }}
|
||||
value={tab}
|
||||
/>
|
||||
</MobilePadding>
|
||||
<Form items={[settings]} {...FORM_STYLE} />
|
||||
<MobilePadding gap={16} top={0}>
|
||||
{tab === Tab.Screenshot && (
|
||||
<Preview
|
||||
imageType={imageType}
|
||||
withBackground={withBackground}
|
||||
withFooter={withFooter}
|
||||
withSystemRole={withSystemRole}
|
||||
/>
|
||||
)}
|
||||
{tab === Tab.ShareGPT && (
|
||||
<Button
|
||||
block
|
||||
loading={shareLoading}
|
||||
onClick={() => shareToShareGPT({ avatar, withPluginInfo, withSystemRole })}
|
||||
size={'large'}
|
||||
type={'primary'}
|
||||
>
|
||||
{t('shareModal.shareToShareGPT')}
|
||||
</Button>
|
||||
)}
|
||||
</MobilePadding>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { ActionIcon, Modal } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { Share2 } from 'lucide-react';
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
import Inner from './Inner';
|
||||
|
|
@ -11,6 +13,9 @@ const ShareButton = memo(() => {
|
|||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const { t } = useTranslation('common');
|
||||
const [shareLoading] = useSessionStore((s) => [s.shareLoading]);
|
||||
const { mobile } = useResponsive();
|
||||
|
||||
const size = mobile ? MOBILE_HEADER_ICON_SIZE : { fontSize: 24 };
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -18,7 +23,7 @@ const ShareButton = memo(() => {
|
|||
icon={Share2}
|
||||
loading={shareLoading}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
size={{ fontSize: 24 }}
|
||||
size={size}
|
||||
title={t('share')}
|
||||
/>
|
||||
<Modal
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { ActionIcon, Logo } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { MessageSquarePlus } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
|
@ -28,9 +27,8 @@ const Header = memo(() => {
|
|||
return (
|
||||
<Flexbox className={styles.top} gap={16} padding={16}>
|
||||
<Flexbox distribution={'space-between'} horizontal>
|
||||
<Link href={'/'}>
|
||||
<Logo className={styles.logo} size={36} type={'text'} />
|
||||
</Link>
|
||||
<Logo className={styles.logo} size={36} type={'text'} />
|
||||
|
||||
<ActionIcon
|
||||
icon={MessageSquarePlus}
|
||||
onClick={() => createSession()}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
|
||||
import { ActionIcon, Avatar, Logo, MobileNavBar } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { MessageSquarePlus, Settings2 } from 'lucide-react';
|
||||
import { MessageSquarePlus } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
|
||||
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
||||
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
export const useStyles = createStyles(({ css, token }) => ({
|
||||
|
|
@ -20,21 +21,21 @@ export const useStyles = createStyles(({ css, token }) => ({
|
|||
const Header = memo(() => {
|
||||
const [createSession] = useSessionStore((s) => [s.createSession]);
|
||||
const router = useRouter();
|
||||
|
||||
const avatar = useGlobalStore((st) => st.settings.avatar);
|
||||
return (
|
||||
<MobileNavBar
|
||||
center={<Logo type={'text'} />}
|
||||
left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
|
||||
left={
|
||||
<div onClick={() => router.push('/settings/mobile')} style={{ marginLeft: 8 }}>
|
||||
{avatar ? <Avatar avatar={avatar} size={28} /> : <Logo size={28} />}
|
||||
</div>
|
||||
}
|
||||
right={
|
||||
<>
|
||||
<ActionIcon icon={MessageSquarePlus} onClick={() => createSession()} />
|
||||
<ActionIcon
|
||||
icon={Settings2}
|
||||
onClick={() => {
|
||||
router.push('/settings');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
<ActionIcon
|
||||
icon={MessageSquarePlus}
|
||||
onClick={() => createSession()}
|
||||
size={MOBILE_HEADER_ICON_SIZE}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Drawer } from 'antd';
|
||||
import { useTheme } from 'antd-style';
|
||||
import { X } from 'lucide-react';
|
||||
import { Modal } from '@lobehub/ui';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
const Mobile = memo<PropsWithChildren>(({ children }) => {
|
||||
const theme = useTheme();
|
||||
const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
|
||||
s.preference.mobileShowTopic,
|
||||
s.toggleMobileTopic,
|
||||
|
|
@ -17,19 +13,9 @@ const Mobile = memo<PropsWithChildren>(({ children }) => {
|
|||
const { t } = useTranslation('common');
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
bodyStyle={{ padding: 0 }}
|
||||
closeIcon={<ActionIcon icon={X} size={{ blockSize: 32, fontSize: 20 }} />}
|
||||
drawerStyle={{ background: theme.colorBgContainer }}
|
||||
headerStyle={{ padding: '8px 4px' }}
|
||||
height={'75vh'}
|
||||
onClose={() => toggleConfig(false)}
|
||||
open={showAgentSettings}
|
||||
placement={'bottom'}
|
||||
title={t('topic.title')}
|
||||
>
|
||||
<Modal onCancel={() => toggleConfig(false)} open={showAgentSettings} title={t('topic.title')}>
|
||||
{children}
|
||||
</Drawer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ import { Flexbox } from 'react-layout-kit';
|
|||
|
||||
import AppLayout from '@/layout/AppLayout';
|
||||
import { useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
import { Sessions } from './features/SessionList';
|
||||
|
||||
const ChatLayout = memo<PropsWithChildren>(({ children }) => {
|
||||
useSwitchSideBarOnInit('chat');
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Chat);
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import { memo } from 'react';
|
|||
|
||||
import AppMobileLayout from '@/layout/AppMobileLayout';
|
||||
import { useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
import { Sessions } from './features/SessionList';
|
||||
import Header from './features/SessionList/Header';
|
||||
|
||||
const ChatMobileLayout = memo(() => {
|
||||
useSwitchSideBarOnInit('chat');
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Chat);
|
||||
|
||||
return (
|
||||
<AppMobileLayout navBar={<Header />} showTabBar>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Dropdown, MenuProps } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { HardDriveDownload, Share2 } from 'lucide-react';
|
||||
import { HardDriveDownload } from 'lucide-react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
|
||||
import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
|
|
@ -42,11 +43,10 @@ const Header = memo<{ mobile?: boolean }>(() => {
|
|||
[],
|
||||
);
|
||||
|
||||
const size = mobile ? undefined : { fontSize: 24 };
|
||||
const size = mobile ? MOBILE_HEADER_ICON_SIZE : { fontSize: 24 };
|
||||
|
||||
return (
|
||||
<Render>
|
||||
<ActionIcon icon={Share2} size={size} title={t('share', { ns: 'common' })} />
|
||||
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
|
||||
<ActionIcon icon={HardDriveDownload} size={size} title={t('export', { ns: 'common' })} />
|
||||
</Dropdown>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,11 @@
|
|||
import { PropsWithChildren, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import AppMobileLayout from '@/layout/AppMobileLayout';
|
||||
|
||||
import Header from './features/Header';
|
||||
|
||||
const MobileLayout = memo<PropsWithChildren>(({ children }) => {
|
||||
return (
|
||||
<AppMobileLayout navBar={<Header />}>
|
||||
<Flexbox gap={16} padding={16}>
|
||||
{children}
|
||||
</Flexbox>
|
||||
</AppMobileLayout>
|
||||
);
|
||||
return <AppMobileLayout navBar={<Header />}>{children}</AppMobileLayout>;
|
||||
});
|
||||
|
||||
export default MobileLayout;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ interface AgentCardBannerProps extends DivProps {
|
|||
}
|
||||
|
||||
const AgentCardBanner = memo<AgentCardBannerProps>(
|
||||
({ meta, className, mask, size = 8, maskColor, ...props }) => {
|
||||
({ meta, className, mask, size = 8, maskColor, children, ...props }) => {
|
||||
const { styles, theme, cx } = useStyles(maskColor);
|
||||
|
||||
return (
|
||||
|
|
@ -56,6 +56,7 @@ const AgentCardBanner = memo<AgentCardBannerProps>(
|
|||
style={{ transform: `scale(${size})` }}
|
||||
/>
|
||||
{mask && <div className={styles.bannerMask} />}
|
||||
{children}
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,27 +1,8 @@
|
|||
import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
|
||||
import { Settings2 } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Logo, MobileNavBar } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
|
||||
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
||||
|
||||
const Header = memo(() => {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<MobileNavBar
|
||||
center={<Logo type={'text'} />}
|
||||
left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
|
||||
right={
|
||||
<ActionIcon
|
||||
icon={Settings2}
|
||||
onClick={() => {
|
||||
router.push('/settings');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <MobileNavBar center={<Logo type={'text'} />} />;
|
||||
});
|
||||
|
||||
export default Header;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
|
|||
import { Center } from 'react-layout-kit';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ const Header = memo(() => {
|
|||
if (!agentItem) return;
|
||||
|
||||
createSession({ config, meta });
|
||||
switchSideBar('chat');
|
||||
switchSideBar(SidebarTabKey.Chat);
|
||||
}}
|
||||
type={'primary'}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Markdown, TabsNav } from '@lobehub/ui';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { memo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
|
@ -22,7 +23,8 @@ const AgentModalInner = memo(() => {
|
|||
s.useFetchAgent,
|
||||
s.currentIdentifier,
|
||||
]);
|
||||
const { styles } = useStyles();
|
||||
const { styles, theme } = useStyles();
|
||||
const { mobile } = useResponsive();
|
||||
const { data, isLoading } = useFetchAgent(currentIdentifier);
|
||||
const { t } = useTranslation('market');
|
||||
const [tab, setTab] = useState<string>(InfoTabs.prompt);
|
||||
|
|
@ -34,7 +36,13 @@ const AgentModalInner = memo(() => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<AgentCardBanner mask meta={meta} size={10} style={{ height: 120, marginBottom: -60 }} />
|
||||
<AgentCardBanner
|
||||
mask
|
||||
maskColor={mobile ? theme.colorBgContainer : undefined}
|
||||
meta={meta}
|
||||
size={10}
|
||||
style={{ height: 120, marginBottom: -60 }}
|
||||
/>
|
||||
<Header />
|
||||
<Flexbox align={'center'}>
|
||||
<TabsNav
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Drawer } from 'antd';
|
||||
import { useTheme } from 'antd-style';
|
||||
import { X } from 'lucide-react';
|
||||
import { Modal } from '@lobehub/ui';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
||||
|
||||
const Mobile = memo<PropsWithChildren>(({ children }) => {
|
||||
const theme = useTheme();
|
||||
const [showAgentSidebar, deactivateAgent] = useMarketStore((s) => [
|
||||
agentMarketSelectors.showSideBar(s),
|
||||
s.deactivateAgent,
|
||||
|
|
@ -17,19 +13,9 @@ const Mobile = memo<PropsWithChildren>(({ children }) => {
|
|||
const { t } = useTranslation('market');
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
bodyStyle={{ padding: 0 }}
|
||||
closeIcon={<ActionIcon icon={X} size={{ blockSize: 32, fontSize: 20 }} />}
|
||||
drawerStyle={{ background: theme.colorBgLayout }}
|
||||
headerStyle={{ padding: '8px 4px' }}
|
||||
height={'75vh'}
|
||||
onClose={deactivateAgent}
|
||||
open={showAgentSidebar}
|
||||
placement={'bottom'}
|
||||
title={t('sidebar.title')}
|
||||
>
|
||||
<Modal onCancel={deactivateAgent} open={showAgentSidebar} title={t('sidebar.title')}>
|
||||
{children}
|
||||
</Drawer>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Center, Flexbox } from 'react-layout-kit';
|
|||
import SafeSpacing from '@/components/SafeSpacing';
|
||||
import { MAX_WIDTH } from '@/const/layoutTokens';
|
||||
import { useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
import AppLayout from '../../layout/AppLayout';
|
||||
import Header from './features/Header';
|
||||
|
|
@ -27,7 +28,7 @@ const useStyles = createStyles(({ css }) => ({
|
|||
const MarketLayout = memo<PropsWithChildren>(({ children }) => {
|
||||
const { theme, styles } = useStyles();
|
||||
|
||||
useSwitchSideBarOnInit('market');
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Market);
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import { Flexbox } from 'react-layout-kit';
|
|||
|
||||
import AppMobileLayout from '@/layout/AppMobileLayout';
|
||||
import { useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
import Header from './features/Header';
|
||||
import SideBar from './features/SideBar';
|
||||
|
||||
const MarketLayout = memo<{ children: ReactNode }>(({ children }) => {
|
||||
useSwitchSideBarOnInit('market');
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Market);
|
||||
|
||||
return (
|
||||
<AppMobileLayout navBar={<Header />} showTabBar>
|
||||
|
|
|
|||
20
src/app/settings/agent/index.tsx
Normal file
20
src/app/settings/agent/index.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
|
||||
import { SettingsTabs } from '@/store/global/initialState';
|
||||
|
||||
import Layout from '../index';
|
||||
import Agent from './Agent';
|
||||
|
||||
const AgentSetting = memo(() => {
|
||||
useSwitchSideBarOnInit(SettingsTabs.Agent);
|
||||
return (
|
||||
<Layout>
|
||||
<Agent />
|
||||
</Layout>
|
||||
);
|
||||
});
|
||||
|
||||
export default AgentSetting;
|
||||
7
src/app/settings/agent/page.tsx
Normal file
7
src/app/settings/agent/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Page from './index';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
|
@ -15,7 +15,7 @@ import { useSessionStore } from '@/store/session';
|
|||
import { ConfigKeys } from '@/types/settings';
|
||||
import { switchLang } from '@/utils/switchLang';
|
||||
|
||||
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../ThemeSwatches';
|
||||
import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from '../features/ThemeSwatches';
|
||||
|
||||
type SettingItemGroup = ItemGroup & {
|
||||
children: {
|
||||
20
src/app/settings/common/index.tsx
Normal file
20
src/app/settings/common/index.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
|
||||
import { SettingsTabs } from '@/store/global/initialState';
|
||||
|
||||
import Layout from '../index';
|
||||
import Common from './Common';
|
||||
|
||||
const CommonSetting = memo(() => {
|
||||
useSwitchSideBarOnInit(SettingsTabs.Common);
|
||||
return (
|
||||
<Layout>
|
||||
<Common />
|
||||
</Layout>
|
||||
);
|
||||
});
|
||||
|
||||
export default CommonSetting;
|
||||
7
src/app/settings/common/page.tsx
Normal file
7
src/app/settings/common/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Page from './index';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
import { GridBackground, Icon, Logo } from '@lobehub/ui';
|
||||
import { createStyles, useResponsive } from 'antd-style';
|
||||
import { PackageCheck } from 'lucide-react';
|
||||
import { rgba } from 'polished';
|
||||
import { ReactNode, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import pkg from '@/../package.json';
|
||||
|
||||
const useStyles = createStyles(({ css, token, isDarkMode, prefixCls }) => ({
|
||||
background: css`
|
||||
position: absolute;
|
||||
bottom: -10%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
`,
|
||||
banner: css`
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
max-width: 1024px;
|
||||
height: 160px;
|
||||
padding: 4px;
|
||||
|
||||
background: radial-gradient(
|
||||
120% 120% at 20% 100%,
|
||||
${isDarkMode ? rgba(token.colorBgContainer, 0.5) : token.colorBgContainer} 32%,
|
||||
${isDarkMode ? token.colorPrimaryBgHover : rgba(token.colorPrimaryBgHover, 0.3)} 50%,
|
||||
${isDarkMode ? token.colorPrimaryHover : rgba(token.colorPrimaryHover, 0.3)} 100%
|
||||
);
|
||||
border: 1px solid ${token.colorBorderSecondary};
|
||||
border-radius: ${token.borderRadiusLG}px;
|
||||
`,
|
||||
logo: css`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 32px;
|
||||
transform: translateY(-50%);
|
||||
`,
|
||||
mobile: css`
|
||||
margin-top: -16px;
|
||||
|
||||
.${prefixCls}-tabs-tab, .${prefixCls}-tabs-tab + .${prefixCls}-tabs-tab {
|
||||
margin: 4px 8px !important;
|
||||
}
|
||||
`,
|
||||
tag: css`
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 12px;
|
||||
|
||||
font-family: ${token.fontFamilyCode};
|
||||
font-size: 12px;
|
||||
color: ${isDarkMode ? token.colorPrimaryBg : token.colorPrimaryActive};
|
||||
|
||||
opacity: 0.5;
|
||||
`,
|
||||
}));
|
||||
|
||||
const Banner = memo<{ nav: ReactNode }>(({ nav }) => {
|
||||
const { styles, theme } = useStyles();
|
||||
const { mobile } = useResponsive();
|
||||
|
||||
if (mobile)
|
||||
return (
|
||||
<Flexbox align={'flex-end'} className={styles.mobile} horizontal justify={'center'}>
|
||||
{nav}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return (
|
||||
<Flexbox align={'flex-end'} className={styles.banner} horizontal justify={'center'}>
|
||||
<GridBackground
|
||||
animation
|
||||
className={styles.background}
|
||||
colorBack={theme.colorFillSecondary}
|
||||
colorFront={theme.colorPrimary}
|
||||
random
|
||||
/>
|
||||
{nav}
|
||||
<div className={styles.logo}>
|
||||
<Logo extra={'Chat'} type={'text'} />
|
||||
</div>
|
||||
<Flexbox align={'center'} className={styles.tag} gap={4} horizontal>
|
||||
<Icon icon={PackageCheck} />
|
||||
<div>{`${pkg.version}`}</div>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
export default Banner;
|
||||
|
|
@ -1,13 +1,20 @@
|
|||
import { ChatHeader, ChatHeaderTitle, Logo } from '@lobehub/ui';
|
||||
import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
const Index = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const tab = useGlobalStore((s) => s.settingsTab);
|
||||
|
||||
return (
|
||||
<ChatHeader
|
||||
left={<ChatHeaderTitle title={<Logo extra={t('header.global')} type={'text'} />} />}
|
||||
left={
|
||||
<div style={{ paddingLeft: 8 }}>
|
||||
<ChatHeaderTitle title={t(`tab.${tab}`)} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ import { useRouter } from 'next/navigation';
|
|||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
const Header = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const tab = useGlobalStore((s) => s.settingsTab);
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<MobileNavBar
|
||||
center={<MobileNavBarTitle title={t('header.global')} />}
|
||||
onBackClick={() => router.push('/chat')}
|
||||
center={<MobileNavBarTitle title={t(`tab.${tab}`)} />}
|
||||
onBackClick={() => router.back()}
|
||||
showBackButton
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
import { TabsNav } from '@lobehub/ui';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Center } from 'react-layout-kit';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
import Banner from '../Banner';
|
||||
import Agent from './Agent';
|
||||
import Common from './Common';
|
||||
import LLM from './LLM';
|
||||
|
||||
const Settings = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
|
||||
const [tab, setTab] = useGlobalStore((s) => [s.settingsTab, s.switchSettingTabs]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
switch (tab) {
|
||||
case 'llm': {
|
||||
return <LLM />;
|
||||
}
|
||||
case 'agent': {
|
||||
return <Agent />;
|
||||
}
|
||||
default:
|
||||
case 'common': {
|
||||
return <Common />;
|
||||
}
|
||||
}
|
||||
}, [tab]);
|
||||
|
||||
return (
|
||||
<Center gap={16} width={'100%'}>
|
||||
<Banner
|
||||
nav={
|
||||
<TabsNav
|
||||
activeKey={tab}
|
||||
items={[
|
||||
{ key: 'common', label: t('tab.common') },
|
||||
{ key: 'llm', label: t('tab.llm') },
|
||||
{ key: 'agent', label: t('tab.agent') },
|
||||
]}
|
||||
onChange={(e) => setTab(e as any)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{content}
|
||||
</Center>
|
||||
);
|
||||
});
|
||||
|
||||
export default Settings;
|
||||
39
src/app/settings/features/SideBar/Item.tsx
Normal file
39
src/app/settings/features/SideBar/Item.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Icon, List } from '@lobehub/ui';
|
||||
import { createStyles, useResponsive } from 'antd-style';
|
||||
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
||||
import { CSSProperties, ReactNode, memo } from 'react';
|
||||
|
||||
const { Item } = List;
|
||||
|
||||
const useStyles = createStyles(({ css }) => ({
|
||||
container: css`
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
`,
|
||||
}));
|
||||
|
||||
export interface ItemProps {
|
||||
active?: boolean;
|
||||
className?: string;
|
||||
icon: LucideIcon;
|
||||
label: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const SettingItem = memo<ItemProps>(({ label, icon, active = false, style, className }) => {
|
||||
const { cx, styles } = useStyles();
|
||||
const { mobile } = useResponsive();
|
||||
return (
|
||||
<Item
|
||||
active={active}
|
||||
avatar={<Icon icon={icon} size={{ fontSize: 16 }} />}
|
||||
className={cx(styles.container, className)}
|
||||
style={style}
|
||||
title={label as string}
|
||||
>
|
||||
{mobile && <Icon icon={ChevronRight} size={{ fontSize: 16 }} />}
|
||||
</Item>
|
||||
);
|
||||
});
|
||||
|
||||
export default SettingItem;
|
||||
30
src/app/settings/features/SideBar/List.tsx
Normal file
30
src/app/settings/features/SideBar/List.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { useResponsive } from 'antd-style';
|
||||
import { Bot, Settings2, Webhook } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SettingsTabs } from '@/store/global/initialState';
|
||||
|
||||
import Item from './Item';
|
||||
|
||||
const List = memo(() => {
|
||||
const { t } = useTranslation('setting');
|
||||
const tab = useGlobalStore((s) => s.settingsTab);
|
||||
const router = useRouter();
|
||||
const { mobile } = useResponsive();
|
||||
const items = [
|
||||
{ icon: Settings2, label: t('tab.common'), value: SettingsTabs.Common },
|
||||
{ icon: Webhook, label: t('tab.llm'), value: SettingsTabs.LLM },
|
||||
{ icon: Bot, label: t('tab.agent'), value: SettingsTabs.Agent },
|
||||
];
|
||||
|
||||
return items.map(({ value, icon, label }) => (
|
||||
<div key={value} onClick={() => router.push(`/settings/${value}`)}>
|
||||
<Item active={mobile ? false : tab === value} icon={icon} label={label} />
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
export default List;
|
||||
38
src/app/settings/features/SideBar/index.tsx
Normal file
38
src/app/settings/features/SideBar/index.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { DraggablePanelBody, Logo } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import FolderPanel from '@/features/FolderPanel';
|
||||
|
||||
import List from './List';
|
||||
|
||||
const useStyles = createStyles(({ stylish, token, css }) => ({
|
||||
body: stylish.noScrollbar,
|
||||
logo: css`
|
||||
fill: ${token.colorText};
|
||||
`,
|
||||
top: css`
|
||||
position: sticky;
|
||||
top: 0;
|
||||
`,
|
||||
}));
|
||||
|
||||
const SideBar = memo(() => {
|
||||
const { styles } = useStyles();
|
||||
|
||||
return (
|
||||
<FolderPanel>
|
||||
<DraggablePanelBody className={styles.body} style={{ padding: 0 }}>
|
||||
<Flexbox className={styles.top} padding={16}>
|
||||
<div>
|
||||
<Logo className={styles.logo} size={36} type={'text'} />
|
||||
</div>
|
||||
</Flexbox>
|
||||
<List />
|
||||
</DraggablePanelBody>
|
||||
</FolderPanel>
|
||||
);
|
||||
});
|
||||
|
||||
export default SideBar;
|
||||
|
|
@ -2,33 +2,31 @@
|
|||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import Head from 'next/head';
|
||||
import { memo } from 'react';
|
||||
import { ReactNode, memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
|
||||
|
||||
import Settings from './features/Settings';
|
||||
import DesktopLayout from './layout.desktop';
|
||||
import MobileLayout from './layout.mobile';
|
||||
|
||||
const Setting = memo(() => {
|
||||
const Setting = memo<{ children: ReactNode }>(({ children }) => {
|
||||
const { mobile } = useResponsive();
|
||||
const { t } = useTranslation('setting');
|
||||
const pageTitle = genSiteHeadTitle(t('header.global'));
|
||||
|
||||
const RenderLayout = mobile ? MobileLayout : DesktopLayout;
|
||||
|
||||
useSwitchSideBarOnInit('settings');
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Setting);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{pageTitle}</title>
|
||||
</Head>
|
||||
<RenderLayout>
|
||||
<Settings />
|
||||
</RenderLayout>
|
||||
<RenderLayout>{children}</RenderLayout>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
import { ReactNode, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
import { Center, Flexbox } from 'react-layout-kit';
|
||||
|
||||
import SafeSpacing from '@/components/SafeSpacing';
|
||||
import AppLayout from '@/layout/AppLayout';
|
||||
|
||||
import Header from './features/Header';
|
||||
import SideBar from './features/SideBar';
|
||||
|
||||
const SettingLayout = memo<{ children: ReactNode }>(({ children }) => {
|
||||
return (
|
||||
<AppLayout>
|
||||
<SideBar />
|
||||
<Flexbox flex={1} height={'100vh'} style={{ position: 'relative' }}>
|
||||
<Header />
|
||||
<Flexbox align={'center'} flex={1} padding={24} style={{ overflow: 'auto' }}>
|
||||
<SafeSpacing />
|
||||
{children}
|
||||
<Center gap={16} width={'100%'}>
|
||||
{children}
|
||||
</Center>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</AppLayout>
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import Header from './features/Header';
|
|||
const SettingLayout = memo<{ children: ReactNode }>(({ children }) => {
|
||||
return (
|
||||
<AppMobileLayout navBar={<Header />}>
|
||||
<Flexbox align={'center'} padding={16} style={{ overflow: 'auto' }}>
|
||||
{children}
|
||||
</Flexbox>
|
||||
<Flexbox style={{ overflow: 'auto' }}>{children}</Flexbox>
|
||||
</AppMobileLayout>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Form, Markdown } from '@lobehub/ui';
|
||||
import { Form, type ItemGroup, Markdown } from '@lobehub/ui';
|
||||
import { Form as AntForm, AutoComplete, Input, Switch } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
|
@ -49,7 +49,7 @@ const LLM = memo(() => {
|
|||
|
||||
const useAzure = useGlobalStore((s) => s.settings.languageModel.openAI.useAzure);
|
||||
|
||||
const openAI = {
|
||||
const openAI: ItemGroup = {
|
||||
children: [
|
||||
{
|
||||
children: (
|
||||
|
|
@ -94,6 +94,7 @@ const LLM = memo(() => {
|
|||
),
|
||||
desc: t('llm.OpenAI.useAzure.desc'),
|
||||
label: t('llm.OpenAI.useAzure.title'),
|
||||
minWidth: undefined,
|
||||
name: [configKey, 'openAI', 'useAzure'],
|
||||
valuePropName: 'checked',
|
||||
},
|
||||
|
|
@ -124,6 +125,7 @@ const LLM = memo(() => {
|
|||
children: <Checker checkModel={!useAzure} />,
|
||||
desc: t('llm.OpenAI.check.desc'),
|
||||
label: t('llm.OpenAI.check.title'),
|
||||
minWidth: undefined,
|
||||
},
|
||||
// {
|
||||
// children: useAzure ? <Flexbox>{t('llm.OpenAI.models.notSupport')}</Flexbox> : <ModelList />,
|
||||
20
src/app/settings/llm/index.tsx
Normal file
20
src/app/settings/llm/index.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useSwitchSideBarOnInit } from '@/store/global/hooks/useSwitchSettingsOnInit';
|
||||
import { SettingsTabs } from '@/store/global/initialState';
|
||||
|
||||
import Layout from '../index';
|
||||
import LLM from './LLM';
|
||||
|
||||
const LLMSetting = memo(() => {
|
||||
useSwitchSideBarOnInit(SettingsTabs.LLM);
|
||||
return (
|
||||
<Layout>
|
||||
<LLM />
|
||||
</Layout>
|
||||
);
|
||||
});
|
||||
|
||||
export default LLMSetting;
|
||||
7
src/app/settings/llm/page.tsx
Normal file
7
src/app/settings/llm/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Page from './index';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
61
src/app/settings/mobile/ExtraList.tsx
Normal file
61
src/app/settings/mobile/ExtraList.tsx
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { Upload } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { Feather, FileClock, HardDriveDownload, HardDriveUpload, Heart } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ABOUT, CHANGELOG, FEEDBACK } from '@/const/url';
|
||||
import { useExportConfig } from '@/hooks/useExportConfig';
|
||||
import { useImportConfig } from '@/hooks/useImportConfig';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
|
||||
import Item from '../features/SideBar/Item';
|
||||
|
||||
const ExtraList = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
const { exportAll } = useExportConfig();
|
||||
const { importConfig } = useImportConfig();
|
||||
const tab = useGlobalStore((s) => s.settingsTab);
|
||||
const { mobile } = useResponsive();
|
||||
const items = [
|
||||
{
|
||||
icon: HardDriveDownload,
|
||||
label: t('export'),
|
||||
onClick: exportAll,
|
||||
value: 'export',
|
||||
},
|
||||
{
|
||||
icon: Feather,
|
||||
label: t('feedback'),
|
||||
onClick: () => window.open(FEEDBACK, '__blank'),
|
||||
value: 'feedback',
|
||||
},
|
||||
{
|
||||
icon: FileClock,
|
||||
label: t('changelog'),
|
||||
onClick: () => window.open(CHANGELOG, '__blank'),
|
||||
value: 'changelog',
|
||||
},
|
||||
{
|
||||
icon: Heart,
|
||||
label: t('about'),
|
||||
onClick: () => window.open(ABOUT, '__blank'),
|
||||
value: 'about',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Upload maxCount={1} onChange={importConfig} showUploadList={false}>
|
||||
<Item icon={HardDriveUpload} label={t('import')} style={{ width: '100vw' }} />
|
||||
</Upload>
|
||||
{items.map(({ value, icon, label, onClick }) => (
|
||||
<div key={value} onClick={onClick}>
|
||||
<Item active={mobile ? false : tab === value} icon={icon} label={label} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default ExtraList;
|
||||
43
src/app/settings/mobile/index.tsx
Normal file
43
src/app/settings/mobile/index.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
'use client';
|
||||
|
||||
import { Divider } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { Center } from 'react-layout-kit';
|
||||
|
||||
import AgentCardBanner from '@/app/market/features/AgentCard/AgentCardBanner';
|
||||
import Header from '@/app/market/features/Header';
|
||||
import AvatarWithUpload from '@/features/AvatarWithUpload';
|
||||
import AppMobileLayout from '@/layout/AppMobileLayout';
|
||||
import { useGlobalStore, useSwitchSideBarOnInit } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { AVATAR } from '@/store/session/slices/chat/actions/share';
|
||||
|
||||
import List from '../features/SideBar/List';
|
||||
import ExtraList from './ExtraList';
|
||||
|
||||
const Setting = memo(() => {
|
||||
const avatar = useGlobalStore((s) => s.settings.avatar);
|
||||
useSwitchSideBarOnInit(SidebarTabKey.Setting);
|
||||
return (
|
||||
<AppMobileLayout navBar={<Header />} showTabBar>
|
||||
<AgentCardBanner
|
||||
mask
|
||||
meta={{ avatar: avatar || AVATAR }}
|
||||
size={10}
|
||||
style={{ height: 180, marginBottom: 0 }}
|
||||
>
|
||||
<Center style={{ position: 'absolute', zIndex: 2 }}>
|
||||
<AvatarWithUpload size={100} />
|
||||
</Center>
|
||||
</AgentCardBanner>
|
||||
<div style={{ width: '100%' }}>
|
||||
<Divider style={{ margin: '0 0 8px' }} />
|
||||
<List />
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
<ExtraList />
|
||||
</div>
|
||||
</AppMobileLayout>
|
||||
);
|
||||
});
|
||||
|
||||
export default Setting;
|
||||
7
src/app/settings/mobile/page.tsx
Normal file
7
src/app/settings/mobile/page.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Page from './index';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
|
@ -1,7 +1 @@
|
|||
import Page from './index';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
export { default } from './common/page';
|
||||
|
|
|
|||
32
src/components/MobilePadding/index.tsx
Normal file
32
src/components/MobilePadding/index.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { useResponsive } from 'antd-style';
|
||||
import { ReactNode, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
interface MobilePaddingProps {
|
||||
bottom?: number;
|
||||
children: ReactNode;
|
||||
gap?: number;
|
||||
left?: number;
|
||||
right?: number;
|
||||
top?: number;
|
||||
}
|
||||
|
||||
const MobilePadding = memo<MobilePaddingProps>(
|
||||
({ children, top = 16, right = 16, left = 16, bottom = 16, gap }) => {
|
||||
const { mobile } = useResponsive();
|
||||
|
||||
if (mobile)
|
||||
return (
|
||||
<Flexbox
|
||||
gap={gap}
|
||||
style={{ paddingBottom: bottom, paddingLeft: left, paddingRight: right, paddingTop: top }}
|
||||
>
|
||||
{children}
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
return children;
|
||||
},
|
||||
);
|
||||
|
||||
export default MobilePadding;
|
||||
|
|
@ -13,3 +13,4 @@ export const FORM_STYLE: FormProps = {
|
|||
itemMinWidth: 'max(30%,240px)',
|
||||
style: { maxWidth: MAX_WIDTH, width: '100%' },
|
||||
};
|
||||
export const MOBILE_HEADER_ICON_SIZE = { blockSize: 36, fontSize: 22 };
|
||||
|
|
|
|||
|
|
@ -78,7 +78,6 @@ const AgentMeta = memo(() => {
|
|||
onChange={(backgroundColor) => updateMeta({ backgroundColor })}
|
||||
/>
|
||||
),
|
||||
divider: false,
|
||||
label: t('settingAgent.backgroundColor.title'),
|
||||
minWidth: undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Input, Modal, Tooltip } from '@lobehub/ui';
|
||||
import { Form } from 'antd';
|
||||
import { Form, Input, Modal } from '@lobehub/ui';
|
||||
import { Alert } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import MobilePadding from '@/components/MobilePadding';
|
||||
import { PLUGINS_INDEX_URL } from '@/const/url';
|
||||
|
||||
interface MarketSettingModalProps {
|
||||
|
|
@ -15,20 +16,32 @@ const MarketSettingModal = memo<MarketSettingModalProps>(({ open, onOpenChange }
|
|||
const { t } = useTranslation('plugin');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
footer={null}
|
||||
onCancel={() => onOpenChange(false)}
|
||||
open={open}
|
||||
title={t('settings.title')}
|
||||
>
|
||||
<Flexbox gap={12}>
|
||||
<Form layout={'vertical'}>
|
||||
<Form.Item extra={t('settings.modalDesc')} label={t('settings.indexUrl.title')}>
|
||||
<Tooltip title={t('settings.indexUrl.tooltip')}>
|
||||
<Input defaultValue={PLUGINS_INDEX_URL} disabled placeholder={'https://xxxxx.com'} />
|
||||
</Tooltip>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Modal footer={null} onCancel={() => onOpenChange(false)} open={open} title={t('setting')}>
|
||||
<Flexbox gap={16}>
|
||||
<MobilePadding bottom={0} gap={16}>
|
||||
<Alert message={t('settings.indexUrl.tooltip')} showIcon type={'warning'} />
|
||||
</MobilePadding>
|
||||
<Form
|
||||
items={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
children: (
|
||||
<Input
|
||||
defaultValue={PLUGINS_INDEX_URL}
|
||||
disabled
|
||||
placeholder={PLUGINS_INDEX_URL}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
),
|
||||
desc: t('settings.modalDesc'),
|
||||
label: t('settings.indexUrl.title'),
|
||||
},
|
||||
],
|
||||
title: t('settings.title'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Flexbox>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { Icon, MobileTabBar, type MobileTabBarProps } from '@lobehub/ui';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { Bot, MessageSquare } from 'lucide-react';
|
||||
import { Bot, MessageSquare, User } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { rgba } from 'polished';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
const useStyles = createStyles(({ css, token }) => ({
|
||||
active: css`
|
||||
|
|
@ -27,20 +28,31 @@ export default memo<{ className?: string }>(({ className }) => {
|
|||
icon: (active) => (
|
||||
<Icon className={active ? styles.active : undefined} icon={MessageSquare} />
|
||||
),
|
||||
key: 'chat',
|
||||
key: SidebarTabKey.Chat,
|
||||
onClick: () => {
|
||||
setTab(SidebarTabKey.Chat);
|
||||
router.push('/chat');
|
||||
},
|
||||
title: t('tab.chat'),
|
||||
},
|
||||
{
|
||||
icon: (active) => <Icon className={active ? styles.active : undefined} icon={Bot} />,
|
||||
key: 'market',
|
||||
key: SidebarTabKey.Market,
|
||||
onClick: () => {
|
||||
setTab(SidebarTabKey.Market);
|
||||
router.push('/market');
|
||||
},
|
||||
title: t('tab.market'),
|
||||
},
|
||||
{
|
||||
icon: (active) => <Icon className={active ? styles.active : undefined} icon={User} />,
|
||||
key: SidebarTabKey.Setting,
|
||||
onClick: () => {
|
||||
setTab(SidebarTabKey.Setting);
|
||||
router.push('/settings/mobile');
|
||||
},
|
||||
title: t('tab.setting'),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Avatar, Form } from '@lobehub/ui';
|
||||
import { Form as AForm, Card, FormInstance, Switch, Tag } from 'antd';
|
||||
import { Form as AForm, FormInstance, Switch, Tag } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
|
@ -28,9 +28,16 @@ const PluginPreview = memo<{ form: FormInstance }>(({ form }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Card size={'small'} title={t('dev.preview.card')}>
|
||||
<Form.Item {...items} colon={false} style={{ marginBottom: 0 }} />
|
||||
</Card>
|
||||
<Form
|
||||
colon={false}
|
||||
items={[
|
||||
{
|
||||
children: [items],
|
||||
title: t('dev.preview.card'),
|
||||
},
|
||||
]}
|
||||
style={{ marginBottom: 0 }}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { Modal } from '@lobehub/ui';
|
||||
import { App, Button, Form, Popconfirm } from 'antd';
|
||||
import { Alert, App, Button, Form, Popconfirm } from 'antd';
|
||||
import { useResponsive } from 'antd-style';
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import MobilePadding from '@/components/MobilePadding';
|
||||
import { CustomPlugin } from '@/types/plugin';
|
||||
|
||||
import ManifestForm from './ManifestForm';
|
||||
|
|
@ -25,7 +27,7 @@ const DevModal = memo<DevModalProps>(
|
|||
const isEditMode = mode === 'edit';
|
||||
const { t } = useTranslation('plugin');
|
||||
const { message } = App.useApp();
|
||||
|
||||
const { mobile } = useResponsive();
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
useEffect(() => {
|
||||
|
|
@ -98,14 +100,16 @@ const DevModal = memo<DevModalProps>(
|
|||
open={open}
|
||||
title={t('dev.title')}
|
||||
>
|
||||
<Flexbox gap={12}>
|
||||
{t('dev.modalDesc')}
|
||||
{/*<Tabs*/}
|
||||
{/* items={[*/}
|
||||
{/* { children: <MetaForm />, key: 'meta', label: t('dev.tabs.meta') },*/}
|
||||
{/* { children: <ManifestForm />, key: 'manifest', label: t('dev.tabs.manifest') },*/}
|
||||
{/* ]}*/}
|
||||
{/*/>*/}
|
||||
<Flexbox gap={mobile ? 0 : 16}>
|
||||
<MobilePadding bottom={0} gap={16}>
|
||||
<Alert message={t('dev.modalDesc')} showIcon type={'info'} />
|
||||
{/*<Tabs*/}
|
||||
{/* items={[*/}
|
||||
{/* { children: <MetaForm />, key: 'meta', label: t('dev.tabs.meta') },*/}
|
||||
{/* { children: <ManifestForm />, key: 'manifest', label: t('dev.tabs.manifest') },*/}
|
||||
{/* ]}*/}
|
||||
{/*/>*/}
|
||||
</MobilePadding>
|
||||
<PluginPreview form={form} />
|
||||
<ManifestForm form={form} />
|
||||
<MetaForm form={form} mode={mode} />
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { ABOUT, CHANGELOG, DISCORD, FEEDBACK, GITHUB } from '@/const/url';
|
|||
import { useExportConfig } from '@/hooks/useExportConfig';
|
||||
import { useImportConfig } from '@/hooks/useImportConfig';
|
||||
import { GlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
export interface BottomActionProps {
|
||||
setTab: GlobalStore['switchSideBar'];
|
||||
|
|
@ -27,7 +28,6 @@ export interface BottomActionProps {
|
|||
const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
|
||||
const { importConfig } = useImportConfig();
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
|
|||
key: 'setting',
|
||||
label: t('setting'),
|
||||
onClick: () => {
|
||||
setTab('settings');
|
||||
setTab(SidebarTabKey.Setting);
|
||||
router.push('/settings');
|
||||
},
|
||||
},
|
||||
|
|
@ -124,7 +124,7 @@ const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
|
|||
title={'Github'}
|
||||
/>
|
||||
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
|
||||
<ActionIcon active={tab === 'settings'} icon={Settings2} />
|
||||
<ActionIcon active={tab === SidebarTabKey.Setting} icon={Settings2} />
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { memo } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { GlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import { useSessionStore } from '@/store/session';
|
||||
|
||||
export interface TopActionProps {
|
||||
|
|
@ -20,25 +21,25 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
|
|||
return (
|
||||
<>
|
||||
<ActionIcon
|
||||
active={tab === 'chat'}
|
||||
active={tab === SidebarTabKey.Chat}
|
||||
icon={MessageSquare}
|
||||
onClick={() => {
|
||||
// 如果已经在 chat 路径下了,那么就不用再跳转了
|
||||
if (pathname?.startsWith('/chat')) return;
|
||||
switchBackToChat();
|
||||
setTab('chat');
|
||||
setTab(SidebarTabKey.Chat);
|
||||
}}
|
||||
placement={'right'}
|
||||
size="large"
|
||||
title={t('tab.chat')}
|
||||
/>
|
||||
<ActionIcon
|
||||
active={tab === 'market'}
|
||||
active={tab === SidebarTabKey.Market}
|
||||
icon={Bot}
|
||||
onClick={() => {
|
||||
if (pathname?.startsWith('/market')) return;
|
||||
router.push('/market');
|
||||
setTab('market');
|
||||
setTab(SidebarTabKey.Market);
|
||||
}}
|
||||
placement={'right'}
|
||||
size="large"
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export default {
|
|||
import: '导入配置',
|
||||
inbox: {
|
||||
defaultMessage:
|
||||
'你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击<kbd>+</kbd>创建自定义助手',
|
||||
'你好,我是你的智能助手,你可以问我任何问题,我会尽力回答你。如果需要获得更加专业或定制的助手,可以点击`+`创建自定义助手',
|
||||
desc: '开启大脑集群,激发思维火花。你的智能助理,在这里与你交流一切',
|
||||
title: '随便聊聊',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -96,10 +96,11 @@ export default {
|
|||
plugins: {
|
||||
unknown: '插件检测中...',
|
||||
},
|
||||
setting: '插件设置',
|
||||
settings: {
|
||||
indexUrl: {
|
||||
title: '市场索引',
|
||||
tooltip: '暂不支持编辑',
|
||||
tooltip: '暂不支持在线编辑,请通过部署时环境变量进行设置',
|
||||
},
|
||||
modalDesc: '配置插件市场的地址后,可以使用自定义的插件市场',
|
||||
title: '设置插件市场',
|
||||
|
|
|
|||
9
src/store/global/hooks/useSwitchSettingsOnInit.ts
Normal file
9
src/store/global/hooks/useSwitchSettingsOnInit.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { SettingsTabs } from '../initialState';
|
||||
import { useGlobalStore } from '../store';
|
||||
|
||||
/**
|
||||
* 切换设置侧边栏选项
|
||||
*/
|
||||
export const useSwitchSideBarOnInit = (key: SettingsTabs) => {
|
||||
useGlobalStore.getState().switchSettingTabs(key);
|
||||
};
|
||||
|
|
@ -1,9 +1,17 @@
|
|||
import { DEFAULT_SETTINGS } from '@/const/settings';
|
||||
import type { GlobalSettings } from '@/types/settings';
|
||||
|
||||
export type SidebarTabKey = 'chat' | 'market' | 'settings';
|
||||
export enum SidebarTabKey {
|
||||
Chat = 'chat',
|
||||
Market = 'market',
|
||||
Setting = 'settings',
|
||||
}
|
||||
|
||||
export type SettingsTabs = 'agent' | 'common' | 'llm';
|
||||
export enum SettingsTabs {
|
||||
Agent = 'agent',
|
||||
Common = 'common',
|
||||
LLM = 'llm',
|
||||
}
|
||||
|
||||
export interface Guide {
|
||||
// Topic 引导
|
||||
|
|
@ -21,7 +29,7 @@ export interface GlobalState {
|
|||
* 用户设置
|
||||
*/
|
||||
settings: GlobalSettings;
|
||||
settingsTab?: SettingsTabs;
|
||||
settingsTab: SettingsTabs;
|
||||
sidebarKey: SidebarTabKey;
|
||||
}
|
||||
|
||||
|
|
@ -44,5 +52,6 @@ export const initialState: GlobalState = {
|
|||
showSessionPanel: true,
|
||||
},
|
||||
settings: DEFAULT_SETTINGS,
|
||||
sidebarKey: 'chat',
|
||||
settingsTab: SettingsTabs.Common,
|
||||
sidebarKey: SidebarTabKey.Chat,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,10 +69,6 @@ const persistOptions: PersistOptions<GlobalStore, GlobalPersist> = {
|
|||
dbName: 'LobeHub',
|
||||
selectors: ['preference', 'settings'],
|
||||
},
|
||||
url: {
|
||||
mode: 'hash',
|
||||
selectors: [{ settingsTab: 'tab' }],
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,31 +1,7 @@
|
|||
import { Theme, css } from 'antd-style';
|
||||
import { readableColor } from 'polished';
|
||||
|
||||
export default ({ token, prefixCls }: { prefixCls: string; token: Theme }) => css`
|
||||
.${prefixCls}-btn {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.${prefixCls}-popover {
|
||||
export default ({ token }: { prefixCls: string; token: Theme }) => css`
|
||||
.${token.prefixCls}-popover {
|
||||
z-index: 1100;
|
||||
}
|
||||
|
||||
.${prefixCls}-slider-track, .${prefixCls}-tabs-ink-bar, .${prefixCls}-switch-checked {
|
||||
background: ${token.colorPrimary} !important;
|
||||
}
|
||||
|
||||
.${prefixCls}-btn-primary:not(.${prefixCls}-btn-dangerous) {
|
||||
color: ${readableColor(token.colorPrimary)};
|
||||
background: ${token.colorPrimary};
|
||||
|
||||
&:hover {
|
||||
color: ${readableColor(token.colorPrimary)} !important;
|
||||
background: ${token.colorPrimaryHover} !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: ${readableColor(token.colorPrimaryActive)} !important;
|
||||
background: ${token.colorPrimaryActive} !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -20,4 +20,13 @@ export default ({ prefixCls }: { prefixCls: string }) => css`
|
|||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
* {
|
||||
::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
|||
Loading…
Reference in a new issue