feat(web): refresh rate limits timestamp and tpm settlement display
This commit is contained in:
+13
-6
@@ -187,6 +187,7 @@ export function App() {
|
|||||||
const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]);
|
const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]);
|
||||||
const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]);
|
const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]);
|
||||||
const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]);
|
const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]);
|
||||||
|
const [modelRateLimitsUpdatedAt, setModelRateLimitsUpdatedAt] = useState<number | null>(null);
|
||||||
const [tenants, setTenants] = useState<GatewayTenant[]>([]);
|
const [tenants, setTenants] = useState<GatewayTenant[]>([]);
|
||||||
const [users, setUsers] = useState<GatewayUser[]>([]);
|
const [users, setUsers] = useState<GatewayUser[]>([]);
|
||||||
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
|
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
|
||||||
@@ -249,6 +250,7 @@ export function App() {
|
|||||||
void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)])
|
void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)])
|
||||||
.then(([rateLimitResponse, platformResponse]) => {
|
.then(([rateLimitResponse, platformResponse]) => {
|
||||||
setModelRateLimits(rateLimitResponse.items);
|
setModelRateLimits(rateLimitResponse.items);
|
||||||
|
setModelRateLimitsUpdatedAt(Date.now());
|
||||||
setPlatforms(platformResponse.items);
|
setPlatforms(platformResponse.items);
|
||||||
loadedDataKeysRef.current.add('modelRateLimits');
|
loadedDataKeysRef.current.add('modelRateLimits');
|
||||||
loadedDataKeysRef.current.add('platforms');
|
loadedDataKeysRef.current.add('platforms');
|
||||||
@@ -299,10 +301,11 @@ export function App() {
|
|||||||
platforms,
|
platforms,
|
||||||
pricingRules,
|
pricingRules,
|
||||||
pricingRuleSets,
|
pricingRuleSets,
|
||||||
providers,
|
providers,
|
||||||
rateLimitWindows,
|
rateLimitWindows,
|
||||||
modelRateLimits,
|
modelRateLimits,
|
||||||
runtimePolicySets,
|
modelRateLimitsUpdatedAt,
|
||||||
|
runtimePolicySets,
|
||||||
taskResult,
|
taskResult,
|
||||||
tasks,
|
tasks,
|
||||||
tenants,
|
tenants,
|
||||||
@@ -310,7 +313,7 @@ export function App() {
|
|||||||
users,
|
users,
|
||||||
walletAccounts,
|
walletAccounts,
|
||||||
walletTransactions,
|
walletTransactions,
|
||||||
}), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, modelRateLimits, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
}), [accessRules, apiKeys, auditLogs, baseModels, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
|
||||||
|
|
||||||
async function refresh(nextToken = token) {
|
async function refresh(nextToken = token) {
|
||||||
await ensureRouteData(nextToken, true);
|
await ensureRouteData(nextToken, true);
|
||||||
@@ -415,7 +418,11 @@ export function App() {
|
|||||||
setRateLimitWindows((await listRateLimitWindows(nextToken)).items);
|
setRateLimitWindows((await listRateLimitWindows(nextToken)).items);
|
||||||
return;
|
return;
|
||||||
case 'modelRateLimits':
|
case 'modelRateLimits':
|
||||||
setModelRateLimits((await listModelRateLimitStatuses(nextToken)).items);
|
{
|
||||||
|
const response = await listModelRateLimitStatuses(nextToken);
|
||||||
|
setModelRateLimits(response.items);
|
||||||
|
setModelRateLimitsUpdatedAt(Date.now());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case 'tenants':
|
case 'tenants':
|
||||||
setTenants((await listTenants(nextToken)).items);
|
setTenants((await listTenants(nextToken)).items);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export interface ConsoleData {
|
|||||||
providers: CatalogProvider[];
|
providers: CatalogProvider[];
|
||||||
rateLimitWindows: RateLimitWindow[];
|
rateLimitWindows: RateLimitWindow[];
|
||||||
modelRateLimits: ModelRateLimitStatus[];
|
modelRateLimits: ModelRateLimitStatus[];
|
||||||
|
modelRateLimitsUpdatedAt: number | null;
|
||||||
runtimePolicySets: RuntimePolicySet[];
|
runtimePolicySets: RuntimePolicySet[];
|
||||||
taskResult: GatewayTask | null;
|
taskResult: GatewayTask | null;
|
||||||
tasks: GatewayTask[];
|
tasks: GatewayTask[];
|
||||||
|
|||||||
@@ -137,9 +137,10 @@ export function AdminPage(props: {
|
|||||||
<PlatformManagementPanel
|
<PlatformManagementPanel
|
||||||
baseModels={props.data.baseModels}
|
baseModels={props.data.baseModels}
|
||||||
message={props.operationMessage}
|
message={props.operationMessage}
|
||||||
networkProxyConfig={props.data.networkProxyConfig}
|
networkProxyConfig={props.data.networkProxyConfig}
|
||||||
modelRateLimits={props.data.modelRateLimits}
|
modelRateLimits={props.data.modelRateLimits}
|
||||||
platformModels={props.data.models}
|
modelRateLimitsUpdatedAt={props.data.modelRateLimitsUpdatedAt}
|
||||||
|
platformModels={props.data.models}
|
||||||
platforms={props.data.platforms}
|
platforms={props.data.platforms}
|
||||||
pricingRuleSets={props.data.pricingRuleSets}
|
pricingRuleSets={props.data.pricingRuleSets}
|
||||||
providers={props.data.providers}
|
providers={props.data.providers}
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ import { ModelCatalogCard } from './ModelCatalogCard';
|
|||||||
|
|
||||||
export function PlatformManagementPanel(props: {
|
export function PlatformManagementPanel(props: {
|
||||||
baseModels: BaseModelCatalogItem[];
|
baseModels: BaseModelCatalogItem[];
|
||||||
message: string;
|
message: string;
|
||||||
modelRateLimits: ModelRateLimitStatus[];
|
modelRateLimits: ModelRateLimitStatus[];
|
||||||
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
|
modelRateLimitsUpdatedAt: number | null;
|
||||||
|
networkProxyConfig: { globalHttpProxy?: string; globalHttpProxySet: boolean; globalHttpProxySource?: string } | null;
|
||||||
platforms: IntegrationPlatform[];
|
platforms: IntegrationPlatform[];
|
||||||
platformModels: PlatformModel[];
|
platformModels: PlatformModel[];
|
||||||
pricingRuleSets: PricingRuleSet[];
|
pricingRuleSets: PricingRuleSet[];
|
||||||
@@ -200,11 +201,16 @@ export function PlatformManagementPanel(props: {
|
|||||||
providerMap={providerMap}
|
providerMap={providerMap}
|
||||||
onModelQueryChange={setModelQuery}
|
onModelQueryChange={setModelQuery}
|
||||||
now={now}
|
now={now}
|
||||||
onPlatformChange={setSelectedPlatformId}
|
onPlatformChange={setSelectedPlatformId}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RateLimitStatusTable statuses={props.modelRateLimits} platformMap={platformMap} now={now} />
|
<RateLimitStatusTable
|
||||||
)}
|
now={now}
|
||||||
|
platformMap={platformMap}
|
||||||
|
statuses={props.modelRateLimits}
|
||||||
|
updatedAt={props.modelRateLimitsUpdatedAt}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{props.message && <p className="formMessage">{props.message}</p>}
|
{props.message && <p className="formMessage">{props.message}</p>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -572,7 +578,7 @@ function PlatformModelTable(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platformMap: Map<string, IntegrationPlatform>; now: number }) {
|
function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platformMap: Map<string, IntegrationPlatform>; now: number; updatedAt: number | null }) {
|
||||||
if (!props.statuses.length) {
|
if (!props.statuses.length) {
|
||||||
return <EmptyState title="暂无限流状态" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />;
|
return <EmptyState title="暂无限流状态" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />;
|
||||||
}
|
}
|
||||||
@@ -580,7 +586,7 @@ function RateLimitStatusTable(props: { statuses: ModelRateLimitStatus[]; platfor
|
|||||||
<section className="platformLimitView">
|
<section className="platformLimitView">
|
||||||
<div className="platformLimitHeader">
|
<div className="platformLimitHeader">
|
||||||
<span><Gauge size={15} />按综合满载率从高到低排序</span>
|
<span><Gauge size={15} />按综合满载率从高到低排序</span>
|
||||||
<small>每 3 秒刷新一次;TPM 显示已结算 + 预占。</small>
|
<small>每 3 秒刷新一次;TPM 显示已结算 + 预占;最新更新时间 {formatTimeOfDay(props.updatedAt)}。</small>
|
||||||
</div>
|
</div>
|
||||||
<div className="platformLimitTableViewport">
|
<div className="platformLimitTableViewport">
|
||||||
<Table className="platformDataTable platformLimitTable">
|
<Table className="platformDataTable platformLimitTable">
|
||||||
@@ -1195,15 +1201,26 @@ function rateLimitMetricText(metric: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function metricCell(metric: ModelRateLimitStatus['rpm'], includeReserved = false) {
|
function metricCell(metric: ModelRateLimitStatus['rpm'], includeReserved = false) {
|
||||||
if (!metric.limited) return <span className="rateMetricCell"><strong>{formatLimit(metric.currentValue)} / 不限</strong><small>未配置上限</small></span>;
|
if (!metric.limited) return <span className="rateMetricCell"><strong>{formatLimit(metric.currentValue)} / 不限</strong><small>{includeReserved ? reservedMetricText(metric) : '未配置上限'}</small></span>;
|
||||||
return (
|
return (
|
||||||
<span className="rateMetricCell">
|
<span className="rateMetricCell">
|
||||||
<strong>{formatLimit(metric.currentValue)} / {formatLimit(metric.limitValue)}</strong>
|
<strong>{formatLimit(metric.currentValue)} / {formatLimit(metric.limitValue)}</strong>
|
||||||
<small>{includeReserved && metric.reservedValue > 0 ? `已用 ${formatLimit(metric.usedValue)} · 预占 ${formatLimit(metric.reservedValue)}` : `窗口 ${formatPercent(metric.ratio)}`}</small>
|
<small>{includeReserved ? reservedMetricText(metric) : `窗口 ${formatPercent(metric.ratio)}`}</small>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reservedMetricText(metric: ModelRateLimitStatus['rpm']) {
|
||||||
|
return `已结算 ${formatLimit(metric.usedValue)} + 预占 ${formatLimit(metric.reservedValue)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimeOfDay(timestamp: number | null) {
|
||||||
|
if (!timestamp) return '暂无';
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
const pad = (value: number) => String(value).padStart(2, '0');
|
||||||
|
return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||||
|
}
|
||||||
|
|
||||||
function modelRuntimeStatusCell(status: ModelRateLimitStatus, now: number) {
|
function modelRuntimeStatusCell(status: ModelRateLimitStatus, now: number) {
|
||||||
const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now);
|
const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now);
|
||||||
const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now);
|
const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now);
|
||||||
|
|||||||
Reference in New Issue
Block a user