@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.
- package/CHANGELOG.md +33 -0
- package/changelog/v1.json +12 -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 +4 -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/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,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
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
1
2
|
import { eq } from 'drizzle-orm/expressions';
|
2
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
3
4
|
|
@@ -1042,41 +1043,6 @@ describe('MessageModel', () => {
|
|
1042
1043
|
});
|
1043
1044
|
});
|
1044
1045
|
|
1045
|
-
describe('countToday', () => {
|
1046
|
-
it('should return the count of messages created today', async () => {
|
1047
|
-
// 创建测试数据
|
1048
|
-
await serverDB.insert(messages).values([
|
1049
|
-
{
|
1050
|
-
id: '1',
|
1051
|
-
userId,
|
1052
|
-
role: 'user',
|
1053
|
-
content: 'message 1',
|
1054
|
-
createdAt: new Date(),
|
1055
|
-
},
|
1056
|
-
{
|
1057
|
-
id: '2',
|
1058
|
-
userId,
|
1059
|
-
role: 'user',
|
1060
|
-
content: 'message 2',
|
1061
|
-
createdAt: new Date(),
|
1062
|
-
},
|
1063
|
-
{
|
1064
|
-
id: '3',
|
1065
|
-
userId,
|
1066
|
-
role: 'user',
|
1067
|
-
content: 'message 3',
|
1068
|
-
createdAt: new Date('2023-01-01'),
|
1069
|
-
},
|
1070
|
-
]);
|
1071
|
-
|
1072
|
-
// 调用 countToday 方法
|
1073
|
-
const result = await messageModel.countToday();
|
1074
|
-
|
1075
|
-
// 断言结果
|
1076
|
-
expect(result).toBe(2);
|
1077
|
-
});
|
1078
|
-
});
|
1079
|
-
|
1080
1046
|
describe('findMessageQueriesById', () => {
|
1081
1047
|
it('should return undefined for non-existent message query', async () => {
|
1082
1048
|
const result = await messageModel.findMessageQueriesById('non-existent-id');
|
@@ -1211,4 +1177,349 @@ describe('MessageModel', () => {
|
|
1211
1177
|
expect(id2).toMatch(/^msg_/);
|
1212
1178
|
});
|
1213
1179
|
});
|
1180
|
+
|
1181
|
+
describe('countWords', () => {
|
1182
|
+
it('should count total words of messages belonging to the user', async () => {
|
1183
|
+
// 创建测试数据
|
1184
|
+
await serverDB.insert(messages).values([
|
1185
|
+
{ id: '1', userId, role: 'user', content: 'hello world' },
|
1186
|
+
{ id: '2', userId, role: 'user', content: 'test message' },
|
1187
|
+
{ id: '3', userId: '456', role: 'user', content: 'other user message' },
|
1188
|
+
]);
|
1189
|
+
|
1190
|
+
// 调用 countWords 方法
|
1191
|
+
const result = await messageModel.countWords();
|
1192
|
+
|
1193
|
+
// 断言结果 - 'hello world' + 'test message' = 23 characters
|
1194
|
+
expect(result).toEqual(23);
|
1195
|
+
});
|
1196
|
+
|
1197
|
+
it('should count words within date range', async () => {
|
1198
|
+
// 创建测试数据
|
1199
|
+
await serverDB.insert(messages).values([
|
1200
|
+
{
|
1201
|
+
id: '1',
|
1202
|
+
userId,
|
1203
|
+
role: 'user',
|
1204
|
+
content: 'old message',
|
1205
|
+
createdAt: new Date('2023-01-01'),
|
1206
|
+
},
|
1207
|
+
{
|
1208
|
+
id: '2',
|
1209
|
+
userId,
|
1210
|
+
role: 'user',
|
1211
|
+
content: 'new message',
|
1212
|
+
createdAt: new Date('2023-06-01'),
|
1213
|
+
},
|
1214
|
+
]);
|
1215
|
+
|
1216
|
+
// 调用 countWords 方法,设置日期范围
|
1217
|
+
const result = await messageModel.countWords({
|
1218
|
+
range: ['2023-05-01', '2023-07-01'],
|
1219
|
+
});
|
1220
|
+
|
1221
|
+
// 断言结果 - 只计算 'new message' = 11 characters
|
1222
|
+
expect(result).toEqual(11);
|
1223
|
+
});
|
1224
|
+
|
1225
|
+
it('should handle empty content', async () => {
|
1226
|
+
// 创建测试数据
|
1227
|
+
await serverDB.insert(messages).values([
|
1228
|
+
{ id: '1', userId, role: 'user', content: '' },
|
1229
|
+
{ id: '2', userId, role: 'user', content: null },
|
1230
|
+
]);
|
1231
|
+
|
1232
|
+
// 调用 countWords 方法
|
1233
|
+
const result = await messageModel.countWords();
|
1234
|
+
|
1235
|
+
// 断言结果
|
1236
|
+
expect(result).toEqual(0);
|
1237
|
+
});
|
1238
|
+
});
|
1239
|
+
|
1240
|
+
describe('getHeatmaps', () => {
|
1241
|
+
it('should return heatmap data for the last year', async () => {
|
1242
|
+
const today = dayjs();
|
1243
|
+
|
1244
|
+
// 创建测试数据
|
1245
|
+
await serverDB.insert(messages).values([
|
1246
|
+
{
|
1247
|
+
id: '1',
|
1248
|
+
userId,
|
1249
|
+
role: 'user',
|
1250
|
+
content: 'message 1',
|
1251
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
1252
|
+
},
|
1253
|
+
{
|
1254
|
+
id: '2',
|
1255
|
+
userId,
|
1256
|
+
role: 'user',
|
1257
|
+
content: 'message 2',
|
1258
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
1259
|
+
},
|
1260
|
+
{
|
1261
|
+
id: '3',
|
1262
|
+
userId,
|
1263
|
+
role: 'user',
|
1264
|
+
content: 'message 3',
|
1265
|
+
createdAt: today.subtract(1, 'day').toDate(),
|
1266
|
+
},
|
1267
|
+
]);
|
1268
|
+
|
1269
|
+
// 调用 getHeatmaps 方法
|
1270
|
+
const result = await messageModel.getHeatmaps();
|
1271
|
+
|
1272
|
+
// 断言结果
|
1273
|
+
expect(result.length).toBeGreaterThan(366);
|
1274
|
+
expect(result.length).toBeLessThan(368);
|
1275
|
+
|
1276
|
+
// 检查两天前的数据
|
1277
|
+
const twoDaysAgo = result.find(
|
1278
|
+
(item) => item.date === today.subtract(2, 'day').format('YYYY-MM-DD'),
|
1279
|
+
);
|
1280
|
+
expect(twoDaysAgo?.count).toBe(2);
|
1281
|
+
expect(twoDaysAgo?.level).toBe(0);
|
1282
|
+
|
1283
|
+
// 检查一天前的数据
|
1284
|
+
const oneDayAgo = result.find(
|
1285
|
+
(item) => item.date === today.subtract(1, 'day').format('YYYY-MM-DD'),
|
1286
|
+
);
|
1287
|
+
expect(oneDayAgo?.count).toBe(1);
|
1288
|
+
expect(oneDayAgo?.level).toBe(0);
|
1289
|
+
|
1290
|
+
// 检查今天的数据
|
1291
|
+
const todayData = result.find((item) => item.date === today.format('YYYY-MM-DD'));
|
1292
|
+
expect(todayData?.count).toBe(0);
|
1293
|
+
expect(todayData?.level).toBe(0);
|
1294
|
+
});
|
1295
|
+
|
1296
|
+
it('should calculate correct levels based on message count', async () => {
|
1297
|
+
const today = dayjs();
|
1298
|
+
|
1299
|
+
// 创建测试数据 - 不同数量的消息以测试不同的等级
|
1300
|
+
await serverDB.insert(messages).values([
|
1301
|
+
// 1 message - level 0
|
1302
|
+
{
|
1303
|
+
id: '1',
|
1304
|
+
userId,
|
1305
|
+
role: 'user',
|
1306
|
+
content: 'message 1',
|
1307
|
+
createdAt: today.subtract(4, 'day').toDate(),
|
1308
|
+
},
|
1309
|
+
// 6 messages - level 1
|
1310
|
+
...Array(6)
|
1311
|
+
.fill(0)
|
1312
|
+
.map((_, i) => ({
|
1313
|
+
id: `2-${i}`,
|
1314
|
+
userId,
|
1315
|
+
role: 'user',
|
1316
|
+
content: `message 2-${i}`,
|
1317
|
+
createdAt: today.subtract(3, 'day').toDate(),
|
1318
|
+
})),
|
1319
|
+
// 11 messages - level 2
|
1320
|
+
...Array(11)
|
1321
|
+
.fill(0)
|
1322
|
+
.map((_, i) => ({
|
1323
|
+
id: `3-${i}`,
|
1324
|
+
userId,
|
1325
|
+
role: 'user',
|
1326
|
+
content: `message 3-${i}`,
|
1327
|
+
createdAt: today.subtract(2, 'day').toDate(),
|
1328
|
+
})),
|
1329
|
+
// 16 messages - level 3
|
1330
|
+
...Array(16)
|
1331
|
+
.fill(0)
|
1332
|
+
.map((_, i) => ({
|
1333
|
+
id: `4-${i}`,
|
1334
|
+
userId,
|
1335
|
+
role: 'user',
|
1336
|
+
content: `message 4-${i}`,
|
1337
|
+
createdAt: today.subtract(1, 'day').toDate(),
|
1338
|
+
})),
|
1339
|
+
// 21 messages - level 4
|
1340
|
+
...Array(21)
|
1341
|
+
.fill(0)
|
1342
|
+
.map((_, i) => ({
|
1343
|
+
id: `5-${i}`,
|
1344
|
+
userId,
|
1345
|
+
role: 'user',
|
1346
|
+
content: `message 5-${i}`,
|
1347
|
+
createdAt: today.toDate(),
|
1348
|
+
})),
|
1349
|
+
]);
|
1350
|
+
|
1351
|
+
// 调用 getHeatmaps 方法
|
1352
|
+
const result = await messageModel.getHeatmaps();
|
1353
|
+
|
1354
|
+
// 检查不同天数的等级
|
1355
|
+
const fourDaysAgo = result.find(
|
1356
|
+
(item) => item.date === today.subtract(4, 'day').format('YYYY-MM-DD'),
|
1357
|
+
);
|
1358
|
+
expect(fourDaysAgo?.count).toBe(1);
|
1359
|
+
expect(fourDaysAgo?.level).toBe(0);
|
1360
|
+
|
1361
|
+
const threeDaysAgo = result.find(
|
1362
|
+
(item) => item.date === today.subtract(3, 'day').format('YYYY-MM-DD'),
|
1363
|
+
);
|
1364
|
+
expect(threeDaysAgo?.count).toBe(6);
|
1365
|
+
expect(threeDaysAgo?.level).toBe(1);
|
1366
|
+
|
1367
|
+
const twoDaysAgo = result.find(
|
1368
|
+
(item) => item.date === today.subtract(2, 'day').format('YYYY-MM-DD'),
|
1369
|
+
);
|
1370
|
+
expect(twoDaysAgo?.count).toBe(11);
|
1371
|
+
expect(twoDaysAgo?.level).toBe(2);
|
1372
|
+
|
1373
|
+
const oneDayAgo = result.find(
|
1374
|
+
(item) => item.date === today.subtract(1, 'day').format('YYYY-MM-DD'),
|
1375
|
+
);
|
1376
|
+
expect(oneDayAgo?.count).toBe(16);
|
1377
|
+
expect(oneDayAgo?.level).toBe(3);
|
1378
|
+
|
1379
|
+
const todayData = result.find((item) => item.date === today.format('YYYY-MM-DD'));
|
1380
|
+
expect(todayData?.count).toBe(21);
|
1381
|
+
expect(todayData?.level).toBe(4);
|
1382
|
+
});
|
1383
|
+
|
1384
|
+
it('should handle empty data', async () => {
|
1385
|
+
// 不创建任何消息数据
|
1386
|
+
|
1387
|
+
// 调用 getHeatmaps 方法
|
1388
|
+
const result = await messageModel.getHeatmaps();
|
1389
|
+
|
1390
|
+
// 断言结果
|
1391
|
+
expect(result.length).toBeGreaterThan(366);
|
1392
|
+
expect(result.length).toBeLessThan(368);
|
1393
|
+
|
1394
|
+
// 检查所有数据的 count 和 level 是否为 0
|
1395
|
+
result.forEach((item) => {
|
1396
|
+
expect(item.count).toBe(0);
|
1397
|
+
expect(item.level).toBe(0);
|
1398
|
+
});
|
1399
|
+
});
|
1400
|
+
});
|
1401
|
+
|
1402
|
+
describe('rankModels', () => {
|
1403
|
+
it('should rank models by usage count', async () => {
|
1404
|
+
// 创建测试数据
|
1405
|
+
await serverDB.insert(messages).values([
|
1406
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-3.5' },
|
1407
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: 'gpt-3.5' },
|
1408
|
+
{ id: '3', userId, role: 'assistant', content: 'message 3', model: 'gpt-4' },
|
1409
|
+
{ id: '4', userId: '456', role: 'assistant', content: 'message 4', model: 'gpt-3.5' }, // 其他用户的消息
|
1410
|
+
]);
|
1411
|
+
|
1412
|
+
// 调用 rankModels 方法
|
1413
|
+
const result = await messageModel.rankModels();
|
1414
|
+
|
1415
|
+
// 断言结果
|
1416
|
+
expect(result).toHaveLength(2);
|
1417
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 2 }); // 当前用户使用 gpt-3.5 两次
|
1418
|
+
expect(result[1]).toEqual({ id: 'gpt-4', count: 1 }); // 当前用户使用 gpt-4 一次
|
1419
|
+
});
|
1420
|
+
|
1421
|
+
it('should only count messages with model field', async () => {
|
1422
|
+
// 创建测试数据,包括没有 model 字段的消息
|
1423
|
+
await serverDB.insert(messages).values([
|
1424
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-3.5' },
|
1425
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: null },
|
1426
|
+
{ id: '3', userId, role: 'user', content: 'message 3' }, // 用户消息通常没有 model
|
1427
|
+
]);
|
1428
|
+
|
1429
|
+
// 调用 rankModels 方法
|
1430
|
+
const result = await messageModel.rankModels();
|
1431
|
+
|
1432
|
+
// 断言结果
|
1433
|
+
expect(result).toHaveLength(1);
|
1434
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 1 });
|
1435
|
+
});
|
1436
|
+
|
1437
|
+
it('should return empty array when no models are used', async () => {
|
1438
|
+
// 创建测试数据,所有消息都没有 model
|
1439
|
+
await serverDB.insert(messages).values([
|
1440
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
1441
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2' },
|
1442
|
+
]);
|
1443
|
+
|
1444
|
+
// 调用 rankModels 方法
|
1445
|
+
const result = await messageModel.rankModels();
|
1446
|
+
|
1447
|
+
// 断言结果
|
1448
|
+
expect(result).toHaveLength(0);
|
1449
|
+
});
|
1450
|
+
|
1451
|
+
it('should order models by count in descending order', async () => {
|
1452
|
+
// 创建测试数据,使用不同次数的模型
|
1453
|
+
await serverDB.insert(messages).values([
|
1454
|
+
{ id: '1', userId, role: 'assistant', content: 'message 1', model: 'gpt-4' },
|
1455
|
+
{ id: '2', userId, role: 'assistant', content: 'message 2', model: 'gpt-3.5' },
|
1456
|
+
{ id: '3', userId, role: 'assistant', content: 'message 3', model: 'gpt-3.5' },
|
1457
|
+
{ id: '4', userId, role: 'assistant', content: 'message 4', model: 'claude' },
|
1458
|
+
{ id: '5', userId, role: 'assistant', content: 'message 5', model: 'gpt-3.5' },
|
1459
|
+
]);
|
1460
|
+
|
1461
|
+
// 调用 rankModels 方法
|
1462
|
+
const result = await messageModel.rankModels();
|
1463
|
+
|
1464
|
+
// 断言结果
|
1465
|
+
expect(result).toHaveLength(3);
|
1466
|
+
expect(result[0]).toEqual({ id: 'gpt-3.5', count: 3 }); // 最多使用
|
1467
|
+
expect(result[1]).toEqual({ id: 'claude', count: 1 });
|
1468
|
+
expect(result[2]).toEqual({ id: 'gpt-4', count: 1 });
|
1469
|
+
});
|
1470
|
+
});
|
1471
|
+
|
1472
|
+
describe('hasMoreThanN', () => {
|
1473
|
+
it('should return true when message count is greater than N', async () => {
|
1474
|
+
// 创建测试数据
|
1475
|
+
await serverDB.insert(messages).values([
|
1476
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
1477
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
1478
|
+
{ id: '3', userId, role: 'user', content: 'message 3' },
|
1479
|
+
]);
|
1480
|
+
|
1481
|
+
// 测试不同的 N 值
|
1482
|
+
const result1 = await messageModel.hasMoreThanN(2); // 3 > 2
|
1483
|
+
const result2 = await messageModel.hasMoreThanN(3); // 3 ≯ 3
|
1484
|
+
const result3 = await messageModel.hasMoreThanN(4); // 3 ≯ 4
|
1485
|
+
|
1486
|
+
expect(result1).toBe(true);
|
1487
|
+
expect(result2).toBe(false);
|
1488
|
+
expect(result3).toBe(false);
|
1489
|
+
});
|
1490
|
+
|
1491
|
+
it('should only count messages belonging to the user', async () => {
|
1492
|
+
// 创建测试数据,包括其他用户的消息
|
1493
|
+
await serverDB.insert(messages).values([
|
1494
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
1495
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
1496
|
+
{ id: '3', userId: '456', role: 'user', content: 'message 3' }, // 其他用户的消息
|
1497
|
+
]);
|
1498
|
+
|
1499
|
+
const result = await messageModel.hasMoreThanN(2);
|
1500
|
+
|
1501
|
+
expect(result).toBe(false); // 当前用户只有 2 条消息,不大于 2
|
1502
|
+
});
|
1503
|
+
|
1504
|
+
it('should return false when no messages exist', async () => {
|
1505
|
+
const result = await messageModel.hasMoreThanN(0);
|
1506
|
+
expect(result).toBe(false);
|
1507
|
+
});
|
1508
|
+
|
1509
|
+
it('should handle edge cases', async () => {
|
1510
|
+
// 创建一条消息
|
1511
|
+
await serverDB
|
1512
|
+
.insert(messages)
|
1513
|
+
.values([{ id: '1', userId, role: 'user', content: 'message 1' }]);
|
1514
|
+
|
1515
|
+
// 测试边界情况
|
1516
|
+
const result1 = await messageModel.hasMoreThanN(0); // 1 > 0
|
1517
|
+
const result2 = await messageModel.hasMoreThanN(1); // 1 ≯ 1
|
1518
|
+
const result3 = await messageModel.hasMoreThanN(-1); // 1 > -1
|
1519
|
+
|
1520
|
+
expect(result1).toBe(true);
|
1521
|
+
expect(result2).toBe(false);
|
1522
|
+
expect(result3).toBe(true);
|
1523
|
+
});
|
1524
|
+
});
|
1214
1525
|
});
|