@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.
- package/CHANGELOG.md +58 -0
- package/changelog/v1.json +21 -0
- package/docs/.cdn.cache.json +1 -0
- package/docs/changelog/2025-01-03-user-profile.mdx +27 -0
- package/docs/changelog/2025-01-03-user-profile.zh-CN.mdx +26 -0
- package/docs/self-hosting/advanced/auth/next-auth/wechat.mdx +3 -1
- package/docs/self-hosting/advanced/auth/next-auth/wechat.zh-CN.mdx +2 -2
- package/locales/ar/auth.json +76 -4
- package/locales/bg-BG/auth.json +75 -3
- package/locales/de-DE/auth.json +78 -6
- package/locales/en-US/auth.json +78 -6
- package/locales/es-ES/auth.json +75 -3
- package/locales/fa-IR/auth.json +77 -5
- package/locales/fr-FR/auth.json +78 -6
- package/locales/it-IT/auth.json +76 -4
- package/locales/ja-JP/auth.json +76 -4
- package/locales/ko-KR/auth.json +75 -3
- package/locales/nl-NL/auth.json +76 -4
- package/locales/pl-PL/auth.json +76 -4
- package/locales/pt-BR/auth.json +76 -4
- package/locales/ru-RU/auth.json +75 -3
- package/locales/tr-TR/auth.json +74 -3
- package/locales/vi-VN/auth.json +75 -3
- package/locales/zh-CN/auth.json +75 -3
- package/locales/zh-TW/auth.json +75 -3
- package/package.json +13 -3
- package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +4 -0
- package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +0 -46
- package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +11 -14
- package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +6 -21
- package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +38 -21
- package/src/app/(main)/(mobile)/me/profile/layout.tsx +0 -3
- package/src/app/(main)/(mobile)/me/profile/page.tsx +3 -3
- package/src/app/(main)/chat/(workspace)/@topic/features/TopicListContent/ByTimeMode/index.tsx +1 -1
- package/src/app/(main)/chat/loading.tsx +2 -2
- package/src/app/(main)/discover/loading.tsx +2 -8
- package/src/app/(main)/files/loading.tsx +2 -2
- package/src/app/(main)/profile/(home)/Client.tsx +53 -0
- package/src/app/(main)/profile/(home)/[[...slugs]]/page.tsx +38 -0
- package/src/app/(main)/profile/@category/default.tsx +9 -0
- package/src/app/(main)/profile/@category/features/CategoryContent.tsx +38 -0
- package/src/app/(main)/profile/_layout/Desktop/Header.tsx +85 -0
- package/src/app/(main)/profile/_layout/Desktop/SideBar.tsx +42 -0
- package/src/app/(main)/profile/_layout/Desktop/index.tsx +48 -0
- package/src/app/(main)/profile/_layout/Mobile/Header.tsx +23 -5
- package/src/app/(main)/profile/_layout/Mobile/index.tsx +12 -5
- package/src/app/(main)/profile/_layout/type.ts +6 -0
- package/src/app/(main)/profile/error.tsx +5 -0
- package/src/app/(main)/profile/features/ClerkProfile.tsx +72 -0
- package/src/app/(main)/profile/hooks/useCategory.tsx +51 -0
- package/src/app/(main)/profile/layout.tsx +7 -17
- package/src/app/(main)/profile/loading.tsx +2 -22
- package/src/app/(main)/profile/not-found.tsx +3 -0
- package/src/app/(main)/profile/security/page.tsx +34 -0
- package/src/app/(main)/profile/stats/Client.tsx +52 -0
- package/src/app/(main)/profile/stats/features/AiHeatmaps.tsx +130 -0
- package/src/app/(main)/profile/stats/features/AssistantsRank.tsx +115 -0
- package/src/app/(main)/profile/stats/features/ModelsRank.tsx +84 -0
- package/src/app/(main)/profile/stats/features/ShareButton/Preview.tsx +159 -0
- package/src/app/(main)/profile/stats/features/ShareButton/ShareModal.tsx +87 -0
- package/src/app/(main)/profile/stats/features/ShareButton/TotalCard.tsx +39 -0
- package/src/app/(main)/profile/stats/features/ShareButton/index.tsx +26 -0
- package/src/app/(main)/profile/stats/features/TimeLabel.tsx +30 -0
- package/src/app/(main)/profile/stats/features/TopicsRank.tsx +103 -0
- package/src/app/(main)/profile/stats/features/TotalAssistants.tsx +56 -0
- package/src/app/(main)/profile/stats/features/TotalMessages.tsx +56 -0
- package/src/app/(main)/profile/stats/features/TotalTopics.tsx +53 -0
- package/src/app/(main)/profile/stats/features/TotalWords.tsx +54 -0
- package/src/app/(main)/profile/stats/features/Welcome.tsx +86 -0
- package/src/app/(main)/profile/{[[...slugs]] → stats}/page.tsx +4 -5
- package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +2 -2
- package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +2 -2
- package/src/app/(main)/settings/@category/features/CategoryContent.tsx +1 -1
- package/src/app/(main)/settings/_layout/Desktop/index.tsx +1 -1
- package/src/app/(main)/settings/_layout/Mobile/Header.tsx +1 -1
- package/src/app/(main)/settings/_layout/Mobile/index.tsx +2 -0
- package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -17
- package/src/app/(main)/settings/loading.tsx +2 -2
- package/src/components/Loading/BrandTextLoading/index.tsx +2 -2
- package/src/components/Statistic/index.tsx +15 -0
- package/src/components/StatisticCard/TitleWithPercentage.tsx +80 -0
- package/src/components/StatisticCard/growthPercentage.tsx +8 -0
- package/src/components/StatisticCard/index.tsx +209 -0
- package/src/const/url.ts +3 -3
- package/src/database/server/models/__tests__/message.test.ts +346 -35
- package/src/database/server/models/__tests__/session.test.ts +185 -2
- package/src/database/server/models/__tests__/topic.test.ts +136 -0
- package/src/database/server/models/__tests__/user.test.ts +140 -1
- package/src/database/server/models/message.ts +109 -14
- package/src/database/server/models/session.ts +75 -4
- package/src/database/server/models/topic.ts +43 -3
- package/src/database/server/models/user.ts +22 -0
- package/src/database/utils/genWhere.ts +39 -0
- package/src/features/ShareModal/ShareImage/index.tsx +11 -24
- package/src/features/ShareModal/ShareImage/type.ts +1 -6
- package/src/features/User/DataStatistics.tsx +21 -14
- package/src/features/User/UserPanel/PanelContent.tsx +12 -16
- package/src/features/User/UserPanel/useMenu.tsx +4 -6
- package/src/features/User/__tests__/PanelContent.test.tsx +4 -0
- package/src/features/User/__tests__/useMenu.test.tsx +1 -21
- package/src/hooks/useActiveTabKey.ts +34 -1
- package/src/{features/ShareModal/ShareImage → hooks}/useScreenshot.ts +51 -6
- package/src/locales/default/auth.ts +74 -2
- package/src/server/ld.test.ts +1 -1
- package/src/server/modules/AssistantStore/index.ts +3 -2
- package/src/server/routers/lambda/message.ts +35 -6
- package/src/server/routers/lambda/session.ts +17 -3
- package/src/server/routers/lambda/topic.ts +17 -3
- package/src/server/routers/lambda/user.ts +4 -0
- package/src/server/services/changelog/index.ts +1 -1
- package/src/services/message/_deprecated.ts +16 -0
- package/src/services/message/client.test.ts +0 -18
- package/src/services/message/client.ts +12 -9
- package/src/services/message/server.ts +12 -4
- package/src/services/message/type.ts +15 -3
- package/src/services/session/_deprecated.ts +5 -0
- package/src/services/session/client.ts +6 -2
- package/src/services/session/server.ts +6 -2
- package/src/services/session/type.ts +7 -1
- package/src/services/topic/_deprecated.ts +5 -0
- package/src/services/topic/client.ts +6 -2
- package/src/services/topic/server.ts +7 -1
- package/src/services/topic/type.ts +7 -2
- package/src/services/user/_deprecated.ts +4 -0
- package/src/services/user/client.ts +4 -0
- package/src/services/user/server.ts +4 -0
- package/src/services/user/type.ts +5 -0
- package/src/store/global/initialState.ts +6 -0
- package/src/store/user/slices/auth/action.test.ts +1 -33
- package/src/store/user/slices/auth/action.ts +0 -9
- package/src/store/user/slices/common/action.test.ts +2 -2
- package/src/types/message/index.ts +5 -0
- package/src/types/session/index.ts +8 -0
- package/src/types/topic/topic.ts +7 -0
- package/src/utils/format.ts +1 -1
- package/src/utils/time.ts +23 -0
- package/src/app/(main)/profile/[[...slugs]]/Client.tsx +0 -76
- package/src/components/Loading/BrandTextLoading/LobeChatText/SVG.tsx +0 -44
- package/src/components/Loading/BrandTextLoading/LobeChatText/index.tsx +0 -6
- package/src/components/Loading/BrandTextLoading/LobeChatText/style.css +0 -32
- package/src/hooks/useActiveSettingsKey.ts +0 -20
@@ -0,0 +1,39 @@
|
|
1
|
+
import dayjs, { Dayjs } from 'dayjs';
|
2
|
+
import { SQL } from 'drizzle-orm';
|
3
|
+
import { and, gte, lte } from 'drizzle-orm/expressions';
|
4
|
+
|
5
|
+
export const genWhere = (sqls: (SQL<any> | undefined)[]): SQL<any> | undefined => {
|
6
|
+
const where = sqls.filter(Boolean);
|
7
|
+
if (where.length > 1) return and(...where);
|
8
|
+
return where[0];
|
9
|
+
};
|
10
|
+
|
11
|
+
export const genStartDateWhere = (
|
12
|
+
date: string | undefined,
|
13
|
+
key: any,
|
14
|
+
format: (date: Dayjs) => any,
|
15
|
+
): SQL | undefined => {
|
16
|
+
if (!date || !dayjs(date).isValid()) return;
|
17
|
+
return gte(key, format(dayjs(new Date(date))));
|
18
|
+
};
|
19
|
+
|
20
|
+
export const genEndDateWhere = (
|
21
|
+
date: string | undefined,
|
22
|
+
key: any,
|
23
|
+
format: (date: Dayjs) => any,
|
24
|
+
): SQL | undefined => {
|
25
|
+
if (!date || !dayjs(date).isValid()) return;
|
26
|
+
return lte(key, format(dayjs(new Date(date)).add(1, 'day')));
|
27
|
+
};
|
28
|
+
|
29
|
+
export const genRangeWhere = (
|
30
|
+
range: [string, string] | undefined,
|
31
|
+
key: any,
|
32
|
+
format: (date: Dayjs) => any,
|
33
|
+
): SQL | undefined => {
|
34
|
+
if (!range) return;
|
35
|
+
if (!dayjs(range[0]).isValid() && !dayjs(range[1]).isValid()) return;
|
36
|
+
if (!dayjs(range[0]).isValid()) return genEndDateWhere(range[1], key, format);
|
37
|
+
if (!dayjs(range[1]).isValid()) return genStartDateWhere(range[0], key, format);
|
38
|
+
return and(genStartDateWhere(range[0], key, format), genEndDateWhere(range[1], key, format));
|
39
|
+
};
|
@@ -1,34 +1,17 @@
|
|
1
1
|
import { Form, type FormItemProps } from '@lobehub/ui';
|
2
|
-
import { Button, Segmented,
|
2
|
+
import { Button, Segmented, Switch } from 'antd';
|
3
3
|
import { memo, useState } from 'react';
|
4
4
|
import { useTranslation } from 'react-i18next';
|
5
5
|
import { Flexbox } from 'react-layout-kit';
|
6
6
|
|
7
7
|
import { FORM_STYLE } from '@/const/layoutTokens';
|
8
8
|
import { useIsMobile } from '@/hooks/useIsMobile';
|
9
|
+
import { ImageType, imageTypeOptions, useScreenshot } from '@/hooks/useScreenshot';
|
10
|
+
import { useSessionStore } from '@/store/session';
|
11
|
+
import { sessionMetaSelectors } from '@/store/session/selectors';
|
9
12
|
|
10
13
|
import Preview from './Preview';
|
11
|
-
import { FieldType
|
12
|
-
import { useScreenshot } from './useScreenshot';
|
13
|
-
|
14
|
-
export const imageTypeOptions: SegmentedProps['options'] = [
|
15
|
-
{
|
16
|
-
label: 'JPG',
|
17
|
-
value: ImageType.JPG,
|
18
|
-
},
|
19
|
-
{
|
20
|
-
label: 'PNG',
|
21
|
-
value: ImageType.PNG,
|
22
|
-
},
|
23
|
-
{
|
24
|
-
label: 'SVG',
|
25
|
-
value: ImageType.SVG,
|
26
|
-
},
|
27
|
-
{
|
28
|
-
label: 'WEBP',
|
29
|
-
value: ImageType.WEBP,
|
30
|
-
},
|
31
|
-
];
|
14
|
+
import { FieldType } from './type';
|
32
15
|
|
33
16
|
const DEFAULT_FIELD_VALUE: FieldType = {
|
34
17
|
imageType: ImageType.JPG,
|
@@ -39,9 +22,13 @@ const DEFAULT_FIELD_VALUE: FieldType = {
|
|
39
22
|
};
|
40
23
|
|
41
24
|
const ShareImage = memo(() => {
|
25
|
+
const currentAgentTitle = useSessionStore(sessionMetaSelectors.currentAgentTitle);
|
42
26
|
const [fieldValue, setFieldValue] = useState<FieldType>(DEFAULT_FIELD_VALUE);
|
43
|
-
const { t } = useTranslation('chat');
|
44
|
-
const { loading, onDownload, title } = useScreenshot(
|
27
|
+
const { t } = useTranslation(['chat', 'common']);
|
28
|
+
const { loading, onDownload, title } = useScreenshot({
|
29
|
+
imageType: fieldValue.imageType,
|
30
|
+
title: currentAgentTitle,
|
31
|
+
});
|
45
32
|
|
46
33
|
const settings: FormItemProps[] = [
|
47
34
|
{
|
@@ -3,17 +3,18 @@
|
|
3
3
|
import { Icon, Tooltip } from '@lobehub/ui';
|
4
4
|
import { Badge } from 'antd';
|
5
5
|
import { createStyles } from 'antd-style';
|
6
|
-
import { isNumber } from 'lodash-es';
|
6
|
+
import { isNumber, isUndefined } from 'lodash-es';
|
7
7
|
import { LoaderCircle } from 'lucide-react';
|
8
8
|
import { memo, useMemo } from 'react';
|
9
9
|
import { useTranslation } from 'react-i18next';
|
10
10
|
import { Flexbox, FlexboxProps } from 'react-layout-kit';
|
11
|
-
import useSWR from 'swr';
|
12
11
|
|
12
|
+
import { useClientDataSWR } from '@/libs/swr';
|
13
13
|
import { messageService } from '@/services/message';
|
14
14
|
import { sessionService } from '@/services/session';
|
15
15
|
import { topicService } from '@/services/topic';
|
16
16
|
import { useServerConfigStore } from '@/store/serverConfig';
|
17
|
+
import { today } from '@/utils/time';
|
17
18
|
|
18
19
|
const useStyles = createStyles(({ css, token }) => ({
|
19
20
|
card: css`
|
@@ -21,6 +22,10 @@ const useStyles = createStyles(({ css, token }) => ({
|
|
21
22
|
padding-inline: 8px;
|
22
23
|
background: ${token.colorFillTertiary};
|
23
24
|
border-radius: ${token.borderRadius}px;
|
25
|
+
|
26
|
+
&:hover {
|
27
|
+
background: ${token.colorFillSecondary};
|
28
|
+
}
|
24
29
|
`,
|
25
30
|
count: css`
|
26
31
|
font-size: 16px;
|
@@ -57,21 +62,23 @@ const formatNumber = (num: any) => {
|
|
57
62
|
const DataStatistics = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest }) => {
|
58
63
|
const mobile = useServerConfigStore((s) => s.isMobile);
|
59
64
|
// sessions
|
60
|
-
const { data: sessions, isLoading: sessionsLoading } =
|
61
|
-
|
62
|
-
sessionService.countSessions,
|
65
|
+
const { data: sessions, isLoading: sessionsLoading } = useClientDataSWR('count-sessions', () =>
|
66
|
+
sessionService.countSessions(),
|
63
67
|
);
|
64
68
|
// topics
|
65
|
-
const { data: topics, isLoading: topicsLoading } =
|
66
|
-
|
67
|
-
topicService.countTopics,
|
69
|
+
const { data: topics, isLoading: topicsLoading } = useClientDataSWR('count-topics', () =>
|
70
|
+
topicService.countTopics(),
|
68
71
|
);
|
69
72
|
// messages
|
70
|
-
const { data: messages, isLoading: messagesLoading } =
|
73
|
+
const { data: { messages, messagesToday } = {}, isLoading: messagesLoading } = useClientDataSWR(
|
71
74
|
'count-messages',
|
72
|
-
|
75
|
+
async () => ({
|
76
|
+
messages: await messageService.countMessages(),
|
77
|
+
messagesToday: await messageService.countMessages({
|
78
|
+
startDate: today().format('YYYY-MM-DD'),
|
79
|
+
}),
|
80
|
+
}),
|
73
81
|
);
|
74
|
-
const { data: messagesToday } = useSWR('today-messages', messageService.countTodayMessages);
|
75
82
|
|
76
83
|
const { styles, theme } = useStyles();
|
77
84
|
const { t } = useTranslation('common');
|
@@ -80,17 +87,17 @@ const DataStatistics = memo<Omit<FlexboxProps, 'children'>>(({ style, ...rest })
|
|
80
87
|
|
81
88
|
const items = [
|
82
89
|
{
|
83
|
-
count: sessionsLoading ? loading : sessions,
|
90
|
+
count: sessionsLoading || isUndefined(sessions) ? loading : sessions,
|
84
91
|
key: 'sessions',
|
85
92
|
title: t('dataStatistics.sessions'),
|
86
93
|
},
|
87
94
|
{
|
88
|
-
count: topicsLoading ? loading : topics,
|
95
|
+
count: topicsLoading || isUndefined(topics) ? loading : topics,
|
89
96
|
key: 'topics',
|
90
97
|
title: t('dataStatistics.topics'),
|
91
98
|
},
|
92
99
|
{
|
93
|
-
count: messagesLoading ? loading : messages,
|
100
|
+
count: messagesLoading || isUndefined(messages) ? loading : messages,
|
94
101
|
countToady: messagesToday,
|
95
102
|
key: 'messages',
|
96
103
|
title: t('dataStatistics.messages'),
|
@@ -1,9 +1,11 @@
|
|
1
|
+
import Link from 'next/link';
|
1
2
|
import { useRouter } from 'next/navigation';
|
2
3
|
import { memo } from 'react';
|
3
4
|
import { Flexbox } from 'react-layout-kit';
|
4
5
|
|
5
6
|
import BrandWatermark from '@/components/BrandWatermark';
|
6
7
|
import Menu from '@/components/Menu';
|
8
|
+
import { isDeprecatedEdition } from '@/const/version';
|
7
9
|
import { useUserStore } from '@/store/user';
|
8
10
|
import { authSelectors } from '@/store/user/selectors';
|
9
11
|
|
@@ -17,21 +19,14 @@ import { useMenu } from './useMenu';
|
|
17
19
|
const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
18
20
|
const router = useRouter();
|
19
21
|
const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
|
20
|
-
const [openSignIn, signOut,
|
22
|
+
const [openSignIn, signOut, enableAuth, enabledNextAuth] = useUserStore((s) => [
|
21
23
|
s.openLogin,
|
22
24
|
s.logout,
|
23
|
-
s.openUserProfile,
|
24
25
|
s.enableAuth(),
|
25
26
|
s.enabledNextAuth,
|
26
27
|
]);
|
27
28
|
const { mainItems, logoutItems } = useMenu();
|
28
29
|
|
29
|
-
const handleOpenProfile = () => {
|
30
|
-
if (!enableAuth) return;
|
31
|
-
openUserProfile();
|
32
|
-
closePopover();
|
33
|
-
};
|
34
|
-
|
35
30
|
const handleSignIn = () => {
|
36
31
|
openSignIn();
|
37
32
|
closePopover();
|
@@ -47,15 +42,16 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
|
47
42
|
|
48
43
|
return (
|
49
44
|
<Flexbox gap={2} style={{ minWidth: 300 }}>
|
50
|
-
{!enableAuth ? (
|
51
|
-
<>
|
52
|
-
<UserInfo />
|
53
|
-
<DataStatistics />
|
54
|
-
</>
|
55
|
-
) : isLoginWithAuth ? (
|
45
|
+
{!enableAuth || (enableAuth && isLoginWithAuth) ? (
|
56
46
|
<>
|
57
|
-
<
|
58
|
-
|
47
|
+
<Link href={'/profile'} style={{ color: 'inherit' }}>
|
48
|
+
<UserInfo />
|
49
|
+
</Link>
|
50
|
+
{!isDeprecatedEdition && (
|
51
|
+
<Link href={'/profile/stats'} style={{ color: 'inherit' }}>
|
52
|
+
<DataStatistics />
|
53
|
+
</Link>
|
54
|
+
)}
|
59
55
|
</>
|
60
56
|
) : (
|
61
57
|
<UserLoginOrSignup onClick={handleSignIn} />
|
@@ -68,19 +68,17 @@ export const useMenu = () => {
|
|
68
68
|
const hasNewVersion = useNewVersion();
|
69
69
|
const { t } = useTranslation(['common', 'setting', 'auth']);
|
70
70
|
const { showCloudPromotion, hideDocs } = useServerConfigStore(featureFlagsSelectors);
|
71
|
-
const [isLogin, isLoginWithAuth
|
71
|
+
const [enableAuth, isLogin, isLoginWithAuth] = useUserStore((s) => [
|
72
|
+
authSelectors.enabledAuth(s),
|
72
73
|
authSelectors.isLogin(s),
|
73
74
|
authSelectors.isLoginWithAuth(s),
|
74
|
-
authSelectors.isLoginWithClerk(s),
|
75
|
-
s.openUserProfile,
|
76
75
|
]);
|
77
76
|
|
78
77
|
const profile: MenuProps['items'] = [
|
79
78
|
{
|
80
79
|
icon: <Icon icon={CircleUserRound} />,
|
81
80
|
key: 'profile',
|
82
|
-
label: t('userPanel.profile')
|
83
|
-
onClick: () => openUserProfile(),
|
81
|
+
label: <Link href={'/profile'}>{t('userPanel.profile')}</Link>,
|
84
82
|
},
|
85
83
|
];
|
86
84
|
|
@@ -227,7 +225,7 @@ export const useMenu = () => {
|
|
227
225
|
{
|
228
226
|
type: 'divider',
|
229
227
|
},
|
230
|
-
...(
|
228
|
+
...(!enableAuth || (enableAuth && isLoginWithAuth) ? profile : []),
|
231
229
|
...(isLogin ? settings : []),
|
232
230
|
/* ↓ cloud slot ↓ */
|
233
231
|
|
@@ -76,26 +76,6 @@ describe('useMenu', () => {
|
|
76
76
|
|
77
77
|
const { result } = renderHook(() => useMenu(), { wrapper });
|
78
78
|
|
79
|
-
act(() => {
|
80
|
-
const { mainItems, logoutItems } = result.current;
|
81
|
-
expect(mainItems?.some((item) => item?.key === 'profile')).toBe(false);
|
82
|
-
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
|
83
|
-
expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
|
84
|
-
expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
|
85
|
-
expect(mainItems?.some((item) => item?.key === 'changelog')).toBe(true);
|
86
|
-
expect(logoutItems.some((item) => item?.key === 'logout')).toBe(true);
|
87
|
-
});
|
88
|
-
});
|
89
|
-
|
90
|
-
it('should provide correct menu items when user is logged in with Clerk', () => {
|
91
|
-
act(() => {
|
92
|
-
useUserStore.setState({ isSignedIn: true });
|
93
|
-
});
|
94
|
-
enableAuth = true;
|
95
|
-
enableClerk = true;
|
96
|
-
|
97
|
-
const { result } = renderHook(() => useMenu(), { wrapper });
|
98
|
-
|
99
79
|
act(() => {
|
100
80
|
const { mainItems, logoutItems } = result.current;
|
101
81
|
expect(mainItems?.some((item) => item?.key === 'profile')).toBe(true);
|
@@ -117,7 +97,7 @@ describe('useMenu', () => {
|
|
117
97
|
|
118
98
|
act(() => {
|
119
99
|
const { mainItems, logoutItems } = result.current;
|
120
|
-
expect(mainItems?.some((item) => item?.key === 'profile')).toBe(
|
100
|
+
expect(mainItems?.some((item) => item?.key === 'profile')).toBe(true);
|
121
101
|
expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
|
122
102
|
expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
|
123
103
|
expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { usePathname } from 'next/navigation';
|
2
2
|
|
3
|
-
import {
|
3
|
+
import { useQuery } from '@/hooks/useQuery';
|
4
|
+
import { ProfileTabs, SettingsTabs, SidebarTabKey } from '@/store/global/initialState';
|
4
5
|
|
5
6
|
/**
|
6
7
|
* Returns the active tab key (chat/market/settings/...)
|
@@ -10,3 +11,35 @@ export const useActiveTabKey = () => {
|
|
10
11
|
|
11
12
|
return pathname.split('/').find(Boolean)! as SidebarTabKey;
|
12
13
|
};
|
14
|
+
|
15
|
+
/**
|
16
|
+
* Returns the active setting page key (common/sync/agent/...)
|
17
|
+
*/
|
18
|
+
export const useActiveSettingsKey = () => {
|
19
|
+
const pathname = usePathname();
|
20
|
+
const { tab } = useQuery();
|
21
|
+
|
22
|
+
const tabs = pathname.split('/').at(-1);
|
23
|
+
|
24
|
+
if (tabs === 'settings') return SettingsTabs.Common;
|
25
|
+
|
26
|
+
if (tabs === 'modal') return tab as SettingsTabs;
|
27
|
+
|
28
|
+
return tabs as SettingsTabs;
|
29
|
+
};
|
30
|
+
|
31
|
+
/**
|
32
|
+
* Returns the active profile page key (profile/security/stats/...)
|
33
|
+
*/
|
34
|
+
export const useActiveProfileKey = () => {
|
35
|
+
const pathname = usePathname();
|
36
|
+
const { tab } = useQuery();
|
37
|
+
|
38
|
+
const tabs = pathname.split('/').at(-1);
|
39
|
+
|
40
|
+
if (tabs === 'profile') return ProfileTabs.Profile;
|
41
|
+
|
42
|
+
if (tabs === 'modal') return tab as ProfileTabs;
|
43
|
+
|
44
|
+
return tabs as ProfileTabs;
|
45
|
+
};
|
@@ -1,16 +1,48 @@
|
|
1
|
+
import { SegmentedProps } from 'antd';
|
1
2
|
import dayjs from 'dayjs';
|
2
3
|
import { domToJpeg, domToPng, domToSvg, domToWebp } from 'modern-screenshot';
|
3
4
|
import { useCallback, useState } from 'react';
|
4
5
|
|
5
6
|
import { BRANDING_NAME } from '@/const/branding';
|
6
|
-
import { useSessionStore } from '@/store/session';
|
7
|
-
import { sessionMetaSelectors } from '@/store/session/selectors';
|
8
7
|
|
9
|
-
|
8
|
+
export enum ImageType {
|
9
|
+
JPG = 'jpg',
|
10
|
+
PNG = 'png',
|
11
|
+
SVG = 'svg',
|
12
|
+
WEBP = 'webp',
|
13
|
+
}
|
10
14
|
|
11
|
-
export const
|
15
|
+
export const imageTypeOptions: SegmentedProps['options'] = [
|
16
|
+
{
|
17
|
+
label: 'JPG',
|
18
|
+
value: ImageType.JPG,
|
19
|
+
},
|
20
|
+
{
|
21
|
+
label: 'PNG',
|
22
|
+
value: ImageType.PNG,
|
23
|
+
},
|
24
|
+
{
|
25
|
+
label: 'SVG',
|
26
|
+
value: ImageType.SVG,
|
27
|
+
},
|
28
|
+
{
|
29
|
+
label: 'WEBP',
|
30
|
+
value: ImageType.WEBP,
|
31
|
+
},
|
32
|
+
];
|
33
|
+
|
34
|
+
export const useScreenshot = ({
|
35
|
+
imageType,
|
36
|
+
title = 'share',
|
37
|
+
id = '#preview',
|
38
|
+
width,
|
39
|
+
}: {
|
40
|
+
id?: string;
|
41
|
+
imageType: ImageType;
|
42
|
+
title?: string;
|
43
|
+
width?: number;
|
44
|
+
}) => {
|
12
45
|
const [loading, setLoading] = useState(false);
|
13
|
-
const title = useSessionStore(sessionMetaSelectors.currentAgentTitle);
|
14
46
|
|
15
47
|
const handleDownload = useCallback(async () => {
|
16
48
|
setLoading(true);
|
@@ -35,13 +67,26 @@ export const useScreenshot = (imageType: ImageType) => {
|
|
35
67
|
}
|
36
68
|
}
|
37
69
|
|
38
|
-
const
|
70
|
+
const dom: HTMLDivElement = document.querySelector(id) as HTMLDivElement;
|
71
|
+
let copy: HTMLDivElement = dom;
|
72
|
+
|
73
|
+
if (width) {
|
74
|
+
copy = dom.cloneNode(true) as HTMLDivElement;
|
75
|
+
copy.style.width = `${width}px`;
|
76
|
+
document.body.append(copy);
|
77
|
+
}
|
78
|
+
|
79
|
+
const dataUrl = await screenshotFn(width ? copy : dom, {
|
39
80
|
features: {
|
40
81
|
// 不启用移除控制符,否则会导致 safari emoji 报错
|
41
82
|
removeControlCharacter: false,
|
42
83
|
},
|
43
84
|
scale: 2,
|
85
|
+
width,
|
44
86
|
});
|
87
|
+
|
88
|
+
if (width && copy) copy?.remove();
|
89
|
+
|
45
90
|
const link = document.createElement('a');
|
46
91
|
link.download = `${BRANDING_NAME}_${title}_${dayjs().format('YYYY-MM-DD')}.${imageType}`;
|
47
92
|
link.href = dataUrl;
|
@@ -1,8 +1,80 @@
|
|
1
1
|
export default {
|
2
|
+
date: {
|
3
|
+
prevMonth: '上个月',
|
4
|
+
recent30Days: '最近30天',
|
5
|
+
},
|
6
|
+
header: {
|
7
|
+
desc: '管理您的账户信息。',
|
8
|
+
title: '账户',
|
9
|
+
},
|
10
|
+
heatmaps: {
|
11
|
+
legend: {
|
12
|
+
less: '不活跃',
|
13
|
+
more: '活跃',
|
14
|
+
},
|
15
|
+
months: {
|
16
|
+
apr: '四月',
|
17
|
+
aug: '八月',
|
18
|
+
dec: '十二月',
|
19
|
+
feb: '二月',
|
20
|
+
jan: '一月',
|
21
|
+
jul: '七月',
|
22
|
+
jun: '六月',
|
23
|
+
mar: '三月',
|
24
|
+
may: '五月',
|
25
|
+
nov: '十一月',
|
26
|
+
oct: '十月',
|
27
|
+
sep: '九月',
|
28
|
+
},
|
29
|
+
tooltip: '{{date}} 当日发送 {{count}} 条消息',
|
30
|
+
totalCount: '过去一年共发送 {{count}} 条消息',
|
31
|
+
},
|
2
32
|
login: '登录',
|
3
33
|
loginOrSignup: '登录 / 注册',
|
4
|
-
profile:
|
5
|
-
|
34
|
+
profile: {
|
35
|
+
avatar: '头像',
|
36
|
+
email: '电子邮件地址',
|
37
|
+
username: '用户名',
|
38
|
+
},
|
6
39
|
signout: '退出登录',
|
7
40
|
signup: '注册',
|
41
|
+
stats: {
|
42
|
+
aiheatmaps: 'AI 指数',
|
43
|
+
assistants: '助手数',
|
44
|
+
assistantsRank: {
|
45
|
+
left: '助手名称',
|
46
|
+
right: '话题数',
|
47
|
+
title: '助手使用率',
|
48
|
+
},
|
49
|
+
createdAt: '用户创建于',
|
50
|
+
days: '天',
|
51
|
+
empty: {
|
52
|
+
desc: '请积累更多聊天数据后查看',
|
53
|
+
title: '暂无数据',
|
54
|
+
},
|
55
|
+
lastYearActivity: '过去一年活跃度',
|
56
|
+
messages: '消息数',
|
57
|
+
modelsRank: {
|
58
|
+
left: '模型名称',
|
59
|
+
right: '消息数',
|
60
|
+
title: '模型使用率',
|
61
|
+
},
|
62
|
+
share: {
|
63
|
+
title: '我的 AI 活跃指数',
|
64
|
+
},
|
65
|
+
topics: '话题数',
|
66
|
+
topicsRank: {
|
67
|
+
left: '话题名称',
|
68
|
+
right: '消息数',
|
69
|
+
title: '话题内容量',
|
70
|
+
},
|
71
|
+
updatedAt: '数据更新至',
|
72
|
+
welcome: '{{username}}, 这是你和 {{appName}} 相伴的第 <span>{{days}}</span> 天',
|
73
|
+
words: '累计字数',
|
74
|
+
},
|
75
|
+
tab: {
|
76
|
+
profile: '个人资料',
|
77
|
+
security: '安全',
|
78
|
+
stats: '数据统计',
|
79
|
+
},
|
8
80
|
};
|
package/src/server/ld.test.ts
CHANGED
@@ -36,7 +36,7 @@ export class AssistantStore {
|
|
36
36
|
}
|
37
37
|
|
38
38
|
if (!res.ok) {
|
39
|
-
console.
|
39
|
+
console.warn('fetch agent index error:', await res.text());
|
40
40
|
return [];
|
41
41
|
}
|
42
42
|
|
@@ -55,7 +55,8 @@ export class AssistantStore {
|
|
55
55
|
|
56
56
|
return data;
|
57
57
|
} catch (e) {
|
58
|
-
console.error('fetch agent index error:'
|
58
|
+
console.error('[AgentIndexFetchError] failed to fetch agent index, error detail:');
|
59
|
+
console.error(e);
|
59
60
|
|
60
61
|
throw e;
|
61
62
|
}
|
@@ -27,12 +27,33 @@ export const messageRouter = router({
|
|
27
27
|
return { added: data.rowCount as number, ids: [], skips: [], success: true };
|
28
28
|
}),
|
29
29
|
|
30
|
-
count: messageProcedure
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
count: messageProcedure
|
31
|
+
.input(
|
32
|
+
z
|
33
|
+
.object({
|
34
|
+
endDate: z.string().optional(),
|
35
|
+
range: z.tuple([z.string(), z.string()]).optional(),
|
36
|
+
startDate: z.string().optional(),
|
37
|
+
})
|
38
|
+
.optional(),
|
39
|
+
)
|
40
|
+
.query(async ({ ctx, input }) => {
|
41
|
+
return ctx.messageModel.count(input);
|
42
|
+
}),
|
43
|
+
|
44
|
+
countWords: messageProcedure
|
45
|
+
.input(
|
46
|
+
z
|
47
|
+
.object({
|
48
|
+
endDate: z.string().optional(),
|
49
|
+
range: z.tuple([z.string(), z.string()]).optional(),
|
50
|
+
startDate: z.string().optional(),
|
51
|
+
})
|
52
|
+
.optional(),
|
53
|
+
)
|
54
|
+
.query(async ({ ctx, input }) => {
|
55
|
+
return ctx.messageModel.countWords(input);
|
56
|
+
}),
|
36
57
|
|
37
58
|
createMessage: messageProcedure
|
38
59
|
.input(z.object({}).passthrough().partial())
|
@@ -56,6 +77,10 @@ export const messageRouter = router({
|
|
56
77
|
return ctx.messageModel.queryBySessionId(input.sessionId);
|
57
78
|
}),
|
58
79
|
|
80
|
+
getHeatmaps: messageProcedure.query(async ({ ctx }) => {
|
81
|
+
return ctx.messageModel.getHeatmaps();
|
82
|
+
}),
|
83
|
+
|
59
84
|
// TODO: 未来这部分方法也需要使用 authedProcedure
|
60
85
|
getMessages: publicProcedure
|
61
86
|
.input(
|
@@ -74,6 +99,10 @@ export const messageRouter = router({
|
|
74
99
|
return messageModel.query(input, { postProcessUrl: (path) => getFullFileUrl(path) });
|
75
100
|
}),
|
76
101
|
|
102
|
+
rankModels: messageProcedure.query(async ({ ctx }) => {
|
103
|
+
return ctx.messageModel.rankModels();
|
104
|
+
}),
|
105
|
+
|
77
106
|
removeAllMessages: messageProcedure.mutation(async ({ ctx }) => {
|
78
107
|
return ctx.messageModel.deleteAllMessages();
|
79
108
|
}),
|
@@ -57,9 +57,19 @@ export const sessionRouter = router({
|
|
57
57
|
return data?.id;
|
58
58
|
}),
|
59
59
|
|
60
|
-
countSessions: sessionProcedure
|
61
|
-
|
62
|
-
|
60
|
+
countSessions: sessionProcedure
|
61
|
+
.input(
|
62
|
+
z
|
63
|
+
.object({
|
64
|
+
endDate: z.string().optional(),
|
65
|
+
range: z.tuple([z.string(), z.string()]).optional(),
|
66
|
+
startDate: z.string().optional(),
|
67
|
+
})
|
68
|
+
.optional(),
|
69
|
+
)
|
70
|
+
.query(async ({ ctx, input }) => {
|
71
|
+
return ctx.sessionModel.count(input);
|
72
|
+
}),
|
63
73
|
|
64
74
|
createSession: sessionProcedure
|
65
75
|
.input(
|
@@ -103,6 +113,10 @@ export const sessionRouter = router({
|
|
103
113
|
return ctx.sessionModel.query({ current, pageSize });
|
104
114
|
}),
|
105
115
|
|
116
|
+
rankSessions: sessionProcedure.input(z.number().optional()).query(async ({ ctx, input }) => {
|
117
|
+
return ctx.sessionModel.rank(input);
|
118
|
+
}),
|
119
|
+
|
106
120
|
removeAllSessions: sessionProcedure.mutation(async ({ ctx }) => {
|
107
121
|
return ctx.sessionModel.deleteAll();
|
108
122
|
}),
|