Files
easyai-ai-gateway/apps/web/src/App.tsx
T

1365 lines
52 KiB
TypeScript

import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import type {
BaseModelCatalogItem,
CatalogProvider,
FileStorageChannel,
FileStorageSettings,
FileStorageSettingsUpdateRequest,
FileStorageChannelUpsertRequest,
GatewayAccessRuleBatchRequest,
GatewayAccessRule,
GatewayAccessRuleUpsertRequest,
GatewayApiKey,
GatewayAuditLog,
GatewayNetworkProxyConfig,
GatewayRunnerPolicy,
GatewayTenantUpsertRequest,
GatewayTask,
GatewayUserUpsertRequest,
GatewayTenant,
GatewayUser,
GatewayWalletAccount,
GatewayWalletTransaction,
IntegrationPlatform,
ModelCatalogResponse,
ModelRateLimitStatus,
PlatformDynamicPriorityUpdateRequest,
PlatformModel,
PricingRule,
PricingRuleSet,
RateLimitWindow,
RuntimePolicySet,
UserGroupUpsertRequest,
UserGroup,
WalletBalanceAdjustmentRequest,
} from '@easyai-ai-gateway/contracts';
import {
batchAccessRules,
batchApiKeyAccessRules,
createAccessRule,
createApiKey,
createFileStorageChannel,
createGatewayUser,
createPlatform,
createTenant,
createUserGroup,
deleteAccessRule,
deleteApiKey,
deleteFileStorageChannel,
deleteGatewayUser,
deletePlatform,
deleteTenant,
deleteUserGroup,
GatewayApiError,
getHealth,
listFileStorageChannels,
getFileStorageSettings,
getNetworkProxyConfig,
getRunnerPolicy,
getWalletSummary,
listAccessRules,
listAuditLogs,
listApiKeyAccessRules,
listApiKeys,
listBaseModels,
listCatalogProviders,
listModelCatalog,
listModelRateLimitStatuses,
listModels,
listPlayableApiKeys,
listPlayableModels,
listPlatforms,
listPricingRules,
listPricingRuleSets,
listPublicBaseModels,
listPublicCatalogProviders,
listRuntimePolicySets,
listTasks,
listWalletTransactions,
listRateLimitWindows,
listTenants,
listUserGroups,
listUsers,
loginLocalAccount,
pollTaskUntilSettled,
registerLocalAccount,
replacePlatformModels,
setUserWalletBalance,
type HealthResponse,
updateAccessRule,
updateFileStorageChannel,
updateFileStorageSettings,
updateGatewayUser,
updatePlatform,
updatePlatformDynamicPriority,
updateTenant,
updateUserGroup,
} from './api';
import type { ConsoleData, StatItem } from './app-state';
import { AppShell } from './components/layout/AppShell';
import { LoginRequiredPanel } from './components/LoginRequiredPanel';
import { useCatalogOperations } from './hooks/useCatalogOperations';
import { usePricingRuleSetOperations } from './hooks/usePricingRuleSetOperations';
import { useRuntimePolicySetOperations } from './hooks/useRuntimePolicySetOperations';
import { persistAccessToken, readStoredAccessToken } from './lib/auth-storage';
import { runTask } from './lib/run-task';
import { AdminPage } from './pages/AdminPage';
import { ApiDocsPage } from './pages/ApiDocsPage';
import { HomePage } from './pages/HomePage';
import { ModelsPage } from './pages/ModelsPage';
import { PlaygroundPage } from './pages/PlaygroundPage';
import { WorkspacePage } from './pages/WorkspacePage';
import {
parseAppRoute,
pathForAdminSection,
pathForApiDocSection,
pathForPage,
pathForPlaygroundMode,
pathForWorkspaceSection,
pathForWorkspaceTaskQuery,
workspaceTaskQueryKey,
type AppRouteState,
} from './routing';
import type {
AdminSection,
ApiDocSection,
ApiKeyForm,
AuthMode,
LoadState,
LoginForm,
PageKey,
PlaygroundMode,
PlatformCreateInput,
PlatformModelBindingInput,
PlatformWithModelsInput,
RegisterForm,
TaskForm,
WorkspaceTaskQuery,
WorkspaceTransactionQuery,
WorkspaceSection,
} from './types';
type DataKey =
| 'health'
| 'publicCatalog'
| 'playgroundApiKeys'
| 'playgroundModels'
| 'modelCatalog'
| 'networkProxyConfig'
| 'fileStorageChannels'
| 'fileStorageSettings'
| 'platforms'
| 'models'
| 'providers'
| 'baseModels'
| 'pricingRules'
| 'pricingRuleSets'
| 'runnerPolicy'
| 'runtimePolicySets'
| 'rateLimitWindows'
| 'modelRateLimits'
| 'tenants'
| 'users'
| 'userGroups'
| 'tasks'
| 'wallet'
| 'walletTransactions'
| 'accessRules'
| 'auditLogs'
| 'apiKeys';
export function App() {
const initialRoute = parseAppRoute();
const [activePage, setActivePage] = useState<PageKey>(initialRoute.activePage);
const [adminSection, setAdminSection] = useState<AdminSection>(initialRoute.adminSection);
const [workspaceSection, setWorkspaceSection] = useState<WorkspaceSection>(initialRoute.workspaceSection);
const [workspaceTaskQuery, setWorkspaceTaskQuery] = useState<WorkspaceTaskQuery>(initialRoute.workspaceTaskQuery);
const [workspaceTransactionQuery, setWorkspaceTransactionQuery] = useState<WorkspaceTransactionQuery>(() => defaultWorkspaceTransactionQuery());
const [apiDocSection, setApiDocSection] = useState<ApiDocSection>(initialRoute.apiDocSection);
const [playgroundMode, setPlaygroundMode] = useState<PlaygroundMode>(initialRoute.playgroundMode);
const [token, setToken] = useState(readStoredAccessToken);
const [externalToken, setExternalToken] = useState('');
const [authMode, setAuthMode] = useState<AuthMode>('login');
const [loginForm, setLoginForm] = useState<LoginForm>({ account: '', password: '' });
const [registerForm, setRegisterForm] = useState<RegisterForm>({ username: '', email: '', password: '', displayName: '', invitationCode: '' });
const [health, setHealth] = useState<HealthResponse | null>(null);
const [platforms, setPlatforms] = useState<IntegrationPlatform[]>([]);
const [models, setModels] = useState<PlatformModel[]>([]);
const [modelCatalog, setModelCatalog] = useState<ModelCatalogResponse>({
items: [],
filters: { capabilities: [], providers: [] },
summary: { modelCount: 0, sourceCount: 0 },
});
const [playgroundModels, setPlaygroundModels] = useState<PlatformModel[]>([]);
const [networkProxyConfig, setNetworkProxyConfig] = useState<GatewayNetworkProxyConfig | null>(null);
const [fileStorageChannels, setFileStorageChannels] = useState<FileStorageChannel[]>([]);
const [fileStorageSettings, setFileStorageSettings] = useState<FileStorageSettings | null>(null);
const [providers, setProviders] = useState<CatalogProvider[]>([]);
const [baseModels, setBaseModels] = useState<BaseModelCatalogItem[]>([]);
const [pricingRules, setPricingRules] = useState<PricingRule[]>([]);
const [pricingRuleSets, setPricingRuleSets] = useState<PricingRuleSet[]>([]);
const [runnerPolicy, setRunnerPolicy] = useState<GatewayRunnerPolicy | null>(null);
const [runtimePolicySets, setRuntimePolicySets] = useState<RuntimePolicySet[]>([]);
const [accessRules, setAccessRules] = useState<GatewayAccessRule[]>([]);
const [auditLogs, setAuditLogs] = useState<GatewayAuditLog[]>([]);
const [rateLimitWindows, setRateLimitWindows] = useState<RateLimitWindow[]>([]);
const [modelRateLimits, setModelRateLimits] = useState<ModelRateLimitStatus[]>([]);
const [modelRateLimitsUpdatedAt, setModelRateLimitsUpdatedAt] = useState<number | null>(null);
const [tenants, setTenants] = useState<GatewayTenant[]>([]);
const [users, setUsers] = useState<GatewayUser[]>([]);
const [userGroups, setUserGroups] = useState<UserGroup[]>([]);
const [apiKeys, setApiKeys] = useState<GatewayApiKey[]>([]);
const [apiKeyForm, setApiKeyForm] = useState<ApiKeyForm>({ name: 'Local smoke key', expiresAt: '' });
const [apiKeySecret, setApiKeySecret] = useState('');
const [apiKeySecretsById, setApiKeySecretsById] = useState<Record<string, string>>({});
const [selectedPlaygroundApiKeyId, setSelectedPlaygroundApiKeyId] = useState('');
const [taskForm, setTaskForm] = useState<TaskForm>({ kind: 'chat.completions', model: 'gpt-4o-mini', prompt: '用一句话确认 AI Gateway simulation 链路正常。' });
const [taskResult, setTaskResult] = useState<GatewayTask | null>(null);
const [tasks, setTasks] = useState<GatewayTask[]>([]);
const [taskTotal, setTaskTotal] = useState(0);
const [walletAccounts, setWalletAccounts] = useState<GatewayWalletAccount[]>([]);
const [walletTransactions, setWalletTransactions] = useState<GatewayWalletTransaction[]>([]);
const [walletTransactionTotal, setWalletTransactionTotal] = useState(0);
const [coreState, setCoreState] = useState<LoadState>('idle');
const [coreMessage, setCoreMessage] = useState('');
const [state, setState] = useState<LoadState>('idle');
const [error, setError] = useState('');
const loadedDataKeysRef = useRef(new Set<DataKey>());
const loadingDataKeysRef = useRef(new Set<DataKey>());
const loadedTaskQueryKeyRef = useRef('');
const currentTaskQueryKeyRef = useRef('');
const loadedTransactionQueryKeyRef = useRef('');
const currentTransactionQueryKeyRef = useRef('');
const { removeBaseModel, removeProvider, resetAllBaseModelsToDefault, resetBaseModelToDefault, saveBaseModel, saveProvider } = useCatalogOperations({
setBaseModels,
setCoreMessage,
setCoreState,
setProviders,
token,
});
const { removePricingRuleSet, savePricingRuleSet } = usePricingRuleSetOperations({
setCoreMessage,
setCoreState,
setPricingRuleSets,
token,
});
const { removeRuntimePolicySet, saveRunnerPolicy, saveRuntimePolicySet } = useRuntimePolicySetOperations({
setCoreMessage,
setCoreState,
setRunnerPolicy,
setRuntimePolicySets,
token,
});
const taskListRequestKey = workspaceTaskQueryKey(workspaceTaskQuery);
const transactionListRequestKey = workspaceTransactionQueryKey(workspaceTransactionQuery);
currentTaskQueryKeyRef.current = taskListRequestKey;
currentTransactionQueryKeyRef.current = transactionListRequestKey;
useEffect(() => {
void ensureData(['health']);
}, []);
useEffect(() => {
void ensureRouteData(token);
}, [activePage, adminSection, taskListRequestKey, transactionListRequestKey, workspaceSection, token]);
useEffect(() => {
if (!token || activePage !== 'admin' || adminSection !== 'realtimeLoad') return undefined;
const timer = window.setInterval(() => {
void Promise.all([listModelRateLimitStatuses(token), listPlatforms(token)])
.then(([rateLimitResponse, platformResponse]) => {
setModelRateLimits(rateLimitResponse.items);
setModelRateLimitsUpdatedAt(Date.now());
setPlatforms(platformResponse.items);
loadedDataKeysRef.current.add('modelRateLimits');
loadedDataKeysRef.current.add('platforms');
})
.catch((err) => {
if (handleAuthExpired(err, token)) return;
loadedDataKeysRef.current.delete('modelRateLimits');
loadedDataKeysRef.current.delete('platforms');
});
}, 3000);
return () => window.clearInterval(timer);
}, [activePage, adminSection, token]);
useEffect(() => {
function handlePopState() {
applyRoute(parseAppRoute());
}
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
const stats = useMemo<StatItem[]>(() => {
const enabledPlatforms = platforms.filter((item) => item.status === 'enabled').length;
const enabledModels = models.filter((item) => item.enabled).length;
const activeProviders = providers.filter((item) => item.status === 'active').length;
const activeRateWindows = rateLimitWindows.filter((item) => item.resetAt >= new Date().toISOString()).length;
return [
{ label: '租户', value: tenants.length, tone: 'cyan' },
{ label: '用户', value: users.length, tone: 'green' },
{ label: '用户组', value: userGroups.length, tone: 'blue' },
{ label: '平台', value: platforms.length, tone: 'blue' },
{ label: '启用平台', value: enabledPlatforms, tone: 'green' },
{ label: '平台模型', value: enabledModels, tone: 'violet' },
{ label: 'Provider', value: activeProviders || providers.length, tone: 'amber' },
{ label: '定价规则', value: pricingRules.length, tone: 'cyan' },
{ label: '运行策略', value: runtimePolicySets.length, tone: 'slate' },
{ label: '访问规则', value: accessRules.length, tone: 'amber' },
{ label: '限流窗口', value: activeRateWindows, tone: 'rose' },
];
}, [accessRules.length, models, platforms, pricingRules.length, providers, rateLimitWindows, runtimePolicySets.length, tenants.length, userGroups.length, users.length]);
const data = useMemo<ConsoleData>(() => ({
accessRules,
auditLogs,
apiKeys,
baseModels,
fileStorageChannels,
fileStorageSettings,
modelCatalog,
models,
networkProxyConfig,
runnerPolicy,
platforms,
pricingRules,
pricingRuleSets,
providers,
rateLimitWindows,
modelRateLimits,
modelRateLimitsUpdatedAt,
runtimePolicySets,
taskResult,
tasks,
tenants,
userGroups,
users,
walletAccounts,
walletTransactions,
}), [accessRules, apiKeys, auditLogs, baseModels, fileStorageChannels, fileStorageSettings, modelCatalog, modelRateLimits, modelRateLimitsUpdatedAt, models, networkProxyConfig, platforms, pricingRuleSets, pricingRules, providers, rateLimitWindows, runnerPolicy, runtimePolicySets, taskResult, tasks, tenants, userGroups, users, walletAccounts, walletTransactions]);
async function refresh(nextToken = token) {
await ensureRouteData(nextToken, true);
}
function invalidateDataKeys(...keys: DataKey[]) {
keys.forEach((key) => loadedDataKeysRef.current.delete(key));
}
async function ensureRouteData(nextToken = token, force = false) {
if (activePage === 'workspace' && workspaceSection === 'tasks' && loadedTaskQueryKeyRef.current !== taskListRequestKey) {
loadedDataKeysRef.current.delete('tasks');
loadingDataKeysRef.current.delete('tasks');
}
if (activePage === 'workspace' && workspaceSection === 'transactions' && loadedTransactionQueryKeyRef.current !== transactionListRequestKey) {
loadedDataKeysRef.current.delete('walletTransactions');
loadingDataKeysRef.current.delete('walletTransactions');
}
await ensureData(dataKeysForRoute(activePage, adminSection, workspaceSection, Boolean(nextToken)), nextToken, force);
}
async function ensureData(keys: DataKey[], nextToken = token, force = false) {
const uniqueKeys = Array.from(new Set(keys));
const requestKeys = uniqueKeys.filter((key) => {
if (!force && loadedDataKeysRef.current.has(key)) return false;
if (loadingDataKeysRef.current.has(key)) return false;
return key === 'health' || key === 'publicCatalog' || Boolean(nextToken);
});
if (requestKeys.length === 0) return;
requestKeys.forEach((key) => loadingDataKeysRef.current.add(key));
setState('loading');
setError('');
try {
await Promise.all(requestKeys.map((key) => loadDataKey(key, nextToken)));
requestKeys.forEach((key) => loadedDataKeysRef.current.add(key));
setState('ready');
} catch (err) {
if (handleAuthExpired(err, nextToken)) return;
setState('error');
setError(err instanceof Error ? err.message : '加载失败');
} finally {
requestKeys.forEach((key) => loadingDataKeysRef.current.delete(key));
}
}
async function loadDataKey(key: DataKey, nextToken: string) {
switch (key) {
case 'health': {
setHealth(await getHealth());
return;
}
case 'publicCatalog': {
const [providersResult, baseModelsResult] = await Promise.all([
listPublicCatalogProviders(),
listPublicBaseModels(),
]);
setProviders(providersResult.items);
setBaseModels(baseModelsResult.items);
return;
}
case 'platforms':
setPlatforms((await listPlatforms(nextToken)).items);
return;
case 'models':
setModels((await listModels(nextToken)).items);
return;
case 'modelCatalog':
setModelCatalog(await listModelCatalog(nextToken));
return;
case 'networkProxyConfig':
setNetworkProxyConfig(await getNetworkProxyConfig(nextToken));
return;
case 'fileStorageChannels':
setFileStorageChannels((await listFileStorageChannels(nextToken)).items);
return;
case 'fileStorageSettings':
setFileStorageSettings(await getFileStorageSettings(nextToken));
return;
case 'playgroundModels':
setPlaygroundModels((await listPlayableModels(nextToken)).items);
return;
case 'playgroundApiKeys': {
const response = await listPlayableApiKeys(nextToken);
setApiKeys(response.items);
setApiKeySecretsById(Object.fromEntries(response.items.map((item) => [item.id, item.secret])));
setSelectedPlaygroundApiKeyId((current) => current && response.items.some((item) => item.id === current) ? current : response.items[0]?.id ?? '');
return;
}
case 'providers':
setProviders((await listCatalogProviders(nextToken)).items);
return;
case 'baseModels':
setBaseModels((await listBaseModels(nextToken)).items);
return;
case 'pricingRules':
setPricingRules((await listPricingRules(nextToken)).items);
return;
case 'pricingRuleSets':
setPricingRuleSets((await listPricingRuleSets(nextToken)).items);
return;
case 'runnerPolicy':
setRunnerPolicy(await getRunnerPolicy(nextToken));
return;
case 'runtimePolicySets':
setRuntimePolicySets((await listRuntimePolicySets(nextToken)).items);
return;
case 'rateLimitWindows':
setRateLimitWindows((await listRateLimitWindows(nextToken)).items);
return;
case 'modelRateLimits':
{
const response = await listModelRateLimitStatuses(nextToken);
setModelRateLimits(response.items);
setModelRateLimitsUpdatedAt(Date.now());
}
return;
case 'tenants':
setTenants((await listTenants(nextToken)).items);
return;
case 'users':
setUsers((await listUsers(nextToken)).items);
return;
case 'userGroups':
setUserGroups((await listUserGroups(nextToken)).items);
return;
case 'tasks':
{
const requestKey = taskListRequestKey;
const response = await listTasks(nextToken, workspaceTaskQuery);
if (requestKey !== currentTaskQueryKeyRef.current) return;
setTasks(response.items);
setTaskTotal(response.total ?? response.items.length);
loadedTaskQueryKeyRef.current = requestKey;
}
return;
case 'wallet': {
const response = await getWalletSummary(nextToken);
setWalletAccounts(response.accounts);
return;
}
case 'walletTransactions': {
const requestKey = transactionListRequestKey;
const response = await listWalletTransactions(nextToken, { ...normalizeWorkspaceTransactionQuery(workspaceTransactionQuery), direction: 'debit' });
if (requestKey !== currentTransactionQueryKeyRef.current) return;
setWalletTransactions(response.items);
setWalletTransactionTotal(response.total ?? response.items.length);
loadedTransactionQueryKeyRef.current = requestKey;
return;
}
case 'accessRules':
setAccessRules((await (activePage === 'workspace' && workspaceSection === 'apiKeys'
? listApiKeyAccessRules(nextToken)
: listAccessRules(nextToken))).items);
return;
case 'auditLogs':
setAuditLogs((await listAuditLogs(nextToken)).items);
return;
case 'apiKeys':
setApiKeys((await listApiKeys(nextToken)).items);
}
}
async function submitLogin(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
await authenticate(() => loginLocalAccount(loginForm), '登录失败');
}
async function submitRegister(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
await authenticate(() => registerLocalAccount(registerForm), '注册失败');
}
async function submitExternalToken(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const nextToken = externalToken.trim();
if (!nextToken) {
setError('请填写 access token');
return;
}
persistAccessToken(nextToken);
setToken(nextToken);
await ensureRouteData(nextToken, true);
}
async function authenticate(request: () => Promise<{ accessToken: string }>, fallback: string) {
setState('loading');
setError('');
try {
const response = await request();
persistAccessToken(response.accessToken);
setToken(response.accessToken);
await ensureRouteData(response.accessToken, true);
} catch (err) {
setState('error');
setError(err instanceof Error ? err.message : fallback);
}
}
async function submitAPIKey(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setCoreState('loading');
setCoreMessage('');
try {
const response = await createApiKey(token, {
name: apiKeyForm.name,
scopes: ['chat', 'image', 'video'],
expiresAt: apiKeyForm.expiresAt ? new Date(apiKeyForm.expiresAt).toISOString() : undefined,
});
setApiKeySecret(response.secret);
setApiKeySecretsById((current) => ({ ...current, [response.apiKey.id]: response.secret }));
setSelectedPlaygroundApiKeyId(response.apiKey.id);
setApiKeys((current) => [response.apiKey, ...current.filter((item) => item.id !== response.apiKey.id)]);
setApiKeyForm({ name: '', expiresAt: '' });
setCoreState('ready');
setCoreMessage('API Key 已创建,secret 仅展示一次。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '创建 API Key 失败');
throw err;
}
}
async function savePlatformWithModels(input: PlatformWithModelsInput) {
setCoreState('loading');
setCoreMessage('');
try {
const platform = input.platformId
? await updatePlatform(token, input.platformId, input.platform)
: await createPlatform(token, input.platform);
const platformForState = withCredentialPreviewFallback(
platform,
input.platform,
input.platformId ? platforms.find((item) => item.id === input.platformId) : undefined,
);
const modelBindings = input.models.map((modelInput) => mergeExistingPlatformModelInput(modelInput, models, platform.id));
const modelsResponse = await replacePlatformModels(token, platform.id, modelBindings);
setPlatforms((current) => [platformForState, ...current.filter((item) => item.id !== platform.id)]);
setModels((current) => [...current.filter((model) => model.platformId !== platform.id), ...modelsResponse.items]);
invalidateDataKeys('modelCatalog', 'modelRateLimits');
setCoreState('ready');
setCoreMessage(input.platformId
? `平台已更新,当前绑定 ${input.models.length} 个模型。`
: `平台已创建,已绑定 ${input.models.length} 个模型。`);
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : input.platformId ? '更新平台失败' : '创建平台失败');
throw err;
}
}
async function savePlatformStatus(platform: IntegrationPlatform, status: 'enabled' | 'disabled') {
setCoreState('loading');
setCoreMessage('');
try {
const input = platformStatusUpdatePayload(platform, status);
const updated = await updatePlatform(token, platform.id, input);
const platformForState = withCredentialPreviewFallback(updated, input, platform);
setPlatforms((current) => current.map((item) => item.id === platform.id ? platformForState : item));
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'playgroundModels');
setCoreState('ready');
setCoreMessage(status === 'enabled' ? '平台已启用。' : '平台已禁用。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '切换平台状态失败');
throw err;
}
}
async function savePlatformDynamicPriority(platformId: string, input: PlatformDynamicPriorityUpdateRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const state = await updatePlatformDynamicPriority(token, platformId, input);
setPlatforms((current) => current.map((platform) => platform.id === platformId
? {
...platform,
dynamicPriority: state.dynamicPriority,
effectivePriority: state.effectivePriority,
priority: state.priority,
updatedAt: state.updatedAt,
}
: platform));
setModelRateLimits((current) => current.map((status) => status.platformId === platformId
? {
...status,
platformDynamicPriority: state.dynamicPriority,
platformEffectivePriority: state.effectivePriority,
platformPriority: state.priority,
}
: status));
invalidateDataKeys('modelCatalog', 'modelRateLimits', 'platforms', 'playgroundModels');
setCoreState('ready');
setCoreMessage(input.reset ? '平台动态优先级已重置。' : '平台动态优先级已更新。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '更新平台动态优先级失败');
throw err;
}
}
async function removePlatform(platformId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deletePlatform(token, platformId);
setPlatforms((current) => current.filter((item) => item.id !== platformId));
setModels((current) => current.filter((item) => item.platformId !== platformId));
invalidateDataKeys('modelCatalog', 'modelRateLimits');
setCoreState('ready');
setCoreMessage('平台已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除平台失败');
throw err;
}
}
async function saveTenant(input: GatewayTenantUpsertRequest, tenantId?: string) {
setCoreState('loading');
setCoreMessage('');
try {
const item = tenantId ? await updateTenant(token, tenantId, input) : await createTenant(token, input);
setTenants((current) => [item, ...current.filter((tenant) => tenant.id !== item.id)]);
setCoreState('ready');
setCoreMessage(tenantId ? '租户已更新。' : '租户已创建。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : tenantId ? '更新租户失败' : '创建租户失败');
throw err;
}
}
async function removeTenant(tenantId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteTenant(token, tenantId);
setTenants((current) => current.filter((tenant) => tenant.id !== tenantId));
setCoreState('ready');
setCoreMessage('租户已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除租户失败');
throw err;
}
}
async function saveUser(input: GatewayUserUpsertRequest, userId?: string) {
setCoreState('loading');
setCoreMessage('');
try {
const item = userId ? await updateGatewayUser(token, userId, input) : await createGatewayUser(token, input);
setUsers((current) => [item, ...current.filter((user) => user.id !== item.id)]);
invalidateDataKeys('playgroundModels');
setCoreState('ready');
setCoreMessage(userId ? '用户已更新。' : '用户已创建。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : userId ? '更新用户失败' : '创建用户失败');
throw err;
}
}
async function saveUserWalletBalance(userId: string, input: WalletBalanceAdjustmentRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const response = await setUserWalletBalance(token, userId, input);
setUsers((current) => current.map((user) => user.id === userId
? {
...user,
walletAccounts: [
response.account,
...(user.walletAccounts ?? []).filter((account) => account.id !== response.account.id),
],
}
: user));
setAuditLogs((current) => [response.auditLog, ...current.filter((item) => item.id !== response.auditLog.id)]);
invalidateDataKeys('auditLogs');
setCoreState('ready');
setCoreMessage('用户余额已更新,审计日志已记录。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '更新用户余额失败');
throw err;
}
}
async function removeUser(userId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteGatewayUser(token, userId);
setUsers((current) => current.filter((user) => user.id !== userId));
setCoreState('ready');
setCoreMessage('用户已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除用户失败');
throw err;
}
}
async function saveUserGroup(input: UserGroupUpsertRequest, groupId?: string) {
setCoreState('loading');
setCoreMessage('');
try {
const item = groupId ? await updateUserGroup(token, groupId, input) : await createUserGroup(token, input);
setUserGroups((current) => [item, ...current.filter((group) => group.id !== item.id)]);
invalidateDataKeys('modelCatalog');
setCoreState('ready');
setCoreMessage(groupId ? '用户组已更新。' : '用户组已创建。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : groupId ? '更新用户组失败' : '创建用户组失败');
throw err;
}
}
async function removeUserGroup(groupId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteUserGroup(token, groupId);
setUserGroups((current) => current.filter((group) => group.id !== groupId));
setTenants((current) => current.map((tenant) => tenant.defaultUserGroupId === groupId ? { ...tenant, defaultUserGroupId: undefined } : tenant));
setUsers((current) => current.map((user) => user.defaultUserGroupId === groupId ? { ...user, defaultUserGroupId: undefined } : user));
invalidateDataKeys('modelCatalog');
invalidateDataKeys('playgroundModels');
setCoreState('ready');
setCoreMessage('用户组已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除用户组失败');
throw err;
}
}
async function removeAPIKey(apiKeyId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteApiKey(token, apiKeyId);
setApiKeys((current) => current.filter((item) => item.id !== apiKeyId));
setAccessRules((current) => current.filter((rule) => !(rule.subjectType === 'api_key' && rule.subjectId === apiKeyId)));
setApiKeySecretsById((current) => {
const next = { ...current };
delete next[apiKeyId];
return next;
});
if (selectedPlaygroundApiKeyId === apiKeyId) setSelectedPlaygroundApiKeyId('');
setCoreState('ready');
setCoreMessage('API Key 已删除,对应权限策略已同步清理。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除 API Key 失败');
throw err;
}
}
async function saveAccessRule(input: GatewayAccessRuleUpsertRequest, ruleId?: string) {
setCoreState('loading');
setCoreMessage('');
try {
const item = ruleId ? await updateAccessRule(token, ruleId, input) : await createAccessRule(token, input);
setAccessRules((current) => [item, ...current.filter((rule) => rule.id !== item.id)]);
invalidateDataKeys('playgroundModels', 'modelCatalog');
setCoreState('ready');
setCoreMessage(ruleId ? '访问权限规则已更新。' : '访问权限规则已创建。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : ruleId ? '更新访问权限失败' : '创建访问权限失败');
throw err;
}
}
async function removeAccessRule(ruleId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteAccessRule(token, ruleId);
setAccessRules((current) => current.filter((rule) => rule.id !== ruleId));
invalidateDataKeys('playgroundModels', 'modelCatalog');
setCoreState('ready');
setCoreMessage('访问权限规则已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除访问权限失败');
throw err;
}
}
async function batchSaveAccessRules(input: GatewayAccessRuleBatchRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const response = await batchAccessRules(token, input);
setAccessRules(response.items);
invalidateDataKeys('playgroundModels', 'modelCatalog');
setCoreState('ready');
setCoreMessage('访问权限已更新。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '批量更新访问权限失败');
throw err;
}
}
async function saveFileStorageChannel(input: FileStorageChannelUpsertRequest, channelId?: string) {
setCoreState('loading');
setCoreMessage('');
try {
const item = channelId
? await updateFileStorageChannel(token, channelId, input)
: await createFileStorageChannel(token, input);
setFileStorageChannels((current) => [item, ...current.filter((channel) => channel.id !== item.id)]);
setCoreState('ready');
setCoreMessage(channelId ? '文件存储渠道已更新。' : '文件存储渠道已新增。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : channelId ? '更新文件存储渠道失败' : '新增文件存储渠道失败');
throw err;
}
}
async function saveFileStorageSettings(input: FileStorageSettingsUpdateRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const settings = await updateFileStorageSettings(token, input);
setFileStorageSettings(settings);
setCoreState('ready');
setCoreMessage('文件存储全局策略已更新。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '更新文件存储全局策略失败');
throw err;
}
}
async function removeFileStorageChannel(channelId: string) {
setCoreState('loading');
setCoreMessage('');
try {
await deleteFileStorageChannel(token, channelId);
setFileStorageChannels((current) => current.filter((channel) => channel.id !== channelId));
setCoreState('ready');
setCoreMessage('文件存储渠道已删除。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '删除文件存储渠道失败');
throw err;
}
}
async function batchSaveAPIKeyAccessRules(input: GatewayAccessRuleBatchRequest) {
setCoreState('loading');
setCoreMessage('');
try {
const response = await batchApiKeyAccessRules(token, input);
setAccessRules(response.items);
setCoreState('ready');
setCoreMessage('API Key 权限已更新。');
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '批量更新 API Key 权限失败');
throw err;
}
}
async function submitTask(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const credential = apiKeySecret || token;
setCoreState('loading');
setCoreMessage('');
try {
const response = await runTask(credential, taskForm);
const syncTask = (detail: GatewayTask) => {
setTaskResult(detail);
setTasks((current) => [detail, ...current.filter((item) => item.id !== detail.id)]);
};
const detail = await pollTaskUntilSettled(credential, response.task, { onUpdate: syncTask });
setTaskResult(detail);
setTasks((current) => [detail, ...current.filter((item) => item.id !== detail.id)]);
invalidateDataKeys('tasks', 'wallet', 'walletTransactions');
setCoreState('ready');
setCoreMessage(`${taskForm.kind} 已通过 ${apiKeySecret ? '本地 API Key' : '当前 Access Token'} 完成 simulation。`);
} catch (err) {
setCoreState('error');
setCoreMessage(err instanceof Error ? err.message : '测试任务失败');
}
}
function resetAuthenticatedSession() {
persistAccessToken('');
setToken('');
loadedDataKeysRef.current = new Set(health ? ['health'] : []);
loadingDataKeysRef.current.clear();
setState('idle');
setPlatforms([]);
setModels([]);
setModelCatalog({ items: [], filters: { capabilities: [], providers: [] }, summary: { modelCount: 0, sourceCount: 0 } });
setPlaygroundModels([]);
setNetworkProxyConfig(null);
setFileStorageChannels([]);
setProviders([]);
setBaseModels([]);
setPricingRules([]);
setPricingRuleSets([]);
setRunnerPolicy(null);
setRuntimePolicySets([]);
setAccessRules([]);
setAuditLogs([]);
setRateLimitWindows([]);
setModelRateLimits([]);
setTenants([]);
setUsers([]);
setUserGroups([]);
setApiKeys([]);
setApiKeySecret('');
setApiKeySecretsById({});
setSelectedPlaygroundApiKeyId('');
setTaskResult(null);
setTasks([]);
setTaskTotal(0);
setWalletAccounts([]);
setWalletTransactions([]);
setWalletTransactionTotal(0);
setWorkspaceTransactionQuery(defaultWorkspaceTransactionQuery());
setCoreMessage('');
}
function handleAuthExpired(err: unknown, failedToken: string) {
if (!failedToken || !(err instanceof GatewayApiError) || err.details.status !== 401) return false;
resetAuthenticatedSession();
setError('');
setAuthMode('login');
navigatePath(pathForWorkspaceSection('overview'));
return true;
}
function signOut() {
resetAuthenticatedSession();
navigatePath('/');
}
function showLogin() {
setAuthMode('login');
navigatePath(pathForWorkspaceSection('overview'));
}
function currentRouteState(): AppRouteState {
return { activePage, adminSection, apiDocSection, playgroundMode, workspaceSection, workspaceTaskQuery };
}
function applyRoute(route: AppRouteState) {
setActivePage(route.activePage);
setAdminSection(route.adminSection);
setApiDocSection(route.apiDocSection);
setPlaygroundMode(route.playgroundMode);
setWorkspaceSection(route.workspaceSection);
setWorkspaceTaskQuery(route.workspaceTaskQuery);
}
function navigatePath(path: string) {
if (`${window.location.pathname}${window.location.search}` !== path) {
window.history.pushState(null, '', path);
}
applyRoute(parseAppRoute(path));
}
function navigatePage(page: PageKey) {
navigatePath(pathForPage(page, currentRouteState()));
}
function navigateAdminSection(section: AdminSection) {
navigatePath(pathForAdminSection(section));
}
function navigateWorkspaceSection(section: WorkspaceSection) {
navigatePath(pathForWorkspaceSection(section));
}
function navigateWorkspaceTaskQuery(query: WorkspaceTaskQuery) {
navigatePath(pathForWorkspaceTaskQuery(query));
}
function navigateApiDocSection(section: ApiDocSection) {
navigatePath(pathForApiDocSection(section));
}
function navigatePlaygroundMode(mode: PlaygroundMode) {
navigatePath(pathForPlaygroundMode(mode));
}
function openApiKeyCreation() {
navigatePath(pathForWorkspaceSection('apiKeys'));
}
function useApiKeyForPlayground(apiKeyId?: string) {
if (apiKeyId) setSelectedPlaygroundApiKeyId(apiKeyId);
navigatePath(pathForPlaygroundMode('chat'));
}
const isAuthenticated = Boolean(token);
return (
<AppShell
activePage={activePage}
health={health}
isAuthenticated={isAuthenticated}
state={state}
onNavigate={navigatePage}
onLogin={showLogin}
onRefresh={() => void refresh()}
onSignOut={signOut}
>
{error && <div className="notice">{error}</div>}
{activePage === 'home' && <HomePage onNavigate={navigatePage} onPlaygroundMode={navigatePlaygroundMode} />}
{activePage === 'playground' && (
<PlaygroundPage
apiKeySecretsById={apiKeySecretsById}
apiKeys={apiKeys}
mode={playgroundMode}
models={playgroundModels}
selectedApiKeyId={selectedPlaygroundApiKeyId}
token={token}
onApiKeyChange={setSelectedPlaygroundApiKeyId}
onCreateApiKey={openApiKeyCreation}
onLogin={showLogin}
onModeChange={navigatePlaygroundMode}
/>
)}
{activePage === 'models' && <ModelsPage data={data} />}
{activePage === 'workspace' && (
isAuthenticated ? (
<WorkspacePage
apiKeyForm={apiKeyForm}
apiKeySecret={apiKeySecret}
apiKeySecretsById={apiKeySecretsById}
apiKeyPolicyModels={playgroundModels}
data={data}
message={coreMessage}
section={workspaceSection}
state={coreState}
token={token}
taskQuery={workspaceTaskQuery}
taskTotal={taskTotal}
transactionQuery={workspaceTransactionQuery}
transactionTotal={walletTransactionTotal}
onBatchAccessRules={batchSaveAPIKeyAccessRules}
onDeleteApiKey={removeAPIKey}
onApiKeyFormChange={setApiKeyForm}
onSectionChange={navigateWorkspaceSection}
onSubmitApiKey={submitAPIKey}
onTaskQueryChange={navigateWorkspaceTaskQuery}
onTransactionQueryChange={setWorkspaceTransactionQuery}
onUseApiKeyForPlayground={useApiKeyForPlayground}
/>
) : (
<LoginRequiredPanel
authMode={authMode}
externalToken={externalToken}
loginForm={loginForm}
registerForm={registerForm}
state={state}
onAuthModeChange={setAuthMode}
onExternalTokenChange={setExternalToken}
onLoginChange={setLoginForm}
onRegisterChange={setRegisterForm}
onSubmitExternalToken={submitExternalToken}
onSubmitLogin={submitLogin}
onSubmitRegister={submitRegister}
/>
)
)}
{activePage === 'admin' && (
isAuthenticated ? (
<AdminPage
data={data}
operationMessage={coreMessage}
section={adminSection}
stats={stats}
state={coreState}
onDeleteBaseModel={removeBaseModel}
onDeletePlatform={removePlatform}
onDeleteProvider={removeProvider}
onDeletePricingRuleSet={removePricingRuleSet}
onDeleteRuntimePolicySet={removeRuntimePolicySet}
onDeleteAccessRule={removeAccessRule}
onDeleteFileStorageChannel={removeFileStorageChannel}
onDeleteTenant={removeTenant}
onDeleteUser={removeUser}
onDeleteUserGroup={removeUserGroup}
onSaveBaseModel={saveBaseModel}
onResetAllBaseModels={resetAllBaseModelsToDefault}
onResetBaseModel={resetBaseModelToDefault}
onSavePlatform={savePlatformWithModels}
onSavePlatformDynamicPriority={savePlatformDynamicPriority}
onTogglePlatformStatus={savePlatformStatus}
onSaveProvider={saveProvider}
onSavePricingRuleSet={savePricingRuleSet}
onSaveRunnerPolicy={saveRunnerPolicy}
onSaveRuntimePolicySet={saveRuntimePolicySet}
onBatchAccessRules={batchSaveAccessRules}
onSaveAccessRule={saveAccessRule}
onSaveFileStorageChannel={saveFileStorageChannel}
onSaveFileStorageSettings={saveFileStorageSettings}
onSaveTenant={saveTenant}
onSaveUser={saveUser}
onSetUserWalletBalance={saveUserWalletBalance}
onSaveUserGroup={saveUserGroup}
onClearOperationMessage={() => setCoreMessage('')}
onSectionChange={navigateAdminSection}
/>
) : (
<LoginRequiredPanel
authMode={authMode}
externalToken={externalToken}
loginForm={loginForm}
registerForm={registerForm}
state={state}
onAuthModeChange={setAuthMode}
onExternalTokenChange={setExternalToken}
onLoginChange={setLoginForm}
onRegisterChange={setRegisterForm}
onSubmitExternalToken={submitExternalToken}
onSubmitLogin={submitLogin}
onSubmitRegister={submitRegister}
/>
)
)}
{activePage === 'docs' && (
<ApiDocsPage
activeDocSection={apiDocSection}
apiKeySecret={apiKeySecret}
canRun={isAuthenticated}
coreMessage={coreMessage}
coreState={coreState}
taskForm={taskForm}
taskResult={taskResult}
onDocSectionChange={navigateApiDocSection}
onLogin={showLogin}
onSubmitTask={submitTask}
onTaskFormChange={setTaskForm}
/>
)}
</AppShell>
);
}
function platformModelIsSelected(model: PlatformModel, selectedModels: PlatformModelBindingInput[]) {
return selectedModels.some((selected) => {
if (selected.baseModelId && model.baseModelId) return selected.baseModelId === model.baseModelId;
return selected.modelName === model.modelName && sameModelTypes(selected.modelType, model.modelType);
});
}
function sameModelTypes(left: string[], right: string[]) {
if (left.length !== right.length) return false;
const rightSet = new Set(right);
return left.every((type) => rightSet.has(type));
}
function mergeExistingPlatformModelInput(input: PlatformModelBindingInput, currentModels: PlatformModel[], platformId: string): PlatformModelBindingInput {
const existing = currentModels.find((model) => model.platformId === platformId && platformModelIsSelected(model, [input]));
if (!existing) return input;
return {
...input,
providerModelName: input.providerModelName ?? existing.providerModelName,
discountFactor: (input.discountFactor ?? existing.discountFactor) || undefined,
pricingRuleSetId: input.pricingRuleSetId ?? existing.pricingRuleSetId,
rateLimitPolicy: input.rateLimitPolicy ?? existing.rateLimitPolicy,
retryPolicy: input.retryPolicy ?? existing.retryPolicy,
runtimePolicyOverride: input.runtimePolicyOverride ?? (existing.runtimePolicyOverride as Record<string, unknown> | undefined),
runtimePolicySetId: input.runtimePolicySetId ?? existing.runtimePolicySetId,
};
}
function platformStatusUpdatePayload(platform: IntegrationPlatform, status: 'enabled' | 'disabled'): PlatformCreateInput {
return {
provider: platform.provider,
platformKey: platform.platformKey,
name: platform.name,
internalName: platform.internalName,
baseUrl: platform.baseUrl,
authType: platform.authType,
config: platform.config ?? {},
retryPolicy: platform.retryPolicy ?? {},
rateLimitPolicy: platform.rateLimitPolicy ?? {},
defaultPricingMode: platform.defaultPricingMode,
defaultDiscountFactor: platform.defaultDiscountFactor,
pricingRuleSetId: platform.pricingRuleSetId,
priority: platform.priority,
status,
};
}
function withCredentialPreviewFallback(
platform: IntegrationPlatform,
input: PlatformCreateInput,
existing?: IntegrationPlatform,
): IntegrationPlatform {
const responsePreview = nonEmptyRecord(platform.credentialsPreview);
if (responsePreview) return platform;
if (input.credentials !== undefined) {
const inputPreview = maskCredentialsPreview(input.credentials);
return { ...platform, credentialsPreview: inputPreview ?? {} };
}
const existingPreview = nonEmptyRecord(existing?.credentialsPreview);
return existingPreview ? { ...platform, credentialsPreview: existingPreview } : platform;
}
function maskCredentialsPreview(credentials: Record<string, unknown> | undefined) {
if (!credentials || Object.keys(credentials).length === 0) return undefined;
return Object.fromEntries(Object.entries(credentials).map(([key, value]) => [key, maskCredentialValue(value)]));
}
function maskCredentialValue(value: unknown): unknown {
if (typeof value === 'string') return maskSecret(value);
if (Array.isArray(value)) return value.map(maskCredentialValue);
if (value && typeof value === 'object') {
return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([key, nested]) => [key, maskCredentialValue(nested)]));
}
return value;
}
function maskSecret(value: string) {
const trimmed = value.trim();
if (!trimmed) return '';
if (trimmed.length <= 6) return '*'.repeat(trimmed.length);
return `${trimmed.slice(0, 3)}${'*'.repeat(trimmed.length - 6)}${trimmed.slice(-3)}`;
}
function nonEmptyRecord(value: Record<string, unknown> | undefined) {
return value && Object.keys(value).length > 0 ? value : undefined;
}
function defaultWorkspaceTransactionQuery(): WorkspaceTransactionQuery {
return {
query: '',
createdFrom: '',
createdTo: '',
page: 1,
pageSize: 10,
};
}
function normalizeWorkspaceTransactionQuery(query: WorkspaceTransactionQuery): WorkspaceTransactionQuery {
return {
query: query.query.trim(),
createdFrom: query.createdFrom.trim(),
createdTo: query.createdTo.trim(),
page: positiveInt(query.page, 1),
pageSize: clampTransactionPageSize(query.pageSize),
};
}
function workspaceTransactionQueryKey(query: WorkspaceTransactionQuery) {
return JSON.stringify(normalizeWorkspaceTransactionQuery(query));
}
function positiveInt(value: number, fallback: number) {
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
}
function clampTransactionPageSize(value: number) {
const normalized = positiveInt(value, 10);
return Math.min(100, Math.max(1, normalized));
}
function dataKeysForRoute(
activePage: PageKey,
adminSection: AdminSection,
workspaceSection: WorkspaceSection,
isAuthenticated: boolean,
): DataKey[] {
if (activePage === 'playground') return isAuthenticated ? ['playgroundModels', 'playgroundApiKeys'] : [];
if (activePage === 'models') {
return isAuthenticated
? ['modelCatalog']
: ['publicCatalog'];
}
if (activePage === 'home' || activePage === 'docs') return [];
if (!isAuthenticated) return [];
if (activePage === 'workspace') {
if (workspaceSection === 'overview') return ['users', 'userGroups', 'apiKeys'];
if (workspaceSection === 'billing') return ['wallet'];
if (workspaceSection === 'apiKeys') return ['apiKeys', 'accessRules', 'playgroundModels'];
if (workspaceSection === 'tasks') return ['tasks'];
if (workspaceSection === 'transactions') return ['wallet', 'walletTransactions'];
return [];
}
if (activePage !== 'admin') return [];
switch (adminSection) {
case 'overview':
return ['platforms', 'models', 'providers', 'pricingRules', 'runtimePolicySets', 'rateLimitWindows', 'modelRateLimits', 'tenants', 'users', 'userGroups', 'accessRules'];
case 'globalModels':
return ['providers'];
case 'pricing':
return ['pricingRuleSets'];
case 'runtime':
return ['runtimePolicySets', 'runnerPolicy'];
case 'baseModels':
return ['baseModels', 'providers', 'pricingRuleSets', 'runtimePolicySets'];
case 'platforms':
return ['platforms', 'models', 'providers', 'baseModels', 'pricingRuleSets', 'networkProxyConfig'];
case 'realtimeLoad':
return ['platforms', 'modelRateLimits'];
case 'tenants':
return ['tenants', 'userGroups'];
case 'users':
return ['users', 'tenants', 'userGroups'];
case 'userGroups':
return ['userGroups'];
case 'auditLogs':
return ['auditLogs'];
case 'accessRules':
return ['accessRules', 'userGroups', 'platforms', 'models'];
case 'systemSettings':
return ['fileStorageSettings', 'fileStorageChannels'];
default:
return [];
}
}