diff --git a/messages/en-US.json b/messages/en-US.json index bb1325ee9..5da27a834 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1941,7 +1941,6 @@ "aiUsageTabOverview": "Overview", "aiUsageTabProviders": "Providers", "aiUsageTabResources": "Resources", - "aiUsageTabUsersRoles": "Users & Roles", "aiUsageFilterProvider": "Provider", "aiUsageFilterModel": "Model", "aiUsageFilterResource": "Resource", diff --git a/src/components/AiUsageAnalyticsData.tsx b/src/components/AiUsageAnalyticsData.tsx index 748fc7cdf..c19ca2346 100644 --- a/src/components/AiUsageAnalyticsData.tsx +++ b/src/components/AiUsageAnalyticsData.tsx @@ -27,7 +27,8 @@ import { HorizontalTabs, type TabItem } from "./HorizontalTabs"; import { OverviewTab } from "./ai-usage-analytics/OverviewTab"; import { ProvidersTab } from "./ai-usage-analytics/ProvidersTab"; import { ResourcesTab } from "./ai-usage-analytics/ResourcesTab"; -import { UsersRolesTab } from "./ai-usage-analytics/UsersRolesTab"; +import { RolesTab } from "./ai-usage-analytics/RolesTab"; +import { UsersTab } from "./ai-usage-analytics/UsersTab"; export type AiUsageAnalyticsDataProps = { orgId: string; @@ -144,7 +145,8 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) { { title: t("aiUsageTabOverview"), href: "#" }, { title: t("aiUsageTabProviders"), href: "#" }, { title: t("aiUsageTabResources"), href: "#" }, - { title: t("aiUsageTabUsersRoles"), href: "#" } + { title: t("aiUsageRolesTab"), href: "#" }, + { title: t("aiUsageUsersTab"), href: "#" } ]; return ( @@ -255,7 +257,8 @@ export function AiUsageAnalyticsData(props: AiUsageAnalyticsDataProps) { - + + ); diff --git a/src/components/ai-usage-analytics/RolesTab.tsx b/src/components/ai-usage-analytics/RolesTab.tsx new file mode 100644 index 000000000..0b60cab3c --- /dev/null +++ b/src/components/ai-usage-analytics/RolesTab.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { aiUsageAnalyticsQueries } from "@app/lib/queries"; +import type { AiUsageAnalyticsFilters } from "@app/lib/queries"; +import { useTranslations } from "next-intl"; +import { Card, CardContent, CardHeader } from "@app/components/ui/card"; +import { ToggleableTrendChart } from "./ToggleableTrendChart"; +import { TopEntitiesList, type TopEntity } from "./TopEntitiesList"; +import { buildSeriesFromData, formatCost } from "./shared"; + +type RolesTabProps = { + orgId: string; + filters: AiUsageAnalyticsFilters; +}; + +export function RolesTab(props: RolesTabProps) { + const t = useTranslations(); + const { data, isLoading } = useQuery( + aiUsageAnalyticsQueries.usersRoles({ + orgId: props.orgId, + filters: props.filters + }) + ); + + const roleNameByKey = new Map(); + for (const r of data?.topRoles ?? []) { + roleNameByKey.set(String(r.roleId), r.name ?? `Role #${r.roleId}`); + } + const roleLabelFor = (key: string) => + roleNameByKey.get(key) ?? `Role #${key}`; + + const roleCostSeries = buildSeriesFromData( + data?.roleCostPerDay ?? [], + roleLabelFor, + t("aiUsageOther") + ); + const roleTokensSeries = buildSeriesFromData( + data?.roleTokensPerDay ?? [], + roleLabelFor, + t("aiUsageOther") + ); + + const topRoles: TopEntity[] = (data?.topRoles ?? []).map((r) => ({ + key: String(r.roleId), + label: r.name ?? `Role #${r.roleId}`, + requests: r.requests, + totalTokens: r.totalTokens, + costUsd: r.costUsd + })); + + return ( +
+ + +

{t("aiUsageTopRoles")}

+
+ + + +
+
+ + + formatCost(v)} + /> + + + + + + + +
+
+ ); +} diff --git a/src/components/ai-usage-analytics/UsersRolesTab.tsx b/src/components/ai-usage-analytics/UsersRolesTab.tsx deleted file mode 100644 index 6e49ce9fb..000000000 --- a/src/components/ai-usage-analytics/UsersRolesTab.tsx +++ /dev/null @@ -1,172 +0,0 @@ -"use client"; - -import { useQuery } from "@tanstack/react-query"; -import { aiUsageAnalyticsQueries } from "@app/lib/queries"; -import type { AiUsageAnalyticsFilters } from "@app/lib/queries"; -import { useTranslations } from "next-intl"; -import { Card, CardContent, CardHeader } from "@app/components/ui/card"; -import { HorizontalTabs, type TabItem } from "@app/components/HorizontalTabs"; -import { ToggleableTrendChart } from "./ToggleableTrendChart"; -import { TopEntitiesList, type TopEntity } from "./TopEntitiesList"; -import { buildSeriesFromData, formatCost } from "./shared"; - -type UsersRolesTabProps = { - orgId: string; - filters: AiUsageAnalyticsFilters; -}; - -const UNKNOWN_USER_KEY = "unknown"; - -export function UsersRolesTab(props: UsersRolesTabProps) { - const t = useTranslations(); - const { data, isLoading } = useQuery( - aiUsageAnalyticsQueries.usersRoles({ - orgId: props.orgId, - filters: props.filters - }) - ); - - const roleNameByKey = new Map(); - for (const r of data?.topRoles ?? []) { - roleNameByKey.set(String(r.roleId), r.name ?? `Role #${r.roleId}`); - } - const roleLabelFor = (key: string) => - roleNameByKey.get(key) ?? `Role #${key}`; - - const userEmailByKey = new Map(); - for (const u of data?.topUsers ?? []) { - if (u.userId) { - userEmailByKey.set(u.userId, u.email ?? u.userId); - } - } - const userLabelFor = (key: string) => - key === UNKNOWN_USER_KEY - ? t("aiUsageUnknownUser") - : (userEmailByKey.get(key) ?? key); - - const roleCostSeries = buildSeriesFromData( - data?.roleCostPerDay ?? [], - roleLabelFor, - t("aiUsageOther") - ); - const roleTokensSeries = buildSeriesFromData( - data?.roleTokensPerDay ?? [], - roleLabelFor, - t("aiUsageOther") - ); - const userCostSeries = buildSeriesFromData( - data?.userCostPerDay ?? [], - userLabelFor, - t("aiUsageOther") - ); - const userTokensSeries = buildSeriesFromData( - data?.userTokensPerDay ?? [], - userLabelFor, - t("aiUsageOther") - ); - - const topRoles: TopEntity[] = (data?.topRoles ?? []).map((r) => ({ - key: String(r.roleId), - label: r.name ?? `Role #${r.roleId}`, - requests: r.requests, - totalTokens: r.totalTokens, - costUsd: r.costUsd - })); - - const topUsers: TopEntity[] = (data?.topUsers ?? []).map((u) => ({ - key: u.userId ?? UNKNOWN_USER_KEY, - label: u.email ?? u.userId ?? t("aiUsageUnknownUser"), - requests: u.requests, - totalTokens: u.totalTokens, - costUsd: u.costUsd - })); - - const tabs: TabItem[] = [ - { title: t("aiUsageRolesTab"), href: "#" }, - { title: t("aiUsageUsersTab"), href: "#" } - ]; - - return ( - -
- - -

- {t("aiUsageTopRoles")} -

-
- - - -
-
- - - formatCost(v)} - /> - - - - - - - -
-
- -
- - -

- {t("aiUsageTopUsers")} -

-
- - - -
-
- - - formatCost(v)} - /> - - - - - - - -
-
-
- ); -} diff --git a/src/components/ai-usage-analytics/UsersTab.tsx b/src/components/ai-usage-analytics/UsersTab.tsx new file mode 100644 index 000000000..25e914e3e --- /dev/null +++ b/src/components/ai-usage-analytics/UsersTab.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { aiUsageAnalyticsQueries } from "@app/lib/queries"; +import type { AiUsageAnalyticsFilters } from "@app/lib/queries"; +import { useTranslations } from "next-intl"; +import { Card, CardContent, CardHeader } from "@app/components/ui/card"; +import { ToggleableTrendChart } from "./ToggleableTrendChart"; +import { TopEntitiesList, type TopEntity } from "./TopEntitiesList"; +import { buildSeriesFromData, formatCost } from "./shared"; + +type UsersTabProps = { + orgId: string; + filters: AiUsageAnalyticsFilters; +}; + +const UNKNOWN_USER_KEY = "unknown"; + +export function UsersTab(props: UsersTabProps) { + const t = useTranslations(); + const { data, isLoading } = useQuery( + aiUsageAnalyticsQueries.usersRoles({ + orgId: props.orgId, + filters: props.filters + }) + ); + + const userEmailByKey = new Map(); + for (const u of data?.topUsers ?? []) { + if (u.userId) { + userEmailByKey.set(u.userId, u.email ?? u.userId); + } + } + const userLabelFor = (key: string) => + key === UNKNOWN_USER_KEY + ? t("aiUsageUnknownUser") + : (userEmailByKey.get(key) ?? key); + + const userCostSeries = buildSeriesFromData( + data?.userCostPerDay ?? [], + userLabelFor, + t("aiUsageOther") + ); + const userTokensSeries = buildSeriesFromData( + data?.userTokensPerDay ?? [], + userLabelFor, + t("aiUsageOther") + ); + + const topUsers: TopEntity[] = (data?.topUsers ?? []).map((u) => ({ + key: u.userId ?? UNKNOWN_USER_KEY, + label: u.email ?? u.userId ?? t("aiUsageUnknownUser"), + requests: u.requests, + totalTokens: u.totalTokens, + costUsd: u.costUsd + })); + + return ( +
+ + +

{t("aiUsageTopUsers")}

+
+ + + +
+
+ + + formatCost(v)} + /> + + + + + + + +
+
+ ); +}