Skip to content
Draft
8 changes: 7 additions & 1 deletion docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,13 @@ For a custom usage interval, the server must confirm the exact requested start a
If an older running proxy does not support those bounds, the dashboard and CLI reject its report;
upgrade and restart that proxy before retrying. Resetting a manual model price affects only that
model, preserving other rates saved independently.
They are not billing receipts or evidence of an actual charge; subscription usage or provider credits
The **Usage** Models and Providers tables show the estimated priced portion for each row. Requests
without a matching price or usable usage are counted as excluded beside that amount when the proxy
reports pricing coverage fields. A row with only excluded requests shows an em dash with that count
instead of a zero-dollar estimate, and a displayed zero-dollar value is a confirmed priced estimate.
An older proxy that reports no coverage fields keeps a bare em dash with no count, so missing
coverage stays distinct from an all-excluded row and from a confirmed zero estimate.
These are not billing receipts or evidence of an actual charge; subscription usage or provider credits
may apply instead.

Provider model rows may include **unresolved requested model usage**: the saved route sent the
Expand Down
Binary file added docs/pr-assets/usage-api-list-price.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,8 @@ export const de: Record<TKey, string> = {
"usage.cost.total": "API-Listenpreis-Äquivalent (dieser Zeitraum)",
"usage.cost.disclaimer": "Kein Abrechnungsbeleg. Stattdessen können Abonnementnutzung oder Anbieter-Guthaben gelten.",
"usage.cost.unpricedNote": "{count} Anfragen ohne Preis oder Nutzung ausgeschlossen",
"usage.cost.excluded": "({count} ohne Preis oder Nutzungsdaten)",
"usage.cost.excludedOne": "({count} ohne Preis oder Nutzungsdaten)",
"logs.detail.section.basic": "Grundinformationen",
"logs.detail.route.section": "Route-Entscheidung",
"logs.detail.route.kind": "Route-Typ",
Expand Down Expand Up @@ -944,6 +946,7 @@ export const de: Record<TKey, string> = {
"usage.col.measured": "Gemessen",
"usage.col.reported": "Gemeldet",
"usage.col.tokens": "Tokens",
"usage.col.apiListPrice": "API-Listenpreis",
"usage.col.share": "Anteil",
"usage.heatmap.less": "Weniger",
"usage.heatmap.more": "Mehr",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,8 @@ export const en = {
"usage.cost.total": "API list-price equivalent (this range)",
"usage.cost.disclaimer": "Not a billing receipt. Subscription usage or provider credits may apply instead.",
"usage.cost.unpricedNote": "{count} requests excluded (no price or usage)",
"usage.cost.excluded": "({count} requests excluded)",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"usage.cost.excludedOne": "({count} request excluded)",
"logs.detail.section.basic": "Basic information",
"logs.detail.route.section": "Route decision",
"logs.detail.route.kind": "Route kind",
Expand Down Expand Up @@ -997,6 +999,7 @@ export const en = {
"usage.col.measured": "Measured",
"usage.col.reported": "Reported",
"usage.col.tokens": "Tokens",
"usage.col.apiListPrice": "API list-price",
"usage.col.share": "Share",
"usage.heatmap.less": "Less",
"usage.heatmap.more": "More",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,8 @@ export const fr: Record<TKey, string> = {
"usage.cost.total": "Équivalent au tarif catalogue de l’API (cette période)",
"usage.cost.disclaimer": "Ceci n’est pas un reçu de facturation. L’utilisation d’un abonnement ou les crédits du fournisseur peuvent s’appliquer à la place.",
"usage.cost.unpricedNote": "{count} requêtes exclues (aucun tarif ni donnée d’utilisation)",
"usage.cost.excluded": "({count} requêtes exclues faute de tarif ou de données d’utilisation)",
"usage.cost.excludedOne": "({count} requête exclue faute de tarif ou de données d’utilisation)",
"logs.detail.section.basic": "Informations générales",
"logs.detail.route.section": "Décision de routage",
"logs.detail.route.kind": "Type de route",
Expand Down Expand Up @@ -974,6 +976,7 @@ export const fr: Record<TKey, string> = {
"usage.col.measured": "Mesurées",
"usage.col.reported": "Communiquées",
"usage.col.tokens": "Jetons",
"usage.col.apiListPrice": "Tarif catalogue API",
"usage.col.share": "Part",
"usage.heatmap.less": "Moins",
"usage.heatmap.more": "Plus",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,8 @@ export const ja: Record<TKey, string> = {
"usage.cost.total": "API 定価相当額(この期間)",
"usage.cost.disclaimer": "請求明細ではありません。サブスクリプション利用量やプロバイダークレジットが代わりに適用される場合があります。",
"usage.cost.unpricedNote": "{count} 件のリクエストを除外(価格または使用量なし)",
"usage.cost.excluded": "({count} 件のリクエストを除外)",
"usage.cost.excludedOne": "({count} 件のリクエストを除外)",
"logs.detail.section.basic": "基本情報",
"logs.detail.route.section": "ルート決定",
"logs.detail.route.kind": "ルート種別",
Expand Down Expand Up @@ -910,6 +912,7 @@ export const ja: Record<TKey, string> = {
"usage.col.measured": "計測",
"usage.col.reported": "報告",
"usage.col.tokens": "トークン",
"usage.col.apiListPrice": "API 定価",
"usage.col.share": "割合",
"usage.heatmap.less": "少ない",
"usage.heatmap.more": "多い",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,8 @@ export const ko: Record<TKey, string> = {
"usage.cost.total": "API 정가 환산치 (이 기간)",
"usage.cost.disclaimer": "결제 영수증이 아닙니다. 구독 사용량 또는 프로바이더 크레딧이 대신 적용될 수 있습니다.",
"usage.cost.unpricedNote": "비용 산정 불가 {count}건 제외",
"usage.cost.excluded": "({count}건 요청 제외)",
"usage.cost.excludedOne": "({count}건 요청 제외)",
"logs.detail.section.basic": "기본 정보",
"logs.detail.route.section": "라우팅 결정",
"logs.detail.route.kind": "라우팅 종류",
Expand Down Expand Up @@ -978,6 +980,7 @@ export const ko: Record<TKey, string> = {
"usage.col.measured": "측정됨",
"usage.col.reported": "측정됨",
"usage.col.tokens": "토큰",
"usage.col.apiListPrice": "API 정가",
"usage.col.share": "비율",
"usage.heatmap.less": "적음",
"usage.heatmap.more": "많음",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,8 @@ export const ru: Record<TKey, string> = {
"usage.cost.total": "Эквивалент стоимости по прайс-листу API (за этот период)",
"usage.cost.disclaimer": "Не является счётом. Расходы могут покрываться подпиской или кредитами провайдера.",
"usage.cost.unpricedNote": "Исключено {count} запросов (нет цены или данных использования)",
"usage.cost.excluded": "({count} исключено)",
"usage.cost.excludedOne": "({count} исключено)",
"logs.detail.section.basic": "Основная информация",
"logs.detail.route.section": "Решение о маршруте",
"logs.detail.route.kind": "Тип маршрута",
Expand Down Expand Up @@ -965,6 +967,7 @@ export const ru: Record<TKey, string> = {
"usage.col.measured": "Измерено",
"usage.col.reported": "Сообщено",
"usage.col.tokens": "Токены",
"usage.col.apiListPrice": "Прайс-лист API",
"usage.col.share": "Доля",
"usage.heatmap.less": "Меньше",
"usage.heatmap.more": "Больше",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,8 @@ export const tr: Record<TKey, string> = {
"usage.cost.total": "API liste fiyatı eşdeğeri",
"usage.cost.disclaimer": "Fatura makbuzu değildir.",
"usage.cost.unpricedNote": "{count} istek hariç tutuldu",
"usage.cost.excluded": "({count} istek hariç tutuldu)",
"usage.cost.excludedOne": "({count} istek hariç tutuldu)",
"logs.detail.section.basic": "Temel bilgiler",
"logs.detail.route.section": "Yönlendirme kararı",
"logs.detail.route.kind": "Yönlendirme türü",
Expand Down Expand Up @@ -984,6 +986,7 @@ export const tr: Record<TKey, string> = {
"usage.col.measured": "Ölçülen",
"usage.col.reported": "Bildirilen",
"usage.col.tokens": "Jetonlar",
"usage.col.apiListPrice": "API liste fiyatı",
"usage.col.share": "Pay",
"usage.heatmap.less": "Daha az",
"usage.heatmap.more": "Daha fazla",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/zh-TW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,8 @@ export const zhTW: Record<TKey, string> = {
"usage.cost.total": "API 標價折算(當前範圍)",
"usage.cost.disclaimer": "這不是帳單或扣費憑證。實際可能計入訂閱用量或消耗服務商額度。",
"usage.cost.unpricedNote": "已排除 {count} 個無法計費的請求",
"usage.cost.excluded": "(已排除 {count} 個請求)",
"usage.cost.excludedOne": "(已排除 {count} 個請求)",
"logs.detail.section.basic": "基本資訊",
"logs.detail.section.performance": "效能",
"logs.detail.section.cost": "API 標價折算",
Expand Down Expand Up @@ -784,6 +786,7 @@ export const zhTW: Record<TKey, string> = {
"usage.col.measured": "已計量",
"usage.col.reported": "已上報",
"usage.col.tokens": "Token 數",
"usage.col.apiListPrice": "API 標價",
"usage.col.share": "佔比",
"usage.heatmap.less": "少",
"usage.heatmap.more": "多",
Expand Down
3 changes: 3 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,8 @@ export const zh: Record<TKey, string> = {
"usage.cost.total": "API 标价折算(当前范围)",
"usage.cost.disclaimer": "这不是账单或扣费凭证。实际可能计入订阅用量或消耗服务商额度。",
"usage.cost.unpricedNote": "已排除 {count} 个无法计费的请求",
"usage.cost.excluded": "(已排除 {count} 个请求)",
"usage.cost.excludedOne": "(已排除 {count} 个请求)",
"logs.detail.section.basic": "基本信息",
"logs.detail.route.section": "路由决策",
"logs.detail.route.kind": "路由类型",
Expand Down Expand Up @@ -959,6 +961,7 @@ export const zh: Record<TKey, string> = {
"usage.col.measured": "已计量",
"usage.col.reported": "已上报",
"usage.col.tokens": "Token 数",
"usage.col.apiListPrice": "API 标价",
"usage.col.share": "占比",
"usage.heatmap.less": "少",
"usage.heatmap.more": "多",
Expand Down
59 changes: 59 additions & 0 deletions gui/src/pages/Usage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ interface UsageModel {
totalTokens: number;
inputTokens: number;
outputTokens: number;
/** API list-price estimate for the priced portion of this row. */
estimatedCostUsd?: number;
/** Requests included in the API list-price estimate. */
pricedRequests?: number;
/** Requests excluded from the estimate because price or usable usage is unavailable. */
unpricedRequests?: number;
shareRatio: number;
}

Expand All @@ -77,6 +83,12 @@ interface UsageProvider {
reportedRequests: number;
estimatedRequests: number;
totalTokens: number;
/** API list-price estimate for the priced portion of this row. */
estimatedCostUsd?: number;
/** Requests included in the API list-price estimate. */
pricedRequests?: number;
/** Requests excluded from the estimate because price or usable usage is unavailable. */
unpricedRequests?: number;
shareRatio: number;
}

Expand Down Expand Up @@ -109,6 +121,45 @@ function formatPct(ratio: number): string {
return `${Math.round(ratio * 100)}%`;
}

type UsageCostRow = Pick<UsageModel, "estimatedCostUsd" | "pricedRequests" | "unpricedRequests">;

/**
* Renders a row's API list-price estimate with explicit pricing coverage.
* Newer proxies return the coverage fields even when every request is
* unpriced; older proxies have none of them, so their cells stay unavailable
* rather than making an unknown amount look free.
*/
function UsageListPrice({ row, locale, t }: { row: UsageCostRow; locale: Locale; t: TFn }) {
const hasPriceData = row.estimatedCostUsd !== undefined
|| row.pricedRequests !== undefined
|| row.unpricedRequests !== undefined;
if (!hasPriceData) return <span className="muted">—</span>;

const excludedRequests = row.unpricedRequests ?? 0;
const excludedCaption = t(
excludedRequests === 1 ? "usage.cost.excludedOne" : "usage.cost.excluded",
{ count: excludedRequests },
);
if (row.estimatedCostUsd === undefined) {
return (
<>
<span className="muted">—</span>
{excludedRequests > 0 && (
<span className="muted text-caption"> {excludedCaption}</span>
)}
</>
);
}
return (
<>
<span className="mono">{formatUsdEstimate(row.estimatedCostUsd ?? 0, locale)}</span>
Comment thread
harryzhou2000 marked this conversation as resolved.
{excludedRequests > 0 && (
<span className="muted text-caption"> {excludedCaption}</span>
)}
</>
);
}

// Stable per-model bar color: hash the provider/model id to a hue so the same model keeps its color
// across days and renders. Saturation/lightness are fixed for a cohesive palette on the dark chart.
function modelColor(model: string, provider: string): string {
Expand Down Expand Up @@ -643,6 +694,7 @@ function UsageModelsTable({
const searchLabel = t("usage.search.models");
const sectionLabel = t("usage.section.models");
const titleId = "usage-models-title";
const listPriceDisclaimerId = "usage-models-list-price-disclaimer";
const searchInput = (
<input
className="input"
Expand All @@ -662,6 +714,7 @@ function UsageModelsTable({
<th className="num">{t("usage.col.requests")}</th>
<th className="num">{t("usage.col.measured")}</th>
<th className="num">{t("usage.col.tokens")}</th>
<th className="num" aria-describedby={listPriceDisclaimerId}>{t("usage.col.apiListPrice")}</th>
<th>{t("usage.col.share")}</th>
</tr>
</thead>
Expand All @@ -673,11 +726,13 @@ function UsageModelsTable({
<td className="num">{model.requests}</td>
<td className="num">{model.measuredRequests}</td>
<td className="num mono">{formatTokens(model.totalTokens, locale)}</td>
<td className="num"><UsageListPrice row={model} locale={locale} t={t} /></td>
<td><div className="usage-bar"><div className="usage-bar-fill" style={{ width: `${Math.round(model.shareRatio * 100)}%` }} /></div></td>
</tr>
))}
</tbody>
</table>
<p id={listPriceDisclaimerId} className="muted text-caption">{t("usage.cost.disclaimer")}</p>
</div>
);

Expand Down Expand Up @@ -714,6 +769,7 @@ function UsageProvidersTable({
}) {
const sectionLabel = t("usage.section.providers");
const titleId = "usage-providers-title";
const listPriceDisclaimerId = "usage-providers-list-price-disclaimer";
const table = (
<div className="tbl-wrap">
<table className="tbl">
Expand All @@ -723,6 +779,7 @@ function UsageProvidersTable({
<th className="num">{t("usage.col.requests")}</th>
<th className="num">{t("usage.col.measured")}</th>
<th className="num">{t("usage.col.tokens")}</th>
<th className="num" aria-describedby={listPriceDisclaimerId}>{t("usage.col.apiListPrice")}</th>
<th>{t("usage.col.share")}</th>
</tr>
</thead>
Expand All @@ -733,11 +790,13 @@ function UsageProvidersTable({
<td className="num">{provider.requests}</td>
<td className="num">{provider.measuredRequests}</td>
<td className="num mono">{formatTokens(provider.totalTokens, locale)}</td>
<td className="num"><UsageListPrice row={provider} locale={locale} t={t} /></td>
<td><div className="usage-bar"><div className="usage-bar-fill" style={{ width: `${Math.round(provider.shareRatio * 100)}%` }} /></div></td>
</tr>
))}
</tbody>
</table>
<p id={listPriceDisclaimerId} className="muted text-caption">{t("usage.cost.disclaimer")}</p>
</div>
);

Expand Down
Loading
Loading