🎉 first commit

This commit is contained in:
LIlGG
2025-09-24 13:06:25 +08:00
commit 1f4fb103e9
409 changed files with 61222 additions and 0 deletions

View File

@@ -0,0 +1,303 @@
import { createScopedLogger } from '~/lib/.server/logger';
import { prisma } from '~/lib/.server/prisma';
const logger = createScopedLogger('chatUsage.server');
/**
* 聊天使用量状态
*/
export enum ChatUsageStatus {
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
PENDING = 'PENDING',
ABORTED = 'ABORTED',
}
/**
* 聊天使用量记录参数接口
*/
export interface ChatUsageParams {
userId: string;
chatId: string;
messageId: string;
status: ChatUsageStatus;
inputTokens?: number;
outputTokens?: number;
cachedTokens?: number;
reasoningTokens?: number;
totalTokens?: number;
modelName?: string;
prompt?: string;
metadata?: Record<string, any>;
}
/**
* 日期过滤器类型
*/
interface DateFilter {
calledAt?: {
gte?: Date;
lte?: Date;
};
}
/**
* 记录聊天使用量
* @param params 使用量参数
* @returns 创建的记录
*/
export async function recordUsage(params: ChatUsageParams) {
const {
userId,
chatId,
messageId,
inputTokens = 0,
outputTokens = 0,
cachedTokens = 0,
reasoningTokens = 0,
status,
prompt,
metadata,
modelName,
} = params;
// 计算总token量
const totalTokens = inputTokens + outputTokens;
try {
// 创建记录
const record = await prisma.chatUsage.create({
data: {
userId,
messageId,
chatId,
inputTokens,
outputTokens,
cachedTokens,
reasoningTokens,
totalTokens,
status,
prompt,
metadata,
modelName,
},
});
if (status === ChatUsageStatus.PENDING) {
logger.info(`[ChatUsage] 初始化用户 ${userId}${modelName} 模型聊天使用量`);
} else {
logger.info(
`[ChatUsage] 记录了用户 ${userId}${modelName} 模型聊天使用量: ${totalTokens} tokens状态: ${status}`,
);
}
return record;
} catch (error) {
logger.error('[ChatUsage] 记录聊天使用量失败:', error);
throw error;
}
}
/**
* 获取按天统计的使用数据
* @param userId 用户ID
* @param days 天数默认为30天
* @returns 每日使用统计数据
*/
export async function getDailyUsageStats(userId: string, days = 30) {
try {
// 计算结束日期为今天(当天结束)
const endDate = new Date();
endDate.setHours(23, 59, 59, 999);
// 计算开始日期为 endDate 前推 days-1 天的开始时间
const startDate = new Date(endDate);
startDate.setDate(endDate.getDate() - (days - 1));
startDate.setHours(0, 0, 0, 0);
const records = await prisma.chatUsage.findMany({
where: {
userId,
calledAt: {
gte: startDate,
lte: endDate,
},
},
select: {
calledAt: true,
totalTokens: true,
},
});
const dateMap: Record<string, { count: number; totalTokens: number }> = {};
// 创建从startDate到endDate的每一天映射
const currentDate = new Date(startDate);
while (currentDate <= endDate) {
const dateStr = currentDate.toISOString().split('T')[0];
dateMap[dateStr] = { count: 0, totalTokens: 0 };
// 增加一天
currentDate.setDate(currentDate.getDate() + 1);
}
// 统计数据
records.forEach((record) => {
const dateStr = record.calledAt.toISOString().split('T')[0];
if (dateMap[dateStr]) {
dateMap[dateStr].count += 1;
dateMap[dateStr].totalTokens += record.totalTokens;
}
});
return Object.entries(dateMap).map(([date, stats]) => ({
date,
count: stats.count,
totalTokens: stats.totalTokens,
}));
} catch (error) {
logger.error('[ChatUsage] 获取每日使用统计失败:', error);
throw error;
}
}
/**
* 获取用户的聊天使用统计
* @param userId 用户ID
* @param startDate 开始日期
* @param endDate 结束日期
* @returns 使用统计数据
*/
export async function getUserUsageStats(userId: string, startDate?: Date, endDate?: Date) {
const dateFilter: DateFilter = {};
if (startDate || endDate) {
dateFilter.calledAt = {};
if (startDate) {
dateFilter.calledAt.gte = startDate;
}
if (endDate) {
dateFilter.calledAt.lte = endDate;
}
}
try {
// 获取总体使用量
const stats = await prisma.chatUsage.aggregate({
where: {
userId,
...dateFilter,
},
_sum: {
inputTokens: true,
outputTokens: true,
cachedTokens: true,
reasoningTokens: true,
totalTokens: true,
},
_count: true,
});
// 按状态分组
const statusStats = await prisma.chatUsage.groupBy({
by: ['status'],
where: {
userId,
...dateFilter,
},
_count: true,
_sum: {
totalTokens: true,
},
});
// 按聊天分组
const chatStats = await prisma.chatUsage.groupBy({
by: ['chatId'],
where: {
userId,
...dateFilter,
},
_sum: {
totalTokens: true,
},
_count: true,
});
// 获取按天统计的数据
const dailyStats = await getDailyUsageStats(userId, 7);
return {
total: stats,
byStatus: statusStats,
byChat: chatStats,
byDate: dailyStats,
};
} catch (error) {
logger.error('[ChatUsage] 获取用户使用统计失败:', error);
throw error;
}
}
/**
* 更新使用记录的状态
* @param id 记录ID
* @param status 新状态
* @param additionalData 额外要更新的数据
* @returns 更新后的记录
*/
export async function updateUsageStatus(
id: string,
status: ChatUsageStatus,
additionalData?: Partial<ChatUsageParams>,
) {
try {
const updatedRecord = await prisma.chatUsage.update({
where: { id },
data: {
status,
...additionalData,
},
});
return updatedRecord;
} catch (error) {
logger.error('[ChatUsage] 更新使用记录状态失败:', error);
throw error;
}
}
export async function updateUsageError(id: string, error: string, additionalData?: Partial<ChatUsageParams>) {
return updateUsageStatus(id, ChatUsageStatus.FAILED, {
...additionalData,
metadata: {
error: error || '未知错误',
} as unknown as Record<string, any>,
});
}
/**
* 获取最近的使用记录
* @param userId 用户ID
* @param limit 限制返回记录数量
* @returns 使用记录列表
*/
export async function getRecentUsage(userId: string, limit = 10) {
try {
const records = await prisma.chatUsage.findMany({
where: {
userId,
},
orderBy: {
calledAt: 'desc',
},
take: limit,
});
return records;
} catch (error) {
logger.error('[ChatUsage] 获取最近使用记录失败:', error);
throw error;
}
}