first-update

This commit is contained in:
2026-03-17 14:36:31 +08:00
parent 72f08aee7c
commit 4eddf05e79
516 changed files with 115270 additions and 1 deletions

View File

@@ -0,0 +1,107 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d';
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
const page = parseInt(searchParams.get('page') || '1', 10);
const pageSize = parseInt(searchParams.get('pageSize') || '10', 10);
const searchTerm = searchParams.get('search') || '';
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
if (searchTerm) {
where.OR = [{ model: { contains: searchTerm } }, { errorMessage: { contains: searchTerm } }];
}
const total = await db.llmUsageLogs.count({ where });
const logs = await db.llmUsageLogs.findMany({
where,
select: {
id: true,
projectId: true,
provider: true,
model: true,
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
errorMessage: true,
createAt: true
},
orderBy: {
createAt: 'desc'
},
skip: (page - 1) * pageSize,
take: pageSize
});
const projectIds = [...new Set(logs.map(log => log.projectId))];
const projects = await db.projects.findMany({
where: { id: { in: projectIds } },
select: { id: true, name: true }
});
const projectMap = projects.reduce((acc, p) => {
acc[p.id] = p.name;
return acc;
}, {});
const details = logs.map(log => ({
id: log.id,
projectId: log.projectId,
projectName: projectMap[log.projectId] || 'Unknown Project',
provider: log.provider,
model: log.model,
status: log.status,
failureReason: log.errorMessage,
inputTokens: log.inputTokens,
outputTokens: log.outputTokens,
totalTokens: log.totalTokens,
calls: 1, // Single record
avgLatency: log.status === 'SUCCESS' ? (log.latency / 1000).toFixed(2) + 's' : '-',
createAt: log.createAt
}));
return NextResponse.json({
details,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
});
} catch (error) {
console.error('Failed to fetch monitoring logs:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,188 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d'; // 24h, 7d, 30d
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
// 1. Fetch data for aggregation
// Note: Prisma aggregation can be slow on very large datasets. If needed, optimize with pre-aggregated tables.
const logs = await db.llmUsageLogs.findMany({
where,
select: {
id: true,
projectId: true,
provider: true,
model: true,
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
errorMessage: true,
createAt: true,
dateString: true
},
orderBy: {
createAt: 'desc'
}
});
// Build project name map
const projects = await db.projects.findMany({
select: { id: true, name: true }
});
const projectMap = projects.reduce((acc, p) => {
acc[p.id] = p.name;
return acc;
}, {});
// 2. Process and aggregate
const summary = {
totalTokens: 0,
inputTokens: 0,
outputTokens: 0,
totalCalls: logs.length,
successCalls: 0,
failedCalls: 0,
totalLatency: 0,
avgLatency: 0
};
const trendMap = {};
const modelStats = {};
const detailedStatsMap = {}; // Key: projectId-model-status-errorMessage
logs.forEach(log => {
// Summary
summary.totalTokens += log.totalTokens;
summary.inputTokens += log.inputTokens;
summary.outputTokens += log.outputTokens;
if (log.status === 'SUCCESS') {
summary.successCalls++;
summary.totalLatency += log.latency;
} else {
summary.failedCalls++;
}
// Trend (by day or hour)
let timeKey;
if (timeRange === '24h') {
const date = new Date(log.createAt);
timeKey = `${String(date.getHours()).padStart(2, '0')}:00`;
} else {
timeKey = log.dateString.slice(5); // MM-DD
}
if (!trendMap[timeKey]) {
trendMap[timeKey] = { name: timeKey, input: 0, output: 0 };
}
trendMap[timeKey].input += log.inputTokens;
trendMap[timeKey].output += log.outputTokens;
// Model Distribution
const modelKey = log.model;
if (!modelStats[modelKey]) {
modelStats[modelKey] = { name: modelKey, value: 0 };
}
modelStats[modelKey].value += log.totalTokens;
// Detailed Table Aggregation
// Key: projectId + model + status + (errorMessage || '')
const errorKey = log.errorMessage || '';
const detailKey = `${log.projectId}|${log.model}|${log.status}|${errorKey}`;
if (!detailedStatsMap[detailKey]) {
detailedStatsMap[detailKey] = {
projectId: log.projectId,
projectName: projectMap[log.projectId] || 'Unknown Project',
provider: log.provider,
model: log.model,
status: log.status,
failureReason: log.errorMessage,
inputTokens: 0,
outputTokens: 0,
totalTokens: 0,
calls: 0,
totalLatency: 0
};
}
const detailItem = detailedStatsMap[detailKey];
detailItem.inputTokens += log.inputTokens;
detailItem.outputTokens += log.outputTokens;
detailItem.totalTokens += log.totalTokens;
detailItem.calls += 1;
if (log.status === 'SUCCESS') {
detailItem.totalLatency += log.latency;
}
});
// Calculate averages
if (summary.successCalls > 0) {
summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls);
}
summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0;
summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0;
// Format chart data
const trend = Object.values(trendMap).sort((a, b) => {
// Simple sorting; for production use, consider stricter time ordering.
return a.name.localeCompare(b.name);
});
const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value);
// Format detailed table data
const details = Object.values(detailedStatsMap)
.map(item => ({
...item,
avgLatency:
item.status === 'SUCCESS' && item.calls > 0 ? (item.totalLatency / item.calls / 1000).toFixed(2) + 's' : '-'
}))
.sort((a, b) => b.totalTokens - a.totalTokens); // Default sorting by token usage
return NextResponse.json({
summary,
trend,
modelDistribution,
details,
projects
});
} catch (error) {
console.error('Failed to fetch monitoring stats:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,132 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d';
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
const logs = await db.llmUsageLogs.findMany({
where,
select: {
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
createAt: true,
dateString: true,
model: true
}
});
const summary = {
totalTokens: 0,
inputTokens: 0,
outputTokens: 0,
totalCalls: logs.length,
successCalls: 0,
failedCalls: 0,
totalLatency: 0,
avgLatency: 0
};
const trendMap = {};
const modelStats = {};
logs.forEach(log => {
summary.totalTokens += log.totalTokens;
summary.inputTokens += log.inputTokens;
summary.outputTokens += log.outputTokens;
if (log.status === 'SUCCESS') {
summary.successCalls++;
summary.totalLatency += log.latency;
} else {
summary.failedCalls++;
}
let timeKey;
if (timeRange === '24h') {
const date = new Date(log.createAt);
timeKey = `${String(date.getHours()).padStart(2, '0')}:00`;
} else {
timeKey = log.dateString.slice(5);
}
if (!trendMap[timeKey]) {
trendMap[timeKey] = { name: timeKey, input: 0, output: 0 };
}
trendMap[timeKey].input += log.inputTokens;
trendMap[timeKey].output += log.outputTokens;
const modelKey = log.model;
if (!modelStats[modelKey]) {
modelStats[modelKey] = { name: modelKey, value: 0 };
}
modelStats[modelKey].value += log.totalTokens;
});
if (summary.successCalls > 0) {
summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls);
}
summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0;
summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0;
const trend = Object.values(trendMap).sort((a, b) => a.name.localeCompare(b.name));
const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value);
const projects = await db.projects.findMany({
select: { id: true, name: true },
orderBy: { createAt: 'desc' }
});
const allLogs = await db.llmUsageLogs.findMany({
select: { provider: true },
distinct: ['provider']
});
const providers = allLogs.map(log => log.provider).filter(Boolean);
return NextResponse.json({
summary,
trend,
modelDistribution,
projects,
providers
});
} catch (error) {
console.error('Failed to fetch monitoring summary:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}