🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)
- Ran: bun run eslint:fix && bun run lint:fix - Inserted AGPL license header via eslint-plugin-header - Enforced no-multiple-empty-lines and other lint rules - Formatted code using Prettier v3 (@so1ve/prettier-config) - No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
@@ -62,35 +62,35 @@ const TokensActions = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1">
|
||||
<div className='flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1'>
|
||||
<Button
|
||||
type="primary"
|
||||
className="flex-1 md:flex-initial"
|
||||
type='primary'
|
||||
className='flex-1 md:flex-initial'
|
||||
onClick={() => {
|
||||
setEditingToken({
|
||||
id: undefined,
|
||||
});
|
||||
setShowEdit(true);
|
||||
}}
|
||||
size="small"
|
||||
size='small'
|
||||
>
|
||||
{t('添加令牌')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type='tertiary'
|
||||
className="flex-1 md:flex-initial"
|
||||
className='flex-1 md:flex-initial'
|
||||
onClick={handleCopySelectedTokens}
|
||||
size="small"
|
||||
size='small'
|
||||
>
|
||||
{t('复制所选令牌')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type='danger'
|
||||
className="w-full md:w-auto"
|
||||
className='w-full md:w-auto'
|
||||
onClick={handleDeleteSelectedTokens}
|
||||
size="small"
|
||||
size='small'
|
||||
>
|
||||
{t('删除所选令牌')}
|
||||
</Button>
|
||||
@@ -115,4 +115,4 @@ const TokensActions = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default TokensActions;
|
||||
export default TokensActions;
|
||||
|
||||
@@ -31,14 +31,14 @@ import {
|
||||
Popover,
|
||||
Typography,
|
||||
Input,
|
||||
Modal
|
||||
Modal,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
timestamp2string,
|
||||
renderGroup,
|
||||
renderQuota,
|
||||
getModelCategories,
|
||||
showError
|
||||
showError,
|
||||
} from '../../../helpers';
|
||||
import {
|
||||
IconTreeTriangleDown,
|
||||
@@ -92,10 +92,15 @@ const renderGroupColumn = (text, t) => {
|
||||
if (text === 'auto') {
|
||||
return (
|
||||
<Tooltip
|
||||
content={t('当前分组为 auto,会自动选择最优分组,当一个组不可用时自动降级到下一个组(熔断机制)')}
|
||||
content={t(
|
||||
'当前分组为 auto,会自动选择最优分组,当一个组不可用时自动降级到下一个组(熔断机制)',
|
||||
)}
|
||||
position='top'
|
||||
>
|
||||
<Tag color='white' shape='circle'> {t('智能熔断')} </Tag>
|
||||
<Tag color='white' shape='circle'>
|
||||
{' '}
|
||||
{t('智能熔断')}{' '}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -105,7 +110,8 @@ const renderGroupColumn = (text, t) => {
|
||||
// Render token key column with show/hide and copy functionality
|
||||
const renderTokenKey = (text, record, showKeys, setShowKeys, copyText) => {
|
||||
const fullKey = 'sk-' + record.key;
|
||||
const maskedKey = 'sk-' + record.key.slice(0, 4) + '**********' + record.key.slice(-4);
|
||||
const maskedKey =
|
||||
'sk-' + record.key.slice(0, 4) + '**********' + record.key.slice(-4);
|
||||
const revealed = !!showKeys[record.id];
|
||||
|
||||
return (
|
||||
@@ -124,7 +130,7 @@ const renderTokenKey = (text, record, showKeys, setShowKeys, copyText) => {
|
||||
aria-label='toggle token visibility'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowKeys(prev => ({ ...prev, [record.id]: !revealed }));
|
||||
setShowKeys((prev) => ({ ...prev, [record.id]: !revealed }));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
@@ -156,14 +162,25 @@ const renderModelLimits = (text, record, t) => {
|
||||
Object.entries(categories).forEach(([key, category]) => {
|
||||
if (key === 'all') return;
|
||||
if (!category.icon || !category.filter) return;
|
||||
const vendorModels = models.filter((m) => category.filter({ model_name: m }));
|
||||
const vendorModels = models.filter((m) =>
|
||||
category.filter({ model_name: m }),
|
||||
);
|
||||
if (vendorModels.length > 0) {
|
||||
vendorAvatars.push(
|
||||
<Tooltip key={key} content={vendorModels.join(', ')} position='top' showArrow>
|
||||
<Avatar size='extra-extra-small' alt={category.label} color='transparent'>
|
||||
<Tooltip
|
||||
key={key}
|
||||
content={vendorModels.join(', ')}
|
||||
position='top'
|
||||
showArrow
|
||||
>
|
||||
<Avatar
|
||||
size='extra-extra-small'
|
||||
alt={category.label}
|
||||
color='transparent'
|
||||
>
|
||||
{category.icon}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
</Tooltip>,
|
||||
);
|
||||
vendorModels.forEach((m) => matchedModels.add(m));
|
||||
}
|
||||
@@ -172,19 +189,20 @@ const renderModelLimits = (text, record, t) => {
|
||||
const unmatchedModels = models.filter((m) => !matchedModels.has(m));
|
||||
if (unmatchedModels.length > 0) {
|
||||
vendorAvatars.push(
|
||||
<Tooltip key='unknown' content={unmatchedModels.join(', ')} position='top' showArrow>
|
||||
<Tooltip
|
||||
key='unknown'
|
||||
content={unmatchedModels.join(', ')}
|
||||
position='top'
|
||||
showArrow
|
||||
>
|
||||
<Avatar size='extra-extra-small' alt='unknown'>
|
||||
{t('其他')}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
</Tooltip>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AvatarGroup size='extra-extra-small'>
|
||||
{vendorAvatars}
|
||||
</AvatarGroup>
|
||||
);
|
||||
return <AvatarGroup size='extra-extra-small'>{vendorAvatars}</AvatarGroup>;
|
||||
} else {
|
||||
return (
|
||||
<Tag color='white' shape='circle'>
|
||||
@@ -226,10 +244,8 @@ const renderAllowIps = (text, t) => {
|
||||
position='top'
|
||||
showArrow
|
||||
>
|
||||
<Tag shape='circle'>
|
||||
{'+' + extraCount}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
<Tag shape='circle'>{'+' + extraCount}</Tag>
|
||||
</Tooltip>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -291,7 +307,16 @@ const renderQuotaUsage = (text, record, t) => {
|
||||
};
|
||||
|
||||
// Render operations column
|
||||
const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit, manageToken, refresh, t) => {
|
||||
const renderOperations = (
|
||||
text,
|
||||
record,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
setShowEdit,
|
||||
manageToken,
|
||||
refresh,
|
||||
t,
|
||||
) => {
|
||||
let chatsArray = [];
|
||||
try {
|
||||
const raw = localStorage.getItem('chats');
|
||||
@@ -317,11 +342,11 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
return (
|
||||
<Space wrap>
|
||||
<SplitButtonGroup
|
||||
className="overflow-hidden"
|
||||
className='overflow-hidden'
|
||||
aria-label={t('项目操作按钮组')}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
size='small'
|
||||
type='tertiary'
|
||||
onClick={() => {
|
||||
if (chatsArray.length === 0) {
|
||||
@@ -334,15 +359,11 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
>
|
||||
{t('聊天')}
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger='click'
|
||||
position='bottomRight'
|
||||
menu={chatsArray}
|
||||
>
|
||||
<Dropdown trigger='click' position='bottomRight' menu={chatsArray}>
|
||||
<Button
|
||||
type='tertiary'
|
||||
icon={<IconTreeTriangleDown />}
|
||||
size="small"
|
||||
size='small'
|
||||
></Button>
|
||||
</Dropdown>
|
||||
</SplitButtonGroup>
|
||||
@@ -350,7 +371,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
{record.status === 1 ? (
|
||||
<Button
|
||||
type='danger'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={async () => {
|
||||
await manageToken(record.id, 'disable', record);
|
||||
await refresh();
|
||||
@@ -360,7 +381,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={async () => {
|
||||
await manageToken(record.id, 'enable', record);
|
||||
await refresh();
|
||||
@@ -372,7 +393,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
|
||||
<Button
|
||||
type='tertiary'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => {
|
||||
setEditingToken(record);
|
||||
setShowEdit(true);
|
||||
@@ -383,7 +404,7 @@ const renderOperations = (text, record, onOpenLink, setEditingToken, setShowEdit
|
||||
|
||||
<Button
|
||||
type='danger'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: t('确定是否要删除此令牌?'),
|
||||
@@ -439,7 +460,8 @@ export const getTokensColumns = ({
|
||||
{
|
||||
title: t('密钥'),
|
||||
key: 'token_key',
|
||||
render: (text, record) => renderTokenKey(text, record, showKeys, setShowKeys, copyText),
|
||||
render: (text, record) =>
|
||||
renderTokenKey(text, record, showKeys, setShowKeys, copyText),
|
||||
},
|
||||
{
|
||||
title: t('可用模型'),
|
||||
@@ -473,16 +495,17 @@ export const getTokensColumns = ({
|
||||
title: '',
|
||||
dataIndex: 'operate',
|
||||
fixed: 'right',
|
||||
render: (text, record, index) => renderOperations(
|
||||
text,
|
||||
record,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
setShowEdit,
|
||||
manageToken,
|
||||
refresh,
|
||||
t
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
renderOperations(
|
||||
text,
|
||||
record,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
setShowEdit,
|
||||
manageToken,
|
||||
refresh,
|
||||
t,
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,9 +26,9 @@ const { Text } = Typography;
|
||||
|
||||
const TokensDescription = ({ compactMode, setCompactMode, t }) => {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-blue-500">
|
||||
<Key size={16} className="mr-2" />
|
||||
<div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'>
|
||||
<div className='flex items-center text-blue-500'>
|
||||
<Key size={16} className='mr-2' />
|
||||
<Text>{t('令牌管理')}</Text>
|
||||
</div>
|
||||
|
||||
@@ -41,4 +41,4 @@ const TokensDescription = ({ compactMode, setCompactMode, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TokensDescription;
|
||||
export default TokensDescription;
|
||||
|
||||
@@ -49,42 +49,42 @@ const TokensFilters = ({
|
||||
}}
|
||||
onSubmit={searchTokens}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
autoComplete='off'
|
||||
layout='horizontal'
|
||||
trigger='change'
|
||||
stopValidateWithError={false}
|
||||
className="w-full md:w-auto order-1 md:order-2"
|
||||
className='w-full md:w-auto order-1 md:order-2'
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-2 w-full md:w-auto">
|
||||
<div className="relative w-full md:w-56">
|
||||
<div className='flex flex-col md:flex-row items-center gap-2 w-full md:w-auto'>
|
||||
<div className='relative w-full md:w-56'>
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
field='searchKeyword'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索关键字')}
|
||||
showClear
|
||||
pure
|
||||
size="small"
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full md:w-56">
|
||||
<div className='relative w-full md:w-56'>
|
||||
<Form.Input
|
||||
field="searchToken"
|
||||
field='searchToken'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('密钥')}
|
||||
showClear
|
||||
pure
|
||||
size="small"
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 w-full md:w-auto">
|
||||
<div className='flex gap-2 w-full md:w-auto'>
|
||||
<Button
|
||||
type="tertiary"
|
||||
htmlType="submit"
|
||||
type='tertiary'
|
||||
htmlType='submit'
|
||||
loading={loading || searching}
|
||||
className="flex-1 md:flex-initial md:w-auto"
|
||||
size="small"
|
||||
className='flex-1 md:flex-initial md:w-auto'
|
||||
size='small'
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
@@ -92,8 +92,8 @@ const TokensFilters = ({
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={handleReset}
|
||||
className="flex-1 md:flex-initial md:w-auto"
|
||||
size="small"
|
||||
className='flex-1 md:flex-initial md:w-auto'
|
||||
size='small'
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
@@ -103,4 +103,4 @@ const TokensFilters = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default TokensFilters;
|
||||
export default TokensFilters;
|
||||
|
||||
@@ -76,13 +76,15 @@ const TokensTable = (tokensData) => {
|
||||
|
||||
// Handle compact mode by removing fixed positioning
|
||||
const tableColumns = useMemo(() => {
|
||||
return compactMode ? columns.map(col => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
}) : columns;
|
||||
return compactMode
|
||||
? columns.map((col) => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
: columns;
|
||||
}, [compactMode, columns]);
|
||||
|
||||
return (
|
||||
@@ -106,15 +108,17 @@ const TokensTable = (tokensData) => {
|
||||
empty={
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
description={t('搜索无结果')}
|
||||
style={{ padding: 30 }}
|
||||
/>
|
||||
}
|
||||
className="rounded-xl overflow-hidden"
|
||||
size="middle"
|
||||
className='rounded-xl overflow-hidden'
|
||||
size='middle'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default TokensTable;
|
||||
export default TokensTable;
|
||||
|
||||
@@ -18,8 +18,20 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Notification, Button, Space, Toast, Typography, Select } from '@douyinfe/semi-ui';
|
||||
import { API, showError, getModelCategories, selectFilter } from '../../../helpers';
|
||||
import {
|
||||
Notification,
|
||||
Button,
|
||||
Space,
|
||||
Toast,
|
||||
Typography,
|
||||
Select,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
API,
|
||||
showError,
|
||||
getModelCategories,
|
||||
selectFilter,
|
||||
} from '../../../helpers';
|
||||
import CardPro from '../../common/ui/CardPro';
|
||||
import TokensTable from './TokensTable';
|
||||
import TokensActions from './TokensActions';
|
||||
@@ -33,9 +45,17 @@ import { createCardProPagination } from '../../../helpers/utils';
|
||||
function TokensPage() {
|
||||
// Define the function first, then pass it into the hook to avoid TDZ errors
|
||||
const openFluentNotificationRef = useRef(null);
|
||||
const tokensData = useTokensData((key) => openFluentNotificationRef.current?.(key));
|
||||
const tokensData = useTokensData((key) =>
|
||||
openFluentNotificationRef.current?.(key),
|
||||
);
|
||||
const isMobile = useIsMobile();
|
||||
const latestRef = useRef({ tokens: [], selectedKeys: [], t: (k) => k, selectedModel: '', prefillKey: '' });
|
||||
const latestRef = useRef({
|
||||
tokens: [],
|
||||
selectedKeys: [],
|
||||
t: (k) => k,
|
||||
selectedModel: '',
|
||||
prefillKey: '',
|
||||
});
|
||||
const [modelOptions, setModelOptions] = useState([]);
|
||||
const [selectedModel, setSelectedModel] = useState('');
|
||||
const [fluentNoticeOpen, setFluentNoticeOpen] = useState(false);
|
||||
@@ -50,7 +70,13 @@ function TokensPage() {
|
||||
selectedModel,
|
||||
prefillKey,
|
||||
};
|
||||
}, [tokensData.tokens, tokensData.selectedKeys, tokensData.t, selectedModel, prefillKey]);
|
||||
}, [
|
||||
tokensData.tokens,
|
||||
tokensData.selectedKeys,
|
||||
tokensData.t,
|
||||
selectedModel,
|
||||
prefillKey,
|
||||
]);
|
||||
|
||||
const loadModels = async () => {
|
||||
try {
|
||||
@@ -68,7 +94,7 @@ function TokensPage() {
|
||||
}
|
||||
return {
|
||||
label: (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className='flex items-center gap-1'>
|
||||
{icon}
|
||||
{model}
|
||||
</span>
|
||||
@@ -90,7 +116,7 @@ function TokensPage() {
|
||||
const SUPPRESS_KEY = 'fluent_notify_suppressed';
|
||||
if (modelOptions.length === 0) {
|
||||
// fire-and-forget; a later effect will refresh the notice content
|
||||
loadModels()
|
||||
loadModels();
|
||||
}
|
||||
if (!key && localStorage.getItem(SUPPRESS_KEY) === '1') return;
|
||||
const container = document.getElementById('fluent-new-api-container');
|
||||
@@ -123,19 +149,29 @@ function TokensPage() {
|
||||
/>
|
||||
</div>
|
||||
<Space>
|
||||
<Button theme="solid" type="primary" onClick={handlePrefillToFluent}>
|
||||
<Button
|
||||
theme='solid'
|
||||
type='primary'
|
||||
onClick={handlePrefillToFluent}
|
||||
>
|
||||
{t('一键填充到 FluentRead')}
|
||||
</Button>
|
||||
{!key && (
|
||||
<Button type="warning" onClick={() => {
|
||||
localStorage.setItem(SUPPRESS_KEY, '1');
|
||||
Notification.close('fluent-detected');
|
||||
Toast.info(t('已关闭后续提醒'));
|
||||
}}>
|
||||
<Button
|
||||
type='warning'
|
||||
onClick={() => {
|
||||
localStorage.setItem(SUPPRESS_KEY, '1');
|
||||
Notification.close('fluent-detected');
|
||||
Toast.info(t('已关闭后续提醒'));
|
||||
}}
|
||||
>
|
||||
{t('不再提醒')}
|
||||
</Button>
|
||||
)}
|
||||
<Button type="tertiary" onClick={() => Notification.close('fluent-detected')}>
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={() => Notification.close('fluent-detected')}
|
||||
>
|
||||
{t('关闭')}
|
||||
</Button>
|
||||
</Space>
|
||||
@@ -149,7 +185,13 @@ function TokensPage() {
|
||||
|
||||
// Prefill to Fluent handler
|
||||
const handlePrefillToFluent = () => {
|
||||
const { tokens, selectedKeys, t, selectedModel: chosenModel, prefillKey: overrideKey } = latestRef.current;
|
||||
const {
|
||||
tokens,
|
||||
selectedKeys,
|
||||
t,
|
||||
selectedModel: chosenModel,
|
||||
prefillKey: overrideKey,
|
||||
} = latestRef.current;
|
||||
const container = document.getElementById('fluent-new-api-container');
|
||||
if (!container) {
|
||||
Toast.error(t('未检测到 Fluent 容器'));
|
||||
@@ -167,7 +209,7 @@ function TokensPage() {
|
||||
try {
|
||||
status = JSON.parse(status);
|
||||
serverAddress = status.server_address || '';
|
||||
} catch (_) { }
|
||||
} catch (_) {}
|
||||
}
|
||||
if (!serverAddress) serverAddress = window.location.origin;
|
||||
|
||||
@@ -175,9 +217,12 @@ function TokensPage() {
|
||||
if (overrideKey) {
|
||||
apiKeyToUse = 'sk-' + overrideKey;
|
||||
} else {
|
||||
const token = (selectedKeys && selectedKeys.length === 1)
|
||||
? selectedKeys[0]
|
||||
: (tokens && tokens.length > 0 ? tokens[0] : null);
|
||||
const token =
|
||||
selectedKeys && selectedKeys.length === 1
|
||||
? selectedKeys[0]
|
||||
: tokens && tokens.length > 0
|
||||
? tokens[0]
|
||||
: null;
|
||||
if (!token) {
|
||||
Toast.warning(t('没有可用令牌用于填充'));
|
||||
return;
|
||||
@@ -192,7 +237,9 @@ function TokensPage() {
|
||||
model: chosenModel,
|
||||
};
|
||||
|
||||
container.dispatchEvent(new CustomEvent('fluent:prefill', { detail: payload }));
|
||||
container.dispatchEvent(
|
||||
new CustomEvent('fluent:prefill', { detail: payload }),
|
||||
);
|
||||
Toast.success(t('已发送到 Fluent'));
|
||||
Notification.close('fluent-detected');
|
||||
};
|
||||
@@ -230,13 +277,18 @@ function TokensPage() {
|
||||
const existing = document.querySelector(selector);
|
||||
if (existing) {
|
||||
console.log('Fluent container detected (initial):', existing);
|
||||
window.dispatchEvent(new CustomEvent('fluent-container:appeared', { detail: existing }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('fluent-container:appeared', { detail: existing }),
|
||||
);
|
||||
}
|
||||
|
||||
const isOrContainsTarget = (node) => {
|
||||
if (!(node && node.nodeType === 1)) return false;
|
||||
if (node.id === 'fluent-new-api-container') return true;
|
||||
return typeof node.querySelector === 'function' && !!node.querySelector(selector);
|
||||
return (
|
||||
typeof node.querySelector === 'function' &&
|
||||
!!node.querySelector(selector)
|
||||
);
|
||||
};
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
@@ -247,7 +299,9 @@ function TokensPage() {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) {
|
||||
console.log('Fluent container appeared:', el);
|
||||
window.dispatchEvent(new CustomEvent('fluent-container:appeared', { detail: el }));
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('fluent-container:appeared', { detail: el }),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -310,7 +364,7 @@ function TokensPage() {
|
||||
/>
|
||||
|
||||
<CardPro
|
||||
type="type1"
|
||||
type='type1'
|
||||
descriptionArea={
|
||||
<TokensDescription
|
||||
compactMode={compactMode}
|
||||
@@ -319,7 +373,7 @@ function TokensPage() {
|
||||
/>
|
||||
}
|
||||
actionsArea={
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-2 w-full">
|
||||
<div className='flex flex-col md:flex-row justify-between items-center gap-2 w-full'>
|
||||
<TokensActions
|
||||
selectedKeys={selectedKeys}
|
||||
setEditingToken={setEditingToken}
|
||||
@@ -330,7 +384,7 @@ function TokensPage() {
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<div className="w-full md:w-full lg:w-auto order-1 md:order-2">
|
||||
<div className='w-full md:w-full lg:w-auto order-1 md:order-2'>
|
||||
<TokensFilters
|
||||
formInitValues={formInitValues}
|
||||
setFormApi={setFormApi}
|
||||
@@ -359,4 +413,4 @@ function TokensPage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default TokensPage;
|
||||
export default TokensPage;
|
||||
|
||||
@@ -49,17 +49,10 @@ const CopyTokensModal = ({ visible, onCancel, selectedKeys, copyText, t }) => {
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<Space>
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={handleCopyWithName}
|
||||
>
|
||||
<Button type='tertiary' onClick={handleCopyWithName}>
|
||||
{t('名称+密钥')}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCopyKeyOnly}
|
||||
>
|
||||
{t('仅密钥')}
|
||||
</Button>
|
||||
<Button onClick={handleCopyKeyOnly}>{t('仅密钥')}</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
@@ -68,4 +61,4 @@ const CopyTokensModal = ({ visible, onCancel, selectedKeys, copyText, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CopyTokensModal;
|
||||
export default CopyTokensModal;
|
||||
|
||||
@@ -20,20 +20,28 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import React from 'react';
|
||||
import { Modal } from '@douyinfe/semi-ui';
|
||||
|
||||
const DeleteTokensModal = ({ visible, onCancel, onConfirm, selectedKeys, t }) => {
|
||||
const DeleteTokensModal = ({
|
||||
visible,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
selectedKeys,
|
||||
t,
|
||||
}) => {
|
||||
return (
|
||||
<Modal
|
||||
title={t('批量删除令牌')}
|
||||
visible={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={onConfirm}
|
||||
type="warning"
|
||||
type='warning'
|
||||
>
|
||||
<div>
|
||||
{t('确定要删除所选的 {{count}} 个令牌吗?', { count: selectedKeys.length })}
|
||||
{t('确定要删除所选的 {{count}} 个令牌吗?', {
|
||||
count: selectedKeys.length,
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteTokensModal;
|
||||
export default DeleteTokensModal;
|
||||
|
||||
@@ -111,7 +111,7 @@ const EditTokenModal = (props) => {
|
||||
}
|
||||
return {
|
||||
label: (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className='flex items-center gap-1'>
|
||||
{icon}
|
||||
{model}
|
||||
</span>
|
||||
@@ -239,7 +239,8 @@ const EditTokenModal = (props) => {
|
||||
let successCount = 0;
|
||||
for (let i = 0; i < count; i++) {
|
||||
let { tokenCount: _tc, ...localInputs } = values;
|
||||
const baseName = values.name.trim() === '' ? 'default' : values.name.trim();
|
||||
const baseName =
|
||||
values.name.trim() === '' ? 'default' : values.name.trim();
|
||||
if (i !== 0 || values.name.trim() === '') {
|
||||
localInputs.name = `${baseName}-${generateRandomSuffix()}`;
|
||||
} else {
|
||||
@@ -343,7 +344,9 @@ const EditTokenModal = (props) => {
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('基本信息')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('设置令牌的基本信息')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('设置令牌的基本信息')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
@@ -387,13 +390,16 @@ const EditTokenModal = (props) => {
|
||||
{
|
||||
validator: (rule, value) => {
|
||||
// 允许 -1 表示永不过期,也允许空值在必填校验时被拦截
|
||||
if (value === -1 || !value) return Promise.resolve();
|
||||
if (value === -1 || !value)
|
||||
return Promise.resolve();
|
||||
const time = Date.parse(value);
|
||||
if (isNaN(time)) {
|
||||
return Promise.reject(t('过期时间格式错误!'));
|
||||
}
|
||||
if (time <= Date.now()) {
|
||||
return Promise.reject(t('过期时间不能早于当前时间!'));
|
||||
return Promise.reject(
|
||||
t('过期时间不能早于当前时间!'),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
@@ -444,7 +450,9 @@ const EditTokenModal = (props) => {
|
||||
label={t('新建数量')}
|
||||
min={1}
|
||||
extraText={t('批量创建时会在名称后自动添加随机后缀')}
|
||||
rules={[{ required: true, message: t('请输入新建数量') }]}
|
||||
rules={[
|
||||
{ required: true, message: t('请输入新建数量') },
|
||||
]}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Col>
|
||||
@@ -460,7 +468,9 @@ const EditTokenModal = (props) => {
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('额度设置')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('设置令牌可用额度和数量')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('设置令牌可用额度和数量')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
@@ -472,7 +482,11 @@ const EditTokenModal = (props) => {
|
||||
type='number'
|
||||
disabled={values.unlimited_quota}
|
||||
extraText={renderQuotaWithPrompt(values.remain_quota)}
|
||||
rules={values.unlimited_quota ? [] : [{ required: true, message: t('请输入额度') }]}
|
||||
rules={
|
||||
values.unlimited_quota
|
||||
? []
|
||||
: [{ required: true, message: t('请输入额度') }]
|
||||
}
|
||||
data={[
|
||||
{ value: 500000, label: '1$' },
|
||||
{ value: 5000000, label: '10$' },
|
||||
@@ -488,7 +502,9 @@ const EditTokenModal = (props) => {
|
||||
field='unlimited_quota'
|
||||
label={t('无限额度')}
|
||||
size='large'
|
||||
extraText={t('令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制')}
|
||||
extraText={t(
|
||||
'令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制',
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -497,12 +513,18 @@ const EditTokenModal = (props) => {
|
||||
{/* 访问限制 */}
|
||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||
<div className='flex items-center mb-2'>
|
||||
<Avatar size='small' color='purple' className='mr-2 shadow-md'>
|
||||
<Avatar
|
||||
size='small'
|
||||
color='purple'
|
||||
className='mr-2 shadow-md'
|
||||
>
|
||||
<IconLink size={16} />
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('访问限制')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('设置令牌的访问限制')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('设置令牌的访问限制')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
@@ -510,7 +532,9 @@ const EditTokenModal = (props) => {
|
||||
<Form.Select
|
||||
field='model_limits'
|
||||
label={t('模型限制列表')}
|
||||
placeholder={t('请选择该令牌支持的模型,留空支持所有模型')}
|
||||
placeholder={t(
|
||||
'请选择该令牌支持的模型,留空支持所有模型',
|
||||
)}
|
||||
multiple
|
||||
optionList={models}
|
||||
extraText={t('非必要,不建议启用模型限制')}
|
||||
@@ -543,4 +567,4 @@ const EditTokenModal = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTokenModal;
|
||||
export default EditTokenModal;
|
||||
|
||||
Reference in New Issue
Block a user