mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-20 01:08:37 +00:00
💱 feat(settings): introduce site-wide quota display type (USD/CNY/TOKENS/CUSTOM)
Replace the legacy boolean “DisplayInCurrencyEnabled” with an injected, type-safe
configuration `general_setting.quota_display_type`, and wire it through the
backend and frontend.
Backend
- Add `QuotaDisplayType` to `operation_setting.GeneralSetting` with injected
registration via `config.GlobalConfig.Register("general_setting", ...)`.
Helpers: `IsCurrencyDisplay()`, `IsCNYDisplay()`, `GetQuotaDisplayType()`.
- Expose `quota_display_type` in `/api/status` and keep legacy
`display_in_currency` for backward compatibility.
- Logger: update `LogQuota` and `FormatQuota` to support USD/CNY/TOKENS. When
CNY is selected, convert using `operation_setting.USDExchangeRate`.
- Controllers:
- `billing`: compute subscription/usage amounts based on the selected type
(USD: divide by `QuotaPerUnit`; CNY: USD→CNY; TOKENS: keep raw tokens).
- `topup` / `topup_stripe`: treat inputs as “amount” for USD/CNY and as
token-count for TOKENS; adjust min topup and pay money accordingly.
- `misc`: include `quota_display_type` in status payload.
- Compatibility: in `model/option.UpdateOption`, map updates to
`DisplayInCurrencyEnabled` → `general_setting.quota_display_type`
(true→USD, false→TOKENS). Keep exporting the legacy key in `OptionMap`.
Frontend
- Settings: replace the “display in currency” switch with a Select
(`general_setting.quota_display_type`) offering USD / CNY / Tokens.
Provide fallback mapping from legacy `DisplayInCurrencyEnabled`.
- Persist `quota_display_type` to localStorage (keep `display_in_currency`
for legacy components).
- Rendering helpers: base all quota/price rendering on `quota_display_type`;
use `usd_exchange_rate` for CNY symbol/values.
- Pricing page: default view currency follows site display type (USD/CNY),
while TOKENS mode still allows per-view currency toggling when needed.
Notes
- No database migrations required.
- Legacy clients remain functional via compatibility fields.
This commit is contained in:
@@ -830,12 +830,25 @@ export function renderQuotaNumberWithDigit(num, digits = 2) {
|
||||
if (typeof num !== 'number' || isNaN(num)) {
|
||||
return 0;
|
||||
}
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
const quotaDisplayType = localStorage.getItem('quota_display_type') || 'USD';
|
||||
num = num.toFixed(digits);
|
||||
if (displayInCurrency) {
|
||||
if (quotaDisplayType === 'CNY') {
|
||||
return '¥' + num;
|
||||
} else if (quotaDisplayType === 'USD') {
|
||||
return '$' + num;
|
||||
} else if (quotaDisplayType === 'CUSTOM') {
|
||||
const statusStr = localStorage.getItem('status');
|
||||
let symbol = '¤';
|
||||
try {
|
||||
if (statusStr) {
|
||||
const s = JSON.parse(statusStr);
|
||||
symbol = s?.custom_currency_symbol || symbol;
|
||||
}
|
||||
} catch (e) {}
|
||||
return symbol + num;
|
||||
} else {
|
||||
return num;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
export function renderNumberWithPoint(num) {
|
||||
@@ -887,33 +900,67 @@ export function getQuotaWithUnit(quota, digits = 6) {
|
||||
}
|
||||
|
||||
export function renderQuotaWithAmount(amount) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
return '$' + amount;
|
||||
} else {
|
||||
const quotaDisplayType = localStorage.getItem('quota_display_type') || 'USD';
|
||||
if (quotaDisplayType === 'TOKENS') {
|
||||
return renderNumber(renderUnitWithQuota(amount));
|
||||
}
|
||||
if (quotaDisplayType === 'CNY') {
|
||||
return '¥' + amount;
|
||||
} else if (quotaDisplayType === 'CUSTOM') {
|
||||
const statusStr = localStorage.getItem('status');
|
||||
let symbol = '¤';
|
||||
try {
|
||||
if (statusStr) {
|
||||
const s = JSON.parse(statusStr);
|
||||
symbol = s?.custom_currency_symbol || symbol;
|
||||
}
|
||||
} catch (e) {}
|
||||
return symbol + amount;
|
||||
}
|
||||
return '$' + amount;
|
||||
}
|
||||
|
||||
export function renderQuota(quota, digits = 2) {
|
||||
let quotaPerUnit = localStorage.getItem('quota_per_unit');
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
const quotaDisplayType = localStorage.getItem('quota_display_type') || 'USD';
|
||||
quotaPerUnit = parseFloat(quotaPerUnit);
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
const result = quota / quotaPerUnit;
|
||||
const fixedResult = result.toFixed(digits);
|
||||
|
||||
// 如果 toFixed 后结果为 0 但原始值不为 0,显示最小值
|
||||
if (parseFloat(fixedResult) === 0 && quota > 0 && result > 0) {
|
||||
const minValue = Math.pow(10, -digits);
|
||||
return '$' + minValue.toFixed(digits);
|
||||
}
|
||||
|
||||
return '$' + fixedResult;
|
||||
if (quotaDisplayType === 'TOKENS') {
|
||||
return renderNumber(quota);
|
||||
}
|
||||
return renderNumber(quota);
|
||||
const resultUSD = quota / quotaPerUnit;
|
||||
let symbol = '$';
|
||||
let value = resultUSD;
|
||||
if (quotaDisplayType === 'CNY') {
|
||||
const statusStr = localStorage.getItem('status');
|
||||
let usdRate = 1;
|
||||
try {
|
||||
if (statusStr) {
|
||||
const s = JSON.parse(statusStr);
|
||||
usdRate = s?.usd_exchange_rate || 1;
|
||||
}
|
||||
} catch (e) {}
|
||||
value = resultUSD * usdRate;
|
||||
symbol = '¥';
|
||||
} else if (quotaDisplayType === 'CUSTOM') {
|
||||
const statusStr = localStorage.getItem('status');
|
||||
let symbolCustom = '¤';
|
||||
let rate = 1;
|
||||
try {
|
||||
if (statusStr) {
|
||||
const s = JSON.parse(statusStr);
|
||||
symbolCustom = s?.custom_currency_symbol || symbolCustom;
|
||||
rate = s?.custom_currency_exchange_rate || rate;
|
||||
}
|
||||
} catch (e) {}
|
||||
value = resultUSD * rate;
|
||||
symbol = symbolCustom;
|
||||
}
|
||||
const fixedResult = value.toFixed(digits);
|
||||
if (parseFloat(fixedResult) === 0 && quota > 0 && value > 0) {
|
||||
const minValue = Math.pow(10, -digits);
|
||||
return symbol + minValue.toFixed(digits);
|
||||
}
|
||||
return symbol + fixedResult;
|
||||
}
|
||||
|
||||
function isValidGroupRatio(ratio) {
|
||||
@@ -1072,7 +1119,7 @@ export function renderModelPrice(
|
||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio +
|
||||
(webSearchCallCount / 1000) * webSearchPrice * groupRatio +
|
||||
(fileSearchCallCount / 1000) * fileSearchPrice * groupRatio +
|
||||
(imageGenerationCallPrice * groupRatio);
|
||||
imageGenerationCallPrice * groupRatio;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1510,9 +1557,8 @@ export function renderAudioModelPrice(
|
||||
}
|
||||
|
||||
export function renderQuotaWithPrompt(quota, digits) {
|
||||
let displayInCurrency = localStorage.getItem('display_in_currency');
|
||||
displayInCurrency = displayInCurrency === 'true';
|
||||
if (displayInCurrency) {
|
||||
const quotaDisplayType = localStorage.getItem('quota_display_type') || 'USD';
|
||||
if (quotaDisplayType !== 'TOKENS') {
|
||||
return i18next.t('等价金额:') + renderQuota(quota, digits);
|
||||
}
|
||||
return '';
|
||||
|
||||
Reference in New Issue
Block a user