Add runtime restore and temp asset cleanup
This commit is contained in:
@@ -84,6 +84,7 @@ import {
|
||||
pollTaskUntilSettled,
|
||||
registerLocalAccount,
|
||||
replacePlatformModels,
|
||||
restoreModelRuntimeStatus,
|
||||
setUserWalletBalance,
|
||||
type HealthResponse,
|
||||
updateAccessRule,
|
||||
@@ -635,6 +636,50 @@ export function App() {
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreRuntimeModel(platformModelId: string) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
try {
|
||||
const restored = await restoreModelRuntimeStatus(token, platformModelId);
|
||||
setModelRateLimits((current) => current.map((status) => {
|
||||
if (status.platformId !== restored.platformId) return status;
|
||||
return {
|
||||
...status,
|
||||
platformStatus: 'enabled',
|
||||
platformCooldownUntil: undefined,
|
||||
platformDisabledReason: undefined,
|
||||
...(status.platformModelId === platformModelId
|
||||
? {
|
||||
enabled: restored.enabled,
|
||||
modelCooldownUntil: restored.modelCooldownUntil,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}));
|
||||
setPlatforms((current) => current.map((platform) => platform.id === restored.platformId
|
||||
? {
|
||||
...platform,
|
||||
status: 'enabled',
|
||||
cooldownUntil: undefined,
|
||||
}
|
||||
: platform));
|
||||
setModels((current) => current.map((model) => model.id === platformModelId
|
||||
? {
|
||||
...model,
|
||||
enabled: true,
|
||||
cooldownUntil: undefined,
|
||||
}
|
||||
: model));
|
||||
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'models', 'platforms', 'playgroundModels');
|
||||
setCoreState('ready');
|
||||
setCoreMessage('模型运行状态已恢复。');
|
||||
} catch (err) {
|
||||
setCoreState('error');
|
||||
setCoreMessage(err instanceof Error ? err.message : '恢复模型运行状态失败');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function removePlatform(platformId: string) {
|
||||
setCoreState('loading');
|
||||
setCoreMessage('');
|
||||
@@ -1143,6 +1188,7 @@ export function App() {
|
||||
onResetBaseModel={resetBaseModelToDefault}
|
||||
onSavePlatform={savePlatformWithModels}
|
||||
onSavePlatformDynamicPriority={savePlatformDynamicPriority}
|
||||
onRestoreRuntimeModel={restoreRuntimeModel}
|
||||
onTogglePlatformStatus={savePlatformStatus}
|
||||
onSaveProvider={saveProvider}
|
||||
onSavePricingRuleSet={savePricingRuleSet}
|
||||
|
||||
@@ -913,6 +913,13 @@ export async function listModelRateLimitStatuses(token: string): Promise<ListRes
|
||||
return request<ListResponse<ModelRateLimitStatus>>('/api/admin/runtime/model-rate-limits', { token });
|
||||
}
|
||||
|
||||
export async function restoreModelRuntimeStatus(token: string, platformModelId: string): Promise<ModelRateLimitStatus> {
|
||||
return request<ModelRateLimitStatus>(`/api/admin/runtime/model-rate-limits/${platformModelId}/restore`, {
|
||||
method: 'POST',
|
||||
token,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getNetworkProxyConfig(token: string): Promise<GatewayNetworkProxyConfig> {
|
||||
return request<GatewayNetworkProxyConfig>('/api/admin/config/network-proxy', { token });
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ export function AdminPage(props: {
|
||||
onBatchAccessRules: (input: GatewayAccessRuleBatchRequest) => Promise<void>;
|
||||
onSavePlatform: (input: PlatformWithModelsInput) => Promise<void>;
|
||||
onSavePlatformDynamicPriority: (platformId: string, input: PlatformDynamicPriorityUpdateRequest) => Promise<void>;
|
||||
onRestoreRuntimeModel: (platformModelId: string) => Promise<void>;
|
||||
onTogglePlatformStatus: (platform: IntegrationPlatform, status: 'enabled' | 'disabled') => Promise<void>;
|
||||
onSaveProvider: (input: CatalogProviderUpsertRequest, providerId?: string) => Promise<void>;
|
||||
onSavePricingRuleSet: (input: PricingRuleSetUpsertRequest, ruleSetId?: string) => Promise<void>;
|
||||
@@ -173,6 +174,7 @@ export function AdminPage(props: {
|
||||
modelRateLimitsUpdatedAt={props.data.modelRateLimitsUpdatedAt}
|
||||
platforms={props.data.platforms}
|
||||
onSavePlatformDynamicPriority={props.onSavePlatformDynamicPriority}
|
||||
onRestoreRuntimeModel={props.onRestoreRuntimeModel}
|
||||
/>
|
||||
)}
|
||||
{props.section === 'tenants' && <TenantsPanel {...identityPanelProps(props)} />}
|
||||
|
||||
@@ -9,11 +9,13 @@ export function RealtimeLoadPanel(props: {
|
||||
modelRateLimitsUpdatedAt: number | null;
|
||||
platforms: IntegrationPlatform[];
|
||||
onSavePlatformDynamicPriority: (platformId: string, input: PlatformDynamicPriorityUpdateRequest) => Promise<void>;
|
||||
onRestoreRuntimeModel: (platformModelId: string) => Promise<void>;
|
||||
}) {
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
const [priorityDialog, setPriorityDialog] = useState<PriorityDialogState | null>(null);
|
||||
const [priorityError, setPriorityError] = useState('');
|
||||
const [prioritySaving, setPrioritySaving] = useState(false);
|
||||
const [restoreSavingId, setRestoreSavingId] = useState<string | null>(null);
|
||||
const platformMap = useMemo(() => new Map(props.platforms.map((item) => [item.id, item])), [props.platforms]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -65,6 +67,17 @@ export function RealtimeLoadPanel(props: {
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreRuntimeModel(platformModelId: string) {
|
||||
setRestoreSavingId(platformModelId);
|
||||
try {
|
||||
await props.onRestoreRuntimeModel(platformModelId);
|
||||
} catch {
|
||||
// App-level state owns the error message.
|
||||
} finally {
|
||||
setRestoreSavingId(null);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="pageStack">
|
||||
<Card>
|
||||
@@ -81,6 +94,8 @@ export function RealtimeLoadPanel(props: {
|
||||
statuses={props.modelRateLimits}
|
||||
updatedAt={props.modelRateLimitsUpdatedAt}
|
||||
onAdjustPriority={openPriorityDialog}
|
||||
onRestoreRuntimeModel={restoreRuntimeModel}
|
||||
restoreSavingId={restoreSavingId}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -109,6 +124,8 @@ function RateLimitStatusTable(props: {
|
||||
now: number;
|
||||
updatedAt: number | null;
|
||||
onAdjustPriority: (status: ModelRateLimitStatus, platform: IntegrationPlatform | undefined) => void;
|
||||
onRestoreRuntimeModel: (platformModelId: string) => Promise<void>;
|
||||
restoreSavingId: string | null;
|
||||
}) {
|
||||
if (!props.statuses.length) {
|
||||
return <EmptyState title="暂无实时负载" description="模型产生请求后会在这里显示实时 RPM、TPM 和并发窗口。" />;
|
||||
@@ -150,7 +167,15 @@ function RateLimitStatusTable(props: {
|
||||
<small>{status.provider}</small>
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="platformLimitStatusCell">{modelRuntimeStatusCell(status, platform, props.now)}</TableCell>
|
||||
<TableCell className="platformLimitStatusCell">
|
||||
{modelRuntimeStatusCell(
|
||||
status,
|
||||
platform,
|
||||
props.now,
|
||||
props.onRestoreRuntimeModel,
|
||||
props.restoreSavingId === status.platformModelId,
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="platformLimitNumberCell">{platformPriorityCell(status, platform, props.onAdjustPriority)}</TableCell>
|
||||
<TableCell className="platformLimitNumberCell">
|
||||
<span className="rateLoadCell" data-overloaded={status.loadRatio > 0.8 ? 'true' : undefined}>
|
||||
@@ -447,50 +472,104 @@ function shortId(value: string | undefined) {
|
||||
return value.length > 8 ? value.slice(0, 8) : value;
|
||||
}
|
||||
|
||||
function modelRuntimeStatusCell(status: ModelRateLimitStatus, platform: IntegrationPlatform | undefined, now: number) {
|
||||
function modelRuntimeStatusCell(
|
||||
status: ModelRateLimitStatus,
|
||||
platform: IntegrationPlatform | undefined,
|
||||
now: number,
|
||||
onRestore: (platformModelId: string) => Promise<void>,
|
||||
restoring: boolean,
|
||||
) {
|
||||
const modelCooldownMs = cooldownRemainingMs(status.modelCooldownUntil, now);
|
||||
const platformCooldownMs = cooldownRemainingMs(status.platformCooldownUntil, now);
|
||||
const platformStatus = platform?.status || status.platformStatus || 'enabled';
|
||||
const restoreButton = runtimeRestoreButton(status, platformStatus, modelCooldownMs, platformCooldownMs, onRestore, restoring);
|
||||
if (modelCooldownMs > 0) {
|
||||
return (
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="warning">模型冷却中</Badge></strong>
|
||||
<small>剩余 {formatCooldownRemaining(modelCooldownMs)}</small>
|
||||
<span className="platformRuntimeStatusCell">
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="warning">模型冷却中</Badge></strong>
|
||||
<small>剩余 {formatCooldownRemaining(modelCooldownMs)}</small>
|
||||
</span>
|
||||
{restoreButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (platformStatus !== 'enabled') {
|
||||
const badge = <Badge variant="warning">已禁用</Badge>;
|
||||
return (
|
||||
<AntPopover
|
||||
align={{ offset: [0, 8] }}
|
||||
content={<PlatformDisabledReasonPopover record={status.platformDisabledReason} />}
|
||||
overlayClassName="priorityDemotionAntPopover"
|
||||
placement="bottomLeft"
|
||||
trigger={['hover', 'focus']}
|
||||
>
|
||||
<span className="platformTableName" tabIndex={0}>
|
||||
<strong>{badge}</strong>
|
||||
<span className="platformRuntimeStatusCell">
|
||||
<AntPopover
|
||||
align={{ offset: [0, 8] }}
|
||||
content={<PlatformDisabledReasonPopover record={status.platformDisabledReason} />}
|
||||
overlayClassName="priorityDemotionAntPopover"
|
||||
placement="bottomLeft"
|
||||
trigger={['hover', 'focus']}
|
||||
>
|
||||
<span className="platformTableName" tabIndex={0}>
|
||||
<strong>{badge}</strong>
|
||||
</span>
|
||||
</AntPopover>
|
||||
{restoreButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (!status.enabled) {
|
||||
return (
|
||||
<span className="platformRuntimeStatusCell">
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="secondary">已停用</Badge></strong>
|
||||
<small>不参与路由</small>
|
||||
</span>
|
||||
</AntPopover>
|
||||
{restoreButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (platformCooldownMs > 0) {
|
||||
return (
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="warning">平台冷却中</Badge></strong>
|
||||
<small>剩余 {formatCooldownRemaining(platformCooldownMs)}</small>
|
||||
<span className="platformRuntimeStatusCell">
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="warning">平台冷却中</Badge></strong>
|
||||
<small>剩余 {formatCooldownRemaining(platformCooldownMs)}</small>
|
||||
</span>
|
||||
{restoreButton}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant={status.enabled ? 'success' : 'secondary'}>{status.enabled ? '可用' : '已停用'}</Badge></strong>
|
||||
<small>{status.enabled ? '参与路由' : '不参与路由'}</small>
|
||||
<span className="platformRuntimeStatusCell">
|
||||
<span className="platformTableName">
|
||||
<strong><Badge variant="success">可用</Badge></strong>
|
||||
<small>参与路由</small>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function runtimeRestoreButton(
|
||||
status: ModelRateLimitStatus,
|
||||
platformStatus: string,
|
||||
modelCooldownMs: number,
|
||||
platformCooldownMs: number,
|
||||
onRestore: (platformModelId: string) => Promise<void>,
|
||||
restoring: boolean,
|
||||
) {
|
||||
const canRestore = modelCooldownMs > 0 || platformCooldownMs > 0 || platformStatus !== 'enabled' || !status.enabled;
|
||||
if (!canRestore) return null;
|
||||
return (
|
||||
<Button
|
||||
className="platformRestoreButton"
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
disabled={restoring}
|
||||
onClick={() => void onRestore(status.platformModelId)}
|
||||
>
|
||||
<RotateCcw size={13} />
|
||||
{restoring ? '恢复中' : '恢复'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function cooldownRemainingMs(cooldownUntil: string | undefined, now: number) {
|
||||
if (!cooldownUntil) return 0;
|
||||
const until = Date.parse(cooldownUntil);
|
||||
|
||||
@@ -1086,8 +1086,8 @@
|
||||
}
|
||||
|
||||
.platformLimitTable .shTableRow {
|
||||
grid-template-columns: minmax(180px, 1.1fr) minmax(160px, 0.9fr) 160px 132px 150px 170px 140px 132px;
|
||||
min-width: 1224px;
|
||||
grid-template-columns: minmax(180px, 1.1fr) minmax(160px, 0.9fr) 178px 132px 150px 170px 140px 132px;
|
||||
min-width: 1242px;
|
||||
}
|
||||
|
||||
.platformLimitTable .shTableHead,
|
||||
@@ -1130,6 +1130,22 @@
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.platformRuntimeStatusCell {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 7px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.platformRestoreButton {
|
||||
justify-self: start;
|
||||
min-height: 22px;
|
||||
padding-inline: 8px;
|
||||
border-color: var(--border);
|
||||
color: var(--text-normal);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.rateMetricCell,
|
||||
.rateLoadCell {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user