/** * 查询与报告生成 * * 从 UsageStatsData 的 daily 数据聚合查询结果。 */ import type { UsageStatsData, UsageQuery, UsageReport, ToolUsageRow } from "./types"; /** 获取今天的 YYYY-MM-DD */ export function today(): string { return new Date().toISOString().slice(0, 10); } /** 获取 N 天前的 YYYY-MM-DD */ export function daysAgo(n: number): string { return new Date(Date.now() - n * 86400000).toISOString().slice(0, 10); } /** 获取日期范围 */ function getDaysForPeriod( period: "today" | "week" | "all", daily: UsageStatsData["daily"], ): string[] { if (period === "today") return [today()]; if (period === "week") { return Array.from({ length: 7 }, (_, i) => daysAgo(i)); } return Object.keys(daily).sort(); } /** 格式化日期范围为可读文本 */ function formatPeriod(period: "today" | "week" | "all", days: string[]): string { if (period === "today") return today(); if (period === "week") return `${days[days.length - 1]} ~ ${days[0]}`; if (days.length === 0) return "无数据"; return `${days[0]} ~ ${days[days.length - 1]}`; } /** 从 daily 数据生成查询报告 */ export function buildReport( data: UsageStatsData, options: UsageQuery = {}, ): UsageReport { const period = options.period ?? "today"; const toolFilter = options.toolName?.toLowerCase(); const days = getDaysForPeriod(period, data.daily); // 聚合 const aggregated = new Map(); for (const day of days) { const dayData = data.daily[day]; if (!dayData) continue; for (const [toolName, stats] of Object.entries(dayData)) { const existing = aggregated.get(toolName) ?? { ai: 0, user: 0, errors: 0 }; existing.ai += stats.ai; existing.user += stats.user; existing.errors += stats.errors; aggregated.set(toolName, existing); } } // 过滤 + 组装 rows const rows: ToolUsageRow[] = []; for (const [toolName, stats] of aggregated) { if (toolFilter && !toolName.toLowerCase().startsWith(toolFilter)) continue; rows.push({ toolName, ai: stats.ai, user: stats.user, errors: stats.errors, total: stats.ai + stats.user, }); } rows.sort((a, b) => b.total - a.total); const totals = rows.reduce( (acc, r) => ({ ai: acc.ai + r.ai, user: acc.user + r.user, errors: acc.errors + r.errors, total: acc.total + r.total, }), { ai: 0, user: 0, errors: 0, total: 0 }, ); return { period: formatPeriod(period, days), rows, totals, uniqueTools: rows.length, }; }