mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-29 17:58:38 +00:00
🎨 style: format all code with gofmt and lint:fix
Apply consistent code formatting across the entire codebase using gofmt and lint:fix tools. This ensures adherence to Go community standards and improves code readability and maintainability. Changes include: - Run gofmt on all .go files to standardize formatting - Apply lint:fix to automatically resolve linting issues - Fix code style inconsistencies and formatting violations No functional changes were made in this commit.
This commit is contained in:
@@ -109,7 +109,9 @@ const renderType = (type, record = {}, t) => {
|
||||
<Tooltip
|
||||
content={
|
||||
<div className='max-w-xs'>
|
||||
<div className='text-xs text-gray-600'>{t('来源于 IO.NET 部署')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('来源于 IO.NET 部署')}
|
||||
</div>
|
||||
{ionetMeta?.deployment_id && (
|
||||
<div className='text-xs text-gray-500 mt-1'>
|
||||
{t('部署 ID')}: {ionetMeta.deployment_id}
|
||||
|
||||
@@ -19,7 +19,14 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Modal, Button, Space, Typography, Input, Banner } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
Space,
|
||||
Typography,
|
||||
Input,
|
||||
Banner,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { API, copy, showError, showSuccess } from '../../../../helpers';
|
||||
|
||||
const { Text } = Typography;
|
||||
@@ -33,14 +40,21 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => {
|
||||
const startOAuth = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await API.post('/api/channel/codex/oauth/start', {}, { skipErrorHandler: true });
|
||||
const res = await API.post(
|
||||
'/api/channel/codex/oauth/start',
|
||||
{},
|
||||
{ skipErrorHandler: true },
|
||||
);
|
||||
if (!res?.data?.success) {
|
||||
console.error('Codex OAuth start failed:', res?.data?.message);
|
||||
throw new Error(t('启动授权失败'));
|
||||
}
|
||||
const url = res?.data?.data?.authorize_url || '';
|
||||
if (!url) {
|
||||
console.error('Codex OAuth start response missing authorize_url:', res?.data);
|
||||
console.error(
|
||||
'Codex OAuth start response missing authorize_url:',
|
||||
res?.data,
|
||||
);
|
||||
throw new Error(t('响应缺少授权链接'));
|
||||
}
|
||||
setAuthorizeUrl(url);
|
||||
@@ -106,7 +120,12 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => {
|
||||
<Button theme='borderless' onClick={onCancel} disabled={loading}>
|
||||
{t('取消')}
|
||||
</Button>
|
||||
<Button theme='solid' type='primary' onClick={completeOAuth} loading={loading}>
|
||||
<Button
|
||||
theme='solid'
|
||||
type='primary'
|
||||
onClick={completeOAuth}
|
||||
loading={loading}
|
||||
>
|
||||
{t('生成并填入')}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -141,7 +160,9 @@ const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => {
|
||||
/>
|
||||
|
||||
<Text type='tertiary' size='small'>
|
||||
{t('说明:生成结果是可直接粘贴到渠道密钥里的 JSON(包含 access_token / refresh_token / account_id)。')}
|
||||
{t(
|
||||
'说明:生成结果是可直接粘贴到渠道密钥里的 JSON(包含 access_token / refresh_token / account_id)。',
|
||||
)}
|
||||
</Text>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
||||
@@ -18,7 +18,14 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { Modal, Button, Progress, Tag, Typography, Spin } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
Progress,
|
||||
Tag,
|
||||
Typography,
|
||||
Spin,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { API, showError } from '../../../../helpers';
|
||||
|
||||
const { Text } = Typography;
|
||||
@@ -134,7 +141,12 @@ const CodexUsageView = ({ t, record, payload, onCopy, onRefresh }) => {
|
||||
</Text>
|
||||
<div className='flex items-center gap-2'>
|
||||
{statusTag}
|
||||
<Button size='small' type='tertiary' theme='borderless' onClick={onRefresh}>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
theme='borderless'
|
||||
onClick={onRefresh}
|
||||
>
|
||||
{tt('刷新')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -243,7 +255,12 @@ const CodexUsageLoader = ({ t, record, initialPayload, onCopy }) => {
|
||||
<div className='flex flex-col gap-3'>
|
||||
<Text type='danger'>{tt('获取用量失败')}</Text>
|
||||
<div className='flex justify-end'>
|
||||
<Button size='small' type='primary' theme='outline' onClick={fetchUsage}>
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={fetchUsage}
|
||||
>
|
||||
{tt('刷新')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2000,171 +2000,180 @@ const EditChannelModal = (props) => {
|
||||
autoComplete='new-password'
|
||||
onChange={(value) => handleInputChange('key', value)}
|
||||
disabled={isIonetLocked}
|
||||
extraText={
|
||||
<div className='flex items-center gap-2 flex-wrap'>
|
||||
{isEdit &&
|
||||
isMultiKeyChannel &&
|
||||
keyMode === 'append' && (
|
||||
<Text type='warning' size='small'>
|
||||
{t(
|
||||
'追加模式:新密钥将添加到现有密钥列表的末尾',
|
||||
)}
|
||||
</Text>
|
||||
extraText={
|
||||
<div className='flex items-center gap-2 flex-wrap'>
|
||||
{isEdit &&
|
||||
isMultiKeyChannel &&
|
||||
keyMode === 'append' && (
|
||||
<Text type='warning' size='small'>
|
||||
{t(
|
||||
'追加模式:新密钥将添加到现有密钥列表的末尾',
|
||||
)}
|
||||
</Text>
|
||||
)}
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleShow2FAModal}
|
||||
>
|
||||
{t('查看密钥')}
|
||||
</Button>
|
||||
)}
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleShow2FAModal}
|
||||
>
|
||||
{t('查看密钥')}
|
||||
</Button>
|
||||
)}
|
||||
{batchExtra}
|
||||
</div>
|
||||
}
|
||||
showClear
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{inputs.type === 57 ? (
|
||||
<>
|
||||
<Form.TextArea
|
||||
field='key'
|
||||
label={
|
||||
isEdit
|
||||
? t('密钥(编辑模式下,保存的密钥不会显示)')
|
||||
: t('密钥')
|
||||
}
|
||||
placeholder={t(
|
||||
'请输入 JSON 格式的 OAuth 凭据,例如:\n{\n "access_token": "...",\n "account_id": "..." \n}',
|
||||
)}
|
||||
rules={
|
||||
isEdit
|
||||
? []
|
||||
: [{ required: true, message: t('请输入密钥') }]
|
||||
}
|
||||
autoComplete='new-password'
|
||||
onChange={(value) => handleInputChange('key', value)}
|
||||
disabled={isIonetLocked}
|
||||
extraText={
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Text type='tertiary' size='small'>
|
||||
{t(
|
||||
'仅支持 JSON 对象,必须包含 access_token 与 account_id',
|
||||
)}
|
||||
</Text>
|
||||
{batchExtra}
|
||||
</div>
|
||||
}
|
||||
showClear
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
{inputs.type === 57 ? (
|
||||
<>
|
||||
<Form.TextArea
|
||||
field='key'
|
||||
label={
|
||||
isEdit
|
||||
? t('密钥(编辑模式下,保存的密钥不会显示)')
|
||||
: t('密钥')
|
||||
}
|
||||
placeholder={t(
|
||||
'请输入 JSON 格式的 OAuth 凭据,例如:\n{\n "access_token": "...",\n "account_id": "..." \n}',
|
||||
)}
|
||||
rules={
|
||||
isEdit
|
||||
? []
|
||||
: [
|
||||
{
|
||||
required: true,
|
||||
message: t('请输入密钥'),
|
||||
},
|
||||
]
|
||||
}
|
||||
autoComplete='new-password'
|
||||
onChange={(value) =>
|
||||
handleInputChange('key', value)
|
||||
}
|
||||
disabled={isIonetLocked}
|
||||
extraText={
|
||||
<div className='flex flex-col gap-2'>
|
||||
<Text type='tertiary' size='small'>
|
||||
{t(
|
||||
'仅支持 JSON 对象,必须包含 access_token 与 account_id',
|
||||
)}
|
||||
</Text>
|
||||
|
||||
<Space wrap spacing='tight'>
|
||||
<Space wrap spacing='tight'>
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={() =>
|
||||
setCodexOAuthModalVisible(true)
|
||||
}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('Codex 授权')}
|
||||
</Button>
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleRefreshCodexCredential}
|
||||
loading={codexCredentialRefreshing}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('刷新凭证')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={() => formatJsonField('key')}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('格式化')}
|
||||
</Button>
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleShow2FAModal}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('查看密钥')}
|
||||
</Button>
|
||||
)}
|
||||
{batchExtra}
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
autosize
|
||||
showClear
|
||||
/>
|
||||
|
||||
<CodexOAuthModal
|
||||
visible={codexOAuthModalVisible}
|
||||
onCancel={() => setCodexOAuthModalVisible(false)}
|
||||
onSuccess={handleCodexOAuthGenerated}
|
||||
/>
|
||||
</>
|
||||
) : inputs.type === 41 &&
|
||||
(inputs.vertex_key_type || 'json') === 'json' ? (
|
||||
<>
|
||||
{!batch && (
|
||||
<div className='flex items-center justify-between mb-3'>
|
||||
<Text className='text-sm font-medium'>
|
||||
{t('密钥输入方式')}
|
||||
</Text>
|
||||
<Space>
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={() =>
|
||||
setCodexOAuthModalVisible(true)
|
||||
type={
|
||||
!useManualInput ? 'primary' : 'tertiary'
|
||||
}
|
||||
disabled={isIonetLocked}
|
||||
onClick={() => {
|
||||
setUseManualInput(false);
|
||||
// 切换到文件上传模式时清空手动输入的密钥
|
||||
if (formApiRef.current) {
|
||||
formApiRef.current.setValue('key', '');
|
||||
}
|
||||
handleInputChange('key', '');
|
||||
}}
|
||||
>
|
||||
{t('Codex 授权')}
|
||||
{t('文件上传')}
|
||||
</Button>
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleRefreshCodexCredential}
|
||||
loading={codexCredentialRefreshing}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('刷新凭证')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={() => formatJsonField('key')}
|
||||
disabled={isIonetLocked}
|
||||
type={
|
||||
useManualInput ? 'primary' : 'tertiary'
|
||||
}
|
||||
onClick={() => {
|
||||
setUseManualInput(true);
|
||||
// 切换到手动输入模式时清空文件上传相关状态
|
||||
setVertexKeys([]);
|
||||
setVertexFileList([]);
|
||||
if (formApiRef.current) {
|
||||
formApiRef.current.setValue(
|
||||
'vertex_files',
|
||||
[],
|
||||
);
|
||||
}
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
vertex_files: [],
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t('格式化')}
|
||||
{t('手动输入')}
|
||||
</Button>
|
||||
{isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='outline'
|
||||
onClick={handleShow2FAModal}
|
||||
disabled={isIonetLocked}
|
||||
>
|
||||
{t('查看密钥')}
|
||||
</Button>
|
||||
)}
|
||||
{batchExtra}
|
||||
</Space>
|
||||
</div>
|
||||
}
|
||||
autosize
|
||||
showClear
|
||||
/>
|
||||
|
||||
<CodexOAuthModal
|
||||
visible={codexOAuthModalVisible}
|
||||
onCancel={() => setCodexOAuthModalVisible(false)}
|
||||
onSuccess={handleCodexOAuthGenerated}
|
||||
/>
|
||||
</>
|
||||
) : inputs.type === 41 &&
|
||||
(inputs.vertex_key_type || 'json') === 'json' ? (
|
||||
<>
|
||||
{!batch && (
|
||||
<div className='flex items-center justify-between mb-3'>
|
||||
<Text className='text-sm font-medium'>
|
||||
{t('密钥输入方式')}
|
||||
</Text>
|
||||
<Space>
|
||||
<Button
|
||||
size='small'
|
||||
type={
|
||||
!useManualInput ? 'primary' : 'tertiary'
|
||||
}
|
||||
onClick={() => {
|
||||
setUseManualInput(false);
|
||||
// 切换到文件上传模式时清空手动输入的密钥
|
||||
if (formApiRef.current) {
|
||||
formApiRef.current.setValue('key', '');
|
||||
}
|
||||
handleInputChange('key', '');
|
||||
}}
|
||||
>
|
||||
{t('文件上传')}
|
||||
</Button>
|
||||
<Button
|
||||
size='small'
|
||||
type={useManualInput ? 'primary' : 'tertiary'}
|
||||
onClick={() => {
|
||||
setUseManualInput(true);
|
||||
// 切换到手动输入模式时清空文件上传相关状态
|
||||
setVertexKeys([]);
|
||||
setVertexFileList([]);
|
||||
if (formApiRef.current) {
|
||||
formApiRef.current.setValue(
|
||||
'vertex_files',
|
||||
[],
|
||||
);
|
||||
}
|
||||
setInputs((prev) => ({
|
||||
...prev,
|
||||
vertex_files: [],
|
||||
}));
|
||||
}}
|
||||
>
|
||||
{t('手动输入')}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{batch && (
|
||||
<Banner
|
||||
|
||||
@@ -533,7 +533,11 @@ const EditTagModal = (props) => {
|
||||
<Card className='!rounded-2xl shadow-sm border-0 mb-6'>
|
||||
{/* Header: Advanced Settings */}
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar size='small' color='orange' className='mr-2 shadow-md'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='orange'
|
||||
className='mr-2 shadow-md'
|
||||
>
|
||||
<IconSetting size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
@@ -549,9 +553,7 @@ const EditTagModal = (props) => {
|
||||
field='param_override'
|
||||
label={t('参数覆盖')}
|
||||
placeholder={
|
||||
t(
|
||||
'此项可选,用于覆盖请求参数。不支持覆盖 stream 参数',
|
||||
) +
|
||||
t('此项可选,用于覆盖请求参数。不支持覆盖 stream 参数') +
|
||||
'\n' +
|
||||
t('旧格式(直接覆盖):') +
|
||||
'\n{\n "temperature": 0,\n "max_tokens": 1000\n}' +
|
||||
|
||||
@@ -104,7 +104,9 @@ const ModelSelectModal = ({
|
||||
}, [normalizedRedirectModels, normalizedSelectedSet]);
|
||||
|
||||
const filteredModels = models.filter((m) =>
|
||||
String(m || '').toLowerCase().includes(keyword.toLowerCase()),
|
||||
String(m || '')
|
||||
.toLowerCase()
|
||||
.includes(keyword.toLowerCase()),
|
||||
);
|
||||
|
||||
// 分类模型:新获取的模型和已有模型
|
||||
|
||||
@@ -30,7 +30,7 @@ const ConfirmationDialog = ({
|
||||
type = 'danger',
|
||||
deployment,
|
||||
t,
|
||||
loading = false
|
||||
loading = false,
|
||||
}) => {
|
||||
const [confirmText, setConfirmText] = useState('');
|
||||
|
||||
@@ -66,17 +66,17 @@ const ConfirmationDialog = ({
|
||||
okButtonProps={{
|
||||
disabled: !isConfirmed,
|
||||
type: type === 'danger' ? 'danger' : 'primary',
|
||||
loading
|
||||
loading,
|
||||
}}
|
||||
width={480}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<Text type="danger" strong>
|
||||
<div className='space-y-4'>
|
||||
<Text type='danger' strong>
|
||||
{t('此操作具有风险,请确认要继续执行')}。
|
||||
</Text>
|
||||
<Text>
|
||||
{t('请输入部署名称以完成二次确认')}:
|
||||
<Text code className="ml-1">
|
||||
<Text code className='ml-1'>
|
||||
{requiredText || t('未知部署')}
|
||||
</Text>
|
||||
</Text>
|
||||
@@ -87,7 +87,7 @@ const ConfirmationDialog = ({
|
||||
autoFocus
|
||||
/>
|
||||
{!isConfirmed && confirmText && (
|
||||
<Text type="danger" size="small">
|
||||
<Text type='danger' size='small'>
|
||||
{t('部署名称不匹配,请检查后重新输入')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -130,9 +130,7 @@ const ExtendDurationModal = ({
|
||||
? details.locations
|
||||
.map((location) =>
|
||||
Number(
|
||||
location?.id ??
|
||||
location?.location_id ??
|
||||
location?.locationId,
|
||||
location?.id ?? location?.location_id ?? location?.locationId,
|
||||
),
|
||||
)
|
||||
.filter((id) => Number.isInteger(id) && id > 0)
|
||||
@@ -181,9 +179,7 @@ const ExtendDurationModal = ({
|
||||
} else {
|
||||
const message = response.data.message || '';
|
||||
setPriceEstimation(null);
|
||||
setPriceError(
|
||||
t('价格计算失败') + (message ? `: ${message}` : ''),
|
||||
);
|
||||
setPriceError(t('价格计算失败') + (message ? `: ${message}` : ''));
|
||||
}
|
||||
} catch (error) {
|
||||
if (costRequestIdRef.current !== requestId) {
|
||||
@@ -192,9 +188,7 @@ const ExtendDurationModal = ({
|
||||
|
||||
const message = error?.response?.data?.message || error.message || '';
|
||||
setPriceEstimation(null);
|
||||
setPriceError(
|
||||
t('价格计算失败') + (message ? `: ${message}` : ''),
|
||||
);
|
||||
setPriceError(t('价格计算失败') + (message ? `: ${message}` : ''));
|
||||
} finally {
|
||||
if (costRequestIdRef.current === requestId) {
|
||||
setCostLoading(false);
|
||||
@@ -269,11 +263,8 @@ const ExtendDurationModal = ({
|
||||
const newTotalTime = `${currentRemainingTime} + ${durationHours}${t('小时')}`;
|
||||
|
||||
const priceData = priceEstimation || {};
|
||||
const breakdown =
|
||||
priceData.price_breakdown || priceData.PriceBreakdown || {};
|
||||
const currencyLabel = (
|
||||
priceData.currency || priceData.Currency || 'USDC'
|
||||
)
|
||||
const breakdown = priceData.price_breakdown || priceData.PriceBreakdown || {};
|
||||
const currencyLabel = (priceData.currency || priceData.Currency || 'USDC')
|
||||
.toString()
|
||||
.toUpperCase();
|
||||
|
||||
@@ -316,7 +307,10 @@ const ExtendDurationModal = ({
|
||||
confirmLoading={loading}
|
||||
okButtonProps={{
|
||||
disabled:
|
||||
!deployment?.id || detailsLoading || !durationHours || durationHours < 1,
|
||||
!deployment?.id ||
|
||||
detailsLoading ||
|
||||
!durationHours ||
|
||||
durationHours < 1,
|
||||
}}
|
||||
width={600}
|
||||
className='extend-duration-modal'
|
||||
@@ -357,9 +351,7 @@ const ExtendDurationModal = ({
|
||||
<p>
|
||||
{t('延长容器时长将会产生额外费用,请确认您有足够的账户余额。')}
|
||||
</p>
|
||||
<p>
|
||||
{t('延长操作一旦确认无法撤销,费用将立即扣除。')}
|
||||
</p>
|
||||
<p>{t('延长操作一旦确认无法撤销,费用将立即扣除。')}</p>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -370,7 +362,9 @@ const ExtendDurationModal = ({
|
||||
onValueChange={(values) => {
|
||||
if (values.duration_hours !== undefined) {
|
||||
const numericValue = Number(values.duration_hours);
|
||||
setDurationHours(Number.isFinite(numericValue) ? numericValue : 0);
|
||||
setDurationHours(
|
||||
Number.isFinite(numericValue) ? numericValue : 0,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -81,7 +81,9 @@ const renderModels = (text, record, t) => {
|
||||
</div>
|
||||
))}
|
||||
{items.length > 3 && (
|
||||
<div className='text-gray-500'>...{t('共')} {items.length} {t('个模型')}</div>
|
||||
<div className='text-gray-500'>
|
||||
...{t('共')} {items.length} {t('个模型')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,14 +27,8 @@ import {
|
||||
import { getSubscriptionsColumns } from './SubscriptionsColumnDefs';
|
||||
|
||||
const SubscriptionsTable = (subscriptionsData) => {
|
||||
const {
|
||||
plans,
|
||||
loading,
|
||||
compactMode,
|
||||
openEdit,
|
||||
disablePlan,
|
||||
t,
|
||||
} = subscriptionsData;
|
||||
const { plans, loading, compactMode, openEdit, disablePlan, t } =
|
||||
subscriptionsData;
|
||||
|
||||
const columns = useMemo(() => {
|
||||
return getSubscriptionsColumns({
|
||||
@@ -47,12 +41,12 @@ const SubscriptionsTable = (subscriptionsData) => {
|
||||
const tableColumns = useMemo(() => {
|
||||
return compactMode
|
||||
? columns.map((col) => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
: columns;
|
||||
}, [compactMode, columns]);
|
||||
|
||||
|
||||
@@ -169,7 +169,10 @@ const AddEditSubscriptionModal = ({
|
||||
next.push({
|
||||
model_name: modelName,
|
||||
quota_type: modelMeta.quota_type,
|
||||
amount_total: Number.isFinite(defaultAmount) && defaultAmount >= 0 ? defaultAmount : 0,
|
||||
amount_total:
|
||||
Number.isFinite(defaultAmount) && defaultAmount >= 0
|
||||
? defaultAmount
|
||||
: 0,
|
||||
});
|
||||
});
|
||||
setItems(next);
|
||||
@@ -216,7 +219,9 @@ const AddEditSubscriptionModal = ({
|
||||
return;
|
||||
}
|
||||
const keySet = new Set(selectedRowKeys);
|
||||
const next = (items || []).filter((it) => !keySet.has(`${it.model_name}-${it.quota_type}`));
|
||||
const next = (items || []).filter(
|
||||
(it) => !keySet.has(`${it.model_name}-${it.quota_type}`),
|
||||
);
|
||||
setItems(next);
|
||||
setSelectedRowKeys([]);
|
||||
showSuccess(t('已删除选中项'));
|
||||
@@ -417,7 +422,9 @@ const AddEditSubscriptionModal = ({
|
||||
field='title'
|
||||
label={t('套餐标题')}
|
||||
placeholder={t('例如:基础套餐')}
|
||||
rules={[{ required: true, message: t('请输入套餐标题') }]}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入套餐标题') },
|
||||
]}
|
||||
showClear
|
||||
/>
|
||||
</Col>
|
||||
@@ -585,7 +592,9 @@ const AddEditSubscriptionModal = ({
|
||||
<Boxes size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('模型权益')}</Text>
|
||||
<Text className='text-lg font-medium'>
|
||||
{t('模型权益')}
|
||||
</Text>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('配置套餐可使用的模型及额度')}
|
||||
</div>
|
||||
@@ -646,7 +655,9 @@ const AddEditSubscriptionModal = ({
|
||||
<Button
|
||||
theme='light'
|
||||
type='primary'
|
||||
onClick={() => applyBulkAmountTotal({ scope: 'selected' })}
|
||||
onClick={() =>
|
||||
applyBulkAmountTotal({ scope: 'selected' })
|
||||
}
|
||||
>
|
||||
{t('应用到选中')}
|
||||
</Button>
|
||||
|
||||
@@ -378,7 +378,12 @@ const EditTokenModal = (props) => {
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={24} style={{ display: values.group === 'auto' ? 'block' : 'none' }}>
|
||||
<Col
|
||||
span={24}
|
||||
style={{
|
||||
display: values.group === 'auto' ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
<Form.Switch
|
||||
field='cross_group_retry'
|
||||
label={t('跨分组重试')}
|
||||
@@ -561,7 +566,9 @@ const EditTokenModal = (props) => {
|
||||
placeholder={t('允许的IP,一行一个,不填写则不限制')}
|
||||
autosize
|
||||
rows={1}
|
||||
extraText={t('请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用')}
|
||||
extraText={t(
|
||||
'请勿过度信任此功能,IP可能被伪造,请配合nginx和cdn等网关使用',
|
||||
)}
|
||||
showClear
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
|
||||
@@ -203,7 +203,7 @@ function renderModelName(record, copyText, t) {
|
||||
if (!modelMapped) {
|
||||
return renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -220,7 +220,7 @@ function renderModelName(record, copyText, t) {
|
||||
</Typography.Text>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
@@ -231,7 +231,7 @@ function renderModelName(record, copyText, t) {
|
||||
{renderModelTag(other.upstream_model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, other.upstream_model_name).then(
|
||||
(r) => { },
|
||||
(r) => {},
|
||||
);
|
||||
},
|
||||
})}
|
||||
@@ -242,7 +242,7 @@ function renderModelName(record, copyText, t) {
|
||||
>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
},
|
||||
suffixIcon: (
|
||||
<Route
|
||||
@@ -581,7 +581,11 @@ export const getLogsColumns = ({
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Tag className='channel-affinity-tag' color='cyan' shape='circle'>
|
||||
<Tag
|
||||
className='channel-affinity-tag'
|
||||
color='cyan'
|
||||
shape='circle'
|
||||
>
|
||||
<span className='channel-affinity-tag-content'>
|
||||
<IconStarStroked style={{ fontSize: 13 }} />
|
||||
{t('优选')}
|
||||
@@ -653,45 +657,45 @@ export const getLogsColumns = ({
|
||||
|
||||
let content = other?.claude
|
||||
? renderModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
other.cache_creation_tokens_5m || 0,
|
||||
other.cache_creation_ratio_5m ||
|
||||
other.cache_creation_ratio ||
|
||||
1.0,
|
||||
other.cache_creation_tokens_1h || 0,
|
||||
other.cache_creation_ratio_1h ||
|
||||
other.cache_creation_ratio ||
|
||||
1.0,
|
||||
false,
|
||||
1.0,
|
||||
other?.is_system_prompt_overwritten,
|
||||
'claude',
|
||||
)
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
other.cache_creation_tokens_5m || 0,
|
||||
other.cache_creation_ratio_5m ||
|
||||
other.cache_creation_ratio ||
|
||||
1.0,
|
||||
other.cache_creation_tokens_1h || 0,
|
||||
other.cache_creation_ratio_1h ||
|
||||
other.cache_creation_ratio ||
|
||||
1.0,
|
||||
false,
|
||||
1.0,
|
||||
other?.is_system_prompt_overwritten,
|
||||
'claude',
|
||||
)
|
||||
: renderModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
0,
|
||||
1.0,
|
||||
0,
|
||||
1.0,
|
||||
0,
|
||||
1.0,
|
||||
false,
|
||||
1.0,
|
||||
other?.is_system_prompt_overwritten,
|
||||
'openai',
|
||||
);
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
0,
|
||||
1.0,
|
||||
0,
|
||||
1.0,
|
||||
0,
|
||||
1.0,
|
||||
false,
|
||||
1.0,
|
||||
other?.is_system_prompt_overwritten,
|
||||
'openai',
|
||||
);
|
||||
// Do not add billing source here; keep details clean.
|
||||
const summary = [content, text ? `${t('详情')}:${text}` : null]
|
||||
.filter(Boolean)
|
||||
|
||||
@@ -121,4 +121,3 @@ const BindSubscriptionModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
};
|
||||
|
||||
export default BindSubscriptionModal;
|
||||
|
||||
|
||||
@@ -129,7 +129,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
if (!user?.id) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await API.get(`/api/subscription/admin/users/${user.id}/subscriptions`);
|
||||
const res = await API.get(
|
||||
`/api/subscription/admin/users/${user.id}/subscriptions`,
|
||||
);
|
||||
if (res.data?.success) {
|
||||
const next = res.data.data || [];
|
||||
setSubs(next);
|
||||
@@ -167,9 +169,12 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
}
|
||||
setCreating(true);
|
||||
try {
|
||||
const res = await API.post(`/api/subscription/admin/users/${user.id}/subscriptions`, {
|
||||
plan_id: selectedPlanId,
|
||||
});
|
||||
const res = await API.post(
|
||||
`/api/subscription/admin/users/${user.id}/subscriptions`,
|
||||
{
|
||||
plan_id: selectedPlanId,
|
||||
},
|
||||
);
|
||||
if (res.data?.success) {
|
||||
showSuccess(t('新增成功'));
|
||||
setSelectedPlanId(null);
|
||||
@@ -217,7 +222,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
okType: 'danger',
|
||||
onOk: async () => {
|
||||
try {
|
||||
const res = await API.delete(`/api/subscription/admin/user_subscriptions/${subId}`);
|
||||
const res = await API.delete(
|
||||
`/api/subscription/admin/user_subscriptions/${subId}`,
|
||||
);
|
||||
if (res.data?.success) {
|
||||
showSuccess(t('已删除'));
|
||||
await loadUserSubscriptions();
|
||||
@@ -247,7 +254,8 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
render: (_, record) => {
|
||||
const sub = record?.subscription;
|
||||
const planId = sub?.plan_id;
|
||||
const title = planTitleMap.get(planId) || (planId ? `#${planId}` : '-');
|
||||
const title =
|
||||
planTitleMap.get(planId) || (planId ? `#${planId}` : '-');
|
||||
return (
|
||||
<div className='min-w-0'>
|
||||
<div className='font-medium truncate'>{title}</div>
|
||||
@@ -292,7 +300,10 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
const content = (
|
||||
<div className='max-w-[320px] space-y-1'>
|
||||
{items.map((it) => (
|
||||
<div key={`${it.id}-${it.model_name}`} className='flex justify-between text-xs'>
|
||||
<div
|
||||
key={`${it.id}-${it.model_name}`}
|
||||
className='flex justify-between text-xs'
|
||||
>
|
||||
<span className='truncate mr-2'>{it.model_name}</span>
|
||||
<span className='text-gray-600'>
|
||||
{it.amount_used}/{it.amount_total}
|
||||
@@ -319,7 +330,8 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
render: (_, record) => {
|
||||
const sub = record?.subscription;
|
||||
const now = Date.now() / 1000;
|
||||
const isExpired = (sub?.end_time || 0) > 0 && (sub?.end_time || 0) < now;
|
||||
const isExpired =
|
||||
(sub?.end_time || 0) > 0 && (sub?.end_time || 0) < now;
|
||||
const isActive = sub?.status === 'active' && !isExpired;
|
||||
const isCancelled = sub?.status === 'cancelled';
|
||||
return (
|
||||
@@ -412,7 +424,9 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
}}
|
||||
empty={
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
||||
image={
|
||||
<IllustrationNoResult style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
@@ -428,4 +442,3 @@ const UserSubscriptionsModal = ({ visible, onCancel, user, t, onSuccess }) => {
|
||||
};
|
||||
|
||||
export default UserSubscriptionsModal;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user