@lobehub/chat 1.42.5 → 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 (141) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -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 +13 -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/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +1 -1
  35. package/src/app/(main)/chat/loading.tsx +2 -2
  36. package/src/app/(main)/discover/loading.tsx +2 -8
  37. package/src/app/(main)/files/loading.tsx +2 -2
  38. package/src/app/(main)/profile/(home)/Client.tsx +53 -0
  39. package/src/app/(main)/profile/(home)/[[...slugs]]/page.tsx +38 -0
  40. package/src/app/(main)/profile/@category/default.tsx +9 -0
  41. package/src/app/(main)/profile/@category/features/CategoryContent.tsx +38 -0
  42. package/src/app/(main)/profile/_layout/Desktop/Header.tsx +85 -0
  43. package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +42 -0
  44. package/src/app/(main)/profile/_layout/Desktop/index.tsx +48 -0
  45. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +23 -5
  46. package/src/app/(main)/profile/_layout/Mobile/index.tsx +12 -5
  47. package/src/app/(main)/profile/_layout/type.ts +6 -0
  48. package/src/app/(main)/profile/error.tsx +5 -0
  49. package/src/app/(main)/profile/features/ClerkProfile.tsx +72 -0
  50. package/src/app/(main)/profile/hooks/useCategory.tsx +51 -0
  51. package/src/app/(main)/profile/layout.tsx +7 -17
  52. package/src/app/(main)/profile/loading.tsx +2 -22
  53. package/src/app/(main)/profile/not-found.tsx +3 -0
  54. package/src/app/(main)/profile/security/page.tsx +34 -0
  55. package/src/app/(main)/profile/stats/Client.tsx +52 -0
  56. package/src/app/(main)/profile/stats/features/AiHeatmaps.tsx +130 -0
  57. package/src/app/(main)/profile/stats/features/AssistantsRank.tsx +115 -0
  58. package/src/app/(main)/profile/stats/features/ModelsRank.tsx +84 -0
  59. package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +159 -0
  60. package/src/app/(main)/profile/stats/features/ShareButton/ShareModal.tsx +87 -0
  61. package/src/app/(main)/profile/stats/features/ShareButton/TotalCard.tsx +39 -0
  62. package/src/app/(main)/profile/stats/features/ShareButton/index.tsx +26 -0
  63. package/src/app/(main)/profile/stats/features/TimeLabel.tsx +30 -0
  64. package/src/app/(main)/profile/stats/features/TopicsRank.tsx +103 -0
  65. package/src/app/(main)/profile/stats/features/TotalAssistants.tsx +56 -0
  66. package/src/app/(main)/profile/stats/features/TotalMessages.tsx +56 -0
  67. package/src/app/(main)/profile/stats/features/TotalTopics.tsx +53 -0
  68. package/src/app/(main)/profile/stats/features/TotalWords.tsx +54 -0
  69. package/src/app/(main)/profile/stats/features/Welcome.tsx +86 -0
  70. package/src/app/(main)/profile/{[[...slugs]] → stats}/page.tsx +4 -5
  71. package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +2 -2
  72. package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +2 -2
  73. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +1 -1
  74. package/src/app/(main)/settings/_layout/Desktop/index.tsx +1 -1
  75. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
  76. package/src/app/(main)/settings/_layout/Mobile/index.tsx +2 -0
  77. package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -17
  78. package/src/app/(main)/settings/loading.tsx +2 -2
  79. package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
  80. package/src/components/Statistic/index.tsx +15 -0
  81. package/src/components/StatisticCard/TitleWithPercentage.tsx +80 -0
  82. package/src/components/StatisticCard/growthPercentage.tsx +8 -0
  83. package/src/components/StatisticCard/index.tsx +209 -0
  84. package/src/const/url.ts +3 -3
  85. package/src/database/server/models/__tests__/message.test.ts +346 -35
  86. package/src/database/server/models/__tests__/session.test.ts +185 -2
  87. package/src/database/server/models/__tests__/topic.test.ts +136 -0
  88. package/src/database/server/models/__tests__/user.test.ts +140 -1
  89. package/src/database/server/models/message.ts +109 -14
  90. package/src/database/server/models/session.ts +75 -4
  91. package/src/database/server/models/topic.ts +43 -3
  92. package/src/database/server/models/user.ts +22 -0
  93. package/src/database/utils/genWhere.ts +39 -0
  94. package/src/features/ShareModal/ShareImage/index.tsx +11 -24
  95. package/src/features/ShareModal/ShareImage/type.ts +1 -6
  96. package/src/features/User/DataStatistics.tsx +21 -14
  97. package/src/features/User/UserPanel/PanelContent.tsx +12 -16
  98. package/src/features/User/UserPanel/useMenu.tsx +4 -6
  99. package/src/features/User/__tests__/PanelContent.test.tsx +4 -0
  100. package/src/features/User/__tests__/useMenu.test.tsx +1 -21
  101. package/src/hooks/useActiveTabKey.ts +34 -1
  102. package/src/{features/ShareModal/ShareImage → hooks}/useScreenshot.ts +51 -6
  103. package/src/locales/default/auth.ts +74 -2
  104. package/src/server/ld.test.ts +1 -1
  105. package/src/server/modules/AssistantStore/index.ts +3 -2
  106. package/src/server/routers/lambda/message.ts +35 -6
  107. package/src/server/routers/lambda/session.ts +17 -3
  108. package/src/server/routers/lambda/topic.ts +17 -3
  109. package/src/server/routers/lambda/user.ts +4 -0
  110. package/src/server/services/changelog/index.ts +1 -1
  111. package/src/services/message/_deprecated.ts +16 -0
  112. package/src/services/message/client.test.ts +0 -18
  113. package/src/services/message/client.ts +12 -9
  114. package/src/services/message/server.ts +12 -4
  115. package/src/services/message/type.ts +15 -3
  116. package/src/services/session/_deprecated.ts +5 -0
  117. package/src/services/session/client.ts +6 -2
  118. package/src/services/session/server.ts +6 -2
  119. package/src/services/session/type.ts +7 -1
  120. package/src/services/topic/_deprecated.ts +5 -0
  121. package/src/services/topic/client.ts +6 -2
  122. package/src/services/topic/server.ts +7 -1
  123. package/src/services/topic/type.ts +7 -2
  124. package/src/services/user/_deprecated.ts +4 -0
  125. package/src/services/user/client.ts +4 -0
  126. package/src/services/user/server.ts +4 -0
  127. package/src/services/user/type.ts +5 -0
  128. package/src/store/global/initialState.ts +6 -0
  129. package/src/store/user/slices/auth/action.test.ts +1 -33
  130. package/src/store/user/slices/auth/action.ts +0 -9
  131. package/src/store/user/slices/common/action.test.ts +2 -2
  132. package/src/types/message/index.ts +5 -0
  133. package/src/types/session/index.ts +8 -0
  134. package/src/types/topic/topic.ts +7 -0
  135. package/src/utils/format.ts +1 -1
  136. package/src/utils/time.ts +23 -0
  137. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +0 -76
  138. package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +0 -44
  139. package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +0 -6
  140. package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +0 -32
  141. package/src/hooks/useActiveSettingsKey.ts +0 -20
