diff --git a/model/user.go b/model/user.go
index b364c02..fb9fc76 100644
--- a/model/user.go
+++ b/model/user.go
@@ -311,11 +311,11 @@ func withNormalizedEmailLock(tx *gorm.DB, email string, fn func(tx *gorm.DB) err
return fn(tx)
case common.UsingMySQL:
lockName := normalizedEmailLockName(email)
- var acquired sql.NullInt64
- if err := tx.Raw("SELECT GET_LOCK(?, ?)", lockName, 10).Scan(&acquired).Error; err != nil {
+ acquired, err := acquireMySQLNamedLock(tx, lockName)
+ if err != nil {
return err
}
- if !acquired.Valid || acquired.Int64 != 1 {
+ if !acquired {
return errors.New("failed to acquire user email lock")
}
released := false
@@ -324,7 +324,7 @@ func withNormalizedEmailLock(tx *gorm.DB, email string, fn func(tx *gorm.DB) err
_ = releaseMySQLNamedLock(tx, lockName)
}
}()
- err := fn(tx)
+ err = fn(tx)
releaseErr := releaseMySQLNamedLock(tx, lockName)
released = true
if releaseErr != nil && err == nil {
@@ -353,11 +353,11 @@ func WithNormalizedEmailWriteTx(email string, fn func(tx *gorm.DB) error) error
lockName := normalizedEmailLockName(email)
return DB.Connection(func(conn *gorm.DB) error {
- var acquired sql.NullInt64
- if err := conn.Raw("SELECT GET_LOCK(?, ?)", lockName, 10).Scan(&acquired).Error; err != nil {
+ acquired, err := acquireMySQLNamedLock(conn, lockName)
+ if err != nil {
return err
}
- if !acquired.Valid || acquired.Int64 != 1 {
+ if !acquired {
return errors.New("failed to acquire user email lock")
}
@@ -368,7 +368,7 @@ func WithNormalizedEmailWriteTx(email string, fn func(tx *gorm.DB) error) error
}
}()
- err := conn.Transaction(fn)
+ err = conn.Transaction(fn)
releaseErr := releaseMySQLNamedLock(conn, lockName)
released = true
if err != nil {
@@ -378,9 +378,34 @@ func WithNormalizedEmailWriteTx(email string, fn func(tx *gorm.DB) error) error
})
}
+// Use database/sql row scanning so sql.NullInt64 is not parsed as a GORM model.
+func scanNullableInt64(row *sql.Row) (sql.NullInt64, error) {
+ var value sql.NullInt64
+ err := row.Scan(&value)
+ return value, err
+}
+
+func acquireMySQLNamedLock(tx *gorm.DB, lockName string) (bool, error) {
+ acquired, err := scanNullableInt64(tx.Raw("SELECT GET_LOCK(?, ?)", lockName, 10).Row())
+ if err != nil {
+ return false, err
+ }
+ return isMySQLNamedLockSuccess(acquired), nil
+}
+
func releaseMySQLNamedLock(tx *gorm.DB, lockName string) error {
- var released sql.NullInt64
- return tx.Raw("SELECT RELEASE_LOCK(?)", lockName).Scan(&released).Error
+ released, err := scanNullableInt64(tx.Raw("SELECT RELEASE_LOCK(?)", lockName).Row())
+ if err != nil {
+ return err
+ }
+ if !isMySQLNamedLockSuccess(released) {
+ return errors.New("failed to release user email lock")
+ }
+ return nil
+}
+
+func isMySQLNamedLockSuccess(value sql.NullInt64) bool {
+ return value.Valid && value.Int64 == 1
}
func GetMaxUserId() int {
diff --git a/model/user_update_test.go b/model/user_update_test.go
index 4bc68c5..a0fab97 100644
--- a/model/user_update_test.go
+++ b/model/user_update_test.go
@@ -2,6 +2,7 @@ package model
import (
"context"
+ "database/sql"
"errors"
"fmt"
"net"
@@ -12,6 +13,7 @@ import (
"github.com/MAX-API-Next/MAX-API/common"
"github.com/MAX-API-Next/MAX-API/dto"
+ "github.com/glebarez/sqlite"
"github.com/go-redis/redis/v8"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -80,6 +82,31 @@ func TestNormalizedEmailLockNameIsStableAndShort(t *testing.T) {
assert.Contains(t, lockName, "maxapi:user-email:")
}
+func TestScanNullableInt64UsesSQLRow(t *testing.T) {
+ db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
+ require.NoError(t, err)
+ if sqlDB, err := db.DB(); err == nil {
+ t.Cleanup(func() {
+ _ = sqlDB.Close()
+ })
+ }
+
+ got, err := scanNullableInt64(db.Raw("SELECT ?", 1).Row())
+ require.NoError(t, err)
+ require.True(t, got.Valid)
+ assert.EqualValues(t, 1, got.Int64)
+
+ nullValue, err := scanNullableInt64(db.Raw("SELECT NULL").Row())
+ require.NoError(t, err)
+ assert.False(t, nullValue.Valid)
+}
+
+func TestMySQLNamedLockResultSuccessRequiresOne(t *testing.T) {
+ assert.True(t, isMySQLNamedLockSuccess(sql.NullInt64{Int64: 1, Valid: true}))
+ assert.False(t, isMySQLNamedLockSuccess(sql.NullInt64{Int64: 0, Valid: true}))
+ assert.False(t, isMySQLNamedLockSuccess(sql.NullInt64{Valid: false}))
+}
+
func mysqlDSNWithClientFoundRowsFalse(dsn string) string {
base, rawQuery, ok := strings.Cut(dsn, "?")
values, err := url.ParseQuery(rawQuery)
diff --git a/web/default/src/features/pricing/components/model-details.tsx b/web/default/src/features/pricing/components/model-details.tsx
index 3262fda..f359216 100644
--- a/web/default/src/features/pricing/components/model-details.tsx
+++ b/web/default/src/features/pricing/components/model-details.tsx
@@ -21,7 +21,7 @@ import { useQuery } from '@tanstack/react-query'
import { useNavigate, useParams, useSearch } from '@tanstack/react-router'
import { ArrowLeft, Code2, HeartPulse, Info, Timer } from 'lucide-react'
import { useTranslation } from 'react-i18next'
-import { DEFAULT_AUTO_ROUTE_KEY, type AutoGroupRoute } from '@/lib/auto-routes'
+import type { AutoGroupRoute } from '@/lib/auto-routes'
import { getLobeIcon } from '@/lib/lobe-icon'
import { cn } from '@/lib/utils'
import { Button } from '@/components/ui/button'
@@ -54,6 +54,10 @@ import {
} from '@/features/performance-metrics/lib/format'
import { DEFAULT_TOKEN_UNIT, QUOTA_TYPE_VALUES } from '../constants'
import { usePricingData } from '../hooks/use-pricing-data'
+import {
+ getAutoRouteLabelOverride,
+ getConfiguredAutoRouteChains,
+} from '../lib/auto-route-view'
import {
getDynamicPriceEntries,
getDynamicPricingSummary,
@@ -748,46 +752,32 @@ function AutoGroupChain(props: {
const modelEnableGroups = Array.isArray(props.model.enable_groups)
? props.model.enable_groups
: []
- const routes =
- props.autoRoutes && props.autoRoutes.length > 0
- ? props.autoRoutes
- : [
- {
- key: DEFAULT_AUTO_ROUTE_KEY,
- enabled: true,
- user_selectable: true,
- groups: props.autoGroups,
- },
- ]
- const routeChains = routes
- .filter((route) => route.enabled)
- .map((route) => ({
- route,
- groups: route.groups.filter((g) => modelEnableGroups.includes(g)),
- }))
- .filter((item) => item.groups.length > 0)
+ const routeChains = getConfiguredAutoRouteChains({
+ autoGroups: props.autoGroups,
+ autoRoutes: props.autoRoutes,
+ groupFilter: (group) => modelEnableGroups.includes(group),
+ })
if (routeChains.length === 0) return null
- const getRouteLabelOverride = (route: AutoGroupRoute) => {
- const name = route.name?.trim()
- if (
- route.key === DEFAULT_AUTO_ROUTE_KEY &&
- (!name || name === 'Auto' || name === DEFAULT_AUTO_ROUTE_KEY)
- ) {
- return undefined
- }
- return name || route.key
- }
-
return (
-
-
{t('Auto Route Chains')}
+
+
+
{t('Auto Route Chains')}
+
+ {t(
+ 'When a token uses an auto route, the system tries the route groups from top to bottom until it finds an available group.'
+ )}{' '}
+ {t(
+ 'Billing and group ratios are calculated from the real group that handles the request.'
+ )}
+
+
{routeChains.map(({ route, groups }) => (
→
@@ -1419,7 +1409,7 @@ export function ModelDetails() {
groupRatio={groupRatio || {}}
usableGroup={usableGroup || {}}
autoGroups={autoGroups || []}
- autoRoutes={autoRoutes || []}
+ autoRoutes={autoRoutes}
priceRate={priceRate ?? 1}
usdExchangeRate={usdExchangeRate ?? 1}
tokenUnit={tokenUnit}
diff --git a/web/default/src/features/pricing/components/pricing-sidebar.tsx b/web/default/src/features/pricing/components/pricing-sidebar.tsx
index 62fe96a..6f49273 100644
--- a/web/default/src/features/pricing/components/pricing-sidebar.tsx
+++ b/web/default/src/features/pricing/components/pricing-sidebar.tsx
@@ -17,10 +17,12 @@ along with this program. If not, see .
For commercial licensing, please contact https://github.com/MAX-API-Next/MAX-API/issues
*/
import type { ReactNode } from 'react'
-import { ChevronDown, RotateCcw } from 'lucide-react'
+import { ChevronDown, Info, RotateCcw } from 'lucide-react'
import { useTranslation } from 'react-i18next'
+import type { AutoGroupRoute } from '@/lib/auto-routes'
import { getLobeIcon } from '@/lib/lobe-icon'
import { cn } from '@/lib/utils'
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import {
@@ -28,6 +30,7 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
+import { GroupBadge } from '@/components/group-badge'
import {
ENDPOINT_TYPES,
FILTER_ALL,
@@ -35,6 +38,10 @@ import {
getEndpointTypeLabels,
getQuotaTypeLabels,
} from '../constants'
+import {
+ getAutoRouteLabelOverride,
+ getConfiguredAutoRouteChains,
+} from '../lib/auto-route-view'
import { parseTags } from '../lib/filters'
import type { PricingModel, PricingVendor } from '../types'
@@ -51,6 +58,7 @@ type FilterSectionProps = {
value: string
options: FilterOption[]
onChange: (value: string) => void
+ children?: ReactNode
}
export interface PricingSidebarProps {
@@ -67,6 +75,8 @@ export interface PricingSidebarProps {
vendors: PricingVendor[]
groups: string[]
groupRatios?: Record
+ autoGroups?: string[]
+ autoRoutes?: AutoGroupRoute[]
tags: string[]
models: PricingModel[]
hasActiveFilters: boolean
@@ -149,11 +159,72 @@ function FilterSection(props: FilterSectionProps) {
/>
))}
+ {props.children}
)
}
+function AutoRouteGroupIntro(props: {
+ autoGroups?: string[]
+ autoRoutes?: AutoGroupRoute[]
+ visibleGroups: string[]
+}) {
+ const { t } = useTranslation()
+ const visibleGroupSet = new Set(props.visibleGroups)
+ const routeChains = getConfiguredAutoRouteChains({
+ autoGroups: props.autoGroups ?? [],
+ autoRoutes: props.autoRoutes,
+ groupFilter: (group) => visibleGroupSet.has(group),
+ })
+
+ if (routeChains.length === 0) return null
+
+ return (
+
+
+
+ {t('Configured auto route groups')}
+
+
+
+ {t(
+ 'Auto route groups are selectable aliases that point to administrator-configured billing groups.'
+ )}
+
+
+ {routeChains.map(({ route, groups }) => (
+
+
+ →
+ {groups.map((group, index) => (
+
+
+ {index < groups.length - 1 && (
+ →
+ )}
+
+ ))}
+
+ ))}
+
+
+ {t(
+ 'Billing and group ratios are calculated from the real group that handles the request.'
+ )}
+
+
+
+ )
+}
+
export function PricingSidebar(props: PricingSidebarProps) {
const { t } = useTranslation()
const quotaTypeLabels = getQuotaTypeLabels(t)
@@ -277,7 +348,13 @@ export function PricingSidebar(props: PricingSidebarProps) {
value={props.groupFilter}
options={groupOptions}
onChange={props.onGroupChange}
- />
+ >
+
+
+ autoGroups?: string[]
+ autoRoutes?: AutoGroupRoute[]
tags: string[]
models: PricingModel[]
hasActiveFilters: boolean
@@ -297,6 +300,8 @@ export function PricingToolbar(props: PricingToolbarProps) {
vendors={props.vendors}
groups={props.groups}
groupRatios={props.groupRatios}
+ autoGroups={props.autoGroups}
+ autoRoutes={props.autoRoutes}
tags={props.tags}
models={props.models}
hasActiveFilters={props.hasActiveFilters}
diff --git a/web/default/src/features/pricing/hooks/use-pricing-data.ts b/web/default/src/features/pricing/hooks/use-pricing-data.ts
index 11f2650..277bd41 100644
--- a/web/default/src/features/pricing/hooks/use-pricing-data.ts
+++ b/web/default/src/features/pricing/hooks/use-pricing-data.ts
@@ -67,7 +67,7 @@ export function usePricingData() {
usableGroup: data?.usable_group ?? {},
endpointMap: data?.supported_endpoint ?? {},
autoGroups: data?.auto_groups ?? [],
- autoRoutes: data?.auto_routes ?? [],
+ autoRoutes: data?.auto_routes,
isLoading,
error,
refetch,
diff --git a/web/default/src/features/pricing/index.tsx b/web/default/src/features/pricing/index.tsx
index 6fb3f00..e009827 100644
--- a/web/default/src/features/pricing/index.tsx
+++ b/web/default/src/features/pricing/index.tsx
@@ -218,6 +218,8 @@ export function Pricing() {
vendors={vendors || []}
groups={availableGroups}
groupRatios={groupRatio}
+ autoGroups={autoGroups || []}
+ autoRoutes={autoRoutes}
tags={availableTags}
models={models || []}
hasActiveFilters={hasActiveFilters}
@@ -250,6 +252,8 @@ export function Pricing() {
vendors={vendors || []}
groups={availableGroups}
groupRatios={groupRatio}
+ autoGroups={autoGroups || []}
+ autoRoutes={autoRoutes}
tags={availableTags}
models={models || []}
hasActiveFilters={hasActiveFilters}
@@ -277,7 +281,7 @@ export function Pricing() {
>) || {}
}
autoGroups={autoGroups || []}
- autoRoutes={autoRoutes || []}
+ autoRoutes={autoRoutes}
priceRate={priceRate ?? 1}
usdExchangeRate={usdExchangeRate ?? 1}
tokenUnit={tokenUnit}
diff --git a/web/default/src/features/pricing/lib/auto-route-view.ts b/web/default/src/features/pricing/lib/auto-route-view.ts
new file mode 100644
index 0000000..8363bd4
--- /dev/null
+++ b/web/default/src/features/pricing/lib/auto-route-view.ts
@@ -0,0 +1,92 @@
+/*
+Copyright (C) 2023-2026 MAX-API-Next
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+
+For commercial licensing, please contact https://github.com/MAX-API-Next/MAX-API/issues
+*/
+import { DEFAULT_AUTO_ROUTE_KEY, type AutoGroupRoute } from '@/lib/auto-routes'
+
+export type PricingAutoRouteChain = {
+ route: AutoGroupRoute
+ groups: string[]
+}
+
+function normalizeDisplayGroups(
+ groups: string[],
+ groupFilter?: (group: string) => boolean
+): string[] {
+ const seen = new Set()
+ const normalized: string[] = []
+
+ for (const rawGroup of groups) {
+ const group = rawGroup.trim()
+ if (!group || seen.has(group)) continue
+ if (groupFilter && !groupFilter(group)) continue
+ seen.add(group)
+ normalized.push(group)
+ }
+
+ return normalized
+}
+
+function getFallbackAutoRoute(autoGroups: string[]): AutoGroupRoute | null {
+ const groups = normalizeDisplayGroups(autoGroups)
+ if (groups.length === 0) return null
+
+ return {
+ key: DEFAULT_AUTO_ROUTE_KEY,
+ enabled: true,
+ user_selectable: true,
+ groups,
+ }
+}
+
+export function getAutoRouteLabelOverride(
+ route: AutoGroupRoute
+): string | undefined {
+ const name = route.name?.trim()
+ if (
+ route.key === DEFAULT_AUTO_ROUTE_KEY &&
+ (!name || name === 'Auto' || name === DEFAULT_AUTO_ROUTE_KEY)
+ ) {
+ return undefined
+ }
+ return name || route.key
+}
+
+export function getConfiguredAutoRouteChains(options: {
+ autoGroups: string[]
+ autoRoutes?: AutoGroupRoute[]
+ groupFilter?: (group: string) => boolean
+}): PricingAutoRouteChain[] {
+ const fallbackRoute = getFallbackAutoRoute(options.autoGroups)
+ const routes =
+ options.autoRoutes !== undefined
+ ? options.autoRoutes
+ : fallbackRoute
+ ? [fallbackRoute]
+ : []
+
+ return routes
+ .filter((route) => route.enabled)
+ .map((route) => ({
+ route,
+ groups: normalizeDisplayGroups(
+ Array.isArray(route.groups) ? route.groups : [],
+ options.groupFilter
+ ),
+ }))
+ .filter((item) => item.groups.length > 0)
+}
diff --git a/web/default/src/i18n/locales/en.json b/web/default/src/i18n/locales/en.json
index fb872b5..591590c 100644
--- a/web/default/src/i18n/locales/en.json
+++ b/web/default/src/i18n/locales/en.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "auto route {{key}} name must not exceed 64 characters",
"Auto route chains": "Auto route chains",
"Auto Route Chains": "Auto Route Chains",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "Auto route groups are selectable aliases that point to administrator-configured billing groups.",
"Auto route key already exists": "Auto route key already exists",
"auto route must be an object": "auto route must be an object",
"Auto Sync Upstream Models": "Auto Sync Upstream Models",
@@ -666,6 +667,7 @@
"Billing Mode": "Billing Mode",
"Billing Process": "Billing Process",
"Billing Source": "Billing Source",
+ "Billing and group ratios are calculated from the real group that handles the request.": "Billing and group ratios are calculated from the real group that handles the request.",
"billing-aware model rules": "billing-aware model rules",
"billing-aware models": "billing-aware models",
"Bind": "Bind",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "Configure Waffo payment aggregation platform integration",
"Configure your account behavior preferences": "Configure your account behavior preferences",
"Configure your account preferences and integrations": "Configure your account preferences and integrations",
+ "Configured auto route groups": "Configured auto route groups",
"Configured routes and latency checks": "Configured routes and latency checks",
"Confirm": "Confirm",
"Confirm Action": "Confirm Action",
diff --git a/web/default/src/i18n/locales/fr.json b/web/default/src/i18n/locales/fr.json
index 8491af3..0a7d14b 100644
--- a/web/default/src/i18n/locales/fr.json
+++ b/web/default/src/i18n/locales/fr.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "Le nom de la route auto {{key}} ne doit pas dépasser 64 caractères",
"Auto route chains": "Chaînes de routes auto",
"Auto Route Chains": "Chaînes de routes auto",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "Les groupes de route auto sont des alias sélectionnables qui pointent vers des groupes de facturation configurés par l'administrateur.",
"Auto route key already exists": "La clé de route auto existe déjà",
"auto route must be an object": "La route auto doit être un objet",
"Auto Sync Upstream Models": "Synchronisation automatique des modèles en amont",
@@ -666,6 +667,7 @@
"Billing Mode": "Mode de facturation",
"Billing Process": "Processus de facturation",
"Billing Source": "Source de facturation",
+ "Billing and group ratios are calculated from the real group that handles the request.": "La facturation et les ratios de groupe sont calculés à partir du groupe réel qui traite la requête.",
"billing-aware model rules": "règles de modèles compatibles avec la facturation",
"billing-aware models": "modèles compatibles avec la facturation",
"Bind": "Lier",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "Configurer l'intégration de la plateforme d'agrégation de paiement Waffo",
"Configure your account behavior preferences": "Configurer les préférences de comportement de votre compte",
"Configure your account preferences and integrations": "Configurer les préférences et les intégrations de votre compte",
+ "Configured auto route groups": "Groupes de routes auto configurés",
"Configured routes and latency checks": "Routes configurées et contrôles de latence",
"Confirm": "Confirmer",
"Confirm Action": "Confirmer l'action",
diff --git a/web/default/src/i18n/locales/ja.json b/web/default/src/i18n/locales/ja.json
index cc335c7..c2d714e 100644
--- a/web/default/src/i18n/locales/ja.json
+++ b/web/default/src/i18n/locales/ja.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "自動ルート {{key}} の名前は64文字以下にしてください",
"Auto route chains": "自動ルートチェーン",
"Auto Route Chains": "自動ルートチェーン",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "自動ルートグループは、管理者が設定した実際の課金グループを指す選択可能なエイリアスです。",
"Auto route key already exists": "自動ルートキーはすでに存在します",
"auto route must be an object": "自動ルートはオブジェクトである必要があります",
"Auto Sync Upstream Models": "アップストリームモデルの自動同期",
@@ -666,6 +667,7 @@
"Billing Mode": "課金モード",
"Billing Process": "課金プロセス",
"Billing Source": "課金ソース",
+ "Billing and group ratios are calculated from the real group that handles the request.": "課金とグループ倍率は、リクエストを処理した実際のグループに基づいて計算されます。",
"billing-aware model rules": "課金対応モデルルール",
"billing-aware models": "課金対応モデル",
"Bind": "バインド",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "Waffo決済アグリゲーションプラットフォームの連携を設定",
"Configure your account behavior preferences": "アカウントの動作設定を設定します。",
"Configure your account preferences and integrations": "アカウントの設定と統合を設定します。",
+ "Configured auto route groups": "設定済みの自動ルートグループ",
"Configured routes and latency checks": "設定済みルートとレイテンシ確認",
"Confirm": "確認",
"Confirm Action": "アクションの確認",
diff --git a/web/default/src/i18n/locales/ru.json b/web/default/src/i18n/locales/ru.json
index 9e01340..9aed47b 100644
--- a/web/default/src/i18n/locales/ru.json
+++ b/web/default/src/i18n/locales/ru.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "Имя автомаршрута {{key}} не должно превышать 64 символа",
"Auto route chains": "Цепочки автомаршрутов",
"Auto Route Chains": "Цепочки автомаршрутов",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "Группы автомаршрутов — это выбираемые псевдонимы, указывающие на реальные группы тарификации, настроенные администратором.",
"Auto route key already exists": "Ключ автомаршрута уже существует",
"auto route must be an object": "Автомаршрут должен быть объектом",
"Auto Sync Upstream Models": "Автоматическая синхронизация моделей провайдера",
@@ -666,6 +667,7 @@
"Billing Mode": "Режим биллинга",
"Billing Process": "Процесс тарификации",
"Billing Source": "Источник биллинга",
+ "Billing and group ratios are calculated from the real group that handles the request.": "Тарификация и коэффициенты групп рассчитываются по реальной группе, обработавшей запрос.",
"billing-aware model rules": "правила моделей с учетом биллинга",
"billing-aware models": "модели с учетом биллинга",
"Bind": "Привязать",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "Настроить интеграцию платёжной платформы Waffo",
"Configure your account behavior preferences": "Настроить предпочтения поведения вашей учетной записи",
"Configure your account preferences and integrations": "Настроить параметры и интеграции вашей учетной записи",
+ "Configured auto route groups": "Настроенные группы автомаршрутов",
"Configured routes and latency checks": "Настроенные маршруты и проверки задержки",
"Confirm": "Подтверждение",
"Confirm Action": "Подтвердить действие",
diff --git a/web/default/src/i18n/locales/vi.json b/web/default/src/i18n/locales/vi.json
index f0bd46f..fe86a05 100644
--- a/web/default/src/i18n/locales/vi.json
+++ b/web/default/src/i18n/locales/vi.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "Tên tuyến tự động {{key}} không được vượt quá 64 ký tự",
"Auto route chains": "Chuỗi tuyến tự động",
"Auto Route Chains": "Chuỗi tuyến tự động",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "Các nhóm tuyến tự động là bí danh có thể chọn, trỏ tới các nhóm tính phí thật do quản trị viên cấu hình.",
"Auto route key already exists": "Khóa tuyến tự động đã tồn tại",
"auto route must be an object": "Tuyến tự động phải là một đối tượng",
"Auto Sync Upstream Models": "Tự động đồng bộ mô hình nguồn",
@@ -666,6 +667,7 @@
"Billing Mode": "Chế độ thanh toán",
"Billing Process": "Quá trình tính phí",
"Billing Source": "Nguồn thanh toán",
+ "Billing and group ratios are calculated from the real group that handles the request.": "Phí và tỷ lệ nhóm được tính theo nhóm thật xử lý yêu cầu.",
"billing-aware model rules": "quy tắc mô hình có nhận thức tính phí",
"billing-aware models": "mô hình có nhận thức tính phí",
"Bind": "Buộc",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "Cấu hình tích hợp nền tảng tổng hợp thanh toán Waffo",
"Configure your account behavior preferences": "Cấu hình tùy chọn hành vi tài khoản của bạn",
"Configure your account preferences and integrations": "Cấu hình các tùy chọn và tích hợp tài khoản của bạn",
+ "Configured auto route groups": "Các nhóm tuyến tự động đã cấu hình",
"Configured routes and latency checks": "Tuyến đã cấu hình và kiểm tra độ trễ",
"Confirm": "Xác nhận",
"Confirm Action": "Xác nhận hành động",
diff --git a/web/default/src/i18n/locales/zh.json b/web/default/src/i18n/locales/zh.json
index 988fb96..cef2175 100644
--- a/web/default/src/i18n/locales/zh.json
+++ b/web/default/src/i18n/locales/zh.json
@@ -557,6 +557,7 @@
"auto route {{key}} name must not exceed 64 characters": "自动链路 {{key}} 的名称不能超过 64 个字符",
"Auto route chains": "自动链路",
"Auto Route Chains": "自动链路",
+ "Auto route groups are selectable aliases that point to administrator-configured billing groups.": "自动链路分组是可选择的分组别名,指向管理员配置的真实计费分组。",
"Auto route key already exists": "自动链路键已存在",
"auto route must be an object": "自动链路必须是对象",
"Auto Sync Upstream Models": "自动同步上游模型",
@@ -666,6 +667,7 @@
"Billing Mode": "计费模式",
"Billing Process": "计费过程",
"Billing Source": "计费来源",
+ "Billing and group ratios are calculated from the real group that handles the request.": "计费和分组倍率按实际处理请求的真实分组计算。",
"billing-aware model rules": "支持计费的模型规则",
"billing-aware models": "支持计费的模型",
"Bind": "绑定",
@@ -1019,6 +1021,7 @@
"Configure Waffo payment aggregation platform integration": "配置 Waffo 支付聚合平台集成",
"Configure your account behavior preferences": "配置您的账户行为偏好",
"Configure your account preferences and integrations": "配置您的账户偏好和集成",
+ "Configured auto route groups": "已配置的自动链路分组",
"Configured routes and latency checks": "已配置路由和延迟检测",
"Confirm": "确认",
"Confirm Action": "确认操作",