@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
@@ -56,9 +56,19 @@ export const topicRouter = router({
56
56
  return data.topic.id;
57
57
  }),
58
58
 
59
- countTopics: topicProcedure.query(async ({ ctx }) => {
60
- return ctx.topicModel.count();
61
- }),
59
+ countTopics: topicProcedure
60
+ .input(
61
+ z
62
+ .object({
63
+ endDate: z.string().optional(),
64
+ range: z.tuple([z.string(), z.string()]).optional(),
65
+ startDate: z.string().optional(),
66
+ })
67
+ .optional(),
68
+ )
69
+ .query(async ({ ctx, input }) => {
70
+ return ctx.topicModel.count(input);
71
+ }),
62
72
 
63
73
  createTopic: topicProcedure
64
74
  .input(
@@ -100,6 +110,10 @@ export const topicRouter = router({
100
110
  return (await ctx.topicModel.count()) === 0;
101
111
  }),
102
112
 
113
+ rankTopics: topicProcedure.input(z.number().optional()).query(async ({ ctx, input }) => {
114
+ return ctx.topicModel.rank(input);
115
+ }),
116
+
103
117
  removeAllTopics: topicProcedure.mutation(async ({ ctx }) => {
104
118
  return ctx.topicModel.deleteAll();
105
119
  }),
@@ -20,6 +20,10 @@ const userProcedure = authedProcedure.use(async (opts) => {
20
20
  });
21
21
 
22
22
  export const userRouter = router({
23
+ getUserRegistrationDuration: userProcedure.query(async ({ ctx }) => {
24
+ return ctx.userModel.getUserRegistrationDuration();
25
+ }),
26
+
23
27
  getUserState: userProcedure.query(async ({ ctx }): Promise<UserInitializationState> => {
24
28
  let state: Awaited<ReturnType<UserModel['getUserState']>> | undefined;
25
29
 
@@ -54,7 +54,7 @@ export class ChangelogService {
54
54
  return this.mergeChangelogs(data.cloud, data.community).slice(0, 5);
55
55
  } catch (e) {
56
56
  const cause = (e as Error).cause as { code: string };
57
- if (cause.code.includes('ETIMEDOUT')) {
57
+ if (cause?.code.includes('ETIMEDOUT')) {
58
58
  console.warn(
59
59
  '[ChangelogFetchTimeout] fail to fetch changelog lists due to network timeout. Please check your network connection.',
60
60
  );
@@ -10,6 +10,7 @@ import {
10
10
  ChatTTS,
11
11
  ChatTranslate,
12
12
  CreateMessageParams,
13
+ ModelRankItem,
13
14
  } from '@/types/message';
14
15
 
15
16
  import { IMessageService } from './type';
@@ -56,6 +57,21 @@ export class ClientService implements IMessageService {
56
57
  return MessageModel.count();
57
58
  }
58
59
 
60
+ // @ts-ignore
61
+ async rankModels(): Promise<ModelRankItem[]> {
62
+ throw new Error('Method not implemented.');
63
+ }
64
+
65
+ // @ts-ignore
66
+ async countWords(): Promise<number> {
67
+ throw new Error('Method not implemented.');
68
+ }
69
+
70
+ // @ts-ignore
71
+ async getHeatmaps() {
72
+ throw new Error('Method not implemented.');
73
+ }
74
+
59
75
  async countTodayMessages() {
60
76
  const topics = await MessageModel.queryAll();
61
77
  return topics.filter(
@@ -350,24 +350,6 @@ describe('MessageClientService', () => {
350
350
  });
351
351
  });
352
352
 
353
- describe('countTodayMessages', () => {
354
- it('should count the number of messages created today', async () => {
355
- // Setup
356
- const mockMessages = [
357
- { ...mockMessage, id: undefined, createdAt: new Date(), userId },
358
- { ...mockMessage, id: undefined, createdAt: new Date(), userId },
359
- { ...mockMessage, id: undefined, createdAt: new Date('2023-01-01'), userId },
360
- ];
361
- await clientDB.insert(messages).values(mockMessages);
362
-
363
- // Execute
364
- const count = await messageService.countTodayMessages();
365
-
366
- // Assert
367
- expect(count).toBe(2);
368
- });
369
- });
370
-
371
353
  describe('updateMessageTTS', () => {
372
354
  it('should update the TTS field of a message', async () => {
373
355
  // Setup
@@ -1,5 +1,3 @@
1
- import dayjs from 'dayjs';
2
-
3
1
  import { INBOX_SESSION_ID } from '@/const/session';
4
2
  import { clientDB } from '@/database/client/db';
5
3
  import { MessageModel } from '@/database/server/models/message';
@@ -52,15 +50,20 @@ export class ClientService extends BaseClientService implements IMessageService
52
50
  return data as unknown as ChatMessage[];
53
51
  };
54
52
 
55
- countMessages: IMessageService['countMessages'] = async () => {
56
- return this.messageModel.count();
53
+ countMessages: IMessageService['countMessages'] = async (params) => {
54
+ return this.messageModel.count(params);
55
+ };
56
+
57
+ countWords: IMessageService['countWords'] = async (params) => {
58
+ return this.messageModel.countWords(params);
59
+ };
60
+
61
+ rankModels: IMessageService['rankModels'] = async () => {
62
+ return this.messageModel.rankModels();
57
63
  };
58
64
 
59
- countTodayMessages: IMessageService['countTodayMessages'] = async () => {
60
- const topics = await this.messageModel.queryAll();
61
- return topics.filter(
62
- (item) => dayjs(item.createdAt).format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD'),
63
- ).length;
65
+ getHeatmaps: IMessageService['getHeatmaps'] = async () => {
66
+ return this.messageModel.getHeatmaps();
64
67
  };
65
68
 
66
69
  getAllMessagesInSession: IMessageService['getAllMessagesInSession'] = async (sessionId) => {
@@ -36,12 +36,20 @@ export class ServerService implements IMessageService {
36
36
  });
37
37
  };
38
38
 
39
- countMessages: IMessageService['countMessages'] = async () => {
40
- return lambdaClient.message.count.query();
39
+ countMessages: IMessageService['countMessages'] = async (params) => {
40
+ return lambdaClient.message.count.query(params);
41
41
  };
42
42
 
43
- countTodayMessages: IMessageService['countTodayMessages'] = async () => {
44
- return lambdaClient.message.countToday.query();
43
+ countWords: IMessageService['countWords'] = async (params) => {
44
+ return lambdaClient.message.countWords.query(params);
45
+ };
46
+
47
+ rankModels: IMessageService['rankModels'] = async () => {
48
+ return lambdaClient.message.rankModels.query();
49
+ };
50
+
51
+ getHeatmaps: IMessageService['getHeatmaps'] = async () => {
52
+ return lambdaClient.message.getHeatmaps.query();
45
53
  };
46
54
 
47
55
  updateMessageError: IMessageService['updateMessageError'] = async (id, error) => {
@@ -1,3 +1,5 @@
1
+ import type { HeatmapsProps } from '@lobehub/charts';
2
+
1
3
  import { MessageItem } from '@/database/schemas';
2
4
  import {
3
5
  ChatMessage,
@@ -5,6 +7,7 @@ import {
5
7
  ChatTTS,
6
8
  ChatTranslate,
7
9
  CreateMessageParams,
10
+ ModelRankItem,
8
11
  } from '@/types/message';
9
12
 
10
13
  /* eslint-disable typescript-sort-keys/interface */
@@ -16,9 +19,18 @@ export interface IMessageService {
16
19
  getMessages(sessionId: string, topicId?: string): Promise<ChatMessage[]>;
17
20
  getAllMessages(): Promise<ChatMessage[]>;
18
21
  getAllMessagesInSession(sessionId: string): Promise<ChatMessage[]>;
19
- countMessages(): Promise<number>;
20
- countTodayMessages(): Promise<number>;
21
-
22
+ countMessages(params?: {
23
+ endDate?: string;
24
+ range?: [string, string];
25
+ startDate?: string;
26
+ }): Promise<number>;
27
+ countWords(params?: {
28
+ endDate?: string;
29
+ range?: [string, string];
30
+ startDate?: string;
31
+ }): Promise<number>;
32
+ rankModels(): Promise<ModelRankItem[]>;
33
+ getHeatmaps(): Promise<HeatmapsProps['data']>;
22
34
  updateMessageError(id: string, error: ChatMessageError): Promise<any>;
23
35
  updateMessage(id: string, message: Partial<MessageItem>): Promise<any>;
24
36
  updateMessageTTS(id: string, tts: Partial<ChatTTS> | false): Promise<any>;
@@ -82,6 +82,11 @@ export class ClientService implements ISessionService {
82
82
  return SessionModel.count();
83
83
  }
84
84
 
85
+ // @ts-ignore
86
+ async rankSessions() {
87
+ throw new Error('Method not implemented.');
88
+ }
89
+
85
90
  async hasSessions() {
86
91
  return (await this.countSessions()) !== 0;
87
92
  }
@@ -74,8 +74,12 @@ export class ClientService extends BaseClientService implements ISessionService
74
74
  }
75
75
  };
76
76
 
77
- countSessions: ISessionService['countSessions'] = async () => {
78
- return this.sessionModel.count();
77
+ countSessions: ISessionService['countSessions'] = async (params) => {
78
+ return this.sessionModel.count(params);
79
+ };
80
+
81
+ rankSessions: ISessionService['rankSessions'] = async (limit) => {
82
+ return this.sessionModel.rank(limit);
79
83
  };
80
84
 
81
85
  searchSessions: ISessionService['searchSessions'] = async (keyword) => {
@@ -36,8 +36,12 @@ export class ServerService implements ISessionService {
36
36
  return lambdaClient.session.getGroupedSessions.query();
37
37
  };
38
38
 
39
- countSessions: ISessionService['countSessions'] = () => {
40
- return lambdaClient.session.countSessions.query();
39
+ countSessions: ISessionService['countSessions'] = async (params) => {
40
+ return lambdaClient.session.countSessions.query(params);
41
+ };
42
+
43
+ rankSessions: ISessionService['rankSessions'] = async (limit) => {
44
+ return lambdaClient.session.rankSessions.query(limit);
41
45
  };
42
46
 
43
47
  updateSession: ISessionService['updateSession'] = (id, data) => {
@@ -11,6 +11,7 @@ import {
11
11
  LobeSessions,
12
12
  SessionGroupItem,
13
13
  SessionGroups,
14
+ SessionRankItem,
14
15
  UpdateSessionParams,
15
16
  } from '@/types/session';
16
17
 
@@ -31,7 +32,12 @@ export interface ISessionService {
31
32
  * @deprecated
32
33
  */
33
34
  getSessionsByType(type?: 'agent' | 'group' | 'all'): Promise<LobeSessions>;
34
- countSessions(): Promise<number>;
35
+ countSessions(params?: {
36
+ endDate?: string;
37
+ range?: [string, string];
38
+ startDate?: string;
39
+ }): Promise<number>;
40
+ rankSessions(limit?: number): Promise<SessionRankItem[]>;
35
41
  searchSessions(keyword: string): Promise<LobeSessions>;
36
42
 
37
43
  updateSession(id: string, data: Partial<UpdateSessionParams>): Promise<any>;
@@ -38,6 +38,11 @@ export class ClientService implements ITopicService {
38
38
  return TopicModel.count();
39
39
  }
40
40
 
41
+ // @ts-ignore
42
+ async rankTopics() {
43
+ throw new Error('Method not implemented.');
44
+ }
45
+
41
46
  async updateTopicFavorite(id: string, favorite?: boolean) {
42
47
  return this.updateTopic(id, { favorite });
43
48
  }
@@ -55,8 +55,12 @@ export class ClientService extends BaseClientService implements ITopicService {
55
55
  return data as unknown as Promise<ChatTopic[]>;
56
56
  };
57
57
 
58
- countTopics: ITopicService['countTopics'] = async () => {
59
- return this.topicModel.count();
58
+ countTopics: ITopicService['countTopics'] = async (params) => {
59
+ return this.topicModel.count(params);
60
+ };
61
+
62
+ rankTopics: ITopicService['rankTopics'] = async (limit) => {
63
+ return this.topicModel.rank(limit);
60
64
  };
61
65
 
62
66
  updateTopic: ITopicService['updateTopic'] = async (id, data) => {
@@ -24,7 +24,13 @@ export class ServerService implements ITopicService {
24
24
  getAllTopics: ITopicService['getAllTopics'] = () =>
25
25
  lambdaClient.topic.getAllTopics.query() as any;
26
26
 
27
- countTopics: ITopicService['countTopics'] = async () => lambdaClient.topic.countTopics.query();
27
+ countTopics: ITopicService['countTopics'] = async (params) => {
28
+ return lambdaClient.topic.countTopics.query(params);
29
+ };
30
+
31
+ rankTopics: ITopicService['rankTopics'] = async (limit) => {
32
+ return lambdaClient.topic.rankTopics.query(limit);
33
+ };
28
34
 
29
35
  searchTopics: ITopicService['searchTopics'] = (keywords, sessionId) =>
30
36
  lambdaClient.topic.searchTopics.query({
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable typescript-sort-keys/interface */
2
2
  import { BatchTaskResult } from '@/types/service';
3
- import { ChatTopic } from '@/types/topic';
3
+ import { ChatTopic, TopicRankItem } from '@/types/topic';
4
4
 
5
5
  export interface CreateTopicParams {
6
6
  favorite?: boolean;
@@ -22,7 +22,12 @@ export interface ITopicService {
22
22
 
23
23
  getTopics(params: QueryTopicParams): Promise<ChatTopic[]>;
24
24
  getAllTopics(): Promise<ChatTopic[]>;
25
- countTopics(): Promise<number>;
25
+ countTopics(params?: {
26
+ endDate?: string;
27
+ range?: [string, string];
28
+ startDate?: string;
29
+ }): Promise<number>;
30
+ rankTopics(limit?: number): Promise<TopicRankItem[]>;
26
31
  searchTopics(keyword: string, sessionId?: string): Promise<ChatTopic[]>;
27
32
 
28
33
  updateTopic(id: string, data: Partial<ChatTopic>): Promise<any>;
@@ -16,6 +16,10 @@ export class ClientService implements IUserService {
16
16
  this.preferenceStorage = new AsyncLocalStorage('LOBE_PREFERENCE');
17
17
  }
18
18
 
19
+ getUserRegistrationDuration = async () => {
20
+ throw new Error('Method not implemented.');
21
+ };
22
+
19
23
  async getUserState(): Promise<UserInitializationState> {
20
24
  const user = await UserModel.getUser();
21
25
  const messageCount = await MessageModel.count();
@@ -27,6 +27,10 @@ export class ClientService extends BaseClientService implements IUserService {
27
27
  this.preferenceStorage = new AsyncLocalStorage('LOBE_PREFERENCE');
28
28
  }
29
29
 
30
+ getUserRegistrationDuration: IUserService['getUserRegistrationDuration'] = async () => {
31
+ return this.userModel.getUserRegistrationDuration();
32
+ };
33
+
30
34
  getUserState: IUserService['getUserState'] = async () => {
31
35
  // if user not exist in the db, create one to make sure the user exist
32
36
  await this.makeSureUserExist();
@@ -2,6 +2,10 @@ import { lambdaClient } from '@/libs/trpc/client';
2
2
  import { IUserService } from '@/services/user/type';
3
3
 
4
4
  export class ServerService implements IUserService {
5
+ getUserRegistrationDuration: IUserService['getUserRegistrationDuration'] = async () => {
6
+ return lambdaClient.user.getUserRegistrationDuration.query();
7
+ };
8
+
5
9
  getUserState: IUserService['getUserState'] = async () => {
6
10
  return lambdaClient.user.getUserState.query();
7
11
  };
@@ -4,6 +4,11 @@ import { UserGuide, UserInitializationState, UserPreference } from '@/types/user
4
4
  import { UserSettings } from '@/types/user/settings';
5
5
 
6
6
  export interface IUserService {
7
+ getUserRegistrationDuration: () => Promise<{
8
+ createdAt: string;
9
+ duration: number;
10
+ updatedAt: string;
11
+ }>;
7
12
  getUserState: () => Promise<UserInitializationState>;
8
13
  resetUserSettings: () => Promise<any>;
9
14
  updateGuide: (guide: Partial<UserGuide>) => Promise<any>;
@@ -31,6 +31,12 @@ export enum SettingsTabs {
31
31
  TTS = 'tts',
32
32
  }
33
33
 
34
+ export enum ProfileTabs {
35
+ Profile = 'profile',
36
+ Security = 'security',
37
+ Stats = 'stats',
38
+ }
39
+
34
40
  export interface SystemStatus {
35
41
  // which sessionGroup should expand
36
42
  expandSessionGroupKeys: string[];
@@ -1,11 +1,8 @@
1
- import { act, renderHook, waitFor } from '@testing-library/react';
1
+ import { act, renderHook } from '@testing-library/react';
2
2
  import { mutate } from 'swr';
3
3
  import { afterEach, describe, expect, it, vi } from 'vitest';
4
- import { withSWR } from '~test-utils';
5
4
 
6
- import { userService } from '@/services/user';
7
5
  import { useUserStore } from '@/store/user';
8
- import { switchLang } from '@/utils/client/switchLang';
9
6
 
10
7
  vi.mock('zustand/traditional');
11
8
 
@@ -170,33 +167,4 @@ describe('createAuthSlice', () => {
170
167
  expect(signIn).not.toHaveBeenCalled();
171
168
  });
172
169
  });
173
-
174
- describe('openUserProfile', () => {
175
- it('should call clerkOpenUserProfile when Clerk is enabled', async () => {
176
- enableClerk = true;
177
-
178
- const clerkOpenUserProfileMock = vi.fn();
179
- useUserStore.setState({ clerkOpenUserProfile: clerkOpenUserProfileMock });
180
-
181
- const { result } = renderHook(() => useUserStore());
182
-
183
- await act(async () => {
184
- await result.current.openUserProfile();
185
- });
186
-
187
- expect(clerkOpenUserProfileMock).toHaveBeenCalled();
188
- });
189
- it('should not call clerkOpenUserProfile when Clerk is disabled', async () => {
190
- const clerkOpenUserProfileMock = vi.fn();
191
- useUserStore.setState({ clerkOpenUserProfile: clerkOpenUserProfileMock });
192
-
193
- const { result } = renderHook(() => useUserStore());
194
-
195
- await act(async () => {
196
- await result.current.openUserProfile();
197
- });
198
-
199
- expect(clerkOpenUserProfileMock).not.toHaveBeenCalled();
200
- });
201
- });
202
170
  });
@@ -14,7 +14,6 @@ export interface UserAuthAction {
14
14
  * universal login method
15
15
  */
16
16
  openLogin: () => Promise<void>;
17
- openUserProfile: () => Promise<void>;
18
17
  }
19
18
 
20
19
  export const createAuthSlice: StateCreator<
@@ -63,12 +62,4 @@ export const createAuthSlice: StateCreator<
63
62
  signIn();
64
63
  }
65
64
  },
66
-
67
- openUserProfile: async () => {
68
- if (enableClerk) {
69
- get().clerkOpenUserProfile?.();
70
-
71
- return;
72
- }
73
- },
74
65
  });
@@ -34,7 +34,7 @@ describe('createCommonSlice', () => {
34
34
  describe('updateAvatar', () => {
35
35
  it('should update avatar', async () => {
36
36
  const { result } = renderHook(() => useUserStore());
37
- const avatar = 'new-avatar';
37
+ const avatar = 'data:image/png;base64,';
38
38
 
39
39
  const spyOn = vi.spyOn(result.current, 'refreshUserState');
40
40
  const updateAvatarSpy = vi
@@ -45,7 +45,7 @@ describe('createCommonSlice', () => {
45
45
  await result.current.updateAvatar(avatar);
46
46
  });
47
47
 
48
- expect(updateAvatarSpy).toHaveBeenCalledWith(avatar);
48
+ expect(updateAvatarSpy).toHaveBeenCalledWith('data:image/png;base64,');
49
49
  expect(spyOn).toHaveBeenCalled();
50
50
  });
51
51
  });
@@ -161,3 +161,8 @@ export interface SendThreadMessageParams {
161
161
  message: string;
162
162
  onlyAddUserMessage?: boolean;
163
163
  }
164
+
165
+ export interface ModelRankItem {
166
+ count: number;
167
+ id: string | null;
168
+ }
@@ -15,3 +15,11 @@ export interface UpdateSessionParams {
15
15
  pinned?: boolean;
16
16
  updatedAt: Date;
17
17
  }
18
+
19
+ export interface SessionRankItem {
20
+ avatar: string | null;
21
+ backgroundColor: string | null;
22
+ count: number;
23
+ id: string;
24
+ title: string | null;
25
+ }
@@ -44,3 +44,10 @@ export interface ChatTopic extends Omit<BaseDataModel, 'meta'> {
44
44
  }
45
45
 
46
46
  export type ChatTopicMap = Record<string, ChatTopic>;
47
+
48
+ export interface TopicRankItem {
49
+ count: number;
50
+ id: string;
51
+ sessionId: string | null;
52
+ title: string | null;
53
+ }
@@ -88,7 +88,7 @@ export const formatNumber = (num: any, fractionDigits?: number) => {
88
88
  return `${numeral(a).format('0,0')}.${b}`;
89
89
  };
90
90
 
91
- export const formatIntergerNumber = (num: any) => {
91
+ export const formatIntergerNumber = (num?: any) => {
92
92
  if (!num && num !== 0) return '--';
93
93
 
94
94
  return numeral(num).format('0,0');
@@ -0,0 +1,23 @@
1
+ import dayjs, { Dayjs } from 'dayjs';
2
+
3
+ const getQuarterStart = (date: Dayjs) => {
4
+ const month = date.month();
5
+ const quarterStartMonth = Math.floor(month / 3) * 3;
6
+ return date.month(quarterStartMonth).startOf('month');
7
+ };
8
+
9
+ export const today = () => dayjs().startOf('day');
10
+ export const thisWeek = () => dayjs().startOf('week');
11
+ export const thisMonth = () => dayjs().startOf('month');
12
+ export const thisQuarter = () => getQuarterStart(today());
13
+ export const thisYear = () => dayjs().startOf('year');
14
+
15
+ export const hoursAgo = (hours: number) => dayjs().subtract(hours, 'hours').startOf('hours');
16
+
17
+ export const daysAgo = (days: number) => dayjs().subtract(days, 'days').startOf('day');
18
+
19
+ export const weeksAgo = (weeks: number) => dayjs().subtract(weeks, 'week').startOf('week');
20
+
21
+ export const monthsAgo = (months: number) => dayjs().subtract(months, 'month').startOf('month');
22
+
23
+ export const lastMonth = () => monthsAgo(1).endOf('month');
@@ -1,76 +0,0 @@
1
- 'use client';
2
-
3
- import { UserProfile } from '@clerk/nextjs';
4
- import { ElementsConfig } from '@clerk/types';
5
- import { createStyles } from 'antd-style';
6
- import { memo } from 'react';
7
-
8
- export const useStyles = createStyles(
9
- ({ css, token, cx }, mobile: boolean) =>
10
- ({
11
- cardBox: css`
12
- width: 100%;
13
- max-width: unset;
14
- height: 100%;
15
-
16
- border: unset;
17
- border-radius: unset;
18
- box-shadow: unset;
19
- `,
20
- footer: cx(
21
- mobile &&
22
- css`
23
- display: none;
24
- `,
25
- ),
26
- navbar: css`
27
- flex: none;
28
-
29
- width: 280px;
30
- max-width: unset;
31
- margin-inline-end: 0;
32
- padding-block: 24px 16px;
33
- padding-inline: 12px;
34
-
35
- background: ${token.colorBgContainer};
36
- border-inline-end: 1px solid ${token.colorSplit};
37
- `,
38
- navbarMobileMenuRow: cx(
39
- mobile &&
40
- css`
41
- display: none;
42
- `,
43
- ),
44
- pageScrollBox: css`
45
- align-self: center;
46
- width: 100%;
47
- max-width: 1024px;
48
- `,
49
- rootBox: css`
50
- width: 100%;
51
- height: 100%;
52
- `,
53
- scrollBox: css`
54
- background: ${token.colorBgLayout};
55
- border: unset;
56
- border-radius: unset;
57
- `,
58
- }) as Partial<{
59
- // eslint-disable-next-line unused-imports/no-unused-vars
60
- [k in keyof ElementsConfig]: any;
61
- }>,
62
- );
63
-
64
- const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
65
- const { styles } = useStyles(mobile);
66
-
67
- return (
68
- <UserProfile
69
- appearance={{
70
- elements: styles,
71
- }}
72
- />
73
- );
74
- });
75
-
76
- export default Client;