@lobehub/chat 1.42.6 → 1.43.1

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 +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 +83 -4
  9. package/locales/bg-BG/auth.json +82 -3
  10. package/locales/de-DE/auth.json +85 -6
  11. package/locales/en-US/auth.json +85 -6
  12. package/locales/es-ES/auth.json +82 -3
  13. package/locales/fa-IR/auth.json +84 -5
  14. package/locales/fr-FR/auth.json +85 -6
  15. package/locales/it-IT/auth.json +83 -4
  16. package/locales/ja-JP/auth.json +83 -4
  17. package/locales/ko-KR/auth.json +82 -3
  18. package/locales/nl-NL/auth.json +83 -4
  19. package/locales/pl-PL/auth.json +83 -4
  20. package/locales/pt-BR/auth.json +83 -4
  21. package/locales/ru-RU/auth.json +82 -3
  22. package/locales/tr-TR/auth.json +82 -3
  23. package/locales/vi-VN/auth.json +82 -3
  24. package/locales/zh-CN/auth.json +82 -3
  25. package/locales/zh-TW/auth.json +82 -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 +104 -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 +76 -4
  90. package/src/database/server/models/topic.ts +44 -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 +81 -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,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, minWidth: 40, width: 40 }} />
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;
@@ -0,0 +1,80 @@
1
+ import { Tag, Typography } from 'antd';
2
+ import { useTheme } from 'antd-style';
3
+ import { CSSProperties, memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { calcGrowthPercentage } from './growthPercentage';
7
+
8
+ const { Title } = Typography;
9
+
10
+ interface TitleWithPercentageProps {
11
+ count?: number;
12
+ inverseColor?: boolean;
13
+ prvCount?: number;
14
+ title: string;
15
+ }
16
+
17
+ const TitleWithPercentage = memo<TitleWithPercentageProps>(
18
+ ({ inverseColor, title, prvCount, count }) => {
19
+ const percentage = calcGrowthPercentage(count || 0, prvCount || 0);
20
+ const theme = useTheme();
21
+
22
+ const upStyle: CSSProperties = {
23
+ background: theme.colorSuccessBg,
24
+ borderColor: theme.colorSuccessBorder,
25
+ color: theme.colorSuccess,
26
+ };
27
+
28
+ const downStyle: CSSProperties = {
29
+ backgroundColor: theme.colorWarningBg,
30
+ borderColor: theme.colorWarningBorder,
31
+ color: theme.colorWarning,
32
+ };
33
+
34
+ return (
35
+ <Flexbox
36
+ align={'center'}
37
+ gap={8}
38
+ horizontal
39
+ justify={'flex-start'}
40
+ style={{
41
+ overflow: 'hidden',
42
+ position: 'inherit',
43
+ }}
44
+ >
45
+ <Title
46
+ ellipsis={{ rows: 1, tooltip: title }}
47
+ level={2}
48
+ style={{
49
+ fontSize: 'inherit',
50
+ fontWeight: 'inherit',
51
+ lineHeight: 'inherit',
52
+ margin: 0,
53
+ overflow: 'hidden',
54
+ }}
55
+ >
56
+ {title}
57
+ </Title>
58
+ {count && prvCount && percentage && percentage !== 0 ? (
59
+ <Tag
60
+ style={{
61
+ borderWidth: 0.5,
62
+ ...(inverseColor
63
+ ? percentage > 0
64
+ ? downStyle
65
+ : upStyle
66
+ : percentage > 0
67
+ ? upStyle
68
+ : downStyle),
69
+ }}
70
+ >
71
+ {percentage > 0 ? '+' : ''}
72
+ {percentage.toFixed(1)}%
73
+ </Tag>
74
+ ) : null}
75
+ </Flexbox>
76
+ );
77
+ },
78
+ );
79
+
80
+ export default TitleWithPercentage;
@@ -0,0 +1,8 @@
1
+ export const calcGrowthPercentage = (currentCount: number, previousCount: number) => {
2
+ if (typeof currentCount !== 'number') return 0;
3
+ return previousCount !== 0
4
+ ? ((currentCount - previousCount) / previousCount) * 100 // 计算增长百分比
5
+ : currentCount > 0
6
+ ? 100
7
+ : 0;
8
+ };
@@ -0,0 +1,209 @@
1
+ import {
2
+ StatisticCard as AntdStatisticCard,
3
+ StatisticCardProps as AntdStatisticCardProps,
4
+ } from '@ant-design/pro-components';
5
+ import { Spin, Typography } from 'antd';
6
+ import { createStyles, useResponsive } from 'antd-style';
7
+ import { adjustHue } from 'polished';
8
+ import { memo } from 'react';
9
+
10
+ const { Title } = Typography;
11
+
12
+ const useStyles = createStyles(
13
+ ({ isDarkMode, css, token, prefixCls, responsive }, highlight: string = '#000') => ({
14
+ card: css`
15
+ border: 1px solid ${isDarkMode ? token.colorFillTertiary : token.colorFillSecondary};
16
+ border-radius: ${token.borderRadiusLG}px;
17
+
18
+ ${responsive.mobile} {
19
+ background: ${token.colorBgContainer};
20
+ border: none;
21
+ border-radius: 0;
22
+ }
23
+ `,
24
+ container: css`
25
+ ${responsive.mobile} {
26
+ background: ${token.colorBgContainer};
27
+ border: none;
28
+ border-radius: 0;
29
+ }
30
+
31
+ .${prefixCls}-pro-card-title {
32
+ overflow: hidden;
33
+
34
+ ${responsive.mobile} {
35
+ margin: 0;
36
+ font-size: 14px;
37
+ line-height: 16px !important;
38
+ }
39
+ }
40
+
41
+ .${prefixCls}-pro-card-body {
42
+ padding: 0;
43
+ .${prefixCls}-pro-statistic-card-content {
44
+ position: relative;
45
+ width: 100%;
46
+ padding-block-end: 16px;
47
+ padding-inline: 24px;
48
+ .${prefixCls}-pro-statistic-card-chart {
49
+ position: relative;
50
+ width: 100%;
51
+ }
52
+ }
53
+
54
+ .${prefixCls}-pro-statistic-card-footer {
55
+ overflow: hidden;
56
+
57
+ margin: 0;
58
+ padding: 0;
59
+
60
+ border-end-start-radius: ${token.borderRadiusLG}px;
61
+ border-end-end-radius: ${token.borderRadiusLG}px;
62
+ }
63
+ }
64
+
65
+ .${prefixCls}-pro-card-loading-content {
66
+ padding-block: 16px;
67
+ padding-inline: 24px;
68
+ }
69
+
70
+ .${prefixCls}-pro-card-header {
71
+ padding-block-start: 16px;
72
+ padding-inline: 24px;
73
+
74
+ .${prefixCls}-pro-card-title {
75
+ line-height: 32px;
76
+ }
77
+
78
+ + .${prefixCls}-pro-card-body {
79
+ padding-block-start: 0;
80
+ }
81
+
82
+ ${responsive.mobile} {
83
+ flex-wrap: wrap;
84
+ gap: 8px;
85
+
86
+ margin-block-end: 8px;
87
+ padding-block-start: 0;
88
+ padding-inline: 0;
89
+ }
90
+ }
91
+
92
+ .${prefixCls}-statistic-content-value-int, .${prefixCls}-statistic-content-value-decimal {
93
+ font-size: 24px;
94
+ font-weight: bold;
95
+ line-height: 1.2;
96
+ }
97
+
98
+ .${prefixCls}-pro-statistic-card-chart {
99
+ margin: 0;
100
+ }
101
+
102
+ .${prefixCls}-pro-statistic-card-content {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 16px;
106
+ ${responsive.mobile} {
107
+ padding-block-end: 0 !important;
108
+ padding-inline: 0 !important;
109
+ }
110
+ }
111
+
112
+ .${prefixCls}-pro-statistic-card-content-horizontal {
113
+ flex-direction: row;
114
+ align-items: center;
115
+
116
+ .${prefixCls}-pro-statistic-card-chart {
117
+ align-self: center;
118
+ }
119
+ }
120
+ `,
121
+ highlight: css`
122
+ overflow: hidden;
123
+
124
+ &::before {
125
+ content: '';
126
+
127
+ position: absolute;
128
+ z-index: 0;
129
+ inset-block-end: -30%;
130
+ inset-inline-end: -30%;
131
+ transform: rotate(-15deg);
132
+
133
+ width: 66%;
134
+ height: 50%;
135
+
136
+ opacity: ${isDarkMode ? 1 : 0.33};
137
+ background-image: linear-gradient(
138
+ 60deg,
139
+ ${adjustHue(-30, highlight)} 20%,
140
+ ${highlight} 80%
141
+ );
142
+ background-repeat: no-repeat;
143
+ background-position: center left;
144
+ background-size: contain;
145
+ filter: blur(32px);
146
+ border-radius: 50%;
147
+ }
148
+
149
+ > div {
150
+ z-index: 1;
151
+ }
152
+ `,
153
+ icon: css`
154
+ background: ${token.colorFillSecondary};
155
+ border-radius: ${token.borderRadius}px;
156
+ `,
157
+ pure: css`
158
+ background: transparent !important;
159
+ border: none !important;
160
+ `,
161
+ }),
162
+ );
163
+
164
+ interface StatisticCardProps extends AntdStatisticCardProps {
165
+ highlight?: string;
166
+ variant?: 'pure' | 'card';
167
+ }
168
+
169
+ const StatisticCard = memo<StatisticCardProps>(
170
+ ({ title, className, highlight, variant, loading, extra, ...rest }) => {
171
+ const { cx, styles } = useStyles(highlight);
172
+ const { mobile } = useResponsive();
173
+ const isPure = variant === 'pure';
174
+ return (
175
+ <AntdStatisticCard
176
+ bordered={!mobile}
177
+ className={cx(
178
+ styles.container,
179
+ isPure ? styles.pure : styles.card,
180
+ highlight && styles.highlight,
181
+ className,
182
+ )}
183
+ extra={loading ? <Spin percent={'auto'} size={'small'} /> : extra}
184
+ title={
185
+ typeof title === 'string' ? (
186
+ <Title
187
+ ellipsis={{ rows: 1, tooltip: title }}
188
+ level={2}
189
+ style={{
190
+ fontSize: 'inherit',
191
+ fontWeight: 'inherit',
192
+ lineHeight: 'inherit',
193
+ margin: 0,
194
+ overflow: 'hidden',
195
+ }}
196
+ >
197
+ {title}
198
+ </Title>
199
+ ) : (
200
+ title
201
+ )
202
+ }
203
+ {...rest}
204
+ />
205
+ );
206
+ },
207
+ );
208
+
209
+ export default StatisticCard;
package/src/const/url.ts CHANGED
@@ -9,9 +9,9 @@ import { INBOX_SESSION_ID } from './session';
9
9
 
10
10
  export const UTM_SOURCE = 'chat_preview';
11
11
 
12
- export const OFFICIAL_URL = 'https://lobechat.com/';
13
- export const OFFICIAL_PREVIEW_URL = 'https://chat-preview.lobehub.com/';
14
- export const OFFICIAL_SITE = 'https://lobehub.com/';
12
+ export const OFFICIAL_URL = 'https://lobechat.com';
13
+ export const OFFICIAL_PREVIEW_URL = 'https://chat-preview.lobehub.com';
14
+ export const OFFICIAL_SITE = 'https://lobehub.com';
15
15
 
16
16
  export const OG_URL = '/og/cover.png?v=1';
17
17