refactor(ui): move per-second cost formatter to dataUtils and type transformModelData input

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-09-18 15:48:56 +00:00
parent 0beb2ffb81
commit b5070408e7
6 changed files with 94 additions and 13 deletions

View file

@ -3,7 +3,6 @@
import { ColumnDef } from "@tanstack/react-table";
import { Copy, Info, Loader2, Pencil, RefreshCw, Trash2 } from "lucide-react";
import { formatPerSecondCost } from "@/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer";
import { ProviderLogo } from "@/components/molecules/models/ProviderLogo";
import { ModelData } from "@/components/model_dashboard/types";
import { DataTableSortHeader } from "@/components/shared/DataTable";
@ -13,7 +12,7 @@ import { Button } from "@/components/ui/button";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card";
import { Switch } from "@/components/ui/switch";
import { getDisplayModelName } from "@/components/view_model/model_name_display";
import { copyToClipboard } from "@/utils/dataUtils";
import { copyToClipboard, formatPerSecondCost } from "@/utils/dataUtils";
export const MODEL_ID_COLUMN_ID = "model_info_id";
export const MODEL_NAME_COLUMN_ID = "model_name";

View file

@ -8,9 +8,6 @@ export const perSecondCostTiers = (modelInfo: Record<string, unknown> | null | u
return resolution !== undefined && typeof value === "number" ? [{ resolution, cost: value }] : [];
});
export const formatPerSecondCost = (cost: number): string =>
`$${cost.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 })}/s`;
interface RawLitellmParams {
model?: string;
custom_llm_provider?: string;
@ -50,8 +47,8 @@ const resolveProvider = (
const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string) => string) => {
const model: RawModel = JSON.parse(JSON.stringify(rawModel));
const litellmParams = model?.litellm_params;
const modelInfo = model?.model_info;
const litellmParams = model.litellm_params;
const modelInfo = model.model_info;
return {
...model,
@ -70,8 +67,11 @@ const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string
};
};
export const transformModelData = (rawModelData: any, getProviderFromModel: (model: string) => string) => {
export const transformModelData = (
rawModelData: { data?: RawModel[] | null } | null | undefined,
getProviderFromModel: (model: string) => string,
) => {
if (!rawModelData?.data) return { data: [] };
return { data: rawModelData.data.map((rawModel: RawModel) => transformModel(rawModel, getProviderFromModel)) };
return { data: rawModelData.data.map((rawModel) => transformModel(rawModel, getProviderFromModel)) };
};

View file

@ -0,0 +1,57 @@
import React from "react";
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { ModelPricingSummary } from "./ModelPricingSummary";
const tokenPriced = { input_cost: "1.50" as unknown as number, output_cost: "2.00" as unknown as number };
describe("ModelPricingSummary", () => {
it("shows per-million-token rates for a token priced model", () => {
render(<ModelPricingSummary model={tokenPriced} />);
expect(screen.getByText("Input: $1.50/1M tokens")).toBeInTheDocument();
expect(screen.getByText("Output: $2.00/1M tokens")).toBeInTheDocument();
});
it("hides $0.00 token rates and shows per-second tiers for a per-second priced model", () => {
render(
<ModelPricingSummary
model={{
input_cost: "0.00" as unknown as number,
output_cost: "0.00" as unknown as number,
output_cost_per_second: 0.1,
output_cost_per_second_tiers: [
{ resolution: "1080p", cost: 0.12 },
{ resolution: "4k", cost: 0.3 },
],
}}
/>,
);
expect(screen.getByText("Output: $0.10/s")).toBeInTheDocument();
expect(screen.getByText("Output (1080p): $0.12/s")).toBeInTheDocument();
expect(screen.getByText("Output (4k): $0.30/s")).toBeInTheDocument();
expect(screen.queryByText(/1M tokens/)).not.toBeInTheDocument();
});
it("keeps a positive token rate next to the per-second rate", () => {
render(
<ModelPricingSummary
model={{
input_cost: "0.60" as unknown as number,
output_cost: "0.00" as unknown as number,
output_cost_per_second: 0.015,
}}
/>,
);
expect(screen.getByText("Input: $0.60/1M tokens")).toBeInTheDocument();
expect(screen.getByText("Output: $0.015/s")).toBeInTheDocument();
expect(screen.queryByText("Output: $0.00/1M tokens")).not.toBeInTheDocument();
});
it("renders a dash when the model has no pricing at all", () => {
render(
<ModelPricingSummary model={{ input_cost: null as unknown as number, output_cost: null as unknown as number }} />,
);
expect(screen.getByText("-")).toBeInTheDocument();
expect(screen.queryByText(/\$/)).not.toBeInTheDocument();
});
});

View file

@ -1,5 +1,5 @@
import { formatPerSecondCost } from "@/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer";
import { ModelData } from "@/components/model_dashboard/types";
import { formatPerSecondCost } from "@/utils/dataUtils";
type PricingFields = Pick<
ModelData,
@ -9,8 +9,12 @@ type PricingFields = Pick<
export function ModelPricingSummary({ model }: { model: PricingFields }) {
const perSecond = model.output_cost_per_second;
const hasPerSecond = perSecond != null;
const showInput = !hasPerSecond || Number(model.input_cost) > 0;
const showOutput = !hasPerSecond || Number(model.output_cost) > 0;
const showInput = model.input_cost != null && (!hasPerSecond || Number(model.input_cost) > 0);
const showOutput = model.output_cost != null && (!hasPerSecond || Number(model.output_cost) > 0);
if (!showInput && !showOutput && !hasPerSecond) {
return <p className="mt-2 text-sm text-muted-foreground">-</p>;
}
return (
<div className="mt-2">

View file

@ -1,7 +1,13 @@
// @vitest-environment jsdom
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
import { copyToClipboard, formatNumberWithCommas, getSpendString, updateExistingKeys } from "./dataUtils";
import {
copyToClipboard,
formatNumberWithCommas,
formatPerSecondCost,
getSpendString,
updateExistingKeys,
} from "./dataUtils";
// Import the mocked module
import { toast } from "@/lib/toast";
@ -115,6 +121,18 @@ describe("dataUtils", () => {
});
});
describe("formatPerSecondCost", () => {
it("should keep at least two decimals and append the per-second unit", () => {
expect(formatPerSecondCost(0.4)).toBe("$0.40/s");
expect(formatPerSecondCost(1)).toBe("$1.00/s");
});
it("should show sub-cent rates without rounding them to zero", () => {
expect(formatPerSecondCost(0.015)).toBe("$0.015/s");
expect(formatPerSecondCost(0.000025)).toBe("$0.000025/s");
});
});
describe("copyToClipboard", () => {
describe("when Clipboard API is available (HTTPS scenario)", () => {
beforeEach(() => {

View file

@ -63,6 +63,9 @@ export const getSpendString = (value: number | null | undefined, decimals: numbe
return `$${formatted}`;
};
export const formatPerSecondCost = (cost: number): string =>
`$${cost.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 6 })}/s`;
export const copyToClipboard = async (
text: string | null | undefined,
messageText: string = "Copied to clipboard",