@lobehub/chat 0.152.9 → 0.152.11
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/locales/ar/common.json +14 -0
- package/locales/bg-BG/common.json +14 -0
- package/locales/de-DE/common.json +14 -0
- package/locales/en-US/common.json +14 -0
- package/locales/es-ES/common.json +14 -0
- package/locales/fr-FR/common.json +14 -0
- package/locales/it-IT/common.json +14 -0
- package/locales/ja-JP/common.json +14 -0
- package/locales/ko-KR/common.json +14 -0
- package/locales/nl-NL/common.json +14 -0
- package/locales/pl-PL/common.json +14 -0
- package/locales/pt-BR/common.json +14 -0
- package/locales/ru-RU/common.json +14 -0
- package/locales/tr-TR/common.json +14 -0
- package/locales/vi-VN/common.json +14 -0
- package/locales/zh-CN/common.json +14 -0
- package/locales/zh-TW/common.json +14 -0
- package/next.config.mjs +7 -0
- package/package.json +1 -1
- package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +9 -5
- package/src/app/(main)/(mobile)/me/features/Cate.tsx +1 -3
- package/src/app/(main)/(mobile)/me/features/ExtraCate.tsx +1 -3
- package/src/app/(main)/(mobile)/me/page.tsx +3 -3
- package/src/app/(main)/@nav/_layout/Desktop/Avatar.test.tsx +55 -0
- package/src/app/(main)/@nav/_layout/Desktop/Avatar.tsx +44 -2
- package/src/app/(main)/@nav/_layout/Desktop/BottomActions.tsx +4 -126
- package/src/app/(main)/@nav/_layout/Desktop/index.tsx +1 -1
- package/src/app/(main)/chat/features/SessionListContent/ListItem/index.tsx +5 -1
- package/src/app/(main)/market/features/AgentCard/index.tsx +4 -2
- package/src/app/(main)/market/features/AgentList.tsx +10 -1
- package/src/app/(main)/market/features/TagList.tsx +8 -4
- package/src/app/(main)/settings/_layout/Desktop/Header.tsx +1 -0
- package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
- package/src/app/(main)/settings/common/features/Common.tsx +6 -6
- package/src/components/Cell/Divider.tsx +2 -2
- package/src/components/Cell/index.tsx +2 -1
- package/src/features/AvatarWithUpload/index.tsx +8 -44
- package/src/features/DataImporter/index.tsx +11 -1
- package/src/features/User/UserAvatar.tsx +67 -0
- package/src/features/User/UserInfo.tsx +41 -0
- package/src/features/User/UserPanel/LangButton.tsx +57 -0
- package/src/features/User/UserPanel/Popover.tsx +35 -0
- package/src/features/User/UserPanel/ThemeButton.tsx +70 -0
- package/src/features/User/UserPanel/UserInfo.tsx +35 -0
- package/src/features/User/UserPanel/index.tsx +62 -0
- package/src/features/User/UserPanel/useMenu.tsx +158 -0
- package/src/features/User/UserPanel/useNewVersion.tsx +12 -0
- package/src/layout/AuthProvider/NextAuth/UserUpdater.tsx +35 -0
- package/src/layout/AuthProvider/NextAuth/index.tsx +8 -1
- package/src/locales/default/common.ts +14 -0
- package/src/store/user/slices/auth/initialState.ts +6 -8
- package/src/store/user/slices/auth/selectors.ts +4 -1
- package/src/store/user/slices/preference/action.test.ts +41 -3
- package/src/store/user/slices/preference/action.ts +8 -2
- package/src/store/user/slices/preference/initialState.ts +14 -5
- package/src/store/user/slices/preference/selectors.test.ts +82 -0
- package/src/store/user/slices/preference/selectors.ts +4 -0
- package/src/store/user/slices/settings/actions/general.ts +8 -0
- package/src/types/user.ts +9 -0
- package/src/app/(main)/settings/page.tsx +0 -7
|
@@ -1,123 +1,14 @@
|
|
|
1
|
-
import { ActionIcon
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
Book,
|
|
5
|
-
Feather,
|
|
6
|
-
FileClock,
|
|
7
|
-
Github,
|
|
8
|
-
HardDriveDownload,
|
|
9
|
-
HardDriveUpload,
|
|
10
|
-
Heart,
|
|
11
|
-
Settings,
|
|
12
|
-
Settings2,
|
|
13
|
-
} from 'lucide-react';
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Book, Github } from 'lucide-react';
|
|
14
3
|
import Link from 'next/link';
|
|
15
|
-
import { useRouter } from 'next/navigation';
|
|
16
4
|
import { memo } from 'react';
|
|
17
5
|
import { useTranslation } from 'react-i18next';
|
|
18
|
-
import { Flexbox } from 'react-layout-kit';
|
|
19
6
|
|
|
20
|
-
import {
|
|
21
|
-
import DataImporter from '@/features/DataImporter';
|
|
22
|
-
import { configService } from '@/services/config';
|
|
23
|
-
import { useGlobalStore } from '@/store/global';
|
|
24
|
-
import { SidebarTabKey } from '@/store/global/initialState';
|
|
7
|
+
import { DOCUMENTS, GITHUB } from '@/const/url';
|
|
25
8
|
|
|
26
|
-
|
|
27
|
-
tab?: SidebarTabKey;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const BottomActions = memo<BottomActionProps>(({ tab }) => {
|
|
31
|
-
const router = useRouter();
|
|
9
|
+
const BottomActions = memo(() => {
|
|
32
10
|
const { t } = useTranslation('common');
|
|
33
11
|
|
|
34
|
-
const [hasNewVersion, useCheckLatestVersion] = useGlobalStore((s) => [
|
|
35
|
-
s.hasNewVersion,
|
|
36
|
-
s.useCheckLatestVersion,
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
useCheckLatestVersion();
|
|
40
|
-
|
|
41
|
-
const items: MenuProps['items'] = [
|
|
42
|
-
{
|
|
43
|
-
icon: <Icon icon={HardDriveUpload} />,
|
|
44
|
-
key: 'import',
|
|
45
|
-
label: <DataImporter>{t('import')}</DataImporter>,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
children: [
|
|
49
|
-
{
|
|
50
|
-
key: 'allAgent',
|
|
51
|
-
label: <div>{t('exportType.allAgent')}</div>,
|
|
52
|
-
onClick: configService.exportAgents,
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
key: 'allAgentWithMessage',
|
|
56
|
-
label: <div>{t('exportType.allAgentWithMessage')}</div>,
|
|
57
|
-
onClick: configService.exportSessions,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
key: 'globalSetting',
|
|
61
|
-
label: <div>{t('exportType.globalSetting')}</div>,
|
|
62
|
-
onClick: configService.exportSettings,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
type: 'divider',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
key: 'all',
|
|
69
|
-
label: <div>{t('exportType.all')}</div>,
|
|
70
|
-
onClick: configService.exportAll,
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
icon: <Icon icon={HardDriveDownload} />,
|
|
74
|
-
key: 'export',
|
|
75
|
-
label: t('export'),
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
type: 'divider',
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
icon: <Icon icon={Feather} />,
|
|
82
|
-
key: 'feedback',
|
|
83
|
-
label: t('feedback'),
|
|
84
|
-
onClick: () => window.open(FEEDBACK, '__blank'),
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
icon: <Icon icon={FileClock} />,
|
|
88
|
-
key: 'changelog',
|
|
89
|
-
label: t('changelog'),
|
|
90
|
-
onClick: () => window.open(CHANGELOG, '__blank'),
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
icon: <Icon icon={DiscordIcon} />,
|
|
94
|
-
key: 'wiki',
|
|
95
|
-
label: 'Discord',
|
|
96
|
-
onClick: () => window.open(DISCORD, '__blank'),
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
icon: <Icon icon={Heart} />,
|
|
100
|
-
key: 'about',
|
|
101
|
-
label: t('about'),
|
|
102
|
-
onClick: () => window.open(ABOUT, '__blank'),
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
type: 'divider',
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
icon: <Icon icon={Settings} />,
|
|
109
|
-
key: 'setting',
|
|
110
|
-
label: (
|
|
111
|
-
<Flexbox align={'center'} distribution={'space-between'} gap={8} horizontal>
|
|
112
|
-
{t('setting')} {hasNewVersion && <Badge count={t('upgradeVersion.hasNew')} />}
|
|
113
|
-
</Flexbox>
|
|
114
|
-
),
|
|
115
|
-
onClick: () => {
|
|
116
|
-
router.push('/settings/common');
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
];
|
|
120
|
-
|
|
121
12
|
return (
|
|
122
13
|
<>
|
|
123
14
|
<Link aria-label={'GitHub'} href={GITHUB} target={'_blank'}>
|
|
@@ -126,19 +17,6 @@ const BottomActions = memo<BottomActionProps>(({ tab }) => {
|
|
|
126
17
|
<Link aria-label={t('document')} href={DOCUMENTS} target={'_blank'}>
|
|
127
18
|
<ActionIcon icon={Book} placement={'right'} title={t('document')} />
|
|
128
19
|
</Link>
|
|
129
|
-
<Dropdown arrow={false} menu={{ items }} trigger={['click']}>
|
|
130
|
-
{hasNewVersion ? (
|
|
131
|
-
<Flexbox>
|
|
132
|
-
<ConfigProvider theme={{ components: { Badge: { dotSize: 8 } } }}>
|
|
133
|
-
<Badge dot offset={[-4, 4]}>
|
|
134
|
-
<ActionIcon active={tab === SidebarTabKey.Setting} icon={Settings2} />
|
|
135
|
-
</Badge>
|
|
136
|
-
</ConfigProvider>
|
|
137
|
-
</Flexbox>
|
|
138
|
-
) : (
|
|
139
|
-
<ActionIcon active={tab === SidebarTabKey.Setting} icon={Settings2} />
|
|
140
|
-
)}
|
|
141
|
-
</Dropdown>
|
|
142
20
|
</>
|
|
143
21
|
);
|
|
144
22
|
});
|
|
@@ -21,11 +21,14 @@ const useStyles = createStyles(({ css, token, responsive }) => {
|
|
|
21
21
|
border-radius: 0;
|
|
22
22
|
}
|
|
23
23
|
`,
|
|
24
|
+
title: css`
|
|
25
|
+
line-height: 1.2;
|
|
26
|
+
`,
|
|
24
27
|
};
|
|
25
28
|
});
|
|
26
29
|
|
|
27
30
|
const ListItem = memo<ListItemProps & { avatar: string; avatarBackground?: string }>(
|
|
28
|
-
({ avatar, avatarBackground, active, showAction, actions, ...props }) => {
|
|
31
|
+
({ avatar, avatarBackground, active, showAction, actions, title, ...props }) => {
|
|
29
32
|
const ref = useRef(null);
|
|
30
33
|
const isHovering = useHover(ref);
|
|
31
34
|
const { mobile } = useResponsive();
|
|
@@ -52,6 +55,7 @@ const ListItem = memo<ListItemProps & { avatar: string; avatarBackground?: strin
|
|
|
52
55
|
className={styles.container}
|
|
53
56
|
ref={ref}
|
|
54
57
|
showAction={actions && (isHovering || showAction)}
|
|
58
|
+
title={<span className={styles.title}>{title}</span>}
|
|
55
59
|
{...(props as any)}
|
|
56
60
|
/>
|
|
57
61
|
);
|
|
@@ -28,12 +28,13 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
|
28
28
|
|
|
29
29
|
background: ${token.colorBgContainer};
|
|
30
30
|
border-radius: ${token.borderRadiusLG}px;
|
|
31
|
-
box-shadow: 0 0 1px 1px ${token.colorFillQuaternary}
|
|
31
|
+
box-shadow: 0 0 1px 1px ${isDarkMode ? token.colorFillQuaternary : token.colorFillSecondary}
|
|
32
|
+
inset;
|
|
32
33
|
|
|
33
34
|
transition: box-shadow 0.2s ${token.motionEaseInOut};
|
|
34
35
|
|
|
35
36
|
&:hover {
|
|
36
|
-
box-shadow: 0 0 1px 1px ${token.colorFillSecondary} inset;
|
|
37
|
+
box-shadow: 0 0 1px 1px ${isDarkMode ? token.colorFillSecondary : token.colorFill} inset;
|
|
37
38
|
}
|
|
38
39
|
`,
|
|
39
40
|
desc: css`
|
|
@@ -50,6 +51,7 @@ const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
|
50
51
|
title: css`
|
|
51
52
|
margin-bottom: 0 !important;
|
|
52
53
|
font-size: 18px !important;
|
|
54
|
+
font-weight: bold;
|
|
53
55
|
`,
|
|
54
56
|
}));
|
|
55
57
|
|
|
@@ -58,7 +58,16 @@ const AgentList = memo<AgentListProps>(({ mobile }) => {
|
|
|
58
58
|
[],
|
|
59
59
|
);
|
|
60
60
|
|
|
61
|
-
if (isLoading
|
|
61
|
+
if (isLoading || (!searchKeywords && agentList?.length === 0)) {
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
<h2 className={styles.title}>{t('title.recentSubmits')}</h2>
|
|
65
|
+
<Skeleton paragraph={{ rows: 8 }} title={false} />
|
|
66
|
+
<h2 className={styles.title}>{t('title.allAgents')}</h2>
|
|
67
|
+
<Skeleton paragraph={{ rows: 8 }} title={false} />
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
62
71
|
|
|
63
72
|
if (searchKeywords) {
|
|
64
73
|
if (agentList.length === 0) return <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { Button } from 'antd';
|
|
3
|
+
import { Button, Skeleton } from 'antd';
|
|
4
4
|
import { createStyles, useResponsive } from 'antd-style';
|
|
5
5
|
import isEqual from 'fast-deep-equal';
|
|
6
6
|
import { startCase } from 'lodash-es';
|
|
@@ -9,7 +9,7 @@ import { Flexbox } from 'react-layout-kit';
|
|
|
9
9
|
|
|
10
10
|
import { agentMarketSelectors, useMarketStore } from '@/store/market';
|
|
11
11
|
|
|
12
|
-
const useStyles = createStyles(({ css, token }) => ({
|
|
12
|
+
const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
13
13
|
active: css`
|
|
14
14
|
color: ${token.colorBgLayout};
|
|
15
15
|
background: ${token.colorPrimary};
|
|
@@ -20,11 +20,11 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
|
20
20
|
}
|
|
21
21
|
`,
|
|
22
22
|
tag: css`
|
|
23
|
-
background: ${token.colorBgContainer};
|
|
23
|
+
background: ${isDarkMode ? token.colorBgContainer : token.colorFillTertiary};
|
|
24
24
|
border: none;
|
|
25
25
|
|
|
26
26
|
&:hover {
|
|
27
|
-
background: ${token.colorBgElevated} !important;
|
|
27
|
+
background: ${isDarkMode ? token.colorBgElevated : token.colorFill} !important;
|
|
28
28
|
}
|
|
29
29
|
`,
|
|
30
30
|
}));
|
|
@@ -38,6 +38,10 @@ const TagList = memo(() => {
|
|
|
38
38
|
]);
|
|
39
39
|
const agentTagList = useMarketStore(agentMarketSelectors.getAgentTagList, isEqual);
|
|
40
40
|
|
|
41
|
+
if (agentTagList?.length === 0) {
|
|
42
|
+
return <Skeleton paragraph={{ rows: 4 }} title={false} />;
|
|
43
|
+
}
|
|
44
|
+
|
|
41
45
|
const list = md ? agentTagList : agentTagList.slice(0, 20);
|
|
42
46
|
|
|
43
47
|
return (
|
|
@@ -22,7 +22,7 @@ const Header = memo(() => {
|
|
|
22
22
|
<MobileNavBarTitle
|
|
23
23
|
title={
|
|
24
24
|
<Flexbox align={'center'} gap={4} horizontal>
|
|
25
|
-
{t(`tab.${activeSettingsKey}`)}
|
|
25
|
+
<span style={{ lineHeight: 1.2 }}> {t(`tab.${activeSettingsKey}`)}</span>
|
|
26
26
|
{activeSettingsKey === SettingsTabs.Sync && (
|
|
27
27
|
<Tag color={'gold'}>{t('tab.experiment')}</Tag>
|
|
28
28
|
)}
|
|
@@ -10,13 +10,12 @@ import { useTranslation } from 'react-i18next';
|
|
|
10
10
|
import { useSyncSettings } from '@/app/(main)/settings/hooks/useSyncSettings';
|
|
11
11
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
|
12
12
|
import { DEFAULT_SETTINGS } from '@/const/settings';
|
|
13
|
-
import { useOAuthSession } from '@/hooks/useOAuthSession';
|
|
14
13
|
import { useChatStore } from '@/store/chat';
|
|
15
14
|
import { useFileStore } from '@/store/file';
|
|
16
15
|
import { useSessionStore } from '@/store/session';
|
|
17
16
|
import { useToolStore } from '@/store/tool';
|
|
18
17
|
import { useUserStore } from '@/store/user';
|
|
19
|
-
import { settingsSelectors } from '@/store/user/selectors';
|
|
18
|
+
import { settingsSelectors, userProfileSelectors } from '@/store/user/selectors';
|
|
20
19
|
|
|
21
20
|
type SettingItemGroup = ItemGroup;
|
|
22
21
|
|
|
@@ -29,7 +28,8 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin
|
|
|
29
28
|
const { t } = useTranslation('setting');
|
|
30
29
|
const [form] = Form.useForm();
|
|
31
30
|
|
|
32
|
-
const
|
|
31
|
+
const isSignedIn = useUserStore((s) => s.isSignedIn);
|
|
32
|
+
const user = useUserStore(userProfileSelectors.userProfile, isEqual);
|
|
33
33
|
|
|
34
34
|
const [clearSessions, clearSessionGroups] = useSessionStore((s) => [
|
|
35
35
|
s.clearSessions,
|
|
@@ -110,18 +110,18 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig, showOAuthLogin
|
|
|
110
110
|
name: 'password',
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
|
-
children:
|
|
113
|
+
children: isSignedIn ? (
|
|
114
114
|
<Button onClick={handleSignOut}>{t('settingSystem.oauth.signout.action')}</Button>
|
|
115
115
|
) : (
|
|
116
116
|
<Button onClick={handleSignIn} type="primary">
|
|
117
117
|
{t('settingSystem.oauth.signin.action')}
|
|
118
118
|
</Button>
|
|
119
119
|
),
|
|
120
|
-
desc:
|
|
120
|
+
desc: isSignedIn
|
|
121
121
|
? `${user?.email} ${t('settingSystem.oauth.info.desc')}`
|
|
122
122
|
: t('settingSystem.oauth.signin.desc'),
|
|
123
123
|
hidden: !showOAuthLogin,
|
|
124
|
-
label:
|
|
124
|
+
label: isSignedIn
|
|
125
125
|
? t('settingSystem.oauth.info.title')
|
|
126
126
|
: t('settingSystem.oauth.signin.title'),
|
|
127
127
|
minWidth: undefined,
|
|
@@ -4,9 +4,9 @@ import { createStyles } from 'antd-style';
|
|
|
4
4
|
import { memo } from 'react';
|
|
5
5
|
|
|
6
6
|
const useStyles = createStyles(
|
|
7
|
-
({ css, token }) => css`
|
|
7
|
+
({ css, token, isDarkMode }) => css`
|
|
8
8
|
height: 6px;
|
|
9
|
-
background: ${token.colorBgLayout};
|
|
9
|
+
background: ${isDarkMode ? token.colorBgContainer : token.colorBgLayout};
|
|
10
10
|
`,
|
|
11
11
|
);
|
|
12
12
|
|
|
@@ -5,10 +5,11 @@ import { ReactNode, memo } from 'react';
|
|
|
5
5
|
|
|
6
6
|
const { Item } = List;
|
|
7
7
|
|
|
8
|
-
const useStyles = createStyles(({ css }) => ({
|
|
8
|
+
const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
|
9
9
|
container: css`
|
|
10
10
|
position: relative;
|
|
11
11
|
padding-block: 16px !important;
|
|
12
|
+
background: ${isDarkMode ? token.colorBgLayout : token.colorBgContainer};
|
|
12
13
|
border-radius: 0;
|
|
13
14
|
`,
|
|
14
15
|
}));
|
|
@@ -1,49 +1,21 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Upload } from 'antd';
|
|
4
|
-
import {
|
|
5
|
-
import NextImage from 'next/image';
|
|
6
|
-
import { CSSProperties, memo, useCallback } from 'react';
|
|
4
|
+
import { memo, useCallback } from 'react';
|
|
7
5
|
|
|
8
|
-
import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
|
|
9
6
|
import { useUserStore } from '@/store/user';
|
|
10
|
-
import { userProfileSelectors } from '@/store/user/selectors';
|
|
11
7
|
import { imageToBase64 } from '@/utils/imageToBase64';
|
|
12
8
|
import { createUploadImageHandler } from '@/utils/uploadFIle';
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
({ css, token }) => css`
|
|
16
|
-
cursor: pointer;
|
|
17
|
-
overflow: hidden;
|
|
18
|
-
border-radius: 50%;
|
|
19
|
-
transition:
|
|
20
|
-
scale 400ms ${token.motionEaseOut},
|
|
21
|
-
box-shadow 100ms ${token.motionEaseOut};
|
|
10
|
+
import UserAvatar, { type UserAvatarProps } from '../User/UserAvatar';
|
|
22
11
|
|
|
23
|
-
|
|
24
|
-
box-shadow: 0 0 0 3px ${token.colorText};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
&:active {
|
|
28
|
-
scale: 0.8;
|
|
29
|
-
}
|
|
30
|
-
`,
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
interface AvatarWithUploadProps {
|
|
12
|
+
interface AvatarWithUploadProps extends UserAvatarProps {
|
|
34
13
|
compressSize?: number;
|
|
35
|
-
id?: string;
|
|
36
|
-
size?: number;
|
|
37
|
-
style?: CSSProperties;
|
|
38
14
|
}
|
|
39
15
|
|
|
40
16
|
const AvatarWithUpload = memo<AvatarWithUploadProps>(
|
|
41
|
-
({ size = 40, compressSize = 256,
|
|
42
|
-
const
|
|
43
|
-
const [avatar, updateAvatar] = useUserStore((s) => [
|
|
44
|
-
userProfileSelectors.userAvatar(s),
|
|
45
|
-
s.updateAvatar,
|
|
46
|
-
]);
|
|
17
|
+
({ size = 40, compressSize = 256, ...rest }) => {
|
|
18
|
+
const updateAvatar = useUserStore((s) => s.updateAvatar);
|
|
47
19
|
|
|
48
20
|
const handleUploadAvatar = useCallback(
|
|
49
21
|
createUploadImageHandler((avatar) => {
|
|
@@ -58,17 +30,9 @@ const AvatarWithUpload = memo<AvatarWithUploadProps>(
|
|
|
58
30
|
);
|
|
59
31
|
|
|
60
32
|
return (
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
alt={avatar ? 'userAvatar' : 'LobeChat'}
|
|
65
|
-
height={size}
|
|
66
|
-
src={!!avatar ? avatar : DEFAULT_USER_AVATAR_URL}
|
|
67
|
-
unoptimized
|
|
68
|
-
width={size}
|
|
69
|
-
/>
|
|
70
|
-
</Upload>
|
|
71
|
-
</div>
|
|
33
|
+
<Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
|
|
34
|
+
<UserAvatar clickable size={size} {...rest} />
|
|
35
|
+
</Upload>
|
|
72
36
|
);
|
|
73
37
|
},
|
|
74
38
|
);
|
|
@@ -14,6 +14,15 @@ const useStyles = createStyles(({ css, token }) => {
|
|
|
14
14
|
const size = 28;
|
|
15
15
|
|
|
16
16
|
return {
|
|
17
|
+
children: css`
|
|
18
|
+
&::before {
|
|
19
|
+
content: '';
|
|
20
|
+
position: absolute;
|
|
21
|
+
inset: 0;
|
|
22
|
+
background-color: transparent;
|
|
23
|
+
}
|
|
24
|
+
`,
|
|
25
|
+
|
|
17
26
|
loader: css`
|
|
18
27
|
transform: translateX(-${size * 2}px);
|
|
19
28
|
|
|
@@ -231,7 +240,8 @@ const DataImporter = memo<DataImporterProps>(({ children, onFinishImport }) => {
|
|
|
231
240
|
maxCount={1}
|
|
232
241
|
showUploadList={false}
|
|
233
242
|
>
|
|
234
|
-
{
|
|
243
|
+
{/* a very hackable solution: add a pseudo before to have a large hot zone */}
|
|
244
|
+
<div className={styles.children}>{children}</div>
|
|
235
245
|
</Upload>
|
|
236
246
|
</>
|
|
237
247
|
);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Avatar, type AvatarProps } from '@lobehub/ui';
|
|
4
|
+
import { createStyles } from 'antd-style';
|
|
5
|
+
import { memo } from 'react';
|
|
6
|
+
|
|
7
|
+
import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
|
|
8
|
+
import { useUserStore } from '@/store/user';
|
|
9
|
+
import { userProfileSelectors } from '@/store/user/selectors';
|
|
10
|
+
|
|
11
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
12
|
+
clickable: css`
|
|
13
|
+
position: relative;
|
|
14
|
+
transition: all 200ms ease-out 0s;
|
|
15
|
+
|
|
16
|
+
&::before {
|
|
17
|
+
content: '';
|
|
18
|
+
|
|
19
|
+
position: absolute;
|
|
20
|
+
transform: skewX(-45deg) translateX(-400%);
|
|
21
|
+
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
width: 25%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
|
|
28
|
+
background: rgba(255, 255, 255, 50%);
|
|
29
|
+
|
|
30
|
+
transition: all 200ms ease-out 0s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:hover {
|
|
34
|
+
box-shadow: 0 0 0 2px ${token.colorPrimary};
|
|
35
|
+
|
|
36
|
+
&::before {
|
|
37
|
+
transform: skewX(-45deg) translateX(400%);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
export interface UserAvatarProps extends AvatarProps {
|
|
44
|
+
clickable?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const UserAvatar = memo<UserAvatarProps>(
|
|
48
|
+
({ size = 40, background, clickable, className, ...rest }) => {
|
|
49
|
+
const { styles, cx } = useStyles();
|
|
50
|
+
const avatar = useUserStore(userProfileSelectors.userAvatar);
|
|
51
|
+
return (
|
|
52
|
+
<Avatar
|
|
53
|
+
alt={avatar ? 'UserAvatar' : 'LobeChat'}
|
|
54
|
+
avatar={avatar || DEFAULT_USER_AVATAR_URL}
|
|
55
|
+
background={avatar ? background : undefined}
|
|
56
|
+
className={cx(clickable && styles.clickable, className)}
|
|
57
|
+
size={size}
|
|
58
|
+
unoptimized
|
|
59
|
+
{...rest}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
UserAvatar.displayName = 'UserAvatar';
|
|
66
|
+
|
|
67
|
+
export default UserAvatar;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createStyles } from 'antd-style';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import { Flexbox } from 'react-layout-kit';
|
|
7
|
+
|
|
8
|
+
import UserAvatar from './UserAvatar';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_USERNAME = 'LobeChat Community Edition';
|
|
11
|
+
|
|
12
|
+
const useStyles = createStyles(({ css, token }) => ({
|
|
13
|
+
nickname: css`
|
|
14
|
+
font-size: 16px;
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
line-height: 1;
|
|
17
|
+
`,
|
|
18
|
+
username: css`
|
|
19
|
+
line-height: 1;
|
|
20
|
+
color: ${token.colorTextDescription};
|
|
21
|
+
`,
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
const UserInfo = memo<{ onClick?: () => void }>(({ onClick }) => {
|
|
25
|
+
const { t } = useTranslation('common');
|
|
26
|
+
const { styles, theme } = useStyles();
|
|
27
|
+
|
|
28
|
+
const DEFAULT_NICKNAME = t('userPanel.defaultNickname');
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Flexbox align={'center'} gap={12} horizontal paddingBlock={12} paddingInline={16}>
|
|
32
|
+
<UserAvatar background={theme.colorFill} onClick={onClick} size={48} />
|
|
33
|
+
<Flexbox flex={1} gap={6}>
|
|
34
|
+
<div className={styles.nickname}>{DEFAULT_NICKNAME}</div>
|
|
35
|
+
<div className={styles.username}>{DEFAULT_USERNAME}</div>
|
|
36
|
+
</Flexbox>
|
|
37
|
+
</Flexbox>
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default UserInfo;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ActionIcon } from '@lobehub/ui';
|
|
2
|
+
import { Popover } from 'antd';
|
|
3
|
+
import { useTheme } from 'antd-style';
|
|
4
|
+
import { Languages } from 'lucide-react';
|
|
5
|
+
import { memo, useMemo } from 'react';
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
import Menu, { type MenuProps } from '@/components/Menu';
|
|
9
|
+
import { localeOptions } from '@/locales/resources';
|
|
10
|
+
import { useUserStore } from '@/store/user';
|
|
11
|
+
import { settingsSelectors } from '@/store/user/selectors';
|
|
12
|
+
|
|
13
|
+
const LangButton = memo(() => {
|
|
14
|
+
const theme = useTheme();
|
|
15
|
+
const [language, switchLocale] = useUserStore((s) => [
|
|
16
|
+
settingsSelectors.currentSettings(s).language,
|
|
17
|
+
s.switchLocale,
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const { t } = useTranslation('setting');
|
|
21
|
+
|
|
22
|
+
const items: MenuProps['items'] = useMemo(
|
|
23
|
+
() => [
|
|
24
|
+
{
|
|
25
|
+
key: 'auto',
|
|
26
|
+
label: t('settingTheme.lang.autoMode'),
|
|
27
|
+
onClick: () => switchLocale('auto'),
|
|
28
|
+
},
|
|
29
|
+
...localeOptions.map((item) => ({
|
|
30
|
+
key: item.value,
|
|
31
|
+
label: item.label,
|
|
32
|
+
onClick: () => switchLocale(item.value),
|
|
33
|
+
})),
|
|
34
|
+
],
|
|
35
|
+
[t],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Popover
|
|
40
|
+
arrow={false}
|
|
41
|
+
content={<Menu items={items} selectable selectedKeys={[language]} />}
|
|
42
|
+
overlayInnerStyle={{
|
|
43
|
+
padding: 0,
|
|
44
|
+
}}
|
|
45
|
+
placement={'right'}
|
|
46
|
+
trigger={['click', 'hover']}
|
|
47
|
+
>
|
|
48
|
+
<ActionIcon
|
|
49
|
+
icon={Languages}
|
|
50
|
+
size={{ blockSize: 32, fontSize: 16 }}
|
|
51
|
+
style={{ border: `1px solid ${theme.colorFillSecondary}` }}
|
|
52
|
+
/>
|
|
53
|
+
</Popover>
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export default LangButton;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { memo } from 'react';
|
|
2
|
+
import { Flexbox } from 'react-layout-kit';
|
|
3
|
+
|
|
4
|
+
import BrandWatermark from '@/components/BrandWatermark';
|
|
5
|
+
import Menu from '@/components/Menu';
|
|
6
|
+
|
|
7
|
+
import UserInfo from '../UserInfo';
|
|
8
|
+
import LangButton from './LangButton';
|
|
9
|
+
import ThemeButton from './ThemeButton';
|
|
10
|
+
import { useMenu } from './useMenu';
|
|
11
|
+
|
|
12
|
+
const PopoverContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
|
13
|
+
const { mainItems } = useMenu();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Flexbox gap={2} style={{ minWidth: 300 }}>
|
|
17
|
+
<UserInfo />
|
|
18
|
+
<Menu items={mainItems} onClick={closePopover} />
|
|
19
|
+
<Flexbox
|
|
20
|
+
align={'center'}
|
|
21
|
+
horizontal
|
|
22
|
+
justify={'space-between'}
|
|
23
|
+
style={{ padding: '6px 6px 6px 16px' }}
|
|
24
|
+
>
|
|
25
|
+
<BrandWatermark />
|
|
26
|
+
<Flexbox align={'center'} flex={'none'} gap={6} horizontal>
|
|
27
|
+
<LangButton />
|
|
28
|
+
<ThemeButton />
|
|
29
|
+
</Flexbox>
|
|
30
|
+
</Flexbox>
|
|
31
|
+
</Flexbox>
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default PopoverContent;
|