diff --git a/next.config.ts b/next.config.ts
index 28aba9f2eff42..35095f78efe47 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -25,7 +25,6 @@ const nextConfig: NextConfig = {
'@icons-pack/react-simple-icons',
'@lobehub/ui',
'gpt-tokenizer',
- 'chroma-js',
],
webVitalsAttribution: ['CLS', 'LCP'],
},
diff --git a/src/app/(main)/profile/stats/Client.tsx b/src/app/(main)/profile/stats/Client.tsx
index 925a8268cfd6e..ce44fb75f4198 100644
--- a/src/app/(main)/profile/stats/Client.tsx
+++ b/src/app/(main)/profile/stats/Client.tsx
@@ -43,7 +43,7 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => {
-
+
diff --git a/src/app/(main)/profile/stats/features/AiHeatmaps.tsx b/src/app/(main)/profile/stats/features/AiHeatmaps.tsx
index e24a4efdc18c8..fec1819e31b9c 100644
--- a/src/app/(main)/profile/stats/features/AiHeatmaps.tsx
+++ b/src/app/(main)/profile/stats/features/AiHeatmaps.tsx
@@ -1,8 +1,46 @@
import { Heatmaps } from '@lobehub/charts';
import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { useClientDataSWR } from '@/libs/swr';
+import { messageService } from '@/services/message';
const AiHeatmaps = memo(() => {
- return ;
+ const { t } = useTranslation('auth');
+ const { data, isLoading } = useClientDataSWR('stats-heatmaps', async () =>
+ messageService.getHeatmaps(),
+ );
+
+ return (
+
+ );
});
export default AiHeatmaps;
diff --git a/src/app/(main)/profile/stats/features/AssistantsRank.tsx b/src/app/(main)/profile/stats/features/AssistantsRank.tsx
index a765cd0077c6f..c7eacfaf872d7 100644
--- a/src/app/(main)/profile/stats/features/AssistantsRank.tsx
+++ b/src/app/(main)/profile/stats/features/AssistantsRank.tsx
@@ -51,7 +51,7 @@ export const AssistantsRank = memo(() => {
};
}) || []
}
- height={420}
+ height={340}
leftLabel={t('stats.assistantsRank.left')}
loading={isLoading}
onValueChange={(item) => router.push(item.link)}
diff --git a/src/app/(main)/profile/stats/features/TopicsRank.tsx b/src/app/(main)/profile/stats/features/TopicsRank.tsx
index ad9300151e2c5..16965fab61bd4 100644
--- a/src/app/(main)/profile/stats/features/TopicsRank.tsx
+++ b/src/app/(main)/profile/stats/features/TopicsRank.tsx
@@ -48,7 +48,7 @@ export const TopicsRank = memo(() => {
};
}) || []
}
- height={420}
+ height={340}
leftLabel={t('stats.topicsRank.left')}
loading={isLoading}
onValueChange={(item) => router.push(item.link)}
diff --git a/src/database/server/models/message.ts b/src/database/server/models/message.ts
index 1686413f63e99..3a8884284c692 100644
--- a/src/database/server/models/message.ts
+++ b/src/database/server/models/message.ts
@@ -1,4 +1,6 @@
-import { count } from 'drizzle-orm';
+import type { HeatmapsProps } from '@lobehub/charts';
+import dayjs from 'dayjs';
+import { count, sql } from 'drizzle-orm';
import { and, asc, desc, eq, inArray, isNull, like } from 'drizzle-orm/expressions';
import { LobeChatDatabase } from '@/database/type';
@@ -17,6 +19,7 @@ import {
CreateMessageParams,
} from '@/types/message';
import { merge } from '@/utils/merge';
+import { today } from '@/utils/time';
import {
MessageItem,
@@ -299,6 +302,54 @@ export class MessageModel {
return result[0].count;
};
+ getHeatmaps = async (): Promise => {
+ const startDate = today().subtract(1, 'year').startOf('day');
+ const endDate = today().endOf('day');
+
+ const result = await this.db
+ .select({
+ count: count(messages.id),
+ date: sql`DATE(${messages.createdAt})`.as('heatmaps_date'),
+ })
+ .from(messages)
+ .where(
+ genWhere([
+ eq(messages.userId, this.userId),
+ genRangeWhere(
+ [startDate.format('YYYY-MM-DD'), endDate.add(1, 'day').format('YYYY-MM-DD')],
+ messages.createdAt,
+ (date) => date.toDate(),
+ ),
+ ]),
+ )
+ .groupBy(sql`heatmaps_date`)
+ .orderBy(desc(sql`heatmaps_date`));
+
+ const heatmapData: HeatmapsProps['data'] = [];
+ let currentDate = startDate;
+
+ while (currentDate.isBefore(endDate) || currentDate.isSame(endDate, 'day')) {
+ const formattedDate = currentDate.format('YYYY-MM-DD');
+ const matchingResult = result.find(
+ (r) => r?.date && dayjs(r.date as string).format('YYYY-MM-DD') === formattedDate,
+ );
+
+ const count = matchingResult ? matchingResult.count : 0;
+ const levelCount = count > 0 ? Math.floor(count / 5) : 0;
+ const level = levelCount > 4 ? 4 : levelCount;
+
+ heatmapData.push({
+ count,
+ date: formattedDate,
+ level,
+ });
+
+ currentDate = currentDate.add(1, 'day');
+ }
+
+ return heatmapData;
+ };
+
hasMoreThanN = async (n: number): Promise => {
const result = await this.db
.select({ id: messages.id })
diff --git a/src/database/server/models/session.ts b/src/database/server/models/session.ts
index b3d11e4e0582b..5aed607909fdd 100644
--- a/src/database/server/models/session.ts
+++ b/src/database/server/models/session.ts
@@ -134,7 +134,7 @@ export class SessionModel {
.leftJoin(agents, eq(agentsToSessions.agentId, agents.id))
.groupBy(sessions.id, agentsToSessions.agentId, agents.id)
.orderBy(desc(sql`count`))
- .limit(10);
+ .limit(8);
};
hasMoreThanN = async (n: number): Promise => {
diff --git a/src/database/server/models/topic.ts b/src/database/server/models/topic.ts
index f37bd8a1bf4fa..9164a9d1b51f0 100644
--- a/src/database/server/models/topic.ts
+++ b/src/database/server/models/topic.ts
@@ -139,7 +139,7 @@ export class TopicModel {
.leftJoin(messages, eq(topics.id, messages.topicId))
.groupBy(topics.id)
.orderBy(desc(sql`count`))
- .limit(10);
+ .limit(8);
};
// **************** Create *************** //
diff --git a/src/locales/default/auth.ts b/src/locales/default/auth.ts
index bbe7df4b05092..e2f0170816672 100644
--- a/src/locales/default/auth.ts
+++ b/src/locales/default/auth.ts
@@ -7,6 +7,28 @@ export default {
desc: '管理您的账户信息。',
title: '账户',
},
+ heatmaps: {
+ legend: {
+ less: '不活跃',
+ more: '活跃',
+ },
+ months: {
+ apr: '四月',
+ aug: '八月',
+ dec: '十二月',
+ feb: '二月',
+ jan: '一月',
+ jul: '七月',
+ jun: '六月',
+ mar: '三月',
+ may: '五月',
+ nov: '十一月',
+ oct: '十月',
+ sep: '九月',
+ },
+ tooltip: '{{date}} 当日发送 {{count}} 条消息',
+ totalCount: '过去一年共发送 {{count}} 条消息',
+ },
login: '登录',
loginOrSignup: '登录 / 注册',
profile: {
@@ -24,7 +46,7 @@ export default {
title: '助手使用率',
},
heatmaps: {
- title: 'AI 指数',
+ title: '创作指数',
},
messages: '消息数',
topics: '话题数',
diff --git a/src/server/routers/lambda/message.ts b/src/server/routers/lambda/message.ts
index dd51b99050e8f..be7c1396fcd36 100644
--- a/src/server/routers/lambda/message.ts
+++ b/src/server/routers/lambda/message.ts
@@ -63,6 +63,10 @@ export const messageRouter = router({
return ctx.messageModel.queryBySessionId(input.sessionId);
}),
+ getHeatmaps: messageProcedure.query(async ({ ctx }) => {
+ return ctx.messageModel.getHeatmaps();
+ }),
+
// TODO: 未来这部分方法也需要使用 authedProcedure
getMessages: publicProcedure
.input(
diff --git a/src/services/message/_deprecated.ts b/src/services/message/_deprecated.ts
index f6d127bced22c..f8292d34c898d 100644
--- a/src/services/message/_deprecated.ts
+++ b/src/services/message/_deprecated.ts
@@ -56,6 +56,11 @@ export class ClientService implements IMessageService {
return MessageModel.count();
}
+ // @ts-ignore
+ async getHeatmaps() {
+ throw new Error('Method not implemented.');
+ }
+
async countTodayMessages() {
const topics = await MessageModel.queryAll();
return topics.filter(
diff --git a/src/services/message/client.ts b/src/services/message/client.ts
index 508412304b241..9edbb3dd72113 100644
--- a/src/services/message/client.ts
+++ b/src/services/message/client.ts
@@ -54,6 +54,10 @@ export class ClientService extends BaseClientService implements IMessageService
return this.messageModel.count(params);
};
+ getHeatmaps: IMessageService['getHeatmaps'] = async () => {
+ return this.messageModel.getHeatmaps();
+ };
+
getAllMessagesInSession: IMessageService['getAllMessagesInSession'] = async (sessionId) => {
const data = this.messageModel.queryBySessionId(this.toDbSessionId(sessionId));
diff --git a/src/services/message/server.ts b/src/services/message/server.ts
index f28dc26052ad8..22808b7de9e21 100644
--- a/src/services/message/server.ts
+++ b/src/services/message/server.ts
@@ -40,6 +40,10 @@ export class ServerService implements IMessageService {
return lambdaClient.message.count.query(params);
};
+ getHeatmaps: IMessageService['getHeatmaps'] = async () => {
+ return lambdaClient.message.getHeatmaps.query();
+ };
+
updateMessageError: IMessageService['updateMessageError'] = async (id, error) => {
return lambdaClient.message.update.mutate({ id, value: { error } });
};
diff --git a/src/services/message/type.ts b/src/services/message/type.ts
index dd8f90a864fb1..afdd74d51db1a 100644
--- a/src/services/message/type.ts
+++ b/src/services/message/type.ts
@@ -1,3 +1,5 @@
+import type { HeatmapsProps } from '@lobehub/charts';
+
import { MessageItem } from '@/database/schemas';
import {
ChatMessage,
@@ -21,7 +23,7 @@ export interface IMessageService {
range?: [string, string];
startDate?: string;
}): Promise;
-
+ getHeatmaps(): Promise;
updateMessageError(id: string, error: ChatMessageError): Promise;
updateMessage(id: string, message: Partial): Promise;
updateMessageTTS(id: string, tts: Partial | false): Promise;