# AI Gateway Frontend AI 网关管理前端,提供供应商配置和用量统计界面。 ## 技术栈 - **运行时**: Bun - **构建工具**: Vite - **语言**: TypeScript (strict mode) - **框架**: React - **UI 组件库**: TDesign - **路由**: React Router v7 - **数据获取**: TanStack Query v5 - **样式**: SCSS Modules(禁止使用纯 CSS) - **测试**: Vitest + React Testing Library + Playwright ## API 层 ### 字段转换机制 后端使用 `snake_case`,前端使用 `camelCase`,API 层自动转换: ```typescript // 发送请求时:camelCase → snake_case toApi({ providerId: "openai" }) // → { provider_id: "openai" } // 接收响应时:snake_case → camelCase fromApi({ provider_id: "openai" }) // → { providerId: "openai" } ``` ### 统一请求函数 ```typescript export async function request(method: string, path: string, body?: unknown): Promise ``` - 自动处理字段转换 - 自动处理 204 响应(无 body) - 抛出 `ApiError` 包含 `status`、`code`、`message` ### 错误处理 ```typescript class ApiError extends Error { status: number; // HTTP 状态码 code?: string; // 业务错误码 message: string; // 错误消息 } ``` ## TanStack Query 模式 ### Query Keys 定义 ```typescript // src/hooks/useProviders.ts export const providerKeys = { all: ['providers'] as const, }; // src/hooks/useModels.ts export const modelKeys = { all: ['models'] as const, byProvider: (providerId: string) => [...modelKeys.all, { providerId }] as const, }; ``` ### Mutation 使用 ```typescript const mutation = useMutation({ mutationFn: createProvider, onSuccess: () => { queryClient.invalidateQueries({ queryKey: providerKeys.all }); }, }); ``` ## 项目结构 ``` frontend/ ├── src/ │ ├── api/ # API 层 │ │ ├── client.ts # 统一 request() + 字段转换 │ │ ├── providers.ts # Provider CRUD │ │ ├── models.ts # Model CRUD │ │ └── stats.ts # Stats 查询 │ ├── components/ │ │ └── AppLayout/ # 侧边栏导航布局 │ ├── hooks/ # TanStack Query hooks │ │ ├── useProviders.ts │ │ ├── useModels.ts │ │ └── useStats.ts │ ├── pages/ │ │ ├── Providers/ # 供应商管理(含内嵌模型管理) │ │ ├── Stats/ # 用量统计 │ │ ├── Settings/ # 设置(开发中) │ │ └── NotFound.tsx │ ├── routes/ │ │ └── index.tsx # 路由配置 │ ├── types/ │ │ └── index.ts # 类型定义 │ ├── __tests__/ # 测试 │ │ ├── setup.ts │ │ ├── api/ │ │ ├── hooks/ │ │ └── components/ │ ├── App.tsx │ ├── main.tsx │ └── index.scss ├── e2e/ # Playwright E2E 测试 ├── vitest.config.ts ├── playwright.config.ts ├── tsconfig.json ├── vite.config.ts └── package.json ``` ## 运行方式 ### 安装依赖 ```bash bun install ``` ### 开发模式 ```bash bun run dev ``` 前端将在端口 5173 启动,API 请求通过 Vite proxy 转发到后端(localhost:9826)。 ### 构建生产版本 ```bash bun run build ``` ### 代码检查 ```bash bun run lint ``` ## 测试 ### 单元测试 + 组件测试 ```bash bun run test # 运行所有测试 bun run test:watch # 监听模式 bun run test:coverage # 生成覆盖率报告 ``` ### E2E 测试 ```bash bun run test:e2e ``` ## 功能 ### 供应商管理 - 查看供应商列表(TDesign Table) - 添加新供应商(Modal Form) - 编辑供应商配置 - 删除供应商(Popconfirm 确认) - API Key 脱敏显示 - 启用/禁用状态标签 - **协议字段**:支持 OpenAI 和 Anthropic 协议选择 ### 模型管理 - 展开供应商行查看关联模型 - 添加/编辑/删除模型 - 按供应商筛选模型 - **统一模型 ID**:显示格式为 `provider_id/model_name`,用于跨协议模型识别 - **UUID 自动生成**:创建模型时后端自动生成 UUID,无需手动输入 ID ### 用量统计 - 查看统计数据 - 按供应商筛选 - 按模型筛选 - 按日期范围筛选(DatePicker.RangePicker) ## 测试策略 ### 目录结构 ``` __tests__/ ├── setup.ts # 测试配置(happy-dom) ├── api/ # API 层测试 │ └── client.test.ts ├── hooks/ # TanStack Query Hook 测试 │ ├── useProviders.test.ts │ └── useModels.test.ts └── components/ # 组件测试 └── AppLayout.test.tsx ``` ### E2E 测试 - 位于 `e2e/` 目录 - 使用 Playwright - 自动启动后端服务(临时端口 19026) - 配置文件:`playwright.config.ts` ### Mock 策略 - API 层测试使用 MSW(Mock Service Worker) - Hook 测试使用 `@testing-library/react-hooks` - 组件测试使用 `@testing-library/react` ## 环境变量 | 变量 | 开发环境 | 生产环境 | 说明 | |------|----------|----------|------| | `VITE_API_BASE` | (空) | `/api` | API 基础路径,空则走 Vite proxy | **E2E 测试特有**: - `NEX_BACKEND_PORT` - E2E 后端端口(默认 19026) - `NEX_E2E_TEMP_DIR` - E2E 临时目录 ## 开发规范 - 所有样式使用 SCSS,禁止使用纯 CSS 文件 - 组件级样式使用 SCSS Modules(*.module.scss) - 图标优先使用 TDesign 图标(tdesign-icons-react) - TypeScript strict 模式,禁止 any 类型 - API 层自动处理 snake_case ↔ camelCase 字段转换 - 使用路径别名 `@/` 引用 src 目录 ### 环境要求 - Bun 1.0 或更高版本 ### 添加新页面 1. 在 `src/pages/` 创建页面目录和组件 2. 在 `src/hooks/` 创建对应的 TanStack Query hook 3. 在 `src/routes/index.tsx` 添加路由 4. 在 `src/components/AppLayout/index.tsx` 的 Menu 中添加 MenuItem