mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 14:12:59 +03:00
- Fix Next.js 15 async params in 4 API route handlers (accounts, providers, registered-keys) - Move providers/[id]/limits → providers/[provider]/limits to resolve slug name conflict - Add keytar to serverExternalPackages and KNOWN_EXTERNALS in next.config.mjs - Fix Zod z.record() arity across a2a.ts and issues/report/route.ts - Fix SearchResponse interface (optional answer property) in SearchTools and ResultsPanel - Fix ProviderLimits implicit any types in index.tsx and utils.tsx - Fix better-sqlite3 prepare<T> generic usage in secrets.ts - Remove duplicate pricing keys (gemini-3-flash-preview) - Cast analytics result, ApiErrorType import, TaskRoutingConfig type - Remove rogue app/ duplicate directory from project root Resolves: #560
116 lines
3.7 KiB
TypeScript
116 lines
3.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getUsageDb } from "@/lib/usageDb";
|
|
import { computeAnalytics } from "@/lib/usageAnalytics";
|
|
import { getDbInstance } from "@/lib/db/core";
|
|
|
|
function getRangeStartIso(range: string): string | null {
|
|
const end = new Date();
|
|
const start = new Date(end);
|
|
|
|
switch (range) {
|
|
case "1d":
|
|
start.setDate(start.getDate() - 1);
|
|
break;
|
|
case "7d":
|
|
start.setDate(start.getDate() - 7);
|
|
break;
|
|
case "30d":
|
|
start.setDate(start.getDate() - 30);
|
|
break;
|
|
case "90d":
|
|
start.setDate(start.getDate() - 90);
|
|
break;
|
|
case "ytd":
|
|
start.setMonth(0, 1);
|
|
start.setHours(0, 0, 0, 0);
|
|
break;
|
|
case "all":
|
|
default:
|
|
return null;
|
|
}
|
|
|
|
return start.toISOString();
|
|
}
|
|
|
|
export async function GET(request) {
|
|
try {
|
|
const { searchParams } = new URL(request.url);
|
|
const range = searchParams.get("range") || "30d";
|
|
|
|
const db = await getUsageDb();
|
|
const history = db.data.history || [];
|
|
|
|
// Build connection map for account names
|
|
const { getProviderConnections } = await import("@/lib/localDb");
|
|
const connectionMap: Record<string, string> = {};
|
|
try {
|
|
const connections = await getProviderConnections();
|
|
for (const connRaw of connections as unknown[]) {
|
|
const conn =
|
|
connRaw && typeof connRaw === "object" && !Array.isArray(connRaw)
|
|
? (connRaw as Record<string, unknown>)
|
|
: {};
|
|
const connectionId =
|
|
typeof conn.id === "string" && conn.id.trim().length > 0 ? conn.id : null;
|
|
if (!connectionId) continue;
|
|
|
|
const name =
|
|
(typeof conn.name === "string" && conn.name.trim()) ||
|
|
(typeof conn.email === "string" && conn.email.trim()) ||
|
|
connectionId;
|
|
connectionMap[connectionId] = name;
|
|
}
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
|
|
const analytics: any = await computeAnalytics(history, range, connectionMap);
|
|
|
|
// T01: fallback transparency metrics from call_logs (requested_model vs routed model).
|
|
try {
|
|
const db = getDbInstance();
|
|
const sinceIso = getRangeStartIso(range);
|
|
const whereClause = sinceIso ? "WHERE timestamp >= @since" : "";
|
|
const row = db
|
|
.prepare(
|
|
`
|
|
SELECT
|
|
COUNT(*) as total,
|
|
SUM(CASE WHEN requested_model IS NOT NULL AND requested_model != '' THEN 1 ELSE 0 END) as with_requested,
|
|
SUM(CASE
|
|
WHEN requested_model IS NOT NULL
|
|
AND requested_model != ''
|
|
AND model IS NOT NULL
|
|
AND requested_model != model
|
|
THEN 1 ELSE 0 END
|
|
) as fallbacks
|
|
FROM call_logs
|
|
${whereClause}
|
|
`
|
|
)
|
|
.get(sinceIso ? { since: sinceIso } : {}) as
|
|
| { total?: number; with_requested?: number; fallbacks?: number }
|
|
| undefined;
|
|
|
|
const total = Number(row?.total || 0);
|
|
const withRequested = Number(row?.with_requested || 0);
|
|
const fallbackCount = Number(row?.fallbacks || 0);
|
|
|
|
analytics.summary.fallbackCount = fallbackCount;
|
|
analytics.summary.fallbackRatePct =
|
|
withRequested > 0 ? Number(((fallbackCount / withRequested) * 100).toFixed(2)) : 0;
|
|
analytics.summary.requestedModelCoveragePct =
|
|
total > 0 ? Number(((withRequested / total) * 100).toFixed(2)) : 0;
|
|
} catch {
|
|
analytics.summary.fallbackCount = 0;
|
|
analytics.summary.fallbackRatePct = 0;
|
|
analytics.summary.requestedModelCoveragePct = 0;
|
|
}
|
|
|
|
return NextResponse.json(analytics);
|
|
} catch (error) {
|
|
console.error("Error computing analytics:", error);
|
|
return NextResponse.json({ error: "Failed to compute analytics" }, { status: 500 });
|
|
}
|
|
}
|