@@ -0,0 +1,51 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { ChartColumnBigIcon, ShieldCheck, UserCircle } from 'lucide-react';
3
+ import Link from 'next/link';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import type { MenuProps } from '@/components/Menu';
7
+ import { isDeprecatedEdition } from '@/const/version';
8
+ import { ProfileTabs } from '@/store/global/initialState';
9
+ import { useUserStore } from '@/store/user';
10
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
11
+
12
+ export const useCategory = () => {
13
+ const { t } = useTranslation('auth');
14
+ const [enableAuth, isLoginWithClerk] = useUserStore((s) => [
15
+ authSelectors.enabledAuth(s),
16
+ authSelectors.isLoginWithClerk(s),
17
+ ]);
18
+
19
+ const cateItems: MenuProps['items'] = [
20
+ {
21
+ icon: <Icon icon={UserCircle} />,
22
+ key: ProfileTabs.Profile,
23
+ label: (
24
+ <Link href={'/profile'} onClick={(e) => e.preventDefault()}>
25
+ {t('tab.profile')}
26
+ </Link>
27
+ ),
28
+ },
29
+ enableAuth &&
30
+ isLoginWithClerk && {
31
+ icon: <Icon icon={ShieldCheck} />,
32
+ key: ProfileTabs.Security,
33
+ label: (
34
+ <Link href={'/profile/security'} onClick={(e) => e.preventDefault()}>
35
+ {t('tab.security')}
36
+ </Link>
37
+ ),
38
+ },
39
+ !isDeprecatedEdition && {
40
+ icon: <Icon icon={ChartColumnBigIcon} />,
41
+ key: ProfileTabs.Stats,
42
+ label: (
43
+ <Link href={'/profile/stats'} onClick={(e) => e.preventDefault()}>
44
+ {t('tab.stats')}
45
+ </Link>
46
+ ),
47
+ },
48
+ ].filter(Boolean) as MenuProps['items'];
49
+
50
+ return cateItems;
51
+ };
@@ -1,21 +1,11 @@
1
- import { notFound } from 'next/navigation';
2
- import { PropsWithChildren } from 'react';
1
+ import ServerLayout from '@/components/server/ServerLayout';
3
2
 
