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;