Files
easyai-ai-gateway/apps/web/src/routing.ts
T
wangbo de10875439 feat(admin): 优化后台运维与任务管理
完善平台、基准模型、定价规则和实时负载的紧凑查询与滚动展示,并固定关键操作列。

新增管理员任务记录、批量计费结算和用户组限流、额度及模型权限维护能力,同时补充任务脱敏、查询索引与 OpenAPI 契约。

验证:pnpm openapi、Go 全量测试、pnpm lint、pnpm test、pnpm build、gofmt 与差异格式检查均通过。
2026-07-25 01:32:49 +08:00

320 lines
12 KiB
TypeScript

import type { AdminSection, AdminTaskQuery, ApiDocSection, PageKey, PlaygroundMode, WorkspaceSection, WorkspaceTaskQuery } from './types';
export interface AppRouteState {
activePage: PageKey;
adminSection: AdminSection;
adminTaskQuery: AdminTaskQuery;
apiDocSection: ApiDocSection;
playgroundMode: PlaygroundMode;
workspaceSection: WorkspaceSection;
workspaceTaskQuery: WorkspaceTaskQuery;
}
export const defaultRouteState: AppRouteState = {
activePage: 'home',
adminSection: 'overview',
adminTaskQuery: defaultAdminTaskQuery(),
apiDocSection: 'openApiOverview',
playgroundMode: 'chat',
workspaceSection: 'overview',
workspaceTaskQuery: defaultWorkspaceTaskQuery(),
};
const workspacePaths: Record<WorkspaceSection, string> = {
overview: '/workspace/overview',
billing: '/workspace/billing',
apiKeys: '/workspace/api-keys',
tasks: '/workspace/tasks',
transactions: '/workspace/transactions',
};
const adminPaths: Record<AdminSection, string> = {
overview: '/admin/overview',
globalModels: '/admin/providers',
baseModels: '/admin/base-models',
pricing: '/admin/pricing',
billingSettlements: '/admin/billing-settlements',
platforms: '/admin/platforms',
realtimeLoad: '/admin/realtime-load',
tasks: '/admin/tasks',
tenants: '/admin/tenants',
users: '/admin/users',
userGroups: '/admin/user-groups',
auditLogs: '/admin/audit-logs',
runtime: '/admin/runtime',
systemSettings: '/admin/system-settings',
};
const docsPaths: Record<ApiDocSection, string> = {
openApiOverview: '/docs/open-api',
compatApiOverview: '/docs/compatible-api',
compatGemini: '/docs/compatible-api/gemini',
compatKling: '/docs/compatible-api/kling',
compatVolces: '/docs/compatible-api/volces',
compatServerMain: '/docs/compatible-api/server-main',
guideBaseUrl: '/docs/auth',
guideWebhook: '/docs/webhook',
guideErrors: '/docs/errors',
guideTesting: '/docs/testing',
chat: '/docs/chat',
responses: '/docs/responses',
embeddings: '/docs/embeddings',
reranks: '/docs/reranks',
imageGeneration: '/docs/images/generations',
imageEdit: '/docs/images/edits',
videoGeneration: '/docs/videos/generations',
asyncMode: '/docs/async',
taskRetrieve: '/docs/tasks/retrieve',
pricing: '/docs/pricing',
files: '/docs/files',
};
const playgroundPaths: Record<PlaygroundMode, string> = {
chat: '/playground/chat',
image: '/playground/image',
video: '/playground/video',
};
const workspaceSections = reverseMap(workspacePaths);
const adminSections = reverseMap(adminPaths);
const docsSections = reverseMap(docsPaths);
const playgroundSections = reverseMap(playgroundPaths);
export function parseAppRoute(input = currentLocationPath()): AppRouteState {
const origin = typeof window === 'undefined' ? 'http://localhost' : window.location.origin;
const url = new URL(input, origin);
const path = normalizePath(url.pathname);
const workspaceTaskQuery = parseWorkspaceTaskQuery(url.searchParams);
const adminTaskQuery = parseAdminTaskQuery(url.searchParams);
if (path === '/') return { ...defaultRouteState };
if (path.startsWith('/playground')) {
return { ...defaultRouteState, activePage: 'playground', playgroundMode: parsePlaygroundMode(path), workspaceTaskQuery, adminTaskQuery };
}
if (path === '/models') return { ...defaultRouteState, activePage: 'models', workspaceTaskQuery, adminTaskQuery };
if (path.startsWith('/workspace')) {
return { ...defaultRouteState, activePage: 'workspace', workspaceSection: parseWorkspaceSection(path), workspaceTaskQuery, adminTaskQuery };
}
if (path.startsWith('/admin')) {
return { ...defaultRouteState, activePage: 'admin', adminSection: parseAdminSection(path), workspaceTaskQuery, adminTaskQuery };
}
if (path.startsWith('/docs')) {
return { ...defaultRouteState, activePage: 'docs', apiDocSection: parseDocSection(path), workspaceTaskQuery, adminTaskQuery };
}
return { ...defaultRouteState };
}
function currentLocationPath() {
if (typeof window === 'undefined') return '/';
return `${window.location.pathname}${window.location.search}`;
}
export function pathForPage(page: PageKey, route: AppRouteState): string {
if (page === 'playground') return pathForPlaygroundMode(route.playgroundMode);
if (page === 'models') return '/models';
if (page === 'workspace') return pathForWorkspaceSection(route.workspaceSection);
if (page === 'admin') return pathForAdminSection(route.adminSection);
if (page === 'docs') return pathForApiDocSection(route.apiDocSection);
return '/';
}
export function pathForWorkspaceSection(section: WorkspaceSection) {
return workspacePaths[section] ?? workspacePaths.overview;
}
export function pathForWorkspaceTaskQuery(query: WorkspaceTaskQuery) {
const normalized = normalizeWorkspaceTaskQuery(query);
const search = new URLSearchParams();
if (normalized.query) search.set('q', normalized.query);
if (normalized.modelType) search.set('type', normalized.modelType);
if (normalized.createdFrom) search.set('from', normalized.createdFrom);
if (normalized.createdTo) search.set('to', normalized.createdTo);
if (normalized.page !== 1) search.set('page', String(normalized.page));
if (normalized.pageSize !== 10) search.set('pageSize', String(normalized.pageSize));
const suffix = search.toString();
return `${workspacePaths.tasks}${suffix ? `?${suffix}` : ''}`;
}
export function pathForAdminTaskQuery(query: AdminTaskQuery) {
const normalized = normalizeAdminTaskQuery(query);
const search = new URLSearchParams();
if (normalized.query) search.set('q', normalized.query);
if (normalized.tenantId) search.set('tenantId', normalized.tenantId);
if (normalized.userId) search.set('userId', normalized.userId);
if (normalized.userGroupId) search.set('userGroupId', normalized.userGroupId);
if (normalized.status) search.set('status', normalized.status);
if (normalized.platformId) search.set('platformId', normalized.platformId);
if (normalized.model) search.set('model', normalized.model);
if (normalized.modelType) search.set('modelType', normalized.modelType);
if (normalized.runMode) search.set('runMode', normalized.runMode);
if (normalized.billingStatus) search.set('billingStatus', normalized.billingStatus);
if (normalized.apiKey) search.set('apiKey', normalized.apiKey);
if (normalized.createdFrom) search.set('createdFrom', normalized.createdFrom);
if (normalized.createdTo) search.set('createdTo', normalized.createdTo);
if (normalized.page !== 1) search.set('page', String(normalized.page));
if (normalized.pageSize !== 20) search.set('pageSize', String(normalized.pageSize));
const suffix = search.toString();
return `${adminPaths.tasks}${suffix ? `?${suffix}` : ''}`;
}
export function pathForAdminSection(section: AdminSection) {
return adminPaths[section] ?? adminPaths.overview;
}
export function pathForApiDocSection(section: ApiDocSection) {
return docsPaths[section] ?? docsPaths.openApiOverview;
}
export function pathForPlaygroundMode(mode: PlaygroundMode) {
return playgroundPaths[mode] ?? playgroundPaths.chat;
}
function parseWorkspaceSection(path: string): WorkspaceSection {
return workspaceSections[path] ?? (path === '/workspace' ? 'overview' : 'overview');
}
function parseAdminSection(path: string): AdminSection {
if (path === '/admin') return 'overview';
if (path === '/admin/models/global') return 'baseModels';
if (path === '/admin/usergroups') return 'userGroups';
if (path === '/admin/access-rules') return 'userGroups';
return adminSections[path] ?? 'overview';
}
function parseDocSection(path: string): ApiDocSection {
if (path === '/docs') return 'openApiOverview';
if (path === '/docs/playground') return 'guideTesting';
if (path === '/docs/api/chat') return 'chat';
if (path === '/docs/api/responses') return 'responses';
if (path === '/docs/api/embeddings') return 'embeddings';
if (path === '/docs/api/reranks') return 'reranks';
if (path === '/docs/api/media') return 'imageGeneration';
if (path === '/docs/api/videos') return 'videoGeneration';
if (path === '/docs/api/tasks') return 'taskRetrieve';
return docsSections[path] ?? 'openApiOverview';
}
function parsePlaygroundMode(path: string): PlaygroundMode {
if (path === '/playground') return 'chat';
return playgroundSections[path] ?? 'chat';
}
function normalizePath(pathname: string) {
const path = pathname.replace(/\/+$/, '');
return path || '/';
}
function reverseMap<T extends string>(value: Record<T, string>) {
return Object.fromEntries(Object.entries(value).map(([key, path]) => [path, key])) as Record<string, T>;
}
export function defaultWorkspaceTaskQuery(): WorkspaceTaskQuery {
return {
query: '',
modelType: '',
createdFrom: '',
createdTo: '',
page: 1,
pageSize: 10,
};
}
export function normalizeWorkspaceTaskQuery(query: WorkspaceTaskQuery): WorkspaceTaskQuery {
return {
query: query.query.trim(),
modelType: query.modelType.trim(),
createdFrom: query.createdFrom.trim(),
createdTo: query.createdTo.trim(),
page: positiveInt(query.page, 1),
pageSize: clampPageSize(query.pageSize),
};
}
export function workspaceTaskQueryKey(query: WorkspaceTaskQuery) {
const normalized = normalizeWorkspaceTaskQuery(query);
return JSON.stringify(normalized);
}
export function defaultAdminTaskQuery(): AdminTaskQuery {
return {
query: '',
tenantId: '',
userId: '',
userGroupId: '',
status: '',
platformId: '',
model: '',
modelType: '',
runMode: '',
billingStatus: '',
apiKey: '',
createdFrom: '',
createdTo: '',
page: 1,
pageSize: 20,
};
}
export function normalizeAdminTaskQuery(query: AdminTaskQuery): AdminTaskQuery {
return {
query: query.query.trim(),
tenantId: query.tenantId.trim(),
userId: query.userId.trim(),
userGroupId: query.userGroupId.trim(),
status: query.status.trim(),
platformId: query.platformId.trim(),
model: query.model.trim(),
modelType: query.modelType.trim(),
runMode: query.runMode.trim(),
billingStatus: query.billingStatus.trim(),
apiKey: query.apiKey.trim(),
createdFrom: query.createdFrom.trim(),
createdTo: query.createdTo.trim(),
page: positiveInt(query.page, 1),
pageSize: Math.min(100, Math.max(1, positiveInt(query.pageSize, 20))),
};
}
export function adminTaskQueryKey(query: AdminTaskQuery) {
return JSON.stringify(normalizeAdminTaskQuery(query));
}
function parseAdminTaskQuery(search: URLSearchParams): AdminTaskQuery {
return normalizeAdminTaskQuery({
query: search.get('q') ?? search.get('query') ?? '',
tenantId: search.get('tenantId') ?? '',
userId: search.get('userId') ?? '',
userGroupId: search.get('userGroupId') ?? '',
status: search.get('status') ?? '',
platformId: search.get('platformId') ?? '',
model: search.get('model') ?? '',
modelType: search.get('modelType') ?? '',
runMode: search.get('runMode') ?? '',
billingStatus: search.get('billingStatus') ?? '',
apiKey: search.get('apiKey') ?? '',
createdFrom: search.get('createdFrom') ?? search.get('from') ?? '',
createdTo: search.get('createdTo') ?? search.get('to') ?? '',
page: Number(search.get('page') ?? 1),
pageSize: Number(search.get('pageSize') ?? 20),
});
}
function parseWorkspaceTaskQuery(search: URLSearchParams): WorkspaceTaskQuery {
return normalizeWorkspaceTaskQuery({
query: search.get('q') ?? search.get('query') ?? '',
modelType: search.get('type') ?? search.get('modelType') ?? '',
createdFrom: search.get('from') ?? search.get('createdFrom') ?? '',
createdTo: search.get('to') ?? search.get('createdTo') ?? '',
page: Number(search.get('page') ?? 1),
pageSize: Number(search.get('pageSize') ?? search.get('limit') ?? 10),
});
}
function positiveInt(value: number, fallback: number) {
return Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
}
function clampPageSize(value: number) {
const normalized = positiveInt(value, 10);
return Math.min(100, Math.max(1, normalized));
}