diff --git a/ui/src/components/AIModelConfigurationV2Editor.tsx b/ui/src/components/AIModelConfigurationV2Editor.tsx
index 281f8dac..ca6e160c 100644
--- a/ui/src/components/AIModelConfigurationV2Editor.tsx
+++ b/ui/src/components/AIModelConfigurationV2Editor.tsx
@@ -22,6 +22,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { VoiceSelectorModal } from "@/components/VoiceSelectorModal";
import { LANGUAGE_DISPLAY_NAMES } from "@/constants/languages";
+import { formatRoundingPolicy } from "@/lib/billingDisplay";
type ModelMode = "realtime" | "dograh" | "byok";
@@ -296,6 +297,25 @@ function formatPricePerMinute(price: ModelConfigurationMetricPrice): string {
}).format(price.price_per_minute);
}
+function MetricPrice({
+ label,
+ price,
+}: {
+ label: string;
+ price: ModelConfigurationMetricPrice;
+}) {
+ return (
+
+
+ {label}: {formatPricePerMinute(price)}/{price.unit}
+
+
+ {formatRoundingPolicy(price.rounding_policy)}
+
+
+ );
+}
+
function PricingSummary({
pricing,
includeDograhModel,
@@ -314,21 +334,16 @@ function PricingSummary({
Usage pricing
{platformPrice && (
-
- Platform usage: {formatPricePerMinute(platformPrice)}/{platformPrice.unit}
-
+
)}
{dograhModelPrice && (
-
- Dograh model usage: {formatPricePerMinute(dograhModelPrice)}/{dograhModelPrice.unit}
-
+
)}
{thirdPartyModels && (
Your selected model provider may charge separately for its usage.
)}
- Usage is rounded up to whole minutes per call.
);
diff --git a/ui/src/lib/billingDisplay.test.ts b/ui/src/lib/billingDisplay.test.ts
new file mode 100644
index 00000000..77ec1837
--- /dev/null
+++ b/ui/src/lib/billingDisplay.test.ts
@@ -0,0 +1,18 @@
+import { describe, expect, it } from "vitest";
+
+import { formatRoundingPolicy } from "@/lib/billingDisplay";
+
+describe("formatRoundingPolicy", () => {
+ it.each([
+ ["ceil_minute", "Rounded up to whole minutes per call."],
+ ["ceil_1_minute", "Rounded up to whole minutes per call."],
+ ["ceil_15_seconds", "Rounded up in 15-second increments per call."],
+ ["ceil_30_second", "Rounded up in 30-second increments per call."],
+ ["ceil_2_minutes", "Rounded up in 2-minute increments per call."],
+ ["none", "Billed using exact measured usage."],
+ ["custom_contract", "Billing policy: custom contract."],
+ ["", "Billing policy details are unavailable."],
+ ])("formats %s", (policy, expected) => {
+ expect(formatRoundingPolicy(policy)).toBe(expected);
+ });
+});
diff --git a/ui/src/lib/billingDisplay.ts b/ui/src/lib/billingDisplay.ts
new file mode 100644
index 00000000..8a493a6e
--- /dev/null
+++ b/ui/src/lib/billingDisplay.ts
@@ -0,0 +1,24 @@
+export function formatRoundingPolicy(roundingPolicy: string): string {
+ const normalizedPolicy = roundingPolicy.trim().toLowerCase();
+ if (!normalizedPolicy) {
+ return "Billing policy details are unavailable.";
+ }
+ if (normalizedPolicy === "ceil_minute" || normalizedPolicy === "ceil_1_minute") {
+ return "Rounded up to whole minutes per call.";
+ }
+ if (normalizedPolicy === "none") {
+ return "Billed using exact measured usage.";
+ }
+
+ const secondsMatch = normalizedPolicy.match(/^ceil_(\d+)_seconds?$/);
+ if (secondsMatch) {
+ return `Rounded up in ${Number(secondsMatch[1])}-second increments per call.`;
+ }
+
+ const minutesMatch = normalizedPolicy.match(/^ceil_(\d+)_minutes?$/);
+ if (minutesMatch) {
+ return `Rounded up in ${Number(minutesMatch[1])}-minute increments per call.`;
+ }
+
+ return `Billing policy: ${roundingPolicy.replaceAll("_", " ")}.`;
+}