From 097696b859a5406ada2412262c999303374702fb Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:52:19 +0800 Subject: [PATCH 01/11] feat(usage): 1 file(s) - external channel billing --- model/log.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/model/log.go b/model/log.go index 1d2b38fc7c..034853f542 100644 --- a/model/log.go +++ b/model/log.go @@ -8,6 +8,7 @@ import ( "time" "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/setting/ratio_setting" "github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/types" @@ -692,6 +693,52 @@ func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelNa return token } +// ExternalBillingRow per-user external (third-party) channel usage aggregation. +type ExternalBillingRow struct { + Username string `json:"username"` + PromptTokens int64 `json:"prompt_tokens"` + CompletionTokens int64 `json:"completion_tokens"` + TotalTokens int64 `json:"total_tokens"` + Quota int64 `json:"quota"` + ModelCount int64 `json:"model_count"` +} + +// SumExternalByUser aggregates, per user, consumption on external channels +// (channels.tag = 'external') restricted to models that have an explicit +// configured price. startTimestamp/endTimestamp are unix seconds (0 = open). +func SumExternalByUser(startTimestamp int64, endTimestamp int64, username string) ([]ExternalBillingRow, error) { + priceKeys := ratio_setting.GetModelPriceMap() + if len(priceKeys) == 0 { + return nil, nil + } + priceList := make([]string, 0, len(priceKeys)) + for k := range priceKeys { + priceList = append(priceList, k) + } + tx := LOG_DB.Table("logs"). + Select("username, COALESCE(sum(prompt_tokens),0) prompt_tokens, COALESCE(sum(completion_tokens),0) completion_tokens, COALESCE(sum(prompt_tokens),0)+COALESCE(sum(completion_tokens),0) total_tokens, COALESCE(sum(quota),0) quota, count(DISTINCT model_name) model_count"). + Joins("JOIN channels ON channels.id = logs.channel_id"). + Where("channels.tag = ?", "external"). + Where("logs.model_name IN ?", priceList). + Where("logs.type = ?", LogTypeConsume) + if startTimestamp != 0 { + tx = tx.Where("logs.created_at >= ?", startTimestamp) + } + if endTimestamp != 0 { + tx = tx.Where("logs.created_at <= ?", endTimestamp) + } + if username != "" { + tx = tx.Where("logs.username = ?", username) + } + tx = tx.Group("logs.username") + var rows []ExternalBillingRow + if err := tx.Scan(&rows).Error; err != nil { + common.SysError("failed to query external billing stat: " + err.Error()) + return nil, errors.New("查询外部渠道统计数据失败") + } + return rows, nil +} + func CountOldLog(ctx context.Context, targetTimestamp int64) (int64, error) { var total int64 if err := LOG_DB.WithContext(ctx).Model(&Log{}).Where("created_at < ?", targetTimestamp).Count(&total).Error; err != nil { From d17ffeb94c98ab71f2dcce7372982b3d5303a00d Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:52:42 +0800 Subject: [PATCH 02/11] feat(usage): 1 file(s) - external channel billing --- router/api-router.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/router/api-router.go b/router/api-router.go index 31c595e00d..ac3b6b46a8 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -273,6 +273,8 @@ func SetApiRouter(router *gin.Engine) { logRoute.GET("/", middleware.AdminAuth(), controller.GetAllLogs) logRoute.GET("/stat", middleware.AdminAuth(), controller.GetLogsStat) logRoute.GET("/self/stat", middleware.UserAuth(), controller.GetLogsSelfStat) + logRoute.GET("/stat/external", middleware.AdminAuth(), controller.GetExternalBillingStat) + logRoute.GET("/self/stat/external", middleware.UserAuth(), controller.GetExternalBillingSelfStat) logRoute.GET("/channel_affinity_usage_cache", middleware.AdminAuth(), controller.GetChannelAffinityUsageCacheStats) logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs) logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogs) From afab0c6a5c82d65cebfe135c4ec4a377eda48805 Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:52:51 +0800 Subject: [PATCH 03/11] feat(usage): 1 file(s) - external channel billing --- .../channels/components/channels-columns.tsx | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/web/src/features/channels/components/channels-columns.tsx b/web/src/features/channels/components/channels-columns.tsx index ad6fadcd7c..5505cd2af5 100644 --- a/web/src/features/channels/components/channels-columns.tsx +++ b/web/src/features/channels/components/channels-columns.tsx @@ -55,7 +55,7 @@ import { import { formatTimestampToDate } from '@/lib/format' import { truncateText } from '@/lib/utils' -import { getCodexUsage, updateChannelBalance } from '../api' +import { getCodexUsage, updateChannelBalance, updateChannel } from '../api' import { CHANNEL_STATUS_CONFIG, MODEL_FETCHABLE_TYPES } from '../constants' import { formatRelativeTime, @@ -584,6 +584,33 @@ export function BalanceCell({ channel }: { channel: Channel }) { /** * Generate channels columns configuration */ +/** + * Clickable external/internal classifier for a channel (persisted into channels.tag). + */ +function ExternalTagToggle({ channel, tag }: { channel: Channel; tag: string }) { + const { t } = useTranslation() + const queryClient = useQueryClient() + const next = tag === 'external' ? 'internal' : 'external' + return ( + + ) +} + export function useChannelsColumns( options: { enableSelection?: boolean @@ -1096,7 +1123,10 @@ export function useChannelsColumns( if (!tag) { return - } - + const orig = row.original as Channel + if (orig?.id && (tag === 'external' || tag === 'internal')) { + return + } return ( Date: Sat, 29 Aug 2026 10:53:07 +0800 Subject: [PATCH 04/11] feat(usage): 1 file(s) - external channel billing --- web/src/features/external-billing/api.ts | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 web/src/features/external-billing/api.ts diff --git a/web/src/features/external-billing/api.ts b/web/src/features/external-billing/api.ts new file mode 100644 index 0000000000..7ea6aa1aef --- /dev/null +++ b/web/src/features/external-billing/api.ts @@ -0,0 +1,50 @@ +/* External billing API: per-user external (third-party) channel usage. */ +import { api } from '@/lib/api' + +export interface ExternalBillingRow { + username: string + prompt_tokens: number + completion_tokens: number + total_tokens: number + quota: number + model_count: number +} + +export interface ExternalBillingResponse { + success: boolean + message: string + data: ExternalBillingRow[] +} + +function buildParams(startTimestamp: number, endTimestamp: number, username?: string) { + const p: Record = {} + if (startTimestamp) p.start_timestamp = startTimestamp + if (endTimestamp) p.end_timestamp = endTimestamp + if (username) p.username = username + return p +} + +/** Admin: all users' external usage. */ +export async function fetchExternalBilling( + startTimestamp: number, + endTimestamp: number, + username?: string +): Promise { + const res = await api.get('/api/log/stat/external', { + params: buildParams(startTimestamp, endTimestamp, username), + }) + return res.data +} + +/** Member: own external usage. */ +export async function fetchExternalBillingSelf( + startTimestamp: number, + endTimestamp: number +): Promise { + const res = await api.get('/api/log/self/stat/external', { + params: buildParams(startTimestamp, endTimestamp), + }) + return res.data +} + +export const QUOTA_PER_USD = 500000 From fc9a88b9d47a27cce2ed6d88b2cf38d86afa53ff Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:53:24 +0800 Subject: [PATCH 05/11] feat(usage): 1 file(s) - external channel billing --- web/src/hooks/use-sidebar-data.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web/src/hooks/use-sidebar-data.ts b/web/src/hooks/use-sidebar-data.ts index 40a0615aa3..bb11d9d838 100644 --- a/web/src/hooks/use-sidebar-data.ts +++ b/web/src/hooks/use-sidebar-data.ts @@ -27,6 +27,7 @@ import { ListTodo, MessageSquare, Radio, + Receipt, ServerCog, Settings, Ticket, @@ -108,6 +109,11 @@ export function useSidebarData(): SidebarData { url: '/wallet', icon: Wallet, }, + { + title: t('My External Usage'), + url: '/external-billing', + icon: Receipt, + }, { title: t('Profile'), url: '/profile', @@ -124,6 +130,11 @@ export function useSidebarData(): SidebarData { url: '/channels', icon: Radio, }, + { + title: t('External Billing'), + url: '/external-billing', + icon: Receipt, + }, { title: t('Models'), url: '/models/metadata', From a7e80eec83a74cf66581491b2be17b7431057bab Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 10:58:16 +0800 Subject: [PATCH 06/11] feat(usage): external channel billing page --- .../_authenticated/external-billing/index.tsx | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 web/src/routes/_authenticated/external-billing/index.tsx diff --git a/web/src/routes/_authenticated/external-billing/index.tsx b/web/src/routes/_authenticated/external-billing/index.tsx new file mode 100644 index 0000000000..154bf9e000 --- /dev/null +++ b/web/src/routes/_authenticated/external-billing/index.tsx @@ -0,0 +1,194 @@ +/* External billing page: per-account external (third-party) channel usage. */ +import { useMemo, useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { createFileRoute } from '@tanstack/react-router' +import { useTranslation } from 'react-i18next' + +import { + Card, + CardContent, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { useIsAdmin } from '@/hooks/use-admin' + +import { + fetchExternalBilling, + fetchExternalBillingSelf, + QUOTA_PER_USD, + type ExternalBillingRow, +} from '@/features/external-billing/api' + +export const Route = createFileRoute('/_authenticated/external-billing/')({ + component: ExternalBillingPage, +}) + +type RangeKey = 'all' | '30d' | '7d' | 'custom' + +function fmtTokens(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M` + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K` + return String(n) +} + +function toTs(ms: number): number { + return Math.floor(ms / 1000) +} + +function nowTs(): number { + return Math.floor(Date.now() / 1000) +} + +export function ExternalBillingPage() { + const { t } = useTranslation() + const isAdmin = useIsAdmin() + const [range, setRange] = useState('all') + const [from, setFrom] = useState('') + const [to, setTo] = useState('') + const [username, setUsername] = useState('') + + const { startTs, endTs } = useMemo(() => { + const now = nowTs() + if (range === '7d') return { startTs: now - 7 * 86400, endTs: 0 } + if (range === '30d') return { startTs: now - 30 * 86400, endTs: 0 } + if (range === 'custom') { + const s = from ? toTs(new Date(from).getTime()) : 0 + const e = to ? toTs(new Date(to).getTime()) : 0 + return { startTs: s, endTs: e } + } + return { startTs: 0, endTs: 0 } + }, [range, from, to]) + + const query = useQuery({ + queryKey: ['external-billing', isAdmin, range, from, to, username], + queryFn: async () => + isAdmin + ? fetchExternalBilling(startTs, endTs, username || undefined) + : fetchExternalBillingSelf(startTs, endTs), + staleTime: 30_000, + }) + + const rows: ExternalBillingRow[] = useMemo( + () => (query.data?.data ?? []).slice().sort((a, b) => b.quota - a.quota), + [query.data] + ) + + const totalQuota = useMemo(() => rows.reduce((s, r) => s + r.quota, 0), [rows]) + const totalTokens = useMemo(() => rows.reduce((s, r) => s + r.total_tokens, 0), [rows]) + + return ( +
+
+

