feat(web): add reusable admin form dialog

This commit is contained in:
2026-05-09 20:15:35 +08:00
parent c0335bd5d0
commit a5e66e79cd
49 changed files with 6013 additions and 1108 deletions
+110
View File
@@ -0,0 +1,110 @@
import type { AdminSection, ApiDocSection, PageKey, WorkspaceSection } from './types';
export interface AppRouteState {
activePage: PageKey;
adminSection: AdminSection;
apiDocSection: ApiDocSection;
workspaceSection: WorkspaceSection;
}
export const defaultRouteState: AppRouteState = {
activePage: 'home',
adminSection: 'overview',
apiDocSection: 'chat',
workspaceSection: 'overview',
};
const workspacePaths: Record<WorkspaceSection, string> = {
overview: '/workspace/overview',
billing: '/workspace/billing',
apiKeys: '/workspace/api-keys',
tasks: '/workspace/tasks',
};
const adminPaths: Record<AdminSection, string> = {
overview: '/admin/overview',
globalModels: '/admin/providers',
baseModels: '/admin/base-models',
pricing: '/admin/pricing',
platforms: '/admin/platforms',
tenants: '/admin/tenants',
users: '/admin/users',
userGroups: '/admin/user-groups',
runtime: '/admin/runtime',
};
const docsPaths: Record<ApiDocSection, string> = {
chat: '/docs/chat',
imageGeneration: '/docs/images/generations',
imageEdit: '/docs/images/edits',
pricing: '/docs/pricing',
files: '/docs/files',
};
const workspaceSections = reverseMap(workspacePaths);
const adminSections = reverseMap(adminPaths);
const docsSections = reverseMap(docsPaths);
export function parseAppRoute(pathname = window.location.pathname): AppRouteState {
const path = normalizePath(pathname);
if (path === '/') return { ...defaultRouteState };
if (path === '/models') return { ...defaultRouteState, activePage: 'models' };
if (path.startsWith('/workspace')) {
return { ...defaultRouteState, activePage: 'workspace', workspaceSection: parseWorkspaceSection(path) };
}
if (path.startsWith('/admin')) {
return { ...defaultRouteState, activePage: 'admin', adminSection: parseAdminSection(path) };
}
if (path.startsWith('/docs')) {
return { ...defaultRouteState, activePage: 'docs', apiDocSection: parseDocSection(path) };
}
return { ...defaultRouteState };
}
export function pathForPage(page: PageKey, route: AppRouteState): string {
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 pathForAdminSection(section: AdminSection) {
return adminPaths[section] ?? adminPaths.overview;
}
export function pathForApiDocSection(section: ApiDocSection) {
return docsPaths[section] ?? docsPaths.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';
return adminSections[path] ?? 'overview';
}
function parseDocSection(path: string): ApiDocSection {
if (path === '/docs') return 'chat';
if (path === '/docs/api/chat') return 'chat';
if (path === '/docs/api/media') return 'imageGeneration';
if (path === '/docs/playground') return 'chat';
return docsSections[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>;
}