4
- import { enableClerk } from '@/const/auth';
5
- import { isMobileDevice } from '@/utils/server/responsive';
3
+ import Desktop from './_layout/Desktop';
4
+ import Mobile from './_layout/Mobile';
5
+ import { LayoutProps } from './_layout/type';
6
6
 
7
- import MobileLayout from './_layout/Mobile';
7
+ const ProfileLayout = ServerLayout<LayoutProps>({ Desktop, Mobile });
8
8
 
9
- const Layout = async ({ children }: PropsWithChildren) => {
10
- if (!enableClerk) return notFound();
9
+ ProfileLayout.displayName = 'ProfileLayout';
11
10
 
12
- const mobile = await isMobileDevice();
13
-
14
- if (mobile) return <MobileLayout>{children}</MobileLayout>;
15
-
16
- return children;
17
- };
18
-
19
- Layout.displayName = 'ProfileLayout';
20
-
21
- export default Layout;
11
+ export default ProfileLayout;
@@ -1,23 +1,3 @@
1
- import { Flexbox } from 'react-layout-kit';
1
+ import Loading from '@/components/Loading/BrandTextLoading';
2
2
 
3
- import SkeletonLoading from '@/components/Loading/SkeletonLoading';
4
- import { isMobileDevice } from '@/utils/server/responsive';
5
-
6
- const Loading = async () => {
7
- const mobile = await isMobileDevice();
8
- if (mobile) return <SkeletonLoading paragraph={{ rows: 8 }} />;
9
- return (
10
- <Flexbox horizontal style={{ position: 'relative' }} width={'100%'}>
11
- <Flexbox padding={24} width={256}>
12
- <SkeletonLoading paragraph={{ rows: 8 }} />;
13
- </Flexbox>
14
- <Flexbox align={'center'} flex={1}>
15
- <Flexbox padding={24} style={{ maxWidth: 1024 }} width={'100%'}>
16
- <SkeletonLoading paragraph={{ rows: 8 }} />;
17
- </Flexbox>
18
- </Flexbox>
19
- </Flexbox>
20
- );
21
- };
22
-
23
- export default Loading;
3
+ export default () => <Loading />;
@@ -0,0 +1,3 @@
1
+ import dynamic from 'next/dynamic';
2
+
3
+ export default dynamic(() => import('@/components/404'));
@@ -0,0 +1,34 @@
1
+ import { Skeleton } from 'antd';
2
+ import dynamic from 'next/dynamic';
3
+ import { notFound } from 'next/navigation';
4
+
5
+ import { enableClerk } from '@/const/auth';
6
+ import { metadataModule } from '@/server/metadata';
7
+ import { translation } from '@/server/translation';
8
+ import { isMobileDevice } from '@/utils/server/responsive';
9
+
10
+ const ClerkProfile = dynamic(() => import('../features/ClerkProfile'), {
11
+ loading: () => (
12
+ <div style={{ flex: 1 }}>
13
+ <Skeleton paragraph={{ rows: 8 }} title={false} />
14
+ </div>
15
+ ),
16
+ });
17
+
18
+ export const generateMetadata = async () => {
19
+ const { t } = await translation('auth');
20
+ return metadataModule.generate({
21
+ description: t('header.desc'),
22
+ title: t('tab.security'),
23
+ url: '/profile/security',
24
+ });
25
+ };
26
+
27
+ const Page = async () => {
28
+ if (!enableClerk) return notFound();
29
+ const mobile = await isMobileDevice();
30
+
31
+ return <ClerkProfile mobile={mobile} />;
32
+ };
33
+
34
+ export default Page;
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { FormGroup, Grid } from '@lobehub/ui';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { FORM_STYLE } from '@/const/layoutTokens';
9
+
10
+ import AiHeatmaps from './features/AiHeatmaps';
11
+ import AssistantsRank from './features/AssistantsRank';
12
+ import ModelsRank from './features/ModelsRank';
13
+ import ShareButton from './features/ShareButton';
14
+ import TopicsRank from './features/TopicsRank';
15
+ import TotalAssistants from './features/TotalAssistants';
16
+ import TotalMessages from './features/TotalMessages';
17
+ import TotalTopics from './features/TotalTopics';
18
+ import TotalWords from './features/TotalWords';
19
+ import Welcome from './features/Welcome';
20
+
21
+ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
22
+ const { t } = useTranslation('auth');
23
+
24
+ return (
25
+ <Flexbox gap={mobile ? 0 : 24}>
26
+ {mobile ? (
27
+ <Welcome mobile />
28
+ ) : (
29
+ <Flexbox align={'flex-start'} gap={16} horizontal justify={'space-between'}>
30
+ <Welcome />
31
+ <ShareButton />
32
+ </Flexbox>
33
+ )}
34
+ <FormGroup style={FORM_STYLE.style} title={t('tab.stats')} variant={'pure'}>
35
+ <Grid maxItemWidth={150} paddingBlock={16} rows={4}>
36
+ <TotalAssistants mobile={mobile} />
37
+ <TotalTopics mobile={mobile} />
38
+ <TotalMessages mobile={mobile} />
39
+ <TotalWords />
40
+ </Grid>
41
+ </FormGroup>
42
+ <AiHeatmaps mobile={mobile} />
43
+ <Grid gap={mobile ? 0 : 48} rows={3}>
44
+ <ModelsRank />
45
+ <AssistantsRank />
46
+ <TopicsRank />
47
+ </Grid>
48
+ </Flexbox>
49
+ );
50
+ });
51
+
52
+ export default Client;
@@ -0,0 +1,130 @@
1
+ import { Heatmaps, HeatmapsProps } from '@lobehub/charts';
2
+ import { FormGroup, Icon } from '@lobehub/ui';
3
+ import { Tag } from 'antd';
4
+ import { useTheme } from 'antd-style';
5
+ import { FlameIcon } from 'lucide-react';
6
+ import { readableColor } from 'polished';
7
+ import { memo } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Flexbox } from 'react-layout-kit';
10
+
11
+ import { FORM_STYLE } from '@/const/layoutTokens';
12
+ import { useClientDataSWR } from '@/libs/swr';
13
+ import { messageService } from '@/services/message';
14
+
15
+ const AiHeatmaps = memo<Omit<HeatmapsProps, 'data'> & { inShare?: boolean; mobile?: boolean }>(
16
+ ({ inShare, mobile, ...rest }) => {
17
+ const { t } = useTranslation('auth');
18
+ const theme = useTheme();
19
+ const { data, isLoading } = useClientDataSWR('stats-heatmaps', async () =>
20
+ messageService.getHeatmaps(),
21
+ );
22
+
23
+ const days = data?.filter((item) => item.level > 0).length || '--';
24
+ const hotDays = data?.filter((item) => item.level >= 3).length || '--';
25
+
26
+ const content = (
27
+ <Heatmaps
28
+ blockMargin={mobile ? 3 : undefined}
29
+ blockRadius={mobile ? 2 : undefined}
30
+ blockSize={mobile ? 6 : 14}
31
+ data={data || []}
32
+ labels={{
33
+ legend: {
34
+ less: t('heatmaps.legend.less'),
35
+ more: t('heatmaps.legend.more'),
36
+ },
37
+ months: [
38
+ t('heatmaps.months.jan'),
39
+ t('heatmaps.months.feb'),
40
+ t('heatmaps.months.mar'),
41
+ t('heatmaps.months.apr'),
42
+ t('heatmaps.months.may'),
43
+ t('heatmaps.months.jun'),
44
+ t('heatmaps.months.jul'),
45
+ t('heatmaps.months.aug'),
46
+ t('heatmaps.months.sep'),
47
+ t('heatmaps.months.oct'),
48
+ t('heatmaps.months.nov'),
49
+ t('heatmaps.months.dec'),
50
+ ],
51
+ tooltip: t('heatmaps.tooltip'),
52
+ totalCount: t('heatmaps.totalCount'),
53
+ }}
54
+ loading={isLoading || !data}
55
+ maxLevel={4}
56
+ {...rest}
57
+ />
58
+ );
59
+
60
+ const fillColor = readableColor(theme.gold);
61
+ const tags = (
62
+ <Flexbox
63
+ gap={4}
64
+ horizontal
65
+ style={{
66
+ alignSelf: 'center',
67
+ flex: 'none',
68
+ zoom: 0.9,
69
+ }}
70
+ >
71
+ <Tag
72
+ bordered={false}
73
+ style={{
74
+ background: theme.colorText,
75
+ color: theme.colorBgLayout,
76
+ fontWeight: 500,
77
+ margin: 0,
78
+ }}
79
+ >
80
+ {[days, t('stats.days')].join(' ')}
81
+ </Tag>
82
+ <Tag
83
+ bordered={false}
84
+ color={'gold'}
85
+ icon={<Icon color={fillColor} fill={fillColor} icon={FlameIcon} />}
86
+ style={{
87
+ background: theme.gold,
88
+ color: fillColor,
89
+ fontWeight: 500,
90
+ margin: 0,
91
+ }}
92
+ >
93
+ {[hotDays, t('stats.days')].join(' ')}
94
+ </Tag>
95
+ </Flexbox>
96
+ );
97
+
98
+ if (inShare) {
99
+ return (
100
+ <Flexbox gap={4}>
101
+ <Flexbox align={'baseline'} gap={4} horizontal justify={'space-between'}>
102
+ <div
103
+ style={{
104
+ color: theme.colorTextDescription,
105
+ fontSize: 12,
106
+ }}
107
+ >
108
+ {t('stats.lastYearActivity')}
109
+ </div>
110
+ {tags}
111
+ </Flexbox>
112
+ {content}
113
+ </Flexbox>
114
+ );
115
+ }
116
+
117
+ return (
118
+ <FormGroup
119
+ extra={tags}
120
+ style={FORM_STYLE.style}
121
+ title={t('stats.lastYearActivity')}
122
+ variant={'pure'}
123
+ >
124
+ <Flexbox paddingBlock={24}>{content}</Flexbox>
125
+ </FormGroup>
126
+ );
127
+ },
128
+ );
129
+
130
+ export default AiHeatmaps;
@@ -0,0 +1,115 @@
1
+ import { BarList } from '@lobehub/charts';
2
+ import { ActionIcon, Avatar, FormGroup, Modal } from '@lobehub/ui';
3
+ import { MaximizeIcon } from 'lucide-react';
4
+ import Link from 'next/link';
5
+ import { useRouter } from 'next/navigation';
6
+ import qs from 'query-string';
7
+ import { memo, useState } from 'react';
8
+ import { useTranslation } from 'react-i18next';
9
+ import { Flexbox } from 'react-layout-kit';
10
+
11
+ import { FORM_STYLE } from '@/const/layoutTokens';
12
+ import { DEFAULT_AVATAR } from '@/const/meta';
13
+ import { INBOX_SESSION_ID } from '@/const/session';
14
+ import { useClientDataSWR } from '@/libs/swr';
15
+ import { sessionService } from '@/services/session';
16
+ import { SessionRankItem } from '@/types/session';
17
+
18
+ export const AssistantsRank = memo(() => {
19
+ const [open, setOpen] = useState(false);
20
+ const { t } = useTranslation(['auth', 'chat']);
21
+ const router = useRouter();
22
+ const { data, isLoading } = useClientDataSWR('rank-sessions', async () =>
23
+ sessionService.rankSessions(),
24
+ );
25
+
26
+ const showExtra = Boolean(data && data?.length > 5);
27
+
28
+ const mapData = (item: SessionRankItem) => {
29
+ const link = qs.stringifyUrl({
30
+ query: {
31
+ session: item.id,
32
+ },
33
+ url: '/chat',
34
+ });
35
+
36
+ return {
37
+ icon: (
38
+ <Avatar
39
+ alt={item.title || t('defaultAgent', { ns: 'chat' })}
40
+ avatar={item.avatar || DEFAULT_AVATAR}
41
+ background={item.backgroundColor || undefined}
42
+ size={28}
43
+ style={{
44
+ backdropFilter: 'blur(8px)',
45
+ }}
46
+ />
47
+ ),
48
+ link,
49
+ name: (
50
+ <Link href={link} style={{ color: 'inherit' }}>
51
+ {item.title
52
+ ? item.id === INBOX_SESSION_ID
53
+ ? t('inbox.title', { ns: 'chat' })
54
+ : item.title
55
+ : t('defaultAgent', { ns: 'chat' })}
56
+ </Link>
57
+ ),
58
+ value: item.count,
59
+ };
60
+ };
61
+
62
+ return (
63
+ <>
64
+ <FormGroup
65
+ extra={
66
+ showExtra && (
67
+ <ActionIcon
68
+ icon={MaximizeIcon}
69
+ onClick={() => setOpen(true)}
70
+ size={{ blockSize: 28, fontSize: 20 }}
71
+ />
72
+ )
73
+ }
74
+ style={FORM_STYLE.style}
75
+ title={t('stats.assistantsRank.title')}
76
+ variant={'pure'}
77
+ >
78
+ <Flexbox paddingBlock={16}>
79
+ <BarList
80
+ data={data?.slice(0, 5).map((item) => mapData(item)) || []}
81
+ height={220}
82
+ leftLabel={t('stats.assistantsRank.left')}
83
+ loading={isLoading || !data}
84
+ noDataText={{
85
+ desc: t('stats.empty.desc'),
86
+ title: t('stats.empty.title'),
87
+ }}
88
+ onValueChange={(item) => router.push(item.link)}
89
+ rightLabel={t('stats.assistantsRank.right')}
90
+ />
91
+ </Flexbox>
92
+ </FormGroup>
93
+ {showExtra && (
94
+ <Modal
95
+ footer={null}
96
+ loading={isLoading || !data}
97
+ onCancel={() => setOpen(false)}
98
+ open={open}
99
+ title={t('stats.assistantsRank.title')}
100
+ >
101
+ <BarList
102
+ data={data?.map((item) => mapData(item)) || []}
103
+ height={340}
104
+ leftLabel={t('stats.assistantsRank.left')}
105
+ loading={isLoading || !data}
106
+ onValueChange={(item) => router.push(item.link)}
107
+ rightLabel={t('stats.assistantsRank.right')}
108
+ />
109
+ </Modal>
110
+ )}
111
+ </>
112
+ );
113
+ });
114
+
115
+ export default AssistantsRank;
@@ -0,0 +1,84 @@
1
+ import { BarList } from '@lobehub/charts';
2
+ import { ModelIcon } from '@lobehub/icons';
3
+ import { ActionIcon, FormGroup, Modal } from '@lobehub/ui';
4
+ import { MaximizeIcon } from 'lucide-react';
5
+ import { memo, useState } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { FORM_STYLE } from '@/const/layoutTokens';
10
+ import { useClientDataSWR } from '@/libs/swr';
11
+ import { messageService } from '@/services/message';
12
+ import { ModelRankItem } from '@/types/message';
13
+
14
+ export const TopicsRank = memo(() => {
15
+ const [open, setOpen] = useState(false);
16
+ const { t } = useTranslation('auth');
17
+ const { data, isLoading } = useClientDataSWR('rank-models', async () =>
18
+ messageService.rankModels(),
19
+ );
20
+
21
+ const showExtra = Boolean(data && data?.length > 5);
22
+
23
+ const mapData = (item: ModelRankItem) => {
24
+ return {
25
+ icon: <ModelIcon model={item.id as string} size={24} />,
26
+ id: item.id,
27
+
28
+ name: item.id,
29
+ value: item.count,
30
+ };
31
+ };
32
+
33
+ return (
34
+ <>
35
+ <FormGroup
36
+ extra={
37
+ showExtra ? (
38
+ <ActionIcon
39
+ icon={MaximizeIcon}
40
+ onClick={() => setOpen(true)}
41
+ size={{ blockSize: 28, fontSize: 20 }}
42
+ />
43
+ ) : undefined
44
+ }
45
+ style={FORM_STYLE.style}
46
+ title={t('stats.modelsRank.title')}
47
+ variant={'pure'}
48
+ >
49
+ <Flexbox horizontal paddingBlock={16}>
50
+ <BarList
51
+ data={data?.slice(0, 5).map((item) => mapData(item)) || []}
52
+ height={220}
53
+ leftLabel={t('stats.modelsRank.left')}
54
+ loading={isLoading || !data}
55
+ noDataText={{
56
+ desc: t('stats.empty.desc'),
57
+ title: t('stats.empty.title'),
58
+ }}
59
+ rightLabel={t('stats.modelsRank.right')}
60
+ />
61
+ </Flexbox>
62
+ </FormGroup>
63
+ {showExtra && (
64
+ <Modal
65
+ footer={null}
66
+ loading={isLoading || !data}
67
+ onCancel={() => setOpen(false)}
68
+ open={open}
69
+ title={t('stats.modelsRank.title')}
70
+ >
71
+ <BarList
72
+ data={data?.map((item) => mapData(item)) || []}
73
+ height={340}
74
+ leftLabel={t('stats.assistantsRank.left')}
75
+ loading={isLoading || !data}
76
+ rightLabel={t('stats.assistantsRank.right')}
77
+ />
78
+ </Modal>
79
+ )}
80
+ </>
81
+ );
82
+ });
83
+
84
+ export default TopicsRank;
@@ -0,0 +1,159 @@
1
+ import { Github } from '@lobehub/icons';
2
+ import { Grid } from '@lobehub/ui';
3
+ import { createStyles } from 'antd-style';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+
8
+ import { ProductLogo } from '@/components/Branding';
9
+ import { OFFICIAL_URL, imageUrl } from '@/const/url';
10
+ import { isServerMode } from '@/const/version';
11
+ import UserAvatar from '@/features/User/UserAvatar';
12
+
13
+ import TotalMessages from '..//TotalMessages';
14
+ import TotalWords from '..//TotalWords';
15
+ import AiHeatmaps from '../AiHeatmaps';
16
+
17
+ const useStyles = createStyles(({ css, token, stylish, cx, responsive }) => ({
18
+ avatar: css`
19
+ box-sizing: content-box;
20
+ background: ${token.colorText};
21
+ border: 4px solid ${token.colorBgLayout};
22
+ `,
23
+ background: css`
24
+ position: relative;
25
+
26
+ width: 100%;
27
+ padding: 24px;
28
+
29
+ background-color: ${token.colorBgLayout};
30
+ background-image: url(${imageUrl('screenshot_background.webp')});
31
+ background-position: center;
32
+ background-size: 120% 120%;
33
+ `,
34
+
35
+ container: css`
36
+ position: relative;
37
+
38
+ overflow: hidden;
39
+
40
+ width: 100%;
41
+
42
+ background: ${token.colorBgLayout};
43
+ border: 1px solid ${token.colorBorder};
44
+ border-radius: ${token.borderRadiusLG * 2}px;
45
+ box-shadow: ${token.boxShadow};
46
+ `,
47
+ decs: css`
48
+ font-size: 12px;
49
+ color: ${token.colorTextDescription};
50
+ `,
51
+ footer: css`
52
+ font-size: 12px;
53
+ color: ${token.colorTextDescription};
54
+ `,
55
+ heatmaps: css`
56
+ .legend-month,
57
+ footer {
58
+ display: none;
59
+ }
60
+ `,
61
+ preview: cx(
62
+ stylish.noScrollbar,
63
+ css`
64
+ overflow: hidden scroll;
65
+
66
+ width: 100%;
67
+ max-height: 70dvh;
68
+
69
+ background: ${token.colorBgLayout};
70
+ border: 1px solid ${token.colorBorder};
71
+ border-radius: ${token.borderRadiusLG}px;
72
+
73
+ * {
74
+ pointer-events: none;
75
+
76
+ ::-webkit-scrollbar {
77
+ width: 0 !important;
78
+ height: 0 !important;
79
+ }
80
+ }
81
+
82
+ ${responsive.mobile} {
83
+ max-height: 40dvh;
84
+ }
85
+ `,
86
+ ),
87
+ title: css`
88
+ font-size: 24px;
89
+ font-weight: bold;
90
+ text-align: center;
91
+ `,
92
+ }));
93
+
94
+ const Preview = memo(() => {
95
+ const { styles } = useStyles();
96
+ const { t } = useTranslation('auth');
97
+ const isOfficial = !isServerMode && OFFICIAL_URL.includes(location.host);
98
+
99
+ return (
100
+ <div className={styles.preview}>
101
+ <div className={styles.background} id={'preview'}>
102
+ <Center className={styles.container} gap={12} padding={24}>
103
+ <ProductLogo size={24} type={'text'} />
104
+ <div className={styles.title}>{t('stats.share.title')}</div>
105
+ <Flexbox align={'center'} horizontal>
106
+ <UserAvatar
107
+ className={styles.avatar}
108
+ size={48}
109
+ style={{
110
+ marginRight: -12,
111
+ zIndex: 2,
112
+ }}
113
+ />
114
+ <Center
115
+ className={styles.avatar}
116
+ height={48}
117
+ style={{
118
+ borderRadius: '50%',
119
+ zIndex: 1,
120
+ }}
121
+ width={48}
122
+ >
123
+ <ProductLogo size={40} />
124
+ </Center>
125
+ </Flexbox>
126
+ <Flexbox gap={12} paddingBlock={12} width={'100%'}>
127
+ <AiHeatmaps
128
+ blockMargin={2}
129
+ blockRadius={1}
130
+ blockSize={4.5}
131
+ className={styles.heatmaps}
132
+ inShare
133
+ style={{
134
+ marginTop: -12,
135
+ }}
136
+ width={'100%'}
137
+ />
138
+ <Grid gap={8} maxItemWidth={100} rows={2} width={'100%'}>
139
+ <TotalMessages inShare />
140
+ <TotalWords inShare />
141
+ </Grid>
142
+ </Flexbox>
143
+ <div className={styles.footer}>
144
+ {isOfficial ? (
145
+ OFFICIAL_URL
146
+ ) : (
147
+ <Flexbox align={'center'} gap={8} horizontal>
148
+ <Github size={16} />
149
+ <span>lobehub/lobe-chat</span>
150
+ </Flexbox>
151
+ )}
152
+ </div>
153
+ </Center>
154
+ </div>
155
+ </div>
156
+ );
157
+ });
158
+
159
+ export default Preview;