{t('External Channel Billing')}

+

+ {t('External (third-party paid) channel usage per account. Models without a configured price are excluded.')} +

+
+ + + + {t('Usage Summary')} +
+ + {range === 'custom' && ( + <> + setFrom(e.target.value)} + className="h-9 rounded-md border border-input px-2 text-sm" + /> + setTo(e.target.value)} + className="h-9 rounded-md border border-input px-2 text-sm" + /> + + )} + {isAdmin && ( + setUsername(e.target.value)} + className="h-9 w-44 rounded-md border border-input px-2 text-sm" + /> + )} +
+
+ +
+ + {t('Accounts')} {rows.length} + + + {t('Total external tokens')} {fmtTokens(totalTokens)} + + + {t('Total external spend')} {(totalQuota / QUOTA_PER_USD).toFixed(4)} + + {query.isFetching && {t('Loading…')}} +
+ + + + + {t('Account')} + {t('External tokens')} + {t('Quota')} + {t('Spend (USD)')} + {t('External models')} + + + + {rows.length === 0 && ( + + + {t('No external usage in this range')} + + + )} + {rows.map((r) => ( + + {r.username} + {fmtTokens(r.total_tokens)} + {r.quota.toLocaleString()} + + {(r.quota / QUOTA_PER_USD).toFixed(4)} + + {r.model_count} + + ))} + +
+
+
+
+ ) +} From 186e2cd39c8bbcbdd7d02c090b6dd242c0939ce7 Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:50:36 +0800 Subject: [PATCH 07/11] feat(usage): add controller/log.go (external channel billing) --- controller/log.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/controller/log.go b/controller/log.go index 470c759fc1..b73483cd06 100644 --- a/controller/log.go +++ b/controller/log.go @@ -149,3 +149,37 @@ func GetLogsSelfStat(c *gin.Context) { }) return } + +// GetExternalBillingStat returns per-user external-channel usage for all users (admin). +func GetExternalBillingStat(c *gin.Context) { + startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) + endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) + username := c.Query("username") + rows, err := model.SumExternalByUser(startTimestamp, endTimestamp, username) + if err != nil { + common.ApiError(c, err) + return + } + if rows == nil { + rows = []model.ExternalBillingRow{} + } + c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": rows}) + return +} + +// GetExternalBillingSelfStat returns the caller's own external-channel usage. +func GetExternalBillingSelfStat(c *gin.Context) { + startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) + endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) + username := c.GetString("username") + rows, err := model.SumExternalByUser(startTimestamp, endTimestamp, username) + if err != nil { + common.ApiError(c, err) + return + } + if rows == nil { + rows = []model.ExternalBillingRow{} + } + c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": rows}) + return +} From 18af5c2fc7e970bb2fd30f0cab8708dfedb1f821 Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:51:00 +0800 Subject: [PATCH 08/11] feat(usage): add web/src/i18n/locales/en.json (external channel billing) --- web/src/i18n/locales/en.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 86095b29c1..583e28db91 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -2069,7 +2069,7 @@ "footer.columns.related.links.oneApi": "One API", "footer.columns.related.title": "Related Projects", "footer.defaultCopyright": "All rights reserved.", - "footer.new\u0061pi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.", + "footer.newapi.projectAttributionSuffix": "All rights reserved. Designed and developed by the project contributors.", "For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "For channels added after May 10, 2025, no need to remove \".\" from model names during deployment", "For private deployments, format: https://fastgpt.run/api/openapi": "For private deployments, format: https://fastgpt.run/api/openapi", "For security, existing access tokens cannot be displayed. Regenerate only when you need a new token.": "For security, existing access tokens cannot be displayed. Regenerate only when you need a new token.", @@ -5316,6 +5316,11 @@ "Zero retention": "Zero retention", "Zhipu": "Zhipu", "Zhipu V4": "Zhipu V4", - "Zoom": "Zoom" + "Zoom": "Zoom", + "External Channel Billing": "External Channel Billing", + "External (third-party paid) channel usage per account. Models without a configured price are excluded.": "External (third-party paid) channel usage per account. Models without a configured price are excluded.", + "External Billing": "External Billing", + "My External Usage": "My External Usage", + "Click to toggle external/internal": "Click to toggle external/internal" } } From c8ce3085cc544beab9b800d1094df53eb2ed799d Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:51:13 +0800 Subject: [PATCH 09/11] feat(usage): add web/src/i18n/locales/zh.json (external channel billing) --- web/src/i18n/locales/zh.json | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index 457b1c53f2..cf2d60e2d6 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -2069,7 +2069,7 @@ "footer.columns.related.links.oneApi": "One API", "footer.columns.related.title": "相关项目", "footer.defaultCopyright": "版权所有。", - "footer.new\u0061pi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。", + "footer.newapi.projectAttributionSuffix": "版权所有,由项目贡献者设计与开发。", "For channels added after May 10, 2025, no need to remove \".\" from model names during deployment": "对于 2025 年 5 月 10 日之后添加的渠道,在部署时无需从模型名称中移除 \".\"", "For private deployments, format: https://fastgpt.run/api/openapi": "对于私有部署,格式为:https://fastgpt.run/api/openapi", "For security, existing access tokens cannot be displayed. Regenerate only when you need a new token.": "出于安全考虑,现有访问令牌无法再次显示。仅在需要新令牌时重新生成。", @@ -5316,6 +5316,27 @@ "Zero retention": "零数据保留", "Zhipu": "智谱", "Zhipu V4": "智谱 V4", - "Zoom": "缩放" + "Zoom": "缩放", + "External Channel Billing": "外部渠道账单", + "External (third-party paid) channel usage per account. Models without a configured price are excluded.": "各账号外部(第三方付费)渠道用量。未配置单价的模型不计入。", + "Usage Summary": "用量汇总", + "All time": "全部时间", + "Last 30 days": "近 30 天", + "Last 7 days": "近 7 天", + "Filter username…": "筛选用户名…", + "Accounts": "账号数", + "Total external tokens": "外部总 Token", + "Total external spend": "外部总消费 (USD)", + "Account": "账号", + "External tokens": "外部 Token", + "Spend (USD)": "消费 (USD)", + "External models": "外部模型数", + "No external usage in this range": "该时间范围内无外部用量", + "External Billing": "外部账单", + "My External Usage": "我的外部用量", + "Click to toggle external/internal": "点击切换 外部/内部", + "External": "外部", + "Internal": "内部", + "Loading…": "加载中…" } } From 144f586c9de77138c1c76c6fd989f5a6275ccb12 Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 13:51:23 +0800 Subject: [PATCH 10/11] feat(usage): add web/src/routeTree.gen.ts (external channel billing) --- web/src/routeTree.gen.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index a72e146cd0..56d1d8120b 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -39,6 +39,7 @@ import { Route as AuthenticatedChatChatIdRouteImport } from './routes/_authentic import { Route as AuthenticatedDashboardIndexRouteImport } from './routes/_authenticated/dashboard/index' import { Route as AuthenticatedDashboardSectionRouteImport } from './routes/_authenticated/dashboard/$section' import { Route as AuthenticatedErrorsErrorRouteImport } from './routes/_authenticated/errors/$error' +import { Route as AuthenticatedExternalBillingIndexRouteImport } from './routes/_authenticated/external-billing/index' import { Route as AuthenticatedKeysIndexRouteImport } from './routes/_authenticated/keys/index' import { Route as AuthenticatedModelsIndexRouteImport } from './routes/_authenticated/models/index' import { Route as AuthenticatedModelsSectionRouteImport } from './routes/_authenticated/models/$section' @@ -221,6 +222,12 @@ const AuthenticatedErrorsErrorRoute = path: '/errors/$error', getParentRoute: () => AuthenticatedRouteRoute, } as any) +const AuthenticatedExternalBillingIndexRoute = + AuthenticatedExternalBillingIndexRouteImport.update({ + id: '/external-billing/', + path: '/external-billing/', + getParentRoute: () => AuthenticatedRouteRoute, + } as any) const AuthenticatedKeysIndexRoute = AuthenticatedKeysIndexRouteImport.update({ id: '/keys/', path: '/keys/', @@ -418,6 +425,7 @@ export interface FileRoutesByFullPath { '/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/channels/': typeof AuthenticatedChannelsIndexRoute '/dashboard/': typeof AuthenticatedDashboardIndexRoute + '/external-billing/': typeof AuthenticatedExternalBillingIndexRoute '/keys/': typeof AuthenticatedKeysIndexRoute '/models/': typeof AuthenticatedModelsIndexRoute '/playground/': typeof AuthenticatedPlaygroundIndexRoute @@ -475,6 +483,7 @@ export interface FileRoutesByTo { '/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/channels': typeof AuthenticatedChannelsIndexRoute '/dashboard': typeof AuthenticatedDashboardIndexRoute + '/external-billing': typeof AuthenticatedExternalBillingIndexRoute '/keys': typeof AuthenticatedKeysIndexRoute '/models': typeof AuthenticatedModelsIndexRoute '/playground': typeof AuthenticatedPlaygroundIndexRoute @@ -536,6 +545,7 @@ export interface FileRoutesById { '/_authenticated/usage-logs/$section': typeof AuthenticatedUsageLogsSectionRoute '/_authenticated/channels/': typeof AuthenticatedChannelsIndexRoute '/_authenticated/dashboard/': typeof AuthenticatedDashboardIndexRoute + '/_authenticated/external-billing/': typeof AuthenticatedExternalBillingIndexRoute '/_authenticated/keys/': typeof AuthenticatedKeysIndexRoute '/_authenticated/models/': typeof AuthenticatedModelsIndexRoute '/_authenticated/playground/': typeof AuthenticatedPlaygroundIndexRoute @@ -596,6 +606,7 @@ export interface FileRouteTypes { | '/usage-logs/$section' | '/channels/' | '/dashboard/' + | '/external-billing/' | '/keys/' | '/models/' | '/playground/' @@ -653,6 +664,7 @@ export interface FileRouteTypes { | '/usage-logs/$section' | '/channels' | '/dashboard' + | '/external-billing' | '/keys' | '/models' | '/playground' @@ -713,6 +725,7 @@ export interface FileRouteTypes { | '/_authenticated/usage-logs/$section' | '/_authenticated/channels/' | '/_authenticated/dashboard/' + | '/_authenticated/external-billing/' | '/_authenticated/keys/' | '/_authenticated/models/' | '/_authenticated/playground/' @@ -972,6 +985,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedErrorsErrorRouteImport parentRoute: typeof AuthenticatedRouteRoute } + '/_authenticated/external-billing/': { + id: '/_authenticated/external-billing/' + path: '/external-billing' + fullPath: '/external-billing/' + preLoaderRoute: typeof AuthenticatedExternalBillingIndexRouteImport + parentRoute: typeof AuthenticatedRouteRoute + } '/_authenticated/keys/': { id: '/_authenticated/keys/' path: '/keys' @@ -1264,6 +1284,7 @@ interface AuthenticatedRouteRouteChildren { AuthenticatedUsageLogsSectionRoute: typeof AuthenticatedUsageLogsSectionRoute AuthenticatedChannelsIndexRoute: typeof AuthenticatedChannelsIndexRoute AuthenticatedDashboardIndexRoute: typeof AuthenticatedDashboardIndexRoute + AuthenticatedExternalBillingIndexRoute: typeof AuthenticatedExternalBillingIndexRoute AuthenticatedKeysIndexRoute: typeof AuthenticatedKeysIndexRoute AuthenticatedModelsIndexRoute: typeof AuthenticatedModelsIndexRoute AuthenticatedPlaygroundIndexRoute: typeof AuthenticatedPlaygroundIndexRoute @@ -1287,6 +1308,8 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = { AuthenticatedUsageLogsSectionRoute: AuthenticatedUsageLogsSectionRoute, AuthenticatedChannelsIndexRoute: AuthenticatedChannelsIndexRoute, AuthenticatedDashboardIndexRoute: AuthenticatedDashboardIndexRoute, + AuthenticatedExternalBillingIndexRoute: + AuthenticatedExternalBillingIndexRoute, AuthenticatedKeysIndexRoute: AuthenticatedKeysIndexRoute, AuthenticatedModelsIndexRoute: AuthenticatedModelsIndexRoute, AuthenticatedPlaygroundIndexRoute: AuthenticatedPlaygroundIndexRoute, From d99043bf1722c545d2e9bc1175117e18c81e6df4 Mon Sep 17 00:00:00 2001 From: 2388832 <50190507+2388832@users.noreply.github.com> Date: Sat, 29 Aug 2026 14:58:51 +0800 Subject: [PATCH 11/11] i18n(en): add 16 missing external billing translation keys --- web/src/i18n/locales/en.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 583e28db91..8c637f6078 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -5321,6 +5321,22 @@ "External (third-party paid) channel usage per account. Models without a configured price are excluded.": "External (third-party paid) channel usage per account. Models without a configured price are excluded.", "External Billing": "External Billing", "My External Usage": "My External Usage", - "Click to toggle external/internal": "Click to toggle external/internal" + "Click to toggle external/internal": "Click to toggle external/internal", + "Usage Summary": "Usage Summary", + "All time": "All time", + "Last 30 days": "Last 30 days", + "Last 7 days": "Last 7 days", + "Filter username…": "Filter username…", + "Accounts": "Accounts", + "Total external tokens": "Total external tokens", + "Total external spend": "Total external spend (USD)", + "Account": "Account", + "External tokens": "External tokens", + "Spend (USD)": "Spend (USD)", + "External models": "External models", + "No external usage in this range": "No external usage in this range", + "External": "External", + "Internal": "Internal", + "Loading…": "Loading…" } }