@lobehub/chat 1.42.6 → 1.43.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/changelog/v1.json +12 -0
  3. package/docs/.cdn.cache.json +1 -0
  4. package/docs/changelog/2025-01-03-user-profile.mdx +27 -0
  5. package/docs/changelog/2025-01-03-user-profile.zh-CN.mdx +26 -0
  6. package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +3 -1
  7. package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
  8. package/locales/ar/auth.json +76 -4
  9. package/locales/bg-BG/auth.json +75 -3
  10. package/locales/de-DE/auth.json +78 -6
  11. package/locales/en-US/auth.json +78 -6
  12. package/locales/es-ES/auth.json +75 -3
  13. package/locales/fa-IR/auth.json +77 -5
  14. package/locales/fr-FR/auth.json +78 -6
  15. package/locales/it-IT/auth.json +76 -4
  16. package/locales/ja-JP/auth.json +76 -4
  17. package/locales/ko-KR/auth.json +75 -3
  18. package/locales/nl-NL/auth.json +76 -4
  19. package/locales/pl-PL/auth.json +76 -4
  20. package/locales/pt-BR/auth.json +76 -4
  21. package/locales/ru-RU/auth.json +75 -3
  22. package/locales/tr-TR/auth.json +74 -3
  23. package/locales/vi-VN/auth.json +75 -3
  24. package/locales/zh-CN/auth.json +75 -3
  25. package/locales/zh-TW/auth.json +75 -3
  26. package/package.json +4 -3
  27. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +4 -0
  28. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -46
  29. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +11 -14
  30. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +6 -21
  31. package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +38 -21
  32. package/src/app/(main)/(mobile)/me/profile/layout.tsx +0 -3
  33. package/src/app/(main)/(mobile)/me/profile/page.tsx +3 -3
  34. package/src/app/(main)/chat/loading.tsx +2 -2
  35. package/src/app/(main)/discover/loading.tsx +2 -8
  36. package/src/app/(main)/files/loading.tsx +2 -2
  37. package/src/app/(main)/profile/(home)/Client.tsx +53 -0
  38. package/src/app/(main)/profile/(home)/[[...slugs]]/page.tsx +38 -0
  39. package/src/app/(main)/profile/@category/default.tsx +9 -0
  40. package/src/app/(main)/profile/@category/features/CategoryContent.tsx +38 -0
  41. package/src/app/(main)/profile/_layout/Desktop/Header.tsx +85 -0
  42. package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +42 -0
  43. package/src/app/(main)/profile/_layout/Desktop/index.tsx +48 -0
  44. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +23 -5
  45. package/src/app/(main)/profile/_layout/Mobile/index.tsx +12 -5
  46. package/src/app/(main)/profile/_layout/type.ts +6 -0
  47. package/src/app/(main)/profile/error.tsx +5 -0
  48. package/src/app/(main)/profile/features/ClerkProfile.tsx +72 -0
  49. package/src/app/(main)/profile/hooks/useCategory.tsx +51 -0
  50. package/src/app/(main)/profile/layout.tsx +7 -17
  51. package/src/app/(main)/profile/loading.tsx +2 -22
  52. package/src/app/(main)/profile/not-found.tsx +3 -0
  53. package/src/app/(main)/profile/security/page.tsx +34 -0
  54. package/src/app/(main)/profile/stats/Client.tsx +52 -0
  55. package/src/app/(main)/profile/stats/features/AiHeatmaps.tsx +130 -0
  56. package/src/app/(main)/profile/stats/features/AssistantsRank.tsx +115 -0
  57. package/src/app/(main)/profile/stats/features/ModelsRank.tsx +84 -0
  58. package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +159 -0
  59. package/src/app/(main)/profile/stats/features/ShareButton/ShareModal.tsx +87 -0
  60. package/src/app/(main)/profile/stats/features/ShareButton/TotalCard.tsx +39 -0
  61. package/src/app/(main)/profile/stats/features/ShareButton/index.tsx +26 -0
  62. package/src/app/(main)/profile/stats/features/TimeLabel.tsx +30 -0
  63. package/src/app/(main)/profile/stats/features/TopicsRank.tsx +103 -0
  64. package/src/app/(main)/profile/stats/features/TotalAssistants.tsx +56 -0
  65. package/src/app/(main)/profile/stats/features/TotalMessages.tsx +56 -0
  66. package/src/app/(main)/profile/stats/features/TotalTopics.tsx +53 -0
  67. package/src/app/(main)/profile/stats/features/TotalWords.tsx +54 -0
  68. package/src/app/(main)/profile/stats/features/Welcome.tsx +86 -0
  69. package/src/app/(main)/profile/{[[...slugs]] → stats}/page.tsx +4 -5
  70. package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +2 -2
  71. package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +2 -2
  72. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +1 -1
  73. package/src/app/(main)/settings/_layout/Desktop/index.tsx +1 -1
  74. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
  75. package/src/app/(main)/settings/_layout/Mobile/index.tsx +2 -0
  76. package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -17
  77. package/src/app/(main)/settings/loading.tsx +2 -2
  78. package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
  79. package/src/components/Statistic/index.tsx +15 -0
  80. package/src/components/StatisticCard/TitleWithPercentage.tsx +80 -0
  81. package/src/components/StatisticCard/growthPercentage.tsx +8 -0
  82. package/src/components/StatisticCard/index.tsx +209 -0
  83. package/src/const/url.ts +3 -3
  84. package/src/database/server/models/__tests__/message.test.ts +346 -35
  85. package/src/database/server/models/__tests__/session.test.ts +185 -2
  86. package/src/database/server/models/__tests__/topic.test.ts +136 -0
  87. package/src/database/server/models/__tests__/user.test.ts +140 -1
  88. package/src/database/server/models/message.ts +109 -14
  89. package/src/database/server/models/session.ts +75 -4
  90. package/src/database/server/models/topic.ts +43 -3
  91. package/src/database/server/models/user.ts +22 -0
  92. package/src/database/utils/genWhere.ts +39 -0
  93. package/src/features/ShareModal/ShareImage/index.tsx +11 -24
  94. package/src/features/ShareModal/ShareImage/type.ts +1 -6
  95. package/src/features/User/DataStatistics.tsx +21 -14
  96. package/src/features/User/UserPanel/PanelContent.tsx +12 -16
  97. package/src/features/User/UserPanel/useMenu.tsx +4 -6
  98. package/src/features/User/__tests__/PanelContent.test.tsx +4 -0
  99. package/src/features/User/__tests__/useMenu.test.tsx +1 -21
  100. package/src/hooks/useActiveTabKey.ts +34 -1
  101. package/src/{features/ShareModal/ShareImage → hooks}/useScreenshot.ts +51 -6
  102. package/src/locales/default/auth.ts +74 -2
  103. package/src/server/ld.test.ts +1 -1
  104. package/src/server/modules/AssistantStore/index.ts +3 -2
  105. package/src/server/routers/lambda/message.ts +35 -6
  106. package/src/server/routers/lambda/session.ts +17 -3
  107. package/src/server/routers/lambda/topic.ts +17 -3
  108. package/src/server/routers/lambda/user.ts +4 -0
  109. package/src/server/services/changelog/index.ts +1 -1
  110. package/src/services/message/_deprecated.ts +16 -0
  111. package/src/services/message/client.test.ts +0 -18
  112. package/src/services/message/client.ts +12 -9
  113. package/src/services/message/server.ts +12 -4
  114. package/src/services/message/type.ts +15 -3
  115. package/src/services/session/_deprecated.ts +5 -0
  116. package/src/services/session/client.ts +6 -2
  117. package/src/services/session/server.ts +6 -2
  118. package/src/services/session/type.ts +7 -1
  119. package/src/services/topic/_deprecated.ts +5 -0
  120. package/src/services/topic/client.ts +6 -2
  121. package/src/services/topic/server.ts +7 -1
  122. package/src/services/topic/type.ts +7 -2
  123. package/src/services/user/_deprecated.ts +4 -0
  124. package/src/services/user/client.ts +4 -0
  125. package/src/services/user/server.ts +4 -0
  126. package/src/services/user/type.ts +5 -0
  127. package/src/store/global/initialState.ts +6 -0
  128. package/src/store/user/slices/auth/action.test.ts +1 -33
  129. package/src/store/user/slices/auth/action.ts +0 -9
  130. package/src/store/user/slices/common/action.test.ts +2 -2
  131. package/src/types/message/index.ts +5 -0
  132. package/src/types/session/index.ts +8 -0
  133. package/src/types/topic/topic.ts +7 -0
  134. package/src/utils/format.ts +1 -1
  135. package/src/utils/time.ts +23 -0
  136. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +0 -76
  137. package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +0 -44
  138. package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +0 -6
  139. package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +0 -32
  140. package/src/hooks/useActiveSettingsKey.ts +0 -20
@@ -0,0 +1,30 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { useTheme } from 'antd-style';
3
+ import { Loader2, LucideIcon } from 'lucide-react';
4
+ import { memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ const TimeLabel = memo<{
8
+ date?: string;
9
+ icon: LucideIcon;
10
+ title: string;
11
+ }>(({ date, icon, title }) => {
12
+ const theme = useTheme();
13
+ return (
14
+ <Flexbox
15
+ align={'center'}
16
+ gap={4}
17
+ horizontal
18
+ style={{
19
+ color: theme.colorTextDescription,
20
+ fontSize: 12,
21
+ }}
22
+ >
23
+ <Icon icon={icon} />
24
+ {title}:{' '}
25
+ {date ? <span style={{ fontWeight: 'bold' }}>{date}</span> : <Icon icon={Loader2} spin />}
26
+ </Flexbox>
27
+ );
28
+ });
29
+
30
+ export default TimeLabel;
@@ -0,0 +1,103 @@
1
+ import { BarList } from '@lobehub/charts';
2
+ import { ActionIcon, FormGroup, Icon, Modal } from '@lobehub/ui';
3
+ import { useTheme } from 'antd-style';
4
+ import { MaximizeIcon, MessageSquareIcon } from 'lucide-react';
5
+ import Link from 'next/link';
6
+ import { useRouter } from 'next/navigation';
7
+ import qs from 'query-string';
8
+ import { memo, useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Flexbox } from 'react-layout-kit';
11
+
12
+ import { FORM_STYLE } from '@/const/layoutTokens';
13
+ import { useClientDataSWR } from '@/libs/swr';
14
+ import { topicService } from '@/services/topic';
15
+ import { TopicRankItem } from '@/types/topic';
16
+
17
+ export const TopicsRank = memo(() => {
18
+ const [open, setOpen] = useState(false);
19
+ const { t } = useTranslation('auth');
20
+ const theme = useTheme();
21
+ const router = useRouter();
22
+ const { data, isLoading } = useClientDataSWR('rank-topics', async () =>
23
+ topicService.rankTopics(),
24
+ );
25
+
26
+ const showExtra = Boolean(data && data?.length > 5);
27
+
28
+ const mapData = (item: TopicRankItem) => {
29
+ const link = qs.stringifyUrl({
30
+ query: {
31
+ session: item.sessionId,
32
+ topic: item.id,
33
+ },
34
+ url: '/chat',
35
+ });
36
+ return {
37
+ icon: (
38
+ <Icon color={theme.colorTextDescription} icon={MessageSquareIcon} size={{ fontSize: 16 }} />
39
+ ),
40
+ link,
41
+ name: (
42
+ <Link href={link} style={{ color: 'inherit' }}>
43
+ {item.title}
44
+ </Link>
45
+ ),
46
+ value: item.count,
47
+ };
48
+ };
49
+
50
+ return (
51
+ <>
52
+ <FormGroup
53
+ extra={
54
+ showExtra && (
55
+ <ActionIcon
56
+ icon={MaximizeIcon}
57
+ onClick={() => setOpen(true)}
58
+ size={{ blockSize: 28, fontSize: 20 }}
59
+ />
60
+ )
61
+ }
62
+ style={FORM_STYLE.style}
63
+ title={t('stats.topicsRank.title')}
64
+ variant={'pure'}
65
+ >
66
+ <Flexbox paddingBlock={16}>
67
+ <BarList
68
+ data={data?.slice(0, 5).map((item) => mapData(item)) || []}
69
+ height={220}
70
+ leftLabel={t('stats.topicsRank.left')}
71
+ loading={isLoading || !data}
72
+ noDataText={{
73
+ desc: t('stats.empty.desc'),
74
+ title: t('stats.empty.title'),
75
+ }}
76
+ onValueChange={(item) => router.push(item.link)}
77
+ rightLabel={t('stats.topicsRank.right')}
78
+ />
79
+ </Flexbox>
80
+ </FormGroup>
81
+ {showExtra && (
82
+ <Modal
83
+ footer={null}
84
+ loading={isLoading || !data}
85
+ onCancel={() => setOpen(false)}
86
+ open={open}
87
+ title={t('stats.topicsRank.title')}
88
+ >
89
+ <BarList
90
+ data={data?.map((item) => mapData(item)) || []}
91
+ height={340}
92
+ leftLabel={t('stats.assistantsRank.left')}
93
+ loading={isLoading || !data}
94
+ onValueChange={(item) => router.push(item.link)}
95
+ rightLabel={t('stats.assistantsRank.right')}
96
+ />
97
+ </Modal>
98
+ )}
99
+ </>
100
+ );
101
+ });
102
+
103
+ export default TopicsRank;
@@ -0,0 +1,56 @@
1
+ import { useTheme } from 'antd-style';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import Statistic from '@/components/Statistic';
6
+ import StatisticCard from '@/components/StatisticCard';
7
+ import TitleWithPercentage from '@/components/StatisticCard/TitleWithPercentage';
8
+ import { useClientDataSWR } from '@/libs/swr';
9
+ import { sessionService } from '@/services/session';
10
+ import { formatIntergerNumber } from '@/utils/format';
11
+ import { lastMonth } from '@/utils/time';
12
+
13
+ import TotalCard from './ShareButton/TotalCard';
14
+
15
+ const TotalMessages = memo<{ inShare?: boolean; mobile?: boolean }>(({ mobile, inShare }) => {
16
+ const { t } = useTranslation('auth');
17
+ const theme = useTheme();
18
+ const { data, isLoading } = useClientDataSWR('stats-sessions', async () => ({
19
+ count: await sessionService.countSessions(),
20
+ prevCount: await sessionService.countSessions({ endDate: lastMonth().format('YYYY-MM-DD') }),
21
+ }));
22
+
23
+ if (inShare)
24
+ return (
25
+ <TotalCard
26
+ count={formatIntergerNumber(data?.prevCount) || '--'}
27
+ title={t('stats.assistants')}
28
+ />
29
+ );
30
+
31
+ return (
32
+ <StatisticCard
33
+ highlight={mobile ? undefined : theme.purple}
34
+ loading={isLoading || !data}
35
+ statistic={{
36
+ description: (
37
+ <Statistic
38
+ title={t('date.prevMonth')}
39
+ value={formatIntergerNumber(data?.prevCount) || '--'}
40
+ />
41
+ ),
42
+ precision: 0,
43
+ value: data?.count || '--',
44
+ }}
45
+ title={
46
+ <TitleWithPercentage
47
+ count={data?.count}
48
+ prvCount={data?.prevCount}
49
+ title={t('stats.assistants')}
50
+ />
51
+ }
52
+ />
53
+ );
54
+ });
55
+
56
+ export default TotalMessages;
@@ -0,0 +1,56 @@
1
+ import { useTheme } from 'antd-style';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import Statistic from '@/components/Statistic';
6
+ import StatisticCard from '@/components/StatisticCard';
7
+ import TitleWithPercentage from '@/components/StatisticCard/TitleWithPercentage';
8
+ import { useClientDataSWR } from '@/libs/swr';
9
+ import { messageService } from '@/services/message';
10
+ import { formatIntergerNumber } from '@/utils/format';
11
+ import { lastMonth } from '@/utils/time';
12
+
13
+ import TotalCard from './ShareButton/TotalCard';
14
+
15
+ const TotalMessages = memo<{ inShare?: boolean; mobile?: boolean }>(({ inShare, mobile }) => {
16
+ const { t } = useTranslation('auth');
17
+ const theme = useTheme();
18
+ const { data, isLoading } = useClientDataSWR('stats-messages', async () => ({
19
+ count: await messageService.countMessages(),
20
+ prevCount: await messageService.countMessages({ endDate: lastMonth().format('YYYY-MM-DD') }),
21
+ }));
22
+
23
+ if (inShare)
24
+ return (
25
+ <TotalCard
26
+ count={formatIntergerNumber(data?.prevCount) || '--'}
27
+ title={t('stats.messages')}
28
+ />
29
+ );
30
+
31
+ return (
32
+ <StatisticCard
33
+ highlight={mobile ? undefined : theme.yellow}
34
+ loading={isLoading || !data}
35
+ statistic={{
36
+ description: (
37
+ <Statistic
38
+ title={t('date.prevMonth')}
39
+ value={formatIntergerNumber(data?.prevCount) || '--'}
40
+ />
41
+ ),
42
+ precision: 0,
43
+ value: data?.count || '--',
44
+ }}
45
+ title={
46
+ <TitleWithPercentage
47
+ count={data?.count}
48
+ prvCount={data?.prevCount}
49
+ title={t('stats.messages')}
50
+ />
51
+ }
52
+ />
53
+ );
54
+ });
55
+
56
+ export default TotalMessages;
@@ -0,0 +1,53 @@
1
+ import { useTheme } from 'antd-style';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import Statistic from '@/components/Statistic';
6
+ import StatisticCard from '@/components/StatisticCard';
7
+ import TitleWithPercentage from '@/components/StatisticCard/TitleWithPercentage';
8
+ import { useClientDataSWR } from '@/libs/swr';
9
+ import { topicService } from '@/services/topic';
10
+ import { formatIntergerNumber } from '@/utils/format';
11
+ import { lastMonth } from '@/utils/time';
12
+
13
+ import TotalCard from './ShareButton/TotalCard';
14
+
15
+ const TotalMessages = memo<{ inShare?: boolean; mobile?: boolean }>(({ inShare, mobile }) => {
16
+ const { t } = useTranslation('auth');
17
+ const theme = useTheme();
18
+ const { data, isLoading } = useClientDataSWR('stats-topics', async () => ({
19
+ count: await topicService.countTopics(),
20
+ prevCount: await topicService.countTopics({ endDate: lastMonth().format('YYYY-MM-DD') }),
21
+ }));
22
+
23
+ if (inShare)
24
+ return (
25
+ <TotalCard count={formatIntergerNumber(data?.prevCount) || '--'} title={t('stats.topics')} />
26
+ );
27
+
28
+ return (
29
+ <StatisticCard
30
+ highlight={mobile ? undefined : theme.gold}
31
+ loading={isLoading || !data}
32
+ statistic={{
33
+ description: (
34
+ <Statistic
35
+ title={t('date.prevMonth')}
36
+ value={formatIntergerNumber(data?.prevCount) || '--'}
37
+ />
38
+ ),
39
+ precision: 0,
40
+ value: data?.count || '--',
41
+ }}
42
+ title={
43
+ <TitleWithPercentage
44
+ count={data?.count}
45
+ prvCount={data?.prevCount}
46
+ title={t('stats.topics')}
47
+ />
48
+ }
49
+ />
50
+ );
51
+ });
52
+
53
+ export default TotalMessages;
@@ -0,0 +1,54 @@
1
+ import { memo } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+
4
+ import Statistic from '@/components/Statistic';
5
+ import StatisticCard from '@/components/StatisticCard';
6
+ import TitleWithPercentage from '@/components/StatisticCard/TitleWithPercentage';
7
+ import { useClientDataSWR } from '@/libs/swr';
8
+ import { messageService } from '@/services/message';
9
+ import { formatShortenNumber } from '@/utils/format';
10
+ import { lastMonth } from '@/utils/time';
11
+
12
+ import TotalCard from './ShareButton/TotalCard';
13
+
14
+ const TotalWords = memo<{ inShare?: boolean }>(({ inShare }) => {
15
+ const { t } = useTranslation('auth');
16
+
17
+ const { data, isLoading } = useClientDataSWR('stats-words', async () => ({
18
+ count: await messageService.countWords(),
19
+ prevCount: await messageService.countWords({ endDate: lastMonth().format('YYYY-MM-DD') }),
20
+ }));
21
+
22
+ if (inShare)
23
+ return (
24
+ <TotalCard count={formatShortenNumber(data?.prevCount) || '--'} title={t('stats.words')} />
25
+ );
26
+
27
+ return (
28
+ <StatisticCard
29
+ loading={isLoading || !data}
30
+ statistic={{
31
+ description: (
32
+ <Statistic
33
+ title={t('date.prevMonth')}
34
+ value={formatShortenNumber(data?.prevCount) || '--'}
35
+ />
36
+ ),
37
+ precision: 0,
38
+ style: {
39
+ fontWeight: 'bold',
40
+ },
41
+ value: formatShortenNumber(data?.count) || '--',
42
+ }}
43
+ title={
44
+ <TitleWithPercentage
45
+ count={data?.count}
46
+ prvCount={data?.prevCount}
47
+ title={t('stats.words')}
48
+ />
49
+ }
50
+ />
51
+ );
52
+ });
53
+
54
+ export default TotalWords;
@@ -0,0 +1,86 @@
1
+ import { FluentEmoji } from '@lobehub/ui';
2
+ import { Skeleton } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import { Clock3Icon, ClockArrowUp } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { Trans, useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import TimeLabel from '@/app/(main)/profile/stats/features/TimeLabel';
10
+ import { BRANDING_NAME } from '@/const/branding';
11
+ import { useClientDataSWR } from '@/libs/swr';
12
+ import { userService } from '@/services/user';
13
+ import { useUserStore } from '@/store/user';
14
+ import { userProfileSelectors } from '@/store/user/slices/auth/selectors';
15
+ import { formatIntergerNumber } from '@/utils/format';
16
+
17
+ const formatEnglishNumber = (number: number) => {
18
+ if (number === 1) return '1st';
19
+ if (number === 2) return '2nd';
20
+ if (number === 3) return '3rd';
21
+ return `${formatIntergerNumber(number)}th`;
22
+ };
23
+
24
+ const Welcome = memo<{ mobile?: boolean }>(({ mobile }) => {
25
+ const { t, i18n } = useTranslation('auth');
26
+ const theme = useTheme();
27
+ const [nickname, username] = useUserStore((s) => [
28
+ userProfileSelectors.nickName(s),
29
+ userProfileSelectors.username(s),
30
+ ]);
31
+
32
+ const { data, isLoading } = useClientDataSWR('welcome', async () =>
33
+ userService.getUserRegistrationDuration(),
34
+ );
35
+
36
+ return (
37
+ <Flexbox gap={8} padding={mobile ? 16 : 0}>
38
+ <Flexbox
39
+ align={'center'}
40
+ gap={8}
41
+ horizontal
42
+ style={{
43
+ fontSize: mobile ? 16 : 20,
44
+ fontWeight: 500,
45
+ }}
46
+ >
47
+ <div>
48
+ <Trans
49
+ components={{
50
+ span:
51
+ isLoading || !data ? (
52
+ <Skeleton.Button active style={{ height: 24 }} />
53
+ ) : (
54
+ <span style={{ fontWeight: 'bold' }} />
55
+ ),
56
+ }}
57
+ i18nKey="stats.welcome"
58
+ ns={'auth'}
59
+ values={{
60
+ appName: BRANDING_NAME,
61
+ days:
62
+ i18n.language === 'en-US'
63
+ ? formatEnglishNumber(Number(data?.duration || 1))
64
+ : formatIntergerNumber(Number(data?.duration || 1)),
65
+ username: nickname || username,
66
+ }}
67
+ />
68
+ </div>
69
+ {!mobile && <FluentEmoji emoji={'🫶'} size={32} type={'anim'} />}
70
+ </Flexbox>
71
+ <Flexbox
72
+ gap={16}
73
+ horizontal
74
+ style={{
75
+ color: theme.colorTextDescription,
76
+ }}
77
+ wrap={'wrap'}
78
+ >
79
+ <TimeLabel date={data?.createdAt} icon={Clock3Icon} title={t('stats.createdAt')} />
80
+ <TimeLabel date={data?.updatedAt} icon={ClockArrowUp} title={t('stats.updatedAt')} />
81
+ </Flexbox>
82
+ </Flexbox>
83
+ );
84
+ });
85
+
86
+ export default Welcome;
@@ -5,17 +5,16 @@ import { isMobileDevice } from '@/utils/server/responsive';
5
5
  import Client from './Client';
6
6
 
7
7
  export const generateMetadata = async () => {
8
- const { t } = await translation('clerk');
8
+ const { t } = await translation('auth');
9
9
  return metadataModule.generate({
10
- description: t('userProfile.navbar.title'),
11
- title: t('userProfile.navbar.description'),
12
- url: '/profile',
10
+ description: t('header.desc'),
11
+ title: t('tab.stats'),
12
+ url: '/profile/stats',
13
13
  });
14
14
  };
15
15
 
16
16
  const Page = async () => {
17
17
  const mobile = await isMobileDevice();
18
-
19
18
  return <Client mobile={mobile} />;
20
19
  };
21
20
 
@@ -3,7 +3,7 @@
3
3
  import { createStyles } from 'antd-style';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import CircleLoading from '@/components/Loading/BrandTextLoading';
6
+ import Loading from '@/components/Loading/BrandTextLoading';
7
7
  import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
8
8
 
9
9
  import DatasetDetail from './DatasetDetail';
@@ -34,7 +34,7 @@ const Dataset = ({ params }: Props) => {
34
34
  const isEmpty = data?.length === 0;
35
35
 
36
36
  return isLoading ? (
37
- <CircleLoading />
37
+ <Loading />
38
38
  ) : isEmpty ? (
39
39
  <EmptyGuide knowledgeBaseId={knowledgeBaseId} />
40
40
  ) : (
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
- import CircleLoading from '@/components/Loading/BrandTextLoading';
5
+ import Loading from '@/components/Loading/BrandTextLoading';
6
6
  import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
7
7
 
8
8
  import EmptyGuide from './EmptyGuide';
@@ -24,7 +24,7 @@ const Evaluation = ({ params }: Props) => {
24
24
  const isEmpty = data?.length === 0;
25
25
 
26
26
  return isLoading ? (
27
- <CircleLoading />
27
+ <Loading />
28
28
  ) : isEmpty ? (
29
29
  <EmptyGuide knowledgeBaseId={knowledgeBaseId} />
30
30
  ) : (
@@ -4,7 +4,7 @@ import { memo } from 'react';
4
4
  import urlJoin from 'url-join';
5
5
 
6
6
  import Menu from '@/components/Menu';
7
- import { useActiveSettingsKey } from '@/hooks/useActiveSettingsKey';
7
+ import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
8
8
  import { useQuery } from '@/hooks/useQuery';
9
9
  import { useQueryRoute } from '@/hooks/useQueryRoute';
10
10
  import { SettingsTabs } from '@/store/global/initialState';
@@ -9,7 +9,7 @@ import { Flexbox } from 'react-layout-kit';
9
9
  import InitClientDB from '@/features/InitClientDB';
10
10
  import Footer from '@/features/Setting/Footer';
11
11
  import SettingContainer from '@/features/Setting/SettingContainer';
12
- import { useActiveSettingsKey } from '@/hooks/useActiveSettingsKey';
12
+ import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
13
13
  import { SettingsTabs } from '@/store/global/initialState';
14
14
 
15
15
  import { LayoutProps } from '../type';
@@ -7,7 +7,7 @@ import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
9
9
 
10
- import { useActiveSettingsKey } from '@/hooks/useActiveSettingsKey';
10
+ import { useActiveSettingsKey } from '@/hooks/useActiveTabKey';
11
11
  import { SettingsTabs } from '@/store/global/initialState';
12
12
  import { useUserStore } from '@/store/user';
13
13
  import { authSelectors } from '@/store/user/selectors';
@@ -1,4 +1,5 @@
1
1
  import MobileContentLayout from '@/components/server/MobileNavLayout';
2
+ import InitClientDB from '@/features/InitClientDB';
2
3
  import Footer from '@/features/Setting/Footer';
3
4
 
4
5
  import { LayoutProps } from '../type';
@@ -9,6 +10,7 @@ const Layout = ({ children }: LayoutProps) => {
9
10
  <MobileContentLayout header={<Header />}>
10
11
  {children}
11
12
  <Footer />
13
+ <InitClientDB />
12
14
  </MobileContentLayout>
13
15
  );
14
16
  };
@@ -11,14 +11,9 @@ import { useTranslation } from 'react-i18next';
11
11
  import { useSyncSettings } from '@/app/(main)/settings/hooks/useSyncSettings';
12
12
  import { FORM_STYLE } from '@/const/layoutTokens';
13
13
  import { imageUrl } from '@/const/url';
14
- import AvatarWithUpload from '@/features/AvatarWithUpload';
15
14
  import { Locales, localeOptions } from '@/locales/resources';
16
15
  import { useUserStore } from '@/store/user';
17
- import {
18
- authSelectors,
19
- settingsSelectors,
20
- userGeneralSettingsSelectors,
21
- } from '@/store/user/selectors';
16
+ import { settingsSelectors, userGeneralSettingsSelectors } from '@/store/user/selectors';
22
17
  import { switchLang } from '@/utils/client/switchLang';
23
18
 
24
19
  import { ThemeSwatchesNeutral, ThemeSwatchesPrimary } from './ThemeSwatches';
@@ -31,11 +26,7 @@ const Theme = memo(() => {
31
26
  const [form] = Form.useForm();
32
27
  const settings = useUserStore(settingsSelectors.currentSettings, isEqual);
33
28
  const themeMode = useUserStore(userGeneralSettingsSelectors.currentThemeMode);
34
- const [setThemeMode, setSettings, enableAuth] = useUserStore((s) => [
35
- s.switchThemeMode,
36
- s.setSettings,
37
- authSelectors.enabledAuth(s),
38
- ]);
29
+ const [setThemeMode, setSettings] = useUserStore((s) => [s.switchThemeMode, s.setSettings]);
39
30
 
40
31
  useSyncSettings(form);
41
32
 
@@ -46,12 +37,6 @@ const Theme = memo(() => {
46
37
 
47
38
  const theme: SettingItemGroup = {
48
39
  children: [
49
- {
50
- children: <AvatarWithUpload />,
51
- hidden: enableAuth,
52
- label: t('settingTheme.avatar.title'),
53
- minWidth: undefined,
54
- },
55
40
  {
56
41
  children: (
57
42
  <SelectWithImg
@@ -1,3 +1,3 @@
1
- import FullLoading from '@/components/Loading/BrandTextLoading';
1
+ import Loading from '@/components/Loading/BrandTextLoading';
2
2
 
3
- export default () => <FullLoading />;
3
+ export default () => <Loading />;
@@ -1,16 +1,16 @@
1
+ import { BrandLoading, LobeChatText } from '@lobehub/ui/brand';
1
2
  import { Center } from 'react-layout-kit';
2
3
 
3
4
  import { isCustomBranding } from '@/const/version';
4
5
 
5
6
  import CircleLoading from '../CircleLoading';
6
- import LobeChatText from './LobeChatText';
7
7
 
8
8
  export default () => {
9
9
  if (isCustomBranding) return <CircleLoading />;
10
10
 
11
11
  return (
12
12
  <Center height={'100%'} width={'100%'}>
13
- <LobeChatText />
13
+ <BrandLoading size={40} style={{ opacity: 0.6 }} text={LobeChatText} />
14
14
  </Center>
15
15
  );
16
16
  };
@@ -0,0 +1,15 @@
1
+ import { useTheme } from 'antd-style';
2
+ import { ReactNode, memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ const Statistic = memo<{ title: ReactNode; value: ReactNode }>(({ value, title }) => {
6
+ const theme = useTheme();
7
+ return (
8
+ <Flexbox gap={4} horizontal style={{ color: theme.colorTextSecondary, fontSize: 12 }}>
9
+ <span style={{ fontWeight: 'bold' }}>{value}</span>
10
+ <span>{title}</span>
11
+ </Flexbox>
12
+ );
13
+ });
14
+
15
+ export default Statistic;