178 lines
14 KiB
Markdown
178 lines
14 KiB
Markdown
# 前端开发
|
||
|
||
开发规范见 [开发规范文档](README.md)。
|
||
|
||
## 布局架构
|
||
|
||
两个布局入口共享 ConsoleShell(`src/web/shared/components/ConsoleShell/`):
|
||
|
||
- **AdminLayout**(`src/web/layouts/admin-layout/`):路由 `/`(总览)、`/projects`、`/models`、`/models/providers`。
|
||
- **WorkbenchLayout**(`src/web/layouts/workbench-layout/`):路由 `/workbench/:projectId`、`/workbench/:projectId/chat`。`WorkbenchProjectGate` 从 URL 读 projectId,通过 `ProjectContext` 提供项目上下文,仅 active 项目渲染。
|
||
|
||
ConsoleShell 包含:`XProvider(zhCN + zhCN_X)` + `AntApp` + `Layout`(Header/Sider/Content) + 主题切换(明亮/黑暗/系统),主题配置由 `shared/theme/theme-config.ts` 的 `buildThemeConfig(effectiveTheme)` 集中构建(含 `cssVar`、`borderRadius`、`controlHeight`、`components.Layout` 配色);侧边栏折叠。Header 显示品牌名、版本号和布局标题。
|
||
|
||
`Sidebar`(`src/web/shared/components/Sidebar/`)纯展示组件,通过 `menuItems` props 接收配置。
|
||
|
||
## 功能模块
|
||
|
||
| 功能模块 | 路径 | 说明 |
|
||
| -------- | --------------------- | --------------------------- |
|
||
| 仪表盘 | `features/dashboard/` | 总览页面 |
|
||
| 项目管理 | `features/projects/` | 项目 CRUD、归档、搜索、排序 |
|
||
| 模型管理 | `features/models/` | 供应商/模型管理、连通性测试 |
|
||
| 聊天 | `features/chat/` | 会话管理、消息渲染、AI 对话 |
|
||
| 收集箱 | `features/inbox/` | 素材 CRUD、持久化、列表管理 |
|
||
|
||
## 页面
|
||
|
||
| 页面 | 路径 | 入口 |
|
||
| -------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| 总览 | `/` | `features/dashboard/index.tsx` |
|
||
| 项目管理 | `/projects` | `features/projects/index.tsx` — FilterToolbar(状态 Select + 搜索 + 新建/归档恢复删除) + ProjectTable + ProjectFormModal。支持创建/编辑/归档/恢复/删除、列表排序、URL 同步筛选参数。 |
|
||
| 模型管理 | `/models` 和 `/models/providers` | 独立路由页面:`ModelListPage.tsx`(FilterToolbar + ModelTable) + `ProviderListPage.tsx`(FilterToolbar + ProviderTable)。模型支持供应商/能力筛选和列表排序,供应商支持类型筛选和列表排序。模型表单使用 `GET /api/providers/options`。供应商表单支持预保存连通性测试(`POST /api/providers/test`)。 |
|
||
| 聊天室 | `/workbench/:id` | `features/chat/index.tsx` |
|
||
| 收集箱 | `/workbench/:id/inbox` | `features/inbox/index.tsx` — 协调层(selectedId + modalOpen)+ MaterialSidebar(列表容器)+ MaterialDetailPanel(详情容器)+ AddMaterialModal。素材 CRUD 通过 TanStack Query hooks 接入后端 API。 |
|
||
| 404 | `*` | `features/not-found/index.tsx` |
|
||
|
||
### 聊天页面
|
||
|
||
`ChatPage` = `ConversationSidebar`(自定义组件)+ `ChatPanel`。
|
||
|
||
- **ConversationSidebar**:会话侧边栏数据加载层(useQuery + 错误处理)。内部渲染 `ConversationList`(搜索框 + OverlayScrollbars 滚动 + 日期分组 + ConversationCard 列表)。对话按 `updatedAt` 分组(今天/昨天/本周/本月/更早),支持搜索过滤和 hover 删除(Popconfirm)。
|
||
- **ChatPanel**:`useChat`(@ai-sdk/react)+ `DefaultChatTransport`(ai 包)与后端 SSE 通信。按 `part.type` 分派渲染:TextPart(markdown-to-jsx 含自定义 overrides:CodeBlock 提供 Shiki 语法高亮和复制按钮、MarkdownTable 提供类 antd 表格样式)、ReasoningPart、ToolPart(四态)。支持编辑重发、重新生成、复制。
|
||
- **Sender**(@ant-design/x):输入框 + 发送/停止按钮 + 模型 Select(footer slot)。
|
||
|
||
## 共享代码
|
||
|
||
### 共享组件
|
||
|
||
| 组件 | 路径 | 说明 |
|
||
| ------------- | ------------------------------------- | ------------------------------------------------------ |
|
||
| ConsoleShell | `shared/components/ConsoleShell/` | 全局布局外壳(Provider + Layout) |
|
||
| FilterToolbar | `shared/components/FilterToolbar.tsx` | 统一筛选工具条(Select 筛选 + 搜索 + 重置 + 操作按钮) |
|
||
| Sidebar | `shared/components/Sidebar/` | 侧边栏纯展示组件 |
|
||
| SidebarGroup | `shared/components/SidebarGroup/` | 可折叠日期分组(聊天室和收集箱共用) |
|
||
| ErrorBoundary | `shared/components/ErrorBoundary.tsx` | 生产环境错误边界 |
|
||
|
||
### 共享 Hooks
|
||
|
||
| Hook | 路径 | 说明 |
|
||
| ------------------------ | --------------------------------------- | --------------------------------------------------------------------- |
|
||
| `use-page-search-params` | `shared/hooks/usePageSearchParams.ts` | URL 查询参数同步(筛选/分页/排序),批量更新 `setParams` 避免闭包覆盖 |
|
||
| `use-confirm-action` | `shared/hooks/useConfirmAction.ts` | 包装异步操作 + toast 成功/失败通知 |
|
||
| `use-meta.ts` | `shared/hooks/use-meta.ts` | `/api/meta`(30s 轮询,5s staleTime) |
|
||
| `use-providers.ts` | `shared/hooks/use-providers.ts` | 供应商 CRUD + test connection |
|
||
| `use-models.ts` | `shared/hooks/use-models.ts` | 模型 CRUD + test connection |
|
||
| `use-projects.ts` | `shared/hooks/use-projects.ts` | 项目 CRUD + archive/restore |
|
||
| `use-conversations.ts` | `shared/hooks/use-conversations.ts` | 会话和消息 fetch 函数(不含 Query hooks) |
|
||
| `use-logger` | `shared/hooks/use-logger.ts` | Logger hook(组件内使用) |
|
||
| `use-theme-preference` | `shared/hooks/use-theme-preference.ts` | 主题偏好 localStorage 持久化 |
|
||
| `use-sidebar-collapsed` | `shared/hooks/use-sidebar-collapsed.ts` | 侧边栏折叠 localStorage 持久化 |
|
||
| `use-is-dark` | `shared/hooks/use-is-dark.ts` | 当前是否暗色主题 |
|
||
|
||
### 共享主题配置
|
||
|
||
| 文件 | 导出 |
|
||
| ----------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||
| `theme/theme-config.ts` | `buildThemeConfig(effectiveTheme)` — 构建 antd ThemeConfig(algorithm、cssVar、token、components.Layout) |
|
||
| `use-current-project` | `shared/hooks/use-current-project.ts` | 当前工作台项目 + ProjectContext(需在 ProjectProvider 内) |
|
||
| `use-materials.ts` | `shared/hooks/use-materials.ts` | 素材 CRUD(create/delete/fetch/list + Query hooks) |
|
||
|
||
### 共享工具函数
|
||
|
||
| 文件 | 导出 |
|
||
| --------------------- | --------------------------------------------------------------------------------------------- |
|
||
| `utils/api.ts` | `handleResponse(response, extract)`、`handleVoidResponse(response)` |
|
||
| `utils/format.ts` | `formatDatetime(iso: string)` — 格式化 ISO 时间字符串为 `YYYY-MM-DD HH:mm` |
|
||
| `utils/time.ts` | `formatCountdown`、`formatDurationUnit`、`formatRelativeTime`、`isOlderThan`、`subtractHours` |
|
||
| `utils/date-group.ts` | `getDateGroup`、`groupByDate`、`GROUP_LABELS`、`GROUP_ORDER`、`DateGroup`、`DateGroupData` |
|
||
|
||
## 更新触发条件
|
||
|
||
修改前端技术栈、组件边界、数据流、样式规则、测试环境、前端验证方式、运行时代码结构、页面组成、组件索引、hooks/工具清单、目录结构或功能模块归属时,必须更新本文档。管理页面 CRUD 通用模式的详细约定见 [crud.md](crud.md)。
|
||
|
||
## 日志模块
|
||
|
||
### Logger 接口
|
||
|
||
`src/web/shared/utils/logger.ts` 提供与后端镜像的 Logger 抽象:
|
||
|
||
```typescript
|
||
export interface Logger {
|
||
child(bindings: Record<string, unknown>): Logger;
|
||
debug(message: string, data?: unknown): void;
|
||
error(message: string, data?: unknown): void;
|
||
info(message: string, data?: unknown): void;
|
||
setLevel(level: LogLevel): void;
|
||
warn(message: string, data?: unknown): void;
|
||
}
|
||
```
|
||
|
||
### 实现
|
||
|
||
| 实现 | 工厂函数 | 用途 |
|
||
| ----------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
||
| `DefaultLogger` + Sinks | `useLogger(bindings?)` / `createDefaultLogger()` | 组件内使用,ConsoleSink + AntdMessageSink 双流;传入 bindings 自动创建带作用域的子 Logger |
|
||
| `ConsoleLogger` | `createConsoleLogger()` | 非组件纯函数(ErrorBoundary、工具函数),仅 ConsoleSink |
|
||
| `NoopLogger` | `createNoopLogger()` | 测试中不需要日志的场景 |
|
||
| `MemoryLogger` | `createMemoryLogger()` | 测试断言日志条目 |
|
||
|
||
### 使用方式
|
||
|
||
**组件内(推荐):**
|
||
|
||
```typescript
|
||
import { useLogger } from "../../shared/hooks/use-logger";
|
||
|
||
function MyComponent() {
|
||
const logger = useLogger({ component: "MyComponent" });
|
||
logger.info("数据加载完成", { count: 42 });
|
||
logger.warn("即将超时");
|
||
logger.error("操作失败", { error: new Error("...") });
|
||
}
|
||
```
|
||
|
||
**非组件纯函数:**
|
||
|
||
```typescript
|
||
import { createConsoleLogger } from "../../shared/utils/logger";
|
||
|
||
const logger = createConsoleLogger();
|
||
logger.debug("调试信息");
|
||
```
|
||
|
||
**作用域绑定:**
|
||
|
||
组件内直接通过 `useLogger` 的 `bindings` 参数传入,hook 内部保证引用稳定(值不变时多次渲染返回同一 Logger):
|
||
|
||
```typescript
|
||
const logger = useLogger({ component: "ChatPanel", page: "workbench" });
|
||
logger.info("页面加载"); // [Alfred:INFO] 页面加载 [component=ChatPanel][page=workbench]
|
||
```
|
||
|
||
非组件场景仍可使用 `logger.child()`:
|
||
|
||
```typescript
|
||
const pageLogger = logger.child({ page: "projects" });
|
||
pageLogger.info("页面加载"); // [Alfred:INFO] 页面加载 [page=projects]
|
||
```
|
||
|
||
### notification 红线
|
||
|
||
- `AntdMessageSink` 仅对 **warn**(`message.warning`)和 **error**(`message.error`)触发用户可见通知。
|
||
- `debug` 和 `info` 级别绝不对用户弹出 notification,仅在开发者控制台输出。
|
||
- 错误详情通过 `data` 参数传入(如 `logger.error("提交失败", { error })`),`data` 不经序列化透传,保留 Error 堆栈展开能力。
|
||
|
||
### 生产环境行为
|
||
|
||
生产环境(`import.meta.env["PROD"]`)自动将 ConsoleSink 最小级别设为 `warn`,屏蔽 debug/info 输出。`useLogger()` 和 `createConsoleLogger()` 自动处理此逻辑,调用方无需关心环境判断。
|
||
|
||
### ErrorBoundary 特殊说明
|
||
|
||
`ErrorBoundary` 是 class 组件,无法使用 `useLogger()` hook。它以 `createConsoleLogger()` 直接创建独立的 ConsoleLogger 实例,仅输出到控制台不触发用户通知。
|
||
|
||
### 测试
|
||
|
||
- 单元测试使用 `createMemoryLogger()` 断言日志记录,使用 `createNoopLogger()` 静默无关日志。
|
||
- `createDefaultLogger(sinks, isProduction)` 接受 `isProduction` 参数,测试中可显式控制级别过滤行为,不依赖 `import.meta.env`。
|