diff --git a/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx new file mode 100644 index 0000000000..de4aa724c6 --- /dev/null +++ b/src/app/(dashboard)/dashboard/analytics/components/DiversityScoreCard.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Card } from "@/shared/components"; +import { useTranslations } from "next-intl"; + +export default function DiversityScoreCard() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const t = useTranslations("analytics"); + + useEffect(() => { + fetch("/api/analytics/diversity") + .then((res) => res.json()) + .then((json) => { + setData(json); + setLoading(false); + }) + .catch((err) => { + console.error(err); + setLoading(false); + }); + }, []); + + if (loading || !data) { + return ( + +
+
+ ); + } + + const scorePercentage = Math.round((data.score || 0) * 100); + + let riskColor = "text-green-500"; + let gaugeColor = "bg-green-500"; + let riskLabel = "Healthy Distribution"; + + if (scorePercentage < 40) { + riskColor = "text-red-500"; + gaugeColor = "bg-red-500"; + riskLabel = "High Vendor Lock-in Risk"; + } else if (scorePercentage < 70) { + riskColor = "text-amber-500"; + gaugeColor = "bg-amber-500"; + riskLabel = "Moderate Distribution"; + } + + return ( + +
+ pie_chart +

+ Provider Diversity Score +

+ + Shannon Entropy + +
+ +
+
+ + {scorePercentage}% + + {riskLabel} +
+ + {/* Simple CSS Donut */} +
+ + + + +
+
+ +
+

+ Provider Share +

+ + {Object.keys(data.providers || {}).length === 0 ? ( +
+ No recent usage data available. +
+ ) : ( +
+ {Object.entries(data.providers) + .sort(([, a]: any, [, b]: any) => b.share - a.share) + .slice(0, 4) // Top 4 providers + .map(([provider, stat]: [string, any]) => ( +
+
+ + {provider} + + + {Math.round(stat.share * 100)}% + +
+
+
+
+
+ ))} +
+ )} +
+ +
+ Window: {data.windowSize} reqs + Based on Last {Math.round(data.ttlMs / 60000)} mins +
+ + ); +} diff --git a/src/app/(dashboard)/dashboard/analytics/page.tsx b/src/app/(dashboard)/dashboard/analytics/page.tsx index 3f97518466..87aa0f69e6 100644 --- a/src/app/(dashboard)/dashboard/analytics/page.tsx +++ b/src/app/(dashboard)/dashboard/analytics/page.tsx @@ -4,6 +4,7 @@ import { useState, Suspense } from "react"; import { UsageAnalytics, CardSkeleton, SegmentedControl } from "@/shared/components"; import EvalsTab from "../usage/components/EvalsTab"; import SearchAnalyticsTab from "./SearchAnalyticsTab"; +import DiversityScoreCard from "./components/DiversityScoreCard"; import { useTranslations } from "next-intl"; export default function AnalyticsPage() { @@ -38,9 +39,14 @@ export default function AnalyticsPage() { /> {activeTab === "overview" && ( - }> - - +
+
+ +
+ }> + + +
)} {activeTab === "evals" && } {activeTab === "search" && } diff --git a/src/app/api/analytics/diversity/route.ts b/src/app/api/analytics/diversity/route.ts new file mode 100644 index 0000000000..6e00929eec --- /dev/null +++ b/src/app/api/analytics/diversity/route.ts @@ -0,0 +1,13 @@ +import { NextResponse } from "next/server"; +import { getDiversityReport } from "../../../../../open-sse/services/autoCombo/providerDiversity"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + const report = getDiversityReport(); + return NextResponse.json(report); + } catch (error: any) { + return NextResponse.json({ error: error.message }, { status: 500 }); + } +}