Files
omniai-web/src/api/publicPricingClient.ts
T

142 lines
3.9 KiB
TypeScript
Raw Normal View History

import { isOptionalApiRouteMissing } from "./apiErrorUtils";
import { isRecord, serverRequest } from "./serverConnection";
export interface PublicModelPrice {
id?: number | string;
modelKey: string;
displayName?: string;
category?: string;
pricingType?: string;
inputPriceMills: number | null;
outputPriceMills: number | null;
flatPriceMills: number | null;
currency: string;
enabled: boolean;
createdAt?: string;
updatedAt?: string;
}
function readString(
record: Record<string, unknown>,
keys: string[],
): string | undefined {
for (const key of keys) {
const value = record[key];
if (typeof value === "string" && value.trim()) return value.trim();
}
return undefined;
}
function readNumber(
record: Record<string, unknown>,
keys: string[],
): number | null {
for (const key of keys) {
const value = record[key];
const parsed =
typeof value === "number"
? value
: typeof value === "string"
? Number(value)
: NaN;
if (Number.isFinite(parsed)) return parsed;
}
return null;
}
function readBoolean(
record: Record<string, unknown>,
keys: string[],
fallback: boolean,
): boolean {
for (const key of keys) {
const value = record[key];
if (typeof value === "boolean") return value;
if (typeof value === "number") return value !== 0;
if (typeof value === "string") {
const normalized = value.trim().toLowerCase();
if (["1", "true", "yes", "enabled"].includes(normalized)) return true;
if (["0", "false", "no", "disabled"].includes(normalized)) return false;
}
}
return fallback;
}
export function normalizePublicModelPrice(
raw: unknown,
): PublicModelPrice | null {
if (!isRecord(raw)) return null;
const modelKey = readString(raw, ["modelKey", "model_key", "key", "model"]);
if (!modelKey) return null;
const displayName = readString(raw, ["displayName", "display_name", "name"]);
const category = readString(raw, ["category", "type"]);
const pricingType = readString(raw, ["pricingType", "pricing_type"]);
const currency = readString(raw, ["currency"]) || "CNY";
const createdAt = readString(raw, ["createdAt", "created_at"]);
const updatedAt = readString(raw, ["updatedAt", "updated_at"]);
const idValue = raw.id;
return {
id:
typeof idValue === "number" || typeof idValue === "string"
? idValue
: undefined,
modelKey,
displayName,
category,
pricingType,
inputPriceMills: readNumber(raw, ["inputPriceMills", "input_price_mills"]),
outputPriceMills: readNumber(raw, [
"outputPriceMills",
"output_price_mills",
]),
flatPriceMills: readNumber(raw, ["flatPriceMills", "flat_price_mills"]),
currency,
enabled: readBoolean(raw, ["enabled", "is_enabled"], true),
createdAt,
updatedAt,
};
}
export function normalizePublicModelPrices(
payload: unknown,
): PublicModelPrice[] {
const rawPrices = Array.isArray(payload)
? payload
: isRecord(payload) && Array.isArray(payload.prices)
? payload.prices
: isRecord(payload) && Array.isArray(payload.models)
? payload.models
: [];
return rawPrices
.map((item) => normalizePublicModelPrice(item))
.filter((item): item is PublicModelPrice => Boolean(item));
}
let cachedPrices: PublicModelPrice[] | null = null;
let pricesRouteMissing = false;
export const publicPricingClient = {
async getPrices(): Promise<PublicModelPrice[]> {
if (cachedPrices) return cachedPrices;
if (pricesRouteMissing) return [];
try {
const payload = await serverRequest<unknown>("prices", {
fallbackMessage: "Model prices request failed",
});
cachedPrices = normalizePublicModelPrices(payload);
return cachedPrices;
} catch (error) {
if (isOptionalApiRouteMissing(error)) {
pricesRouteMissing = true;
return [];
}
throw error;
}
},
};