Usage View Select

This commit is contained in:
yuneng-jiang
2025-12-11 17:37:09 -08:00
parent a48f048219
commit dfdd74f9ac
3 changed files with 586 additions and 411 deletions
@@ -51,6 +51,7 @@ import UserAgentActivity from "../../user_agent_activity";
import ViewUserSpend from "../../view_user_spend";
import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { UsageViewSelect, UsageOption } from "./UsageViewSelect/UsageViewSelect";
interface UsagePageProps {
teams: Team[];
@@ -86,7 +87,8 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
const [isGlobalExportModalOpen, setIsGlobalExportModalOpen] = useState(false);
const [showOrganizationBanner, setShowOrganizationBanner] = useState(true);
const [showCustomerBanner, setShowCustomerBanner] = useState(true);
const [usageView, setUsageView] = useState<UsageOption>("global");
const [showAgentBanner, setShowAgentBanner] = useState(true);
const getAllTags = async () => {
if (!accessToken) {
return;
@@ -416,431 +418,433 @@ const UsagePage: React.FC<UsagePageProps> = ({ teams, organizations }) => {
{/* Global Date Picker and Tabs - Single Row */}
<div className="flex items-end justify-between gap-6 mb-6">
<div className="flex-1">
<TabGroup>
<div className="flex items-end justify-between gap-6 mb-6 w-full">
<TabList variant="solid">
{all_admin_roles.includes(userRole || "") ? <Tab>Global Usage</Tab> : <Tab>Your Usage</Tab>}
{all_admin_roles.includes(userRole || "") ? (
<Tab>Organization Usage</Tab>
) : (
<Tab>Your Organization Usage</Tab>
)}
<Tab>Team Usage</Tab>
{all_admin_roles.includes(userRole || "") ? <Tab>Customer Usage</Tab> : <></>}
{all_admin_roles.includes(userRole || "") ? <Tab>Tag Usage</Tab> : <></>}
{all_admin_roles.includes(userRole || "") ? <Tab>Agent Usage</Tab> : <></>}
{all_admin_roles.includes(userRole || "") ? <Tab>User Agent Activity</Tab> : <></>}
</TabList>
<AdvancedDatePicker value={dateValue} onValueChange={handleDateChange} />
</div>
<TabPanels>
{/* Your Usage Panel */}
<TabPanel>
<TabGroup>
<div className="flex justify-between items-center">
<TabList variant="solid" className="mt-1">
<Tab>Cost</Tab>
<Tab>Model Activity</Tab>
<Tab>Key Activity</Tab>
<Tab>MCP Server Activity</Tab>
</TabList>
<Button
onClick={() => setIsGlobalExportModalOpen(true)}
icon={() => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
<div className="flex items-end justify-between gap-6 mb-4 w-full">
<UsageViewSelect
value={usageView}
onChange={(value) => setUsageView(value)}
isAdmin={all_admin_roles.includes(userRole || "")}
/>
<AdvancedDatePicker value={dateValue} onValueChange={handleDateChange} />
</div>
{/* Your Usage Panel */}
{usageView === "global" && (
<TabGroup>
<div className="flex justify-between items-center">
<TabList variant="solid" className="mt-1">
<Tab>Cost</Tab>
<Tab>Model Activity</Tab>
<Tab>Key Activity</Tab>
<Tab>MCP Server Activity</Tab>
</TabList>
<Button
onClick={() => setIsGlobalExportModalOpen(true)}
icon={() => (
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
)}
>
Export Data
</Button>
</div>
<TabPanels>
{/* Cost Panel */}
<TabPanel>
<Grid numItems={2} className="gap-2 w-full">
{/* Total Spend Card */}
<Col numColSpan={2}>
<Text className="text-tremor-default text-tremor-content dark:text-dark-tremor-content mb-2 mt-2 text-lg">
Project Spend{" "}
{dateValue.from && dateValue.to && (
<>
{dateValue.from.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined,
})}
{" - "}
{dateValue.to.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
})}
</>
)}
</Text>
<ViewUserSpend userSpend={totalSpend} selectedTeam={null} userMaxBudget={null} />
</Col>
<Col numColSpan={2}>
<Card>
<Title>Usage Metrics</Title>
<Grid numItems={5} className="gap-4 mt-4">
<Card>
<Title>Total Requests</Title>
<Text className="text-2xl font-bold mt-2">
{userSpendData.metadata?.total_api_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Successful Requests</Title>
<Text className="text-2xl font-bold mt-2 text-green-600">
{userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Failed Requests</Title>
<Text className="text-2xl font-bold mt-2 text-red-600">
{userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Total Tokens</Title>
<Text className="text-2xl font-bold mt-2">
{userSpendData.metadata?.total_tokens?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Average Cost per Request</Title>
<Text className="text-2xl font-bold mt-2">
$
{formatNumberWithCommas(
(totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1),
4,
)}
</Text>
</Card>
</Grid>
</Card>
</Col>
{/* Daily Spend Chart */}
<Col numColSpan={2}>
<Card>
<Title>Daily Spend</Title>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<BarChart
data={[...userSpendData.results].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)}
index="date"
categories={["metrics.spend"]}
colors={["cyan"]}
valueFormatter={valueFormatterSpend}
yAxisWidth={100}
showLegend={false}
customTooltip={({ payload, active }) => {
if (!active || !payload?.[0]) return null;
const data = payload[0].payload;
return (
<div className="bg-white p-4 shadow-lg rounded-lg border">
<p className="font-bold">{data.date}</p>
<p className="text-cyan-500">
Spend: ${formatNumberWithCommas(data.metrics.spend, 2)}
</p>
<p className="text-gray-600">Requests: {data.metrics.api_requests}</p>
<p className="text-gray-600">Successful: {data.metrics.successful_requests}</p>
<p className="text-gray-600">Failed: {data.metrics.failed_requests}</p>
<p className="text-gray-600">Tokens: {data.metrics.total_tokens}</p>
</div>
);
}}
/>
</svg>
)}
>
Export Data
</Button>
</div>
<TabPanels>
{/* Cost Panel */}
<TabPanel>
<Grid numItems={2} className="gap-2 w-full">
{/* Total Spend Card */}
<Col numColSpan={2}>
<Text className="text-tremor-default text-tremor-content dark:text-dark-tremor-content mb-2 mt-2 text-lg">
Project Spend{" "}
{dateValue.from && dateValue.to && (
<>
{dateValue.from.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year:
dateValue.from.getFullYear() !== dateValue.to.getFullYear() ? "numeric" : undefined,
})}
{" - "}
{dateValue.to.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
})}
</>
)}
</Text>
)}
</Card>
</Col>
{/* Top API Keys */}
<Col numColSpan={1}>
<Card className="h-full">
<Title>Top Virtual Keys</Title>
<TopKeyView topKeys={getTopKeys()} teams={null} />
</Card>
</Col>
<ViewUserSpend userSpend={totalSpend} selectedTeam={null} userMaxBudget={null} />
</Col>
{/* Top Models */}
<Col numColSpan={1}>
<Card className="h-full">
<div className="flex justify-between items-center mb-4">
<Title>{modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"}</Title>
<div className="flex bg-gray-100 rounded-lg p-1">
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "groups"
? "bg-white shadow-sm text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("groups")}
>
Public Model Name
</button>
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "individual"
? "bg-white shadow-sm text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("individual")}
>
Litellm Model Name
</button>
</div>
</div>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<BarChart
className="mt-4 h-40"
data={modelViewType === "groups" ? getTopModelGroups() : getTopModels()}
index="key"
categories={["spend"]}
colors={["cyan"]}
valueFormatter={valueFormatterSpend}
layout="vertical"
yAxisWidth={200}
showLegend={false}
customTooltip={({ payload, active }) => {
if (!active || !payload?.[0]) return null;
const data = payload[0].payload;
return (
<div className="bg-white p-4 shadow-lg rounded-lg border">
<p className="font-bold">{data.key}</p>
<p className="text-cyan-500">Spend: ${formatNumberWithCommas(data.spend, 2)}</p>
<p className="text-gray-600">Total Requests: {data.requests.toLocaleString()}</p>
<p className="text-green-600">
Successful: {data.successful_requests.toLocaleString()}
</p>
<p className="text-red-600">Failed: {data.failed_requests.toLocaleString()}</p>
<p className="text-gray-600">Tokens: {data.tokens.toLocaleString()}</p>
</div>
);
}}
/>
)}
</Card>
</Col>
<Col numColSpan={2}>
<Card>
<Title>Usage Metrics</Title>
<Grid numItems={5} className="gap-4 mt-4">
<Card>
<Title>Total Requests</Title>
<Text className="text-2xl font-bold mt-2">
{userSpendData.metadata?.total_api_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Successful Requests</Title>
<Text className="text-2xl font-bold mt-2 text-green-600">
{userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Failed Requests</Title>
<Text className="text-2xl font-bold mt-2 text-red-600">
{userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Total Tokens</Title>
<Text className="text-2xl font-bold mt-2">
{userSpendData.metadata?.total_tokens?.toLocaleString() || 0}
</Text>
</Card>
<Card>
<Title>Average Cost per Request</Title>
<Text className="text-2xl font-bold mt-2">
$
{formatNumberWithCommas(
(totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1),
4,
)}
</Text>
</Card>
</Grid>
</Card>
</Col>
{/* Daily Spend Chart */}
<Col numColSpan={2}>
<Card>
<Title>Daily Spend</Title>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<BarChart
data={[...userSpendData.results].sort(
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime(),
)}
index="date"
categories={["metrics.spend"]}
colors={["cyan"]}
valueFormatter={valueFormatterSpend}
yAxisWidth={100}
showLegend={false}
customTooltip={({ payload, active }) => {
if (!active || !payload?.[0]) return null;
const data = payload[0].payload;
return (
<div className="bg-white p-4 shadow-lg rounded-lg border">
<p className="font-bold">{data.date}</p>
<p className="text-cyan-500">
Spend: ${formatNumberWithCommas(data.metrics.spend, 2)}
</p>
<p className="text-gray-600">Requests: {data.metrics.api_requests}</p>
<p className="text-gray-600">Successful: {data.metrics.successful_requests}</p>
<p className="text-gray-600">Failed: {data.metrics.failed_requests}</p>
<p className="text-gray-600">Tokens: {data.metrics.total_tokens}</p>
</div>
);
}}
/>
)}
</Card>
</Col>
{/* Top API Keys */}
<Col numColSpan={1}>
<Card className="h-full">
<Title>Top Virtual Keys</Title>
<TopKeyView topKeys={getTopKeys()} teams={null} />
</Card>
</Col>
{/* Top Models */}
<Col numColSpan={1}>
<Card className="h-full">
<div className="flex justify-between items-center mb-4">
<Title>
{modelViewType === "groups" ? "Top Public Model Names" : "Top Litellm Models"}
</Title>
<div className="flex bg-gray-100 rounded-lg p-1">
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "groups"
? "bg-white shadow-sm text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("groups")}
>
Public Model Name
</button>
<button
className={`px-3 py-1 text-sm rounded-md transition-colors ${
modelViewType === "individual"
? "bg-white shadow-sm text-gray-900"
: "text-gray-600 hover:text-gray-900"
}`}
onClick={() => setModelViewType("individual")}
>
Litellm Model Name
</button>
</div>
</div>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<BarChart
{/* Spend by Provider */}
<Col numColSpan={2}>
<Card className="h-full">
<div className="flex justify-between items-center mb-4">
<Title>Spend by Provider</Title>
</div>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<Grid numItems={2}>
<Col numColSpan={1}>
<DonutChart
className="mt-4 h-40"
data={modelViewType === "groups" ? getTopModelGroups() : getTopModels()}
index="key"
categories={["spend"]}
data={getProviderSpend()}
index="provider"
category="spend"
valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`}
colors={["cyan"]}
valueFormatter={valueFormatterSpend}
layout="vertical"
yAxisWidth={200}
showLegend={false}
customTooltip={({ payload, active }) => {
if (!active || !payload?.[0]) return null;
const data = payload[0].payload;
return (
<div className="bg-white p-4 shadow-lg rounded-lg border">
<p className="font-bold">{data.key}</p>
<p className="text-cyan-500">Spend: ${formatNumberWithCommas(data.spend, 2)}</p>
<p className="text-gray-600">Total Requests: {data.requests.toLocaleString()}</p>
<p className="text-green-600">
Successful: {data.successful_requests.toLocaleString()}
</p>
<p className="text-red-600">Failed: {data.failed_requests.toLocaleString()}</p>
<p className="text-gray-600">Tokens: {data.tokens.toLocaleString()}</p>
</div>
);
}}
/>
)}
</Card>
</Col>
{/* Spend by Provider */}
<Col numColSpan={2}>
<Card className="h-full">
<div className="flex justify-between items-center mb-4">
<Title>Spend by Provider</Title>
</div>
{loading ? (
<ChartLoader isDateChanging={isDateChanging} />
) : (
<Grid numItems={2}>
<Col numColSpan={1}>
<DonutChart
className="mt-4 h-40"
data={getProviderSpend()}
index="provider"
category="spend"
valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`}
colors={["cyan"]}
/>
</Col>
<Col numColSpan={1}>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Provider</TableHeaderCell>
<TableHeaderCell>Spend</TableHeaderCell>
<TableHeaderCell className="text-green-600">Successful</TableHeaderCell>
<TableHeaderCell className="text-red-600">Failed</TableHeaderCell>
<TableHeaderCell>Tokens</TableHeaderCell>
</Col>
<Col numColSpan={1}>
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Provider</TableHeaderCell>
<TableHeaderCell>Spend</TableHeaderCell>
<TableHeaderCell className="text-green-600">Successful</TableHeaderCell>
<TableHeaderCell className="text-red-600">Failed</TableHeaderCell>
<TableHeaderCell>Tokens</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{getProviderSpend()
.filter((provider) => provider.spend > 0)
.map((provider) => (
<TableRow key={provider.provider}>
<TableCell>
<div className="flex items-center space-x-2">
{provider.provider && (
<img
src={getProviderLogoAndName(provider.provider).logo}
alt={`${provider.provider} logo`}
className="w-4 h-4"
onError={(e) => {
const target = e.target as HTMLImageElement;
const parent = target.parentElement;
if (parent) {
const fallbackDiv = document.createElement("div");
fallbackDiv.className =
"w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs";
fallbackDiv.textContent = provider.provider?.charAt(0) || "-";
parent.replaceChild(fallbackDiv, target);
}
}}
/>
)}
<span>{provider.provider}</span>
</div>
</TableCell>
<TableCell>${formatNumberWithCommas(provider.spend, 2)}</TableCell>
<TableCell className="text-green-600">
{provider.successful_requests.toLocaleString()}
</TableCell>
<TableCell className="text-red-600">
{provider.failed_requests.toLocaleString()}
</TableCell>
<TableCell>{provider.tokens.toLocaleString()}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{getProviderSpend()
.filter((provider) => provider.spend > 0)
.map((provider) => (
<TableRow key={provider.provider}>
<TableCell>
<div className="flex items-center space-x-2">
{provider.provider && (
<img
src={getProviderLogoAndName(provider.provider).logo}
alt={`${provider.provider} logo`}
className="w-4 h-4"
onError={(e) => {
const target = e.target as HTMLImageElement;
const parent = target.parentElement;
if (parent) {
const fallbackDiv = document.createElement("div");
fallbackDiv.className =
"w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs";
fallbackDiv.textContent = provider.provider?.charAt(0) || "-";
parent.replaceChild(fallbackDiv, target);
}
}}
/>
)}
<span>{provider.provider}</span>
</div>
</TableCell>
<TableCell>${formatNumberWithCommas(provider.spend, 2)}</TableCell>
<TableCell className="text-green-600">
{provider.successful_requests.toLocaleString()}
</TableCell>
<TableCell className="text-red-600">
{provider.failed_requests.toLocaleString()}
</TableCell>
<TableCell>{provider.tokens.toLocaleString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Col>
</Grid>
)}
</Card>
</Col>
))}
</TableBody>
</Table>
</Col>
</Grid>
)}
</Card>
</Col>
{/* Usage Metrics */}
</Grid>
</TabPanel>
{/* Usage Metrics */}
</Grid>
</TabPanel>
{/* Activity Panel */}
<TabPanel>
<ActivityMetrics modelMetrics={modelMetrics} />
</TabPanel>
<TabPanel>
<ActivityMetrics modelMetrics={keyMetrics} />
</TabPanel>
<TabPanel>
<ActivityMetrics modelMetrics={mcpServerMetrics} />
</TabPanel>
</TabPanels>
</TabGroup>
</TabPanel>
{/* Activity Panel */}
<TabPanel>
<ActivityMetrics modelMetrics={modelMetrics} />
</TabPanel>
<TabPanel>
<ActivityMetrics modelMetrics={keyMetrics} />
</TabPanel>
<TabPanel>
<ActivityMetrics modelMetrics={mcpServerMetrics} />
</TabPanel>
</TabPanels>
</TabGroup>
)}
{/* Organization Usage Panel */}
{/* Organization Usage Panel */}
<TabPanel>
{showOrganizationBanner && (
<Alert
banner
type="info"
message="Organization usage is a new feature."
description="Spend is tracked from feature launch and previous data isn't backfilled, so only future usage appears here."
closable
onClose={() => setShowOrganizationBanner(false)}
className="mb-5"
/>
)}
<EntityUsage
accessToken={accessToken}
entityType="organization"
userID={userID}
userRole={userRole}
dateValue={dateValue}
entityList={
organizations?.map((organization) => ({
label: organization.organization_alias,
value: organization.organization_id,
})) || null
}
premiumUser={premiumUser}
{usageView === "organization" && (
<>
{showOrganizationBanner && (
<Alert
banner
type="info"
message="Organization usage is a new feature."
description="Spend is tracked from feature launch and previous data isn't backfilled, so only future usage appears here."
closable
onClose={() => setShowOrganizationBanner(false)}
className="mb-5"
/>
</TabPanel>
)}
<EntityUsage
accessToken={accessToken}
entityType="organization"
userID={userID}
userRole={userRole}
dateValue={dateValue}
entityList={
organizations?.map((organization) => ({
label: organization.organization_alias,
value: organization.organization_id,
})) || null
}
premiumUser={premiumUser}
/>
</>
)}
{/* Team Usage Panel */}
<TabPanel>
<EntityUsage
accessToken={accessToken}
entityType="team"
userID={userID}
userRole={userRole}
entityList={
teams?.map((team) => ({
label: team.team_alias,
value: team.team_id,
})) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
/>
</TabPanel>
{/* Team Usage Panel */}
{usageView === "team" && (
<EntityUsage
accessToken={accessToken}
entityType="team"
userID={userID}
userRole={userRole}
entityList={
teams?.map((team) => ({
label: team.team_alias,
value: team.team_id,
})) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
/>
)}
{/* Customer Usage Panel */}
<TabPanel>
{showCustomerBanner && (
<Alert
banner
type="info"
message="Customer usage is a new feature."
description="Spend is tracked from feature launch and previous data isn't backfilled, so only future usage appears here."
closable
onClose={() => setShowCustomerBanner(false)}
className="mb-5"
/>
)}
<EntityUsage
accessToken={accessToken}
entityType="customer"
userID={userID}
userRole={userRole}
entityList={
customers?.map((customer) => ({
label: customer.alias || customer.user_id,
value: customer.user_id,
})) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
{/* Customer Usage Panel */}
{usageView === "customer" && (
<>
{showCustomerBanner && (
<Alert
banner
type="info"
message="Customer usage is a new feature."
description="Spend is tracked from feature launch and previous data isn't backfilled, so only future usage appears here."
closable
onClose={() => setShowCustomerBanner(false)}
className="mb-5"
/>
</TabPanel>
{/* Tag Usage Panel */}
<TabPanel>
<EntityUsage
accessToken={accessToken}
entityType="tag"
userID={userID}
userRole={userRole}
entityList={allTags}
premiumUser={premiumUser}
dateValue={dateValue}
)}
<EntityUsage
accessToken={accessToken}
entityType="customer"
userID={userID}
userRole={userRole}
entityList={
customers?.map((customer) => ({
label: customer.alias || customer.user_id,
value: customer.user_id,
})) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
/>
</>
)}
{/* Tag Usage Panel */}
{usageView === "tag" && (
<EntityUsage
accessToken={accessToken}
entityType="tag"
userID={userID}
userRole={userRole}
entityList={allTags}
premiumUser={premiumUser}
dateValue={dateValue}
/>
)}
{usageView === "agent" && (
<>
{showAgentBanner && (
<Alert
banner
type="info"
message="Agent usage (A2A) is a new feature."
description="Spend is tracked from feature launch and previous data isn't backfilled, so only future usage appears here."
closable
onClose={() => setShowAgentBanner(false)}
className="mb-5"
/>
</TabPanel>
<TabPanel>
<EntityUsage
accessToken={accessToken}
entityType="agent"
userID={userID}
userRole={userRole}
entityList={
agentsResponse?.agents?.map((agent) => ({ label: agent.agent_name, value: agent.agent_id })) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
/>
</TabPanel>
{/* User Agent Activity Panel */}
<TabPanel>
<UserAgentActivity accessToken={accessToken} userRole={userRole} dateValue={dateValue} />
</TabPanel>
</TabPanels>
</TabGroup>
)}
<EntityUsage
accessToken={accessToken}
entityType="agent"
userID={userID}
userRole={userRole}
entityList={
agentsResponse?.agents?.map((agent) => ({ label: agent.agent_name, value: agent.agent_id })) || null
}
premiumUser={premiumUser}
dateValue={dateValue}
/>{" "}
</>
)}
{/* User Agent Activity Panel */}
{usageView === "user-agent-activity" && (
<UserAgentActivity accessToken={accessToken} userRole={userRole} dateValue={dateValue} />
)}
</div>
</div>
@@ -0,0 +1,171 @@
import React from "react";
import { Select } from "antd";
import {
GlobalOutlined,
BankOutlined,
TeamOutlined,
ShoppingCartOutlined,
TagsOutlined,
RobotOutlined,
LineChartOutlined,
BarChartOutlined,
} from "@ant-design/icons";
export type UsageOption = "global" | "organization" | "team" | "customer" | "tag" | "agent" | "user-agent-activity";
export interface UsageViewSelectProps {
value: UsageOption;
onChange: (value: UsageOption) => void;
isAdmin: boolean;
title?: string;
description?: string;
"data-id"?: string;
}
interface OptionConfig {
value: UsageOption;
label: string;
description: string;
icon: React.ReactNode;
adminOnly?: boolean;
showForAdmin?: string;
showForNonAdmin?: string;
descriptionForAdmin?: string;
descriptionForNonAdmin?: string;
}
const OPTIONS: OptionConfig[] = [
{
value: "global",
label: "Global Usage",
showForAdmin: "Global Usage",
showForNonAdmin: "Your Usage",
description: "View usage across all resources",
descriptionForAdmin: "View usage across all resources and users",
descriptionForNonAdmin: "View your personal usage statistics",
icon: <GlobalOutlined style={{ fontSize: "16px" }} />,
},
{
value: "organization",
label: "Organization Usage",
showForAdmin: "Organization Usage",
showForNonAdmin: "Your Organization Usage",
description: "View organization-level usage",
descriptionForAdmin: "View usage across all organizations",
descriptionForNonAdmin: "View your organization's usage statistics",
icon: <BankOutlined style={{ fontSize: "16px" }} />,
},
{
value: "team",
label: "Team Usage",
description: "View usage by team",
icon: <TeamOutlined style={{ fontSize: "16px" }} />,
},
{
value: "customer",
label: "Customer Usage",
description: "View usage by customer accounts",
icon: <ShoppingCartOutlined style={{ fontSize: "16px" }} />,
adminOnly: true,
},
{
value: "tag",
label: "Tag Usage",
description: "View usage grouped by tags",
icon: <TagsOutlined style={{ fontSize: "16px" }} />,
adminOnly: true,
},
{
value: "agent",
label: "Agent Usage (A2A)",
description: "View usage by AI agents",
icon: <RobotOutlined style={{ fontSize: "16px" }} />,
adminOnly: true,
},
{
value: "user-agent-activity",
label: "User Agent Activity",
description: "View detailed user agent activity logs",
icon: <LineChartOutlined style={{ fontSize: "16px" }} />,
adminOnly: true,
},
];
export const UsageViewSelect: React.FC<UsageViewSelectProps> = ({
value,
onChange,
isAdmin,
title = "Usage View",
description = "Select the usage data you want to view",
"data-id": dataId,
}) => {
const getFilteredOptions = () => {
return OPTIONS.filter((option) => {
if (option.adminOnly && !isAdmin) {
return false;
}
return true;
}).map((option) => {
let label = option.label;
let desc = option.description;
if (option.showForAdmin && option.showForNonAdmin) {
label = isAdmin ? option.showForAdmin : option.showForNonAdmin;
}
if (option.descriptionForAdmin && option.descriptionForNonAdmin) {
desc = isAdmin ? option.descriptionForAdmin : option.descriptionForNonAdmin;
}
return {
value: option.value,
label,
description: desc,
icon: option.icon,
};
});
};
const filteredOptions = getFilteredOptions();
return (
<div className="w-full" data-id={dataId}>
<div className="flex flex-wrap items-center justify-start gap-4">
<div className="flex items-stretch gap-2 min-w-0">
<div className="flex-shrink-0 flex items-center">
<BarChartOutlined style={{ fontSize: "32px" }} />
</div>
<div className="flex-1 min-w-0">
<h3 className="text-sm font-semibold text-gray-900 mb-0.5 leading-tight">{title}</h3>
<p className="text-xs text-gray-600 leading-tight">{description}</p>
</div>
</div>
<div className="flex-shrink-0">
<Select
value={value}
onChange={onChange}
className="w-48 sm:w-64 md:w-72"
size="large"
options={filteredOptions.map((opt) => ({
value: opt.value,
label: opt.label,
}))}
optionRender={(option) => {
const opt = filteredOptions.find((o) => o.value === option.value);
if (!opt) return option.label;
return (
<div className="flex items-start gap-2 py-1">
<div className="flex-shrink-0 mt-0.5">{opt.icon}</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-gray-900">{opt.label}</div>
<div className="text-xs text-gray-600 mt-0.5">{opt.description}</div>
</div>
</div>
);
}}
labelRender={(props) => {
const opt = filteredOptions.find((o) => o.value === props.value);
if (!opt) return props.label;
return (
<div className="flex items-center gap-2">
<div>{opt.icon}</div>
<span className="text-sm">{opt.label}</span>
</div>
);
}}
/>
</div>
</div>
</div>
);
};