mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-18 00:17:26 +00:00
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.
159 lines
4.7 KiB
JavaScript
159 lines
4.7 KiB
JavaScript
/*
|
|
Copyright (C) 2025 QuantumNous
|
|
|
|
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 <https://www.gnu.org/licenses/>.
|
|
|
|
For commercial licensing, please contact support@quantumnous.com
|
|
*/
|
|
|
|
import React, { memo, useCallback } from 'react';
|
|
import { Input, Button, Switch, Select, Divider } from '@douyinfe/semi-ui';
|
|
import { IconSearch, IconCopy, IconFilter } from '@douyinfe/semi-icons';
|
|
|
|
const SearchActions = memo(
|
|
({
|
|
selectedRowKeys = [],
|
|
copyText,
|
|
handleChange,
|
|
handleCompositionStart,
|
|
handleCompositionEnd,
|
|
isMobile = false,
|
|
searchValue = '',
|
|
setShowFilterModal,
|
|
showWithRecharge,
|
|
setShowWithRecharge,
|
|
currency,
|
|
setCurrency,
|
|
showRatio,
|
|
setShowRatio,
|
|
viewMode,
|
|
setViewMode,
|
|
tokenUnit,
|
|
setTokenUnit,
|
|
t,
|
|
}) => {
|
|
const handleCopyClick = useCallback(() => {
|
|
if (copyText && selectedRowKeys.length > 0) {
|
|
copyText(selectedRowKeys);
|
|
}
|
|
}, [copyText, selectedRowKeys]);
|
|
|
|
const handleFilterClick = useCallback(() => {
|
|
setShowFilterModal?.(true);
|
|
}, [setShowFilterModal]);
|
|
|
|
const handleViewModeToggle = useCallback(() => {
|
|
setViewMode?.(viewMode === 'table' ? 'card' : 'table');
|
|
}, [viewMode, setViewMode]);
|
|
|
|
const handleTokenUnitToggle = useCallback(() => {
|
|
setTokenUnit?.(tokenUnit === 'K' ? 'M' : 'K');
|
|
}, [tokenUnit, setTokenUnit]);
|
|
|
|
return (
|
|
<div className='flex items-center gap-2 w-full'>
|
|
<div className='flex-1'>
|
|
<Input
|
|
prefix={<IconSearch />}
|
|
placeholder={t('模糊搜索模型名称')}
|
|
value={searchValue}
|
|
onCompositionStart={handleCompositionStart}
|
|
onCompositionEnd={handleCompositionEnd}
|
|
onChange={handleChange}
|
|
showClear
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
theme='outline'
|
|
type='primary'
|
|
icon={<IconCopy />}
|
|
onClick={handleCopyClick}
|
|
disabled={selectedRowKeys.length === 0}
|
|
className='!bg-blue-500 hover:!bg-blue-600 !text-white disabled:!bg-gray-300 disabled:!text-gray-500'
|
|
>
|
|
{t('复制')}
|
|
</Button>
|
|
|
|
{!isMobile && (
|
|
<>
|
|
<Divider layout='vertical' margin='8px' />
|
|
|
|
{/* 充值价格显示开关 */}
|
|
<div className='flex items-center gap-2'>
|
|
<span className='text-sm text-gray-600'>{t('充值价格显示')}</span>
|
|
<Switch
|
|
checked={showWithRecharge}
|
|
onChange={setShowWithRecharge}
|
|
/>
|
|
</div>
|
|
|
|
{/* 货币单位选择 */}
|
|
{showWithRecharge && (
|
|
<Select
|
|
value={currency}
|
|
onChange={setCurrency}
|
|
optionList={[
|
|
{ value: 'USD', label: 'USD' },
|
|
{ value: 'CNY', label: 'CNY' },
|
|
{ value: 'CUSTOM', label: t('自定义货币') },
|
|
]}
|
|
/>
|
|
)}
|
|
|
|
{/* 显示倍率开关 */}
|
|
<div className='flex items-center gap-2'>
|
|
<span className='text-sm text-gray-600'>{t('倍率')}</span>
|
|
<Switch checked={showRatio} onChange={setShowRatio} />
|
|
</div>
|
|
|
|
{/* 视图模式切换按钮 */}
|
|
<Button
|
|
theme={viewMode === 'table' ? 'solid' : 'outline'}
|
|
type={viewMode === 'table' ? 'primary' : 'tertiary'}
|
|
onClick={handleViewModeToggle}
|
|
>
|
|
{t('表格视图')}
|
|
</Button>
|
|
|
|
{/* Token单位切换按钮 */}
|
|
<Button
|
|
theme={tokenUnit === 'K' ? 'solid' : 'outline'}
|
|
type={tokenUnit === 'K' ? 'primary' : 'tertiary'}
|
|
onClick={handleTokenUnitToggle}
|
|
>
|
|
{tokenUnit}
|
|
</Button>
|
|
</>
|
|
)}
|
|
|
|
{isMobile && (
|
|
<Button
|
|
theme='outline'
|
|
type='tertiary'
|
|
icon={<IconFilter />}
|
|
onClick={handleFilterClick}
|
|
>
|
|
{t('筛选')}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
SearchActions.displayName = 'SearchActions';
|
|
|
|
export default SearchActions;
|