fix(ui): type transformModelData output as ModelData so the dashboard build typechecks

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-09-18 16:13:28 +00:00
parent b5070408e7
commit d197ca20fa
6 changed files with 40 additions and 42 deletions

View file

@ -11,8 +11,8 @@ const makeModel = (overrides: Partial<ModelData> = {}): ModelData =>
model_name: "gpt-4-public",
litellm_model_name: "openai/gpt-4",
provider: "openai",
input_cost: 30 as unknown as number,
output_cost: 60 as unknown as number,
input_cost: "30",
output_cost: "60",
max_tokens: 8192,
max_input_tokens: 8192,
litellm_params: { model: "openai/gpt-4" },
@ -178,7 +178,7 @@ describe("AllModelsTable", () => {
rerender(
<AllModelsTable
{...baseProps}
data={[makeModel({ input_cost: null as unknown as number, output_cost: null as unknown as number })]}
data={[makeModel({ input_cost: null, output_cost: null })]}
/>,
);
expect(screen.queryByText(/^\$/)).not.toBeInTheDocument();
@ -190,8 +190,8 @@ describe("AllModelsTable", () => {
{...baseProps}
data={[
makeModel({
input_cost: "0.00" as unknown as number,
output_cost: "0.00" as unknown as number,
input_cost: "0.00",
output_cost: "0.00",
output_cost_per_second: 0.4,
}),
]}
@ -205,8 +205,8 @@ describe("AllModelsTable", () => {
{...baseProps}
data={[
makeModel({
input_cost: "0.60" as unknown as number,
output_cost: "0.00" as unknown as number,
input_cost: "0.60",
output_cost: "0.00",
output_cost_per_second: 0.015,
}),
]}

View file

@ -1,4 +1,4 @@
import { PerSecondCostTier } from "@/components/model_dashboard/types";
import { LiteLLMParams, ModelData, ModelInfo, PerSecondCostTier } from "@/components/model_dashboard/types";
const PER_SECOND_TIER_KEY = /^output_cost_per_second_(.+)$/;
@ -8,15 +8,11 @@ export const perSecondCostTiers = (modelInfo: Record<string, unknown> | null | u
return resolution !== undefined && typeof value === "number" ? [{ resolution, cost: value }] : [];
});
interface RawLitellmParams {
model?: string;
custom_llm_provider?: string;
api_base?: string;
interface RawLitellmParams extends LiteLLMParams {
output_cost_per_second?: number;
[key: string]: unknown;
}
interface RawModelInfo {
interface RawModelInfo extends ModelInfo {
input_cost_per_token?: number | null;
output_cost_per_token?: number | null;
output_cost_per_second?: number;
@ -25,14 +21,15 @@ interface RawModelInfo {
[key: string]: unknown;
}
interface RawModel {
litellm_params?: RawLitellmParams;
model_info?: RawModelInfo;
export interface RawModel {
model_name: string;
litellm_params: RawLitellmParams;
model_info: RawModelInfo;
[key: string]: unknown;
}
const costPerMillionTokens = (costPerToken: number | null | undefined) =>
costPerToken == null ? costPerToken : (Number(costPerToken) * 1000000).toFixed(2);
const costPerMillionTokens = (costPerToken: number | null | undefined): string | null =>
costPerToken == null ? null : (Number(costPerToken) * 1000000).toFixed(2);
const resolveProvider = (
litellmModelName: string | null | undefined,
@ -45,24 +42,24 @@ const resolveProvider = (
return splitModel.length === 1 ? getProviderFromModel(litellmModelName) : splitModel[0];
};
const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string) => string) => {
const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string) => string): ModelData => {
const model: RawModel = JSON.parse(JSON.stringify(rawModel));
const litellmParams = model.litellm_params;
const modelInfo = model.model_info;
return {
...model,
provider: resolveProvider(litellmParams?.model, litellmParams?.custom_llm_provider, getProviderFromModel),
input_cost: modelInfo ? costPerMillionTokens(modelInfo.input_cost_per_token) : null,
output_cost: modelInfo ? costPerMillionTokens(modelInfo.output_cost_per_token) : null,
output_cost_per_second: litellmParams?.output_cost_per_second ?? modelInfo?.output_cost_per_second ?? null,
provider: resolveProvider(litellmParams.model, litellmParams.custom_llm_provider, getProviderFromModel),
input_cost: costPerMillionTokens(modelInfo?.input_cost_per_token),
output_cost: costPerMillionTokens(modelInfo?.output_cost_per_token),
output_cost_per_second: litellmParams.output_cost_per_second ?? modelInfo?.output_cost_per_second ?? null,
output_cost_per_second_tiers: perSecondCostTiers(modelInfo),
litellm_model_name: litellmParams?.model,
max_tokens: modelInfo ? modelInfo.max_tokens : "Undefined",
max_input_tokens: modelInfo ? modelInfo.max_input_tokens : "Undefined",
api_base: litellmParams?.api_base,
litellm_model_name: litellmParams.model,
max_tokens: modelInfo?.max_tokens,
max_input_tokens: modelInfo?.max_input_tokens,
api_base: litellmParams.api_base,
cleanedLitellmParams: Object.fromEntries(
Object.entries(litellmParams ?? {}).filter(([key]) => key !== "model" && key !== "api_base"),
Object.entries(litellmParams).filter(([key]) => key !== "model" && key !== "api_base"),
),
};
};
@ -70,7 +67,7 @@ const transformModel = (rawModel: RawModel, getProviderFromModel: (model: string
export const transformModelData = (
rawModelData: { data?: RawModel[] | null } | null | undefined,
getProviderFromModel: (model: string) => string,
) => {
): { data: ModelData[] } => {
if (!rawModelData?.data) return { data: [] };
return { data: rawModelData.data.map((rawModel) => transformModel(rawModel, getProviderFromModel)) };

View file

@ -270,7 +270,7 @@ const displayCost = (localModelData: any, field: TouchedPricingField): string =>
interface ModelInfoEditFormProps {
localModelData: any;
modelData: { model_info: { team_id?: string | null } & Record<string, unknown> };
modelData: { model_info: { team_id?: string | null } };
teamAlias: string | null;
accessToken: string | null;
isEditing: boolean;

View file

@ -13,6 +13,7 @@ export interface ModelInfo {
access_groups: string[] | null;
blocked?: boolean;
team_public_model_name?: string;
key?: string;
}
export interface LiteLLMParams {
@ -30,12 +31,12 @@ export interface ModelData {
model_name: string;
provider: string;
litellm_model_name: string;
input_cost: number;
output_cost: number;
input_cost: string | null;
output_cost: string | null;
output_cost_per_second?: number | null;
output_cost_per_second_tiers?: PerSecondCostTier[];
max_tokens: number;
max_input_tokens: number;
max_tokens?: number;
max_input_tokens?: number;
api_base?: string;
litellm_params: LiteLLMParams;
cleanedLitellmParams: Record<string, any>;

View file

@ -365,7 +365,7 @@ export default function ModelInfoView({
// Parse the model_info from the form values
let updatedModelInfo;
try {
updatedModelInfo = values.model_info ? JSON.parse(values.model_info) : modelData.model_info;
updatedModelInfo = values.model_info ? JSON.parse(values.model_info) : modelData?.model_info;
// Update access_groups from the form
if (values.model_access_group) {
updatedModelInfo = {

View file

@ -3,7 +3,7 @@ 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 };
const tokenPriced = { input_cost: "1.50", output_cost: "2.00" };
describe("ModelPricingSummary", () => {
it("shows per-million-token rates for a token priced model", () => {
@ -16,8 +16,8 @@ describe("ModelPricingSummary", () => {
render(
<ModelPricingSummary
model={{
input_cost: "0.00" as unknown as number,
output_cost: "0.00" as unknown as number,
input_cost: "0.00",
output_cost: "0.00",
output_cost_per_second: 0.1,
output_cost_per_second_tiers: [
{ resolution: "1080p", cost: 0.12 },
@ -36,8 +36,8 @@ describe("ModelPricingSummary", () => {
render(
<ModelPricingSummary
model={{
input_cost: "0.60" as unknown as number,
output_cost: "0.00" as unknown as number,
input_cost: "0.60",
output_cost: "0.00",
output_cost_per_second: 0.015,
}}
/>,
@ -49,7 +49,7 @@ describe("ModelPricingSummary", () => {
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 }} />,
<ModelPricingSummary model={{ input_cost: null, output_cost: null }} />,
);
expect(screen.getByText("-")).toBeInTheDocument();
expect(screen.queryByText(/\$/)).not.toBeInTheDocument();