📝 docs: add some cursor rules (#8338)
Some checks failed
Upstream Sync / Sync latest commits from upstream repo (push) Has been cancelled
Release CI / Release (push) Has been cancelled
Test CI / test (push) Has been cancelled
Lighthouse Badger / LobeChat | Chat (push) Has been cancelled
Lighthouse Badger / LobeChat | Market (push) Has been cancelled
Issue Close Require / issue-check-inactive (push) Has been cancelled
Issue Close Require / issue-close-require (push) Has been cancelled
Daily i18n Update / update-i18n (push) Has been cancelled

This commit is contained in:
Arvin Xu
2025-07-04 19:07:16 +08:00
committed by GitHub
parent b7042b6542
commit 987b633336
16 changed files with 1655 additions and 277 deletions

View File

@@ -28,70 +28,73 @@ LobeChat 的后端设计注重模块化、可测试性和灵活性,以适应
其主要分层如下:
1. **客户端服务层 (`src/services`)**:
* 位于 src/services/。
* 这是客户端业务逻辑的核心层,负责封装各种业务操作和数据处理逻辑。
* **环境适配**: 根据不同的运行环境,服务层会选择合适的数据访问方式:
* **本地数据库模式**: 直接调用 `Model` 层进行数据操作,适用于浏览器 PGLite 和本地 Electron 应用。
* **远程数据库模式**: 通过 `tRPC` 客户端调用服务端 API适用于需要云同步的场景。
* **类型转换**: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]>`
* 每个服务模块通常包含 `client.ts`(本地模式)、`server.ts`(远程模式)和 `type.ts`(接口定义)文件,在实现时应该确保本地模式和远程模式业务逻辑实现一致,只是数据库不同。
1. 客户端服务层 (`src/services`):
2. **API 接口层 (`TRPC`)**:
* 位于 src/server/routers/
* 使用 `tRPC` 构建类型安全的 API。Router 根据运行环境(如 Edge Functions, Node.js Lambda进行组织。
* 负责接收客户端请求,并将其路由到相应的 `Service` 层进行处理
* 新建 lambda 端点时可以参考 src/server/routers/lambda/_template.ts
- 位于 src/services/。
- 这是客户端业务逻辑的核心层,负责封装各种业务操作和数据处理逻辑。
- 环境适配: 根据不同的运行环境,服务层会选择合适的数据访问方式:
- 本地数据库模式: 直接调用 `Model` 层进行数据操作,适用于浏览器 PGLite 和本地 Electron 应用
- 远程数据库模式: 通过 `tRPC` 客户端调用服务端 API适用于需要云同步的场景。
- 类型转换: 对于简单的数据类型转换,直接在此层进行类型断言,如 `this.pluginModel.query() as Promise<LobeTool[]>`
- 每个服务模块通常包含 `client.ts`(本地模式)、`server.ts`(远程模式)和 `type.ts`(接口定义)文件,在实现时应该确保本地模式和远程模式业务逻辑实现一致,只是数据库不同。
3. **服务端服务层 (`server/services`)**:
* 位于 src/server/services/。
* 核心职责是封装独立的、可复用的业务逻辑单元。这些服务应易于测试。
* **平台差异抽象**: 一个关键特性是通过其内部的 `impls` 子目录(例如 src/server/services/file/impls 包含 s3.ts 和 local.ts来抹平不同运行环境带来的差异例如云端使用 S3 存储,桌面版使用本地文件系统)。这使得上层(如 `tRPC` routers无需关心底层具体实现。
* 目标是使 `tRPC` router 层的逻辑尽可能纯粹,专注于请求处理和业务流程编排。
* 服务会调用 `Repository` 层或直接调用 `Model` 层进行数据持久化和检索,也可能调用其他服务。
2. API 接口层 (`TRPC`):
4. **仓库层 (`Repositories`)**:
* 位于 src/database/repositories/
* 主要处理**复杂的跨表查询和数据聚合**逻辑,特别是当需要从**多个 `Model`** 获取数据并进行组合时
* 与 `Model` 层不同,`Repository` 层专注于复杂的业务查询场景,而不涉及简单的领域模型转换。
* 当业务逻辑涉及多表关联、复杂的数据统计或需要事务处理时,会使用 `Repository` 层。
* 如果数据操作简单(仅涉及单个 `Model`),则通常直接在 `src/services` 层调用 `Model` 并进行简单的类型断言。
- 位于 src/server/routers/
- 使用 `tRPC` 构建类型安全的 API。Router 根据运行时环境(如 Edge Functions, Node.js Lambda进行组织
- 负责接收客户端请求,并将其路由到相应的 `Service` 层进行处理
- 新建 lambda 端点时可以参考 src/server/routers/lambda/\_template.ts
5. **模型层 (`Models`)**:
* 位于 src/database/models/ (例如 src/database/models/plugin.ts 和 src/database/models/document.ts)。
* 提供对数据库中各个表(由 src/database/schemas/ 中的 Drizzle ORM schema 定义)的基本 CRUD (创建、读取、更新、删除) 操作和简单的查询能力。
* `Model` 类专注于单个数据表的直接操作,**不涉及复杂的领域模型转换**,这些转换通常在上层的 `src/services` 中通过类型断言完成。
* model例如 Topic 层接口经常需要从对应的 schema 层导入 NewTopic 和 TopicItem
* 创建新的 model 时可以参考 src/database/models/_template.ts
3. 仓库层 (`Repositories`):
6. **数据库 (`Database`)**:
* **客户端模式 (浏览器/PWA)**: 使用 PGLite (基于 WASM 的 PostgreSQL),数据存储在用户浏览器本地
* **服务端模式 (云部署)**: 使用远程 PostgreSQL 数据库
* **Electron 桌面应用**:
* Electron 客户端会启动一个本地 Node.js 服务
* 本地服务通过 `tRPC` 与 Electron 的渲染进程通信。
* 数据库选择依赖于是否开启**云同步**功能:
* **云同步开启**: 连接到远程 PostgreSQL 数据库。
* **云同步关闭**: 使用 PGLite (通过 Node.js 的 WASM 实现) 在本地存储数据
- 位于 src/database/repositories/。
- 主要处理复杂的跨表查询和数据聚合逻辑,特别是当需要从多个 `Model` 获取数据并进行组合时
- 与 `Model` 层不同,`Repository` 层专注于复杂的业务查询场景,而不涉及简单的领域模型转换
- 当业务逻辑涉及多表关联、复杂的数据统计或需要事务处理时,会使用 `Repository` 层。
- 如果数据操作简单(仅涉及单个 `Model`),则通常直接在 `src/services` 层调用 `Model` 并进行简单的类型断言
4. 模型层 (`Models`):
- 位于 src/database/models/ (例如 src/database/models/plugin.ts 和 src/database/models/document.ts)
- 提供对数据库中各个表(由 src/database/schemas/ 中的 Drizzle ORM schema 定义)的基本 CRUD (创建、读取、更新、删除) 操作和简单的查询能力。
- `Model` 类专注于单个数据表的直接操作,不涉及复杂的领域模型转换,这些转换通常在上层的 `src/services` 中通过类型断言完成。
- model例如 Topic 层接口经常需要从对应的 schema 层导入 NewTopic 和 TopicItem
- 创建新的 model 时可以参考 src/database/models/\_template.ts
5. 数据库 (`Database`):
- 客户端模式 (浏览器/PWA): 使用 PGLite (基于 WASM 的 PostgreSQL),数据存储在用户浏览器本地。
- 服务端模式 (云部署): 使用远程 PostgreSQL 数据库。
- Electron 桌面应用:
- Electron 客户端会启动一个本地 Node.js 服务。
- 本地服务通过 `tRPC` 与 Electron 的渲染进程通信。
- 数据库选择依赖于是否开启云同步功能:
- 云同步开启: 连接到远程 PostgreSQL 数据库。
- 云同步关闭: 使用 PGLite (通过 Node.js 的 WASM 实现) 在本地存储数据。
## 数据流向说明
### 浏览器/PWA 模式
```
UI (React) → Zustand State → Model Layer → PGLite (本地数据库)
UI (React) → Zustand action -> Client Service → Model Layer → PGLite (本地数据库)
```
### 服务端模式
```
UI (React) → Zustand State → tRPC Client → tRPC Routers → Services → Repositories/Models → Remote PostgreSQL
UI (React) → Zustand action → Client Service -> TRPC Client → TRPC Routers → Repositories/Models → Remote PostgreSQL
```
### Electron 桌面应用模式
```
UI (Electron Renderer) → Zustand State → tRPC Client → 本地 Node.js 服务 → tRPC Routers → Services → Repositories/Models → PGLite/Remote PostgreSQL (取决于云同步设置)
UI (Electron Renderer) → Zustand action → Client Service -> TRPC Client → 本地 Node.js 服务 → TRPC Routers → Repositories/Models → PGLite/Remote PostgreSQL (取决于云同步设置)
```
## 服务层 (Server Services)
- 位于 src/server/services/。
- 核心职责是封装独立的、可复用的业务逻辑单元。这些服务应易于测试。
- 平台差异抽象: 一个关键特性是通过其内部的 `impls` 子目录(例如 src/server/services/file/impls 包含 s3.ts 和 local.ts来抹平不同运行环境带来的差异例如云端使用 S3 存储,桌面版使用本地文件系统)。这使得上层(如 `tRPC` routers无需关心底层具体实现。
- 目标是使 `tRPC` router 层的逻辑尽可能纯粹,专注于请求处理和业务流程编排。
- 服务可能会调用 `Repository` 层或直接调用 `Model` 层进行数据持久化和检索,也可能调用其他服务。

View File

@@ -0,0 +1,69 @@
---
description: How to code review
globs:
alwaysApply: false
---
# Role Description
- You are a senior full-stack engineer skilled in performance optimization, security, and design systems.
- You excel at reviewing code and providing constructive feedback.
- Your task is to review submitted Git diffs **in Chinese** and return a structured review report.
- Review style: concise, direct, focused on what matters most, with actionable suggestions.
## Before the Review
Gather the modified code and context. Please strictly follow the process below:
1. Use `read_file` to read [package.json](mdc:package.json)
2. Use terminal to run command `git diff HEAD | cat` to obtain the diff and list the changed files. If you recieived empty result, run the same command once more.
3. Use `read_file` to open each changed file.
4. Use `read_file` to read [rules-attach.mdc](mdc:.cursor/rules/rules-attach.mdc). Even if you think it's unnecessary, you must read it.
5. combine changed files, step3 and `agent_requestable_workspace_rules`, list the rules which need to read
6. Use `read_file` to read the rules list in step 5
## Review
### Code Style
- Ensure JSDoc comments accurately reflect the implementation; update them when needed.
- Look for opportunities to simplify or modernize code with the latest JavaScript/TypeScript features.
- Prefer `async`/`await` over callbacks or chained `.then` promises.
- Use consistent, descriptive naming—avoid obscure abbreviations.
- Replace magic numbers or strings with well-named constants.
- Use semantically meaningful variable, function, and class names.
- Ignore purely formatting issues and other autofixable lint problems.
### Code Optimization
- Prefer `for…of` loops to index-based `for` loops when feasible.
- Decide whether callbacks should be **debounced** or **throttled**.
- Use components from `@lobehub/ui`, Ant Design, or the existing design system instead of raw HTML tags (e.g., `Button` vs. `button`).
- reuse npm packages already installed (e.g., `lodash/omit`) rather than reinventing the wheel.
- Design for dark mode and mobile responsiveness:
- Use the `antd-style` token system instead of hard-coded colors.
- Select the proper component variants.
- Performance considerations:
- Where safe, convert sequential async flows to concurrent ones with `Promise.all`, `Promise.race`, etc.
- Query only the required columns from a database rather than selecting entire rows.
### Obvious Bugs
- Do not silently swallow errors in `catch` blocks; at minimum, log them.
- Revert temporary code used only for testing (e.g., debug logs, temporary configs).
- Remove empty handlers (e.g., an empty `onClick`).
- Confirm the UI degrades gracefully for unauthenticated users.
## After the Review: output
1. Summary
- Start with a brief explanation of what the change set does.
- Summarize the changes for each modified file (or logical group).
2. Comments Issues
- List the most critical issues first.
- Use an ordered list, which will be convenient for me to reference later.
- For each issue:
- Mark severity tag (`❌ Must fix`, `⚠️ Should fix`, `💅 Nitpick`)
- Provode file path to the relevant file.
- Provide recommended fix
- End with a **git commit** command, instruct the author to run it.
- We use gitmoji to label commit messages, format: [emoji] <type>(<scope>): <subject>

View File

@@ -0,0 +1,28 @@
---
description: cursor rules writing and optimization guide
globs:
alwaysApply: false
---
当你编写或修改 Cursor Rule 时,请遵循以下准则:
- 当你知道 rule 的文件名时,使用 `read_file` 而不是 `fetch_rules` 去读取它们,它们都在项目根目录的 `.cursor/rules/` 文件夹下
- 代码示例
- 示例应尽量精简,仅保留演示核心
- 删除与示例无关的导入/导出语句,但保留必要的导入
- 同一文件存在多个示例时,若前文已演示模块导入,后续示例可省略重复导入
- 无需书写 `export`
- 可省略与演示无关或重复的 props、配置对象属性、try/catch、CSS 等代码
- 删除无关注释,保留有助理解的注释
- 格式
- 修改前请先确认原始文档语言,并保持一致
- 无序列表统一使用 `-`
- 列表末尾的句号是多余的
- 非必要不使用加粗、行内代码等样式Rule 主要供 LLM 阅读
- 避免中英文逐句对照。若括号内容为示例而非翻译,可保留
- Review
- 修正 Markdown 语法问题
- 纠正错别字
- 指出示例与说明不一致之处

View File

@@ -1,27 +0,0 @@
---
description:
globs:
alwaysApply: false
---
## Formatting Response
This is about how you should format your responses.
### Render Markdown Table
- Be aware that the cursor chat you are in can't render markdown table correctly.
- IMPORTANT: Tables need to be rendered in plain text and not markdown
When rendering tables, do not use markdown table syntax or plain text alone. Instead, place the entire table inside a code/text block (using triple backticks). This ensures the table formatting is preserved and readable in the chat interface.
Example:
```plaintext
+----+---------+-----------+
| ID | Name | Role |
+----+---------+-----------+
| 1 | Alice | Admin |
| 2 | Bob | User |
| 3 | Charlie | Moderator |
+----+---------+-----------+
```

View File

@@ -0,0 +1,94 @@
---
description:
globs:
alwaysApply: true
---
# Guide to Optimize Output(Response) Rendering
## File Path and Code Symbol Rendering
- When rendering file paths, use backtick wrapping instead of markdown links so they can be parsed as clickable links in Cursor IDE.
- Good: `src/components/Button.tsx`
- Bad: [src/components/Button.tsx](mdc:src/components/Button.tsx)
- When rendering functions, variables, or other code symbols, use backtick wrapping so they can be parsed as navigable links in Cursor IDE
- Good: The `useState` hook in `MyComponent`
- Bad: The useState hook in MyComponent
## Markdown Render
- don't use br tag to wrap in table cell
## Terminal Command Output
- If terminal commands don't produce output, it's likely due to paging issues. Try piping the command to `cat` to ensure full output is displayed.
- Good: `git show commit_hash -- file.txt | cat`
- Good: `git log --oneline | cat`
- Reason: Some git commands use pagers by default, which may prevent output from being captured properly
## Mermaid Diagram Generation: Strict Syntax Validation Checklist
Before producing any Mermaid diagram, you **must** compare your final code line-by-line against every rule in the following checklist to ensure 100% compliance. **This is a hard requirement and takes precedence over other stylistic suggestions.** Please follow these action steps:
1. Plan the Mermaid diagram logic in your mind.
2. Write the Mermaid code.
3. **Carefully review your code line-by-line against the entire checklist below.**
4. Fix any aspect of your code that doesn't comply.
5. Use the `validateMermaid` tool to check your code for syntax errors. Only proceed if validation passes.
6. Output the final, compliant, and copy-ready Mermaid code block.
7. Immediately after the Mermaid code block, output:
I have checked that the Mermaid syntax fully complies with the validation checklist.
---
### Checklist Details
#### Rule 1: Edge Labels Must Be Plain Text Only
> **Essence:** Anything inside `|...|` must contain pure, unformatted text. Absolutely NO Markdown, list markers, or parentheses/brackets allowed—these often cause rendering failures.
- **✅ Do:** `A -->|Process plain text data| B`
- **❌ Don't:** `A -->|1. Ordered list item| B` (No numbered lists)
- **❌ Don't:** `CC --"1. fetch('/api/...')"--> API` (No square brackets)
- **❌ Don't:** `A -->|- Unordered list item| B` (No hyphen lists)
- **❌ Don't:** `A -->|Transform (important)| B` (No parentheses)
- **❌ Don't:** `A -->|Transform [important]| B` (No square brackets)
#### Rule 2: Node Definition Handle Special Characters with Care
> **Essence:** When node text or subgraph titles contain special characters like `()` or `[]`, wrap the text in quotes to avoid conflicts with Mermaid shape syntax.
- **When your node text includes parentheses (e.g., 'React (JSX)'):**
- **✅ Do:** `I_REACT["<b>React component (JSX)</b>"]` (Quotes wrap all text)
- **❌ Don't:** `I_REACT(<b>React component (JSX)</b>)` (Wrong, Mermaid parses this as a shape)
- **❌ Don't:** `subgraph Plugin Features (Plugins)` (Wrong, subgraph titles with parentheses must also be wrapped in quotes)
#### Rule 3: Double Quotes in Text Must Be Escaped
> **Essence:** Use `&quot;` for double quotes **inside node text**.
- **✅ Do:** `A[This node contains &quot;quotes&quot;]`
- **❌ Don't:** `A[This node contains "quotes"]`
#### Rule 4: All Formatting Must Use HTML Tags (NOT Markdown!)
> **Essence:** For newlines, bold, and other text formatting in nodes, use HTML tags only. Markdown is not supported.
- **✅ Do (robust):** `A["<b>Bold</b> and <code>code</code><br>This is a new line"]`
- **❌ Don't (not rendered):** `C["# This is a heading"]`
- **❌ Don't (not rendered):** ``C["`const` means constant"]``
- **⚠️ Warning (unreliable):** `B["Markdown **bold** might sometimes work but DON'T rely on it"]`
#### Rule 5: No HTML Tags for Participants and Message Labels (Sequence Diagrams)
> **Important Addition:**
> In Mermaid sequence diagrams, you MUST NOT use any HTML tags (such as `<b>`, `<code>`, etc.) in:
> - `participant` display names (`as` part)
> - Message labels (the text after `:` in diagram flows)
>
> These tags are generally not rendered—they may appear as-is or cause compatibility issues.
- **✅ Do:** `participant A as Client`
- **❌ Don't:** `participant A as <b>Client</b>`
- **✅ Do:** `A->>B: 1. Establish connection`
- **❌ Don't:** `A->>B: 1. <code>Establish connection</code>`
---
**Validate each Mermaid code block by running it through the `validateMermaid` tool before delivering your output!**

193
.cursor/rules/debug.mdc Normal file
View File

@@ -0,0 +1,193 @@
---
description: Debug 调试指南
globs:
alwaysApply: false
---
# Debug 调试指南
## 💡 调试流程概览
当遇到问题时,请按照以下优先级进行处理:
1. **快速判断** - 对于熟悉的错误,直接提供解决方案
2. **信息收集** - 使用工具搜索相关代码和配置
3. **网络搜索** - 查找现有解决方案
4. **定位调试** - 添加日志进行问题定位
5. **临时方案** - 如果找不到根本解决方案,提供临时解决方案
6. **解决实施** - 提供可维护的最终解决方案
## 🔍 错误信息分析
### 错误来源识别
错误信息可能来自:
- **Terminal 输出** - 构建、运行时错误
- **浏览器控制台** - 前端 JavaScript 错误
- **开发工具** - ESLint、TypeScript、测试框架等
- **服务器日志** - API、数据库连接等后端错误
- **截图或文本** - 用户直接提供的错误信息
## 🛠️ 信息收集工具
### 代码搜索工具
使用以下工具收集相关信息,并根据场景选择最合适的工具:
- **`codebase_search` (语义搜索)**
- **何时使用**: 当你不确定具体的代码实现,想要寻找相关概念、功能或逻辑时。
- **示例**: `查询"文件上传"功能的实现`
- **`grep_search` (精确/正则搜索)**
- **何时使用**: 当你知道要查找的确切字符串、函数名、变量名或一个特定的模式时。
- **示例**: `查找所有使用了 'useState' 的地方`
- **`file_search` (文件搜索)**
- **何时使用**: 当你知道文件名的一部分,需要快速定位文件时。
- **示例**: `查找 'Button.tsx' 组件`
- **`read_file` (内容读取)**
- **何时使用**: 在定位到具体文件后,用于查看其完整内容和上下文。
- **`web_search` (网络搜索)**
- **何时使用**: 当错误信息可能与第三方库、API 或常见问题相关时,用于获取外部信息。
### 环境与依赖检查
- **检查 `package.json`**: 查看 `scripts` 了解项目如何运行、构建和测试。查看 `dependencies` 和 `devDependencies` 确认库版本,版本冲突有时是问题的根源。
- **运行测试**: 使用 `ni vitest` 运行单元测试和集成测试,这可以快速定位功能回归或组件错误。
### 项目特定搜索目标
针对 lobe-chat 项目,重点关注:
- **配置文件**: [package.json](mdc:package.json), [next.config.mjs](mdc:next.config.mjs)
- **核心功能**: `src/features/` 下的相关模块
- **状态管理**: `src/store/` 下的 Zustand stores
- **数据库**: `src/database/` 和 `src/migrations/`
- **类型定义**: `src/types/` 下的类型文件
- **服务层**: `src/services/` 下的 API 服务
- **启动流程**: [apps/desktop/src/main/core/App.ts](mdc:apps/desktop/src/main/core/App.ts) - 了解应用启动流程
## 🌐 网络搜索策略
### 搜索顺序优先级
1. **和问题相关的项目的 github issue**
2. **技术社区**
- Stack Overflow
- GitHub Discussions
- Reddit
3. **官方文档**
- 使用 `mcp_context7_resolve-library-id` 和 `mcp_context7_get-library-docs` 工具
- 查阅官方文档网站
### 搜索关键词策略
- **错误信息**: 完整的错误消息
- **技术栈**: "Next.js 15" + "error message"
- **上下文**: 添加功能相关的关键词
## 🔧 问题定位与结构化思考
如果问题比较复杂,我们要按照先定位问题,再解决问题的大方向进行。
### 结构化思考工具
对于复杂或多步骤的调试任务,使用 `mcp_sequential-thinking_sequentialthinking` 工具来结构化思考过程。这有助于:
- **分解问题**: 将大问题拆解成可管理的小步骤。
- **清晰追踪**: 记录每一步的发现和决策,避免遗漏。
- **自我修正**: 在过程中评估和调整调试路径。
### 日志调试
在问题产生的路径上添加日志,可以简单使用 `console.log` 或者参考 [debug-usage.mdc](mdc:.cursor/rules/debug-usage.mdc) 使用 `debug` 模块。添加完日志后,请求我运行相关的代码并提供关键输出和错误信息。
### 引导式交互调试
虽然我无法直接操作浏览器开发者工具,但我可以引导你进行交互式调试:
1. **设置断点**: 我会告诉你可以在哪些关键代码行设置断点。
2. **检查变量**: 我会请你在断点处检查特定变量的值或 `props`/`state`。
3. **分析调用栈**: 我会请你提供调用栈信息,以帮助理解代码执行流程。
## 💡 临时解决方案策略
当无法找到根本解决方案时,提供临时解决方案:
### 临时方案准则
- **快速修复** - 优先让功能可用
- **最小修改** - 减少对现有代码的影响
- **清晰标记** - 明确标注这是临时方案
- **后续计划** - 说明后续如何找到更好的解决方案
### 临时方案模板
```markdown
## 临时解决方案 ⚠️
**问题**: [简要描述问题]
**临时修复**:
[具体的临时修复步骤]
**风险说明**:
- [可能的副作用或限制]
- [需要注意的事项]
**后续计划**:
- [ ] 深入调研根本原因
- [ ] 寻找更优雅的解决方案
- [ ] 监控是否有其他影响
```
## ✅ 解决方案准则
### 方案质量标准
提供的解决方案应该:
- **✅ 低侵入性** - 最小化对现有代码的修改
- **✅ 可维护性** - 易于理解和后续维护
- **✅ 类型安全** - 符合 TypeScript 规范
- **✅ 最佳实践** - 遵循项目的编码规范
- **✅ 测试友好** - 便于编写和运行测试
- **❌ 避免长期 Hack** - 临时方案可以 hack但要明确标注
### 解决方案模板
```markdown
## 问题原因
[简要说明问题产生的根本原因]
## 解决方案
[详细的解决步骤]
## 代码修改
[具体的代码变更]
## 验证方法
[如何验证问题已解决]
## 预防措施
[如何避免类似问题再次发生]
```
## 🔄 迭代调试流程
如果初次解决方案无效:
1. **重新收集信息** - 基于新的错误信息搜索
2. **深入代码分析** - 查看更多相关代码文件
3. **运行相关测试** - 编写或运行一个失败的测试来稳定复现问题。
4. **扩大搜索范围** - 搜索更广泛的相关问题
5. **请求更多日志** - 添加更详细的调试信息
6. **提供临时方案** - 如果根本解决方案复杂,先提供临时修复
7. **分解问题** - 将复杂问题拆解为更小的子问题

View File

@@ -3,52 +3,52 @@ description: i18n workflow and troubleshooting
globs:
alwaysApply: false
---
# LobeChat Internationalization (i18n) Guide
# LobeChat 国际化指南
## Architecture Overview
## 架构概览
LobeChat uses **react-i18next** for internationalization with a well-structured namespace approach:
LobeChat 使用 react-i18next 进行国际化,采用良好的命名空间架构:
- **Default language**: Chinese (zh-CN) - serves as the source language
- **Supported locales**: 18 languages including English, Japanese, Korean, Arabic, etc.
- **Framework**: react-i18next with Next.js app router
- **Translation automation**: [@lobehub/i18n-cli](mdc:package.json) for automated translations, config file: .i18nrc.js
- 默认语言中文zh-CN作为源语言
- 支持语言18 种语言,包括英语、日语、韩语、阿拉伯语等
- 框架:react-i18next 配合 Next.js app router
- 翻译自动化:@lobehub/i18n-cli 用于自动翻译,配置文件:.i18nrc.js
## Directory Structure
## 目录结构
```
src/locales/
├── default/ # Source language files (zh-CN)
│ ├── index.ts # Namespace exports
│ ├── common.ts # Common translations
│ ├── chat.ts # Chat-related translations
│ ├── setting.ts # Settings translations
│ └── ... # Other namespace files
└── resources.ts # Type definitions and locale config
├── default/ # 源语言文件(zh-CN
│ ├── index.ts # 命名空间导出
│ ├── common.ts # 通用翻译
│ ├── chat.ts # 聊天相关翻译
│ ├── setting.ts # 设置翻译
│ └── ... # 其他命名空间文件
└── resources.ts # 类型定义和语言配置
locales/ # Translated files
├── en-US/ # English translations
│ ├── common.json # Common translations
│ ├── chat.json # Chat translations
│ ├── setting.json # Settings translations
│ └── ... # Other namespace JSON files
├── ja-JP/ # Japanese translations
locales/ # 翻译文件
├── en-US/ # 英语翻译
│ ├── common.json # 通用翻译
│ ├── chat.json # 聊天翻译
│ ├── setting.json # 设置翻译
│ └── ... # 其他命名空间 JSON 文件
├── ja-JP/ # 日语翻译
│ ├── common.json
│ ├── chat.json
│ └── ...
└── ... # Other language folders
└── ... # 其他语言文件夹
```
## Workflow for Adding New Translations
## 添加新翻译的工作流程
### 1. Add New Translation Keys
### 1. 添加新的翻译键
**Step 1**: Add translation key to the appropriate namespace file in [src/locales/default/](mdc:src/locales/default)
第一步:在 src/locales/default 目录下的相应命名空间文件中添加翻译键
```typescript
// Example: src/locales/default/common.ts
// 示例:src/locales/default/common.ts
export default {
// ... existing keys
// ... 现有键
newFeature: {
title: "新功能标题",
description: "功能描述文案",
@@ -57,40 +57,40 @@ export default {
};
```
**Step 2**: If creating a new namespace, export it in [src/locales/default/index.ts](mdc:src/locales/default/index.ts)
第二步:如果创建新命名空间,需要在 src/locales/default/index.ts 中导出
```typescript
import newNamespace from "./newNamespace";
const resources = {
// ... existing namespaces
// ... 现有命名空间
newNamespace,
} as const;
```
### 2. Translation Process
### 2. 翻译过程
**Development Mode** (Recommended):
开发模式:
- Manually add Chinese translations to corresponding JSON files in `locales/zh-CN/namespace.json`, this avoids running slow automation during development
- Don't auto add translations for other language like English etc, most of developer is Chinese,
一般情况下不需要你帮我跑自动翻译工具,跑一次很久,需要的时候我会自己跑。
但是为了立马能看到效果,还是需要先翻译 `locales/zh-CN/namespace.json`,不需要翻译其它语言。
**Production Mode**:
生产模式:
```bash
# Generate translations for all languages
# 为所有语言生成翻译
npm run i18n
```
## Usage in Components
## 在组件中使用
### Basic Usage with Hooks
### 基本用法
```tsx
import { useTranslation } from "react-i18next";
const MyComponent = () => {
const { t } = useTranslation("common"); // namespace
const { t } = useTranslation("common");
return (
<div>
@@ -102,58 +102,56 @@ const MyComponent = () => {
};
```
### With Parameters
### 带参数的用法
```tsx
const { t } = useTranslation("common");
// Translation key with interpolation
<p>{t("welcome.message", { name: "John" })}</p>;
// Corresponding locale file:
// 对应的语言文件:
// welcome: { message: '欢迎 {{name}} 使用!' }
```
### Multiple Namespaces
### 多个命名空间
```tsx
const { t } = useTranslation(['common', 'chat']);
// Access different namespaces
<button>{t('common:save')}</button>
<span>{t('chat:typing')}</span>
```
## Type Safety
## 类型安全
The project uses TypeScript for type-safe translations with auto-generated types from [src/locales/resources.ts](mdc:src/locales/resources.ts):
项目使用 TypeScript 实现类型安全的翻译,类型从 src/locales/resources.ts 自动生成:
```typescript
import type { DefaultResources, NS, Locales } from "@/locales/resources";
// Available types:
// - NS: Available namespace keys ('common' | 'chat' | 'setting' | ...)
// - Locales: Supported locale codes ('en-US' | 'zh-CN' | 'ja-JP' | ...)
// 可用类型:
// - NS: 可用命名空间键 ('common' | 'chat' | 'setting' | ...)
// - Locales: 支持的语言代码 ('en-US' | 'zh-CN' | 'ja-JP' | ...)
// Type-safe namespace usage
const namespace: NS = "common"; // ✅ Valid
const locale: Locales = "en-US"; // ✅ Valid
const namespace: NS = "common";
const locale: Locales = "en-US";
```
## Best Practices
## 最佳实践
### 1. Namespace Organization
### 1. 命名空间组织
- **common**: Shared UI elements (buttons, labels, actions)
- **chat**: Chat-specific features
- **setting**: Configuration and settings
- **error**: Error messages and handling
- **[feature]**: Feature-specific or page specific namespaces
- common: 共享 UI 元素(按钮、标签、操作)
- chat: 聊天特定功能
- setting: 配置和设置
- error: 错误消息和处理
- [feature]: 功能特定或页面特定的命名空间
- components: 可复用组件文案
### 2. Key Naming Conventions
### 2. 键命名约定
```typescript
// ✅ Good: Hierarchical structure
// ✅ 好:层次结构
export default {
modal: {
confirm: {
@@ -167,17 +165,17 @@ export default {
},
};
// ❌ Avoid: Flat structure
// ❌ 避免:扁平结构
export default {
modalConfirmTitle: "确认操作",
modalConfirmMessage: "确定要执行此操作吗?",
};
```
## Troubleshooting
## 故障排除
### Missing Translation Keys
### 缺少翻译键
- Check if the key exists in src/locales/default/namespace.ts
- Ensure proper namespace import in component
- Ensure new namespaces are exported in [src/locales/default/index.ts](mdc:src/locales/default/index.ts)
- 检查键是否存在于 src/locales/default/namespace.ts
- 确保在组件中正确导入命名空间
- 确保新命名空间已在 src/locales/default/index.ts 中导出

View File

@@ -1,72 +0,0 @@
---
description: @lobehub/ui components list
globs:
alwaysApply: false
---
## @lobehub/ui Components
- General
ActionIcon
ActionIconGroup
Block
Button
Icon
- Data Display
Avatar
Collapse
FileTypeIcon
FluentEmoji
GuideCard
Highlighter
Hotkey
Image
List
Markdown
MaterialFileTypeIcon
Mermaid
Segmented
Snippet
SortableList
Tag
Tooltip
Video
- Data Entry
AutoComplete
CodeEditor
ColorSwatches
CopyButton
DatePicker
EditableText
EmojiPicker
Form
FormModal
HotkeyInput
ImageSelect
Input
SearchBar
Select
SliderWithInput
ThemeSwitch
- Feedback
Alert
Drawer
Modal
- Layout
DraggablePanel
Footer
Grid
Header
Layout
MaskShadow
ScrollShadow
- Navigation
Burger
Dropdown
Menu
SideNav
Tabs
Toc
- Theme
ConfigProvider
FontLoader
ThemeProvider

View File

@@ -5,11 +5,11 @@ alwaysApply: false
---
# React Layout Kit 使用指南
`react-layout-kit` 是一个功能丰富的 React flex 布局组件库,在 lobe-chat 项目中被广泛使用。以下是重点组件的使用方法:
react-layout-kit 是一个功能丰富的 React flex 布局组件库,在 lobe-chat 项目中被广泛使用。以下是重点组件的使用方法:
## Flexbox 组件
Flexbox 是最常用的布局组件,用于创建弹性布局,类似于 CSS 的 `display: flex`
Flexbox 是最常用的布局组件,用于创建弹性布局,类似于 CSS 的 display: flex。
### 基本用法
@@ -31,16 +31,16 @@ import { Flexbox } from 'react-layout-kit';
### 常用属性
- `horizontal`: 布尔值,设置为水平方向布局
- `flex`: 数值或字符串,控制 flex 属性
- `gap`: 数值,设置子元素之间的间距
- `align`: 对齐方式,如 'center', 'flex-start' 等
- `justify`: 主轴对齐方式,如 'space-between', 'center' 等
- `padding`: 内边距值
- `paddingInline`: 水平内边距值
- `paddingBlock`: 垂直内边距值
- `width/height`: 设置宽高,通常用 `'100%'` 或具体像素值
- `style`: 自定义样式对象
- horizontal: 布尔值,设置为水平方向布局
- flex: 数值或字符串,控制 flex 属性
- gap: 数值,设置子元素之间的间距
- align: 对齐方式,如 'center', 'flex-start' 等
- justify: 主轴对齐方式,如 'space-between', 'center' 等
- padding: 内边距值
- paddingInline: 水平内边距值
- paddingBlock: 垂直内边距值
- width/height: 设置宽高,通常用 '100%' 或具体像素值
- style: 自定义样式对象
### 实际应用示例
@@ -60,12 +60,7 @@ import { Flexbox } from 'react-layout-kit';
</Flexbox>
{/* 中间内容区 */}
<Flexbox
flex={1}
style={{
height: '100%',
}}
>
<Flexbox flex={1} style={{ height: '100%' }}>
{/* 主要内容 */}
<Flexbox flex={1} padding={24} style={{ overflowY: 'auto' }}>
<MainContent />
@@ -91,8 +86,6 @@ Center 是对 Flexbox 的封装,使子元素水平和垂直居中。
### 基本用法
```jsx
import { Center } from 'react-layout-kit';
<Center width={'100%'} height={'100%'}>
<Content />
</Center>
@@ -118,9 +111,9 @@ Center 组件继承了 Flexbox 的所有属性,同时默认设置了居中对
## 最佳实践
1. 使用 `flex={1}` 让组件填充可用空间
2. 使用 `gap` 代替传统的 margin 设置元素间距
3. 嵌套 Flexbox 创建复杂布局
4. 设置 `overflow: 'auto'` 使内容可滚动
5. 使用 `horizontal` 创建水平布局,默认为垂直布局
6.`antd-style``useTheme` hook 配合使用创建主题响应式的布局
- 使用 flex={1} 让组件填充可用空间
- 使用 gap 代替传统的 margin 设置元素间距
- 嵌套 Flexbox 创建复杂布局
- 设置 overflow: 'auto' 使内容可滚动
- 使用 horizontal 创建水平布局,默认为垂直布局
- 与 antd-style 的 useTheme hook 配合使用创建主题响应式的布局

View File

@@ -6,12 +6,14 @@ alwaysApply: false
# react component 编写指南
- 如果要写复杂样式的话用 antd-style ,简单的话可以用 style 属性直接写内联样式
- 如果需要 flex 布局或者居中布局应该使用 react-layout-kit
- 选择组件库中的组件时优先使用 [lobe-ui.mdc](mdc:.cursor/rules/package-usage/lobe-ui.mdc) 有的,然后才是 antd 的,不知道 @lobehub/ui 的组件怎么用,有哪些属性,就自己搜下这个项目其它地方怎么用的,不要瞎猜
- 如果需要 flex 布局或者居中布局应该使用 react-layout-kit 的 Flexbox 和 Center 组件
- 选择组件时优先顺序应该是 src/components > 安装的组件 package > lobe-ui > antd
## 访问 theme 的两种方式
## antd-style token system
### 使用 antd-style 的 useTheme hook
### 访问 token system 的两种方式
#### 使用 antd-style 的 useTheme hook
```tsx
import { useTheme } from 'antd-style';
@@ -32,7 +34,7 @@ const MyComponent = () => {
}
```
### 使用 antd-style 的 createStyles
#### 使用 antd-style 的 createStyles
```tsx
const useStyles = createStyles(({ css, token }) => {
@@ -65,4 +67,87 @@ const Card: FC<CardProps> = ({ title, content }) => {
</Flexbox>
);
};
```
```
### 一些你经常会忘记使用的 token
请注意使用下面的 token 而不是 css 字面值。可以访问 https://ant.design/docs/react/customize-theme-cn 了解所有 token
- 动画类
- token.motionDurationMid
- token.motionEaseInOut
- 包围盒属性
- token.paddingSM
- token.marginLG
## Lobe UI 包含的组件
- 不知道 @lobehub/ui 的组件怎么用,有哪些属性,就自己搜下这个项目其它地方怎么用的,不要瞎猜,大部分组件都是在 antd 的基础上扩展了属性
- 具体用法不懂可以联网搜索,例如 ActionIcon 就爬取 https://ui.lobehub.com/components/action-icon
- General
ActionIcon
ActionIconGroup
Block
Button
Icon
- Data Display
Avatar
Collapse
FileTypeIcon
FluentEmoji
GuideCard
Highlighter
Hotkey
Image
List
Markdown
MaterialFileTypeIcon
Mermaid
Segmented
Snippet
SortableList
Tag
Tooltip
Video
- Data Entry
AutoComplete
CodeEditor
ColorSwatches
CopyButton
DatePicker
EditableText
EmojiPicker
Form
FormModal
HotkeyInput
ImageSelect
Input
SearchBar
Select
SliderWithInput
ThemeSwitch
- Feedback
Alert
Drawer
Modal
- Layout
DraggablePanel
Footer
Grid
Header
Layout
MaskShadow
ScrollShadow
- Navigation
Burger
Dropdown
Menu
SideNav
Tabs
Toc
- Theme
ConfigProvider
FontLoader
ThemeProvider

View File

@@ -0,0 +1,76 @@
---
description:
globs:
alwaysApply: true
---
# LobeChat Cursor Rules System Guide
This document explains how the LobeChat project's Cursor rules system works and serves as an index for manually accessible rules.
## 🎯 Core Principle
**All rules are equal** - there are no priorities or "recommendations" between different rule sources. You should follow all applicable rules simultaneously.
## 📚 Four Ways to Access Rules
### 1. **Always Applied Rules** - `always_applied_workspace_rules`
- **What**: Core project guidelines that are always active
- **Content**: Project tech stack, basic coding standards, output formatting rules
- **Access**: No tools needed - automatically provided in every conversation
### 2. **Dynamic Context Rules** - `cursor_rules_context`
- **What**: Rules automatically matched based on files referenced in the conversation
- **Trigger**: Only when user **explicitly @ mentions files** or **opens files in Cursor**
- **Content**: May include brief descriptions or full rule content, depending on relevance
- **Access**: No tools needed - automatically updated when files are referenced
### 3. **Agent Requestable Rules** - `agent_requestable_workspace_rules`
- **What**: Detailed operational guides that can be requested on-demand
- **Access**: Use `fetch_rules` tool with rule names
- **Examples**: `debug`, `i18n/i18n`, `code-review`
### 4. **Manual Rules Index** - This file + `read_file`
- **What**: Additional rules not covered by the above mechanisms
- **Why needed**: Cursor's rule system only supports "agent request" or "auto attach" modes
- **Access**: Use `read_file` tool to read specific `.mdc` files
## 🔧 When to Use `read_file` for Rules
Use `read_file` to access rules from the index below when:
1. **Gap identification**: You determine a rule is needed for the current task
2. **No auto-trigger**: The rule isn't provided in `cursor_rules_context` (because relevant files weren't @ mentioned)
3. **Not agent-requestable**: The rule isn't available via `fetch_rules`
## 📋 Available Rules Index
The following rules are available via `read_file` from the `.cursor/rules/` directory:
- `backend-architecture.mdc` Backend layer architecture and design guidelines
- `zustand-action-patterns.mdc` Recommended patterns for organizing Zustand actions
- `zustand-slice-organization.mdc` Best practices for structuring Zustand slices
- `drizzle-schema-style-guide.mdc` Style guide for defining Drizzle ORM schemas
- `react-component.mdc` React component style guide and conventions
## ❌ Common Misunderstandings to Avoid
1. **"Priority confusion"**: There's no hierarchy between rule sources - they're complementary, not competitive
2. **"Dynamic expectations"**: `cursor_rules_context` only updates when you @ files - it won't automatically include rules for tasks you're thinking about
3. **"Tool redundancy"**: Each access method serves a different purpose - they're not alternatives to choose from
## 🛠️ Practical Workflow
```
1. Start with always_applied_workspace_rules (automatic)
2. Check cursor_rules_context for auto-matched rules (automatic)
3. If you need specific guides: fetch_rules (manual)
4. If you identify gaps: consult this index → read_file (manual)
```
## Example Decision Flow
**Scenario**: Working on a new Zustand store slice
1. Follow always_applied_workspace_rules ✅
2. If store files were @ mentioned → use cursor_rules_context rules ✅
3. Need detailed Zustand guidance → `read_file('.cursor/rules/zustand-slice-organization.mdc')` ✅
4. All rules apply simultaneously - no conflicts ✅

View File

@@ -14,9 +14,9 @@ You are an expert in UI/UX design, proficient in web interaction patterns, respo
## Problem Solving
- Before formulating any response, you must first gather context by using tools like codebase_search, grep_search, file_search, web_search, fetch_rules, context7, and read_file to avoid making assumptions.
- When modifying existing code, clearly describe the differences and reasons for the changes
- Provide alternative solutions that may be better overall or superior in specific aspects
- Always consider using the latest technologies, standards, and APIs to strive for code optimization, not just the conventional wisdom
- Provide optimization suggestions for deprecated API usage
- Cite sources whenever possible at the end, not inline
- When you provide multiple solutions, provide the recommended solution first, and note it as `Recommended`
@@ -25,18 +25,14 @@ You are an expert in UI/UX design, proficient in web interaction patterns, respo
## Code Implementation
- Write minimal code changes that are ONLY directly related to the requirements
- Write correct, up-to-date, bug-free, fully functional, secure, maintainable and efficient code
- First, think step-by-step: describe your plan in detailed pseudocode before implementation
- Confirm the plan before writing code
- Focus on maintainable over being performant
- Leave NO TODOs, placeholders, or missing pieces
- Be sure to reference file names
- Please respect my prettier preferences when you provide code
- When you notice I have manually modified the code, that was definitely on purpose and do not revert them
- Don't remove meaningful code comments, be sure to keep original comments when providing applied code
- Update the code comments when needed after you modify the related code
- If documentation links or required files are missing, ask for them before proceeding with the task rather than making assumptions
- If you're unable to access or retrieve content from websites, please inform me immediately and request the specific information needed rather than making assumptions
- Sometimes ESLint errors may not be reasonable, and making changes could introduce logical bugs. If you find an ESLint rule unreasonable, disable it directly. For example, with the 'prefer-dom-node-text-content' rule, there are actual differences between innerText and textContent
- You can use emojis, npm packages like `chalk`/`chalk-animation`/`terminal-link`/`gradient-string`/`log-symbols`/`boxen`/`consola`/`@clack/prompts` to create beautiful terminal output
- Don't run `tsc --noEmit` to check ts syntax error, because our project is very large and the validate very slow

View File

@@ -0,0 +1,881 @@
---
description:
globs: *.test.ts,*.test.tsx
alwaysApply: false
---
---
type: agent-requested
title: 测试指南 - LobeChat Testing Guide
description: LobeChat 项目的 Vitest 测试环境配置、运行方式、修复原则指南
---
# 测试指南 - LobeChat Testing Guide
## 🧪 测试环境概览
LobeChat 项目使用 Vitest 测试库,配置了两种不同的测试环境:
### 客户端测试环境 (DOM Environment)
- **配置文件**: [vitest.config.ts](mdc:vitest.config.ts)
- **环境**: Happy DOM (浏览器环境模拟)
- **数据库**: PGLite (浏览器环境的 PostgreSQL)
- **用途**: 测试前端组件、客户端逻辑、React 组件等
- **设置文件**: [tests/setup.ts](mdc:tests/setup.ts)
### 服务端测试环境 (Node Environment)
- **配置文件**: [vitest.config.server.ts](mdc:vitest.config.server.ts)
- **环境**: Node.js
- **数据库**: 真实的 PostgreSQL 数据库
- **并发限制**: 单线程运行 (`singleFork: true`)
- **用途**: 测试数据库模型、服务端逻辑、API 端点等
- **设置文件**: [tests/setup-db.ts](mdc:tests/setup-db.ts)
## 🚀 测试运行命令
### package.json 脚本说明
查看 [package.json](mdc:package.json) 中的测试相关脚本:
```json
{
"test": "npm run test-app && npm run test-server",
"test-app": "vitest run --config vitest.config.ts",
"test-app:coverage": "vitest run --config vitest.config.ts --coverage",
"test-server": "vitest run --config vitest.config.server.ts",
"test-server:coverage": "vitest run --config vitest.config.server.ts --coverage"
}
```
### 推荐的测试运行方式
#### ✅ 正确的命令格式
```bash
# 运行所有客户端测试
npx vitest run --config vitest.config.ts
# 运行所有服务端测试
npx vitest run --config vitest.config.server.ts
# 运行特定测试文件 (支持模糊匹配)
npx vitest run --config vitest.config.ts basic
npx vitest run --config vitest.config.ts user.test.ts
# 运行特定文件的特定行号
npx vitest run --config vitest.config.ts src/utils/helper.test.ts:25
npx vitest run --config vitest.config.ts basic/foo.test.ts:10,basic/foo.test.ts:25
# 过滤特定测试用例名称
npx vitest -t "test case name" --config vitest.config.ts
# 组合使用文件和测试名称过滤
npx vitest run --config vitest.config.ts filename.test.ts -t "specific test"
```
#### ❌ 避免的命令格式
```bash
# ❌ 不要使用 pnpm test xxx (这不是有效的 vitest 命令)
pnpm test some-file
# ❌ 不要使用裸 vitest (会进入 watch 模式)
vitest test-file.test.ts
# ❌ 不要混淆测试环境
npx vitest run --config vitest.config.server.ts client-component.test.ts
```
### 关键运行参数说明
- **`vitest run`**: 运行一次测试然后退出 (避免 watch 模式)
- **`vitest`**: 默认进入 watch 模式,持续监听文件变化
- **`--config`**: 指定配置文件,选择正确的测试环境
- **`-t`**: 过滤测试用例名称,支持正则表达式
- **`--coverage`**: 生成测试覆盖率报告
## 🔧 测试修复原则
### 核心原则 ⚠️
1. **充分阅读测试代码**: 在修复测试之前,必须完整理解测试的意图和实现
2. **测试优先修复**: 如果是测试本身写错了,修改测试而不是实现代码
3. **专注单一问题**: 只修复指定的测试,不要添加额外测试或功能
4. **不自作主张**: 不要因为发现其他问题就直接修改,先提出再讨论
### 测试修复流程
```mermaid
flowchart TD
subgraph "阶段一:分析与复现"
A[开始:收到测试失败报告] --> B[定位并运行失败的测试];
B --> C{是否能在本地复现?};
C -->|否| D[检查测试环境/配置/依赖];
C -->|是| E[分析阅读测试代码、错误日志、Git 历史];
end
subgraph "阶段二:诊断与调试"
E --> F[建立假设:问题出在测试、代码还是环境?];
F --> G["调试:使用 console.log 或 debugger 深入检查"];
G --> H{假设是否被证实?};
H -->|否, 重新假设| F;
end
subgraph "阶段三:修复与验证"
H -->|是| I{确定根本原因};
I -->|测试逻辑错误| J[修复测试代码];
I -->|实现代码 Bug| K[修复实现代码];
I -->|环境/配置问题| L[修复配置或依赖];
J --> M[验证修复:重新运行失败的测试];
K --> M;
L --> M;
M --> N{测试是否通过?};
N -->|否, 修复无效| F;
N -->|是| O[扩大验证:运行当前文件内所有测试];
O --> P{是否全部通过?};
P -->|否, 引入新问题| F;
end
subgraph "阶段四:总结"
P -->|是| Q[完成:撰写修复总结];
end
D --> F;
```
### 修复完成后的总结
测试修复完成后,应该提供简要说明,包括:
1. **错误原因分析**: 说明测试失败的根本原因
- 测试逻辑错误
- 实现代码bug
- 环境配置问题
- 依赖变更导致的问题
2. **修复方法说明**: 简述采用的修复方式
- 修改了哪些文件
- 采用了什么解决方案
- 为什么选择这种修复方式
**示例格式**:
```markdown
## 测试修复总结
**错误原因**: 测试中的 mock 数据格式与实际 API 返回格式不匹配,导致断言失败。
**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。
```
## 📂 测试文件组织
### 文件命名约定
- **客户端测试**: `*.test.ts`, `*.test.tsx` (任意位置)
- **服务端测试**: `src/database/models/**/*.test.ts`, `src/database/server/**/*.test.ts` (限定路径)
### 测试文件组织风格
项目采用 **测试文件与源文件同目录** 的组织风格:
- 测试文件放在对应源文件的同一目录下
- 命名格式:`原文件名.test.ts` 或 `原文件名.test.tsx`
例如:
```
src/components/Button/
├── index.tsx # 源文件
└── index.test.tsx # 测试文件
```
## 🛠️ 测试调试技巧
### 运行失败测试的步骤
1. **确定测试类型**: 查看文件路径确定使用哪个配置
2. **运行单个测试**: 使用 `-t` 参数隔离问题
3. **检查错误日志**: 仔细阅读错误信息和堆栈跟踪
4. **查看最近修改记录**: 检查相关文件的最近变更情况
5. **添加调试日志**: 在测试中添加 `console.log` 了解执行流程
### Electron IPC 接口测试策略 🖥️
对于涉及 Electron IPC 接口的测试,由于提供真实的 Electron 环境比较复杂,采用 **Mock 返回值** 的方式进行测试。
#### 基本 Mock 设置
```typescript
import { vi } from "vitest";
import { electronIpcClient } from "@/server/modules/ElectronIPCClient";
// Mock Electron IPC 客户端
vi.mock("@/server/modules/ElectronIPCClient", () => ({
electronIpcClient: {
getFilePathById: vi.fn(),
deleteFiles: vi.fn(),
// 根据需要添加其他 IPC 方法
},
}));
```
#### 在测试中设置 Mock 行为
```typescript
beforeEach(() => {
// 重置所有 Mock
vi.resetAllMocks();
// 设置默认的 Mock 返回值
vi.mocked(electronIpcClient.getFilePathById).mockResolvedValue(
"/path/to/file.txt"
);
vi.mocked(electronIpcClient.deleteFiles).mockResolvedValue({
success: true,
});
});
```
#### 测试不同场景的示例
```typescript
it("应该处理文件删除成功的情况", async () => {
// 设置成功场景的 Mock
vi.mocked(electronIpcClient.deleteFiles).mockResolvedValue({
success: true,
});
const result = await service.deleteFiles(["desktop://file1.txt"]);
expect(electronIpcClient.deleteFiles).toHaveBeenCalledWith([
"desktop://file1.txt",
]);
expect(result.success).toBe(true);
});
it("应该处理文件删除失败的情况", async () => {
// 设置失败场景的 Mock
vi.mocked(electronIpcClient.deleteFiles).mockRejectedValue(
new Error("删除失败")
);
const result = await service.deleteFiles(["desktop://file1.txt"]);
expect(result.success).toBe(false);
expect(result.errors).toBeDefined();
});
```
#### Mock 策略的优势
1. **环境简化**: 避免了复杂的 Electron 环境搭建
2. **测试可控**: 可以精确控制 IPC 调用的返回值和行为
3. **场景覆盖**: 容易测试各种成功/失败场景
4. **执行速度**: Mock 调用比真实 IPC 调用更快
#### 注意事项
- **Mock 准确性**: 确保 Mock 的行为与真实 IPC 接口行为一致
- **类型安全**: 使用 `vi.mocked()` 确保类型安全
- **Mock 重置**: 在 `beforeEach` 中重置 Mock 状态,避免测试间干扰
- **调用验证**: 不仅要验证返回值,还要验证 IPC 方法是否被正确调用
### 检查最近修改记录 🔍
为了更好地判断测试失败的根本原因,需要**系统性地检查相关文件的修改历史**。这是问题定位的关键步骤。
#### 第一步:确定需要检查的文件范围
1. **测试文件本身**: `path/to/component.test.ts`
2. **对应的实现文件**: `path/to/component.ts` 或 `path/to/component/index.ts`
3. **相关依赖文件**: 测试或实现中导入的其他模块
#### 第二步:检查当前工作目录状态
```bash
# 查看所有未提交的修改状态
git status
# 重点关注测试文件和实现文件是否有未提交的修改
git status | grep -E "(test|spec)"
```
#### 第三步:检查未提交的修改内容
```bash
# 查看测试文件的未提交修改 (工作区 vs 暂存区)
git diff path/to/component.test.ts | cat
# 查看对应实现文件的未提交修改
git diff path/to/component.ts | cat
# 查看已暂存但未提交的修改
git diff --cached path/to/component.test.ts | cat
git diff --cached path/to/component.ts | cat
```
#### 第四步:检查提交历史和时间相关性
**首先查看提交时间,判断修改的时效性**
```bash
# 查看测试文件的最近提交历史,包含提交时间
git log --pretty=format:"%h %ad %s" --date=relative -5 path/to/component.test.ts | cat
# 查看实现文件的最近提交历史,包含提交时间
git log --pretty=format:"%h %ad %s" --date=relative -5 path/to/component.ts | cat
# 查看详细的提交时间ISO格式便于精确判断
git log --pretty=format:"%h %ad %an %s" --date=iso -3 path/to/component.ts | cat
git log --pretty=format:"%h %ad %an %s" --date=iso -3 path/to/component.test.ts | cat
```
**判断提交的参考价值**
1. **最近提交24小时内**: 🔴 **高度相关** - 很可能是导致测试失败的直接原因
2. **近期提交1-7天内**: 🟡 **中等相关** - 可能相关,需要仔细分析修改内容
3. **较早提交超过1周**: ⚪ **低相关性** - 除非是重大重构,否则不太可能是直接原因
#### 第五步:基于时间相关性查看具体修改内容
**根据提交时间的远近,优先查看最近的修改**
```bash
# 如果有24小时内的提交重点查看这些修改
git show HEAD -- path/to/component.test.ts | cat
git show HEAD -- path/to/component.ts | cat
# 查看次新的提交(如果最新提交时间较远)
git show HEAD~1 -- path/to/component.ts | cat
git show <recent-commit-hash> -- path/to/component.ts | cat
# 对比最近两次提交的差异
git diff HEAD~1 HEAD -- path/to/component.ts | cat
```
#### 第六步:分析修改与测试失败的关系
基于修改记录和时间相关性判断:
1. **最近修改了实现代码**:
```bash
# 重点检查实现逻辑的变化
git diff HEAD~1 path/to/component.ts | cat
```
- 很可能是实现代码的变更导致测试失败
- 检查实现逻辑是否正确
- 确认测试是否需要相应更新
2. **最近修改了测试代码**:
```bash
# 重点检查测试逻辑的变化
git diff HEAD~1 path/to/component.test.ts | cat
```
- 可能是测试本身写错了
- 检查测试逻辑和断言是否正确
- 确认测试是否符合实现的预期行为
3. **两者都有最近修改**:
```bash
# 对比两个文件的修改时间
git log --pretty=format:"%ad %f" --date=iso -1 path/to/component.ts | cat
git log --pretty=format:"%ad %f" --date=iso -1 path/to/component.test.ts | cat
```
- 需要综合分析两者的修改
- 确定哪个修改更可能导致问题
- 优先检查时间更近的修改
4. **都没有最近修改**:
- 可能是依赖变更或环境问题
- 检查 `package.json`、配置文件等的修改
- 查看是否有全局性的代码重构
#### 修改记录检查示例
```bash
# 完整的检查流程示例
echo "=== 检查文件修改状态 ==="
git status | grep component
echo "=== 检查未提交修改 ==="
git diff src/components/Button/index.test.tsx | cat
git diff src/components/Button/index.tsx | cat
echo "=== 检查提交历史和时间 ==="
git log --pretty=format:"%h %ad %s" --date=relative -3 src/components/Button/index.test.tsx | cat
git log --pretty=format:"%h %ad %s" --date=relative -3 src/components/Button/index.tsx | cat
echo "=== 根据时间优先级查看修改内容 ==="
# 如果有24小时内的提交重点查看
git show HEAD -- src/components/Button/index.tsx | cat
```
## 🗃️ 数据库 Model 测试指南
### 测试环境选择 💡
数据库 Model 层通过环境变量控制数据库类型,在两种测试环境下有不同的数据库后端:客户端环境 (PGLite) 和 服务端环境 (PostgreSQL)
### ⚠️ 双环境验证要求
**对于所有 Model 测试,必须在两个环境下都验证通过**
#### 完整验证流程
```bash
# 1. 先在客户端环境测试(快速验证)
npx vitest run --config vitest.config.ts src/database/models/__tests__/myModel.test.ts
# 2. 再在服务端环境测试(兼容性验证)
npx vitest run --config vitest.config.server.ts src/database/models/__tests__/myModel.test.ts
```
### 创建新 Model 测试的最佳实践 📋
#### 1. 参考现有实现和测试模板
创建新 Model 测试前,**必须先参考现有的实现模式**
- **Model 实现参考**:
- **测试模板参考**:
- **复杂示例参考**:
#### 2. 用户权限检查 - 安全第一 🔒
这是**最关键的安全要求**。所有涉及用户数据的操作都必须包含用户权限检查:
**❌ 错误示例 - 存在安全漏洞**:
```typescript
// 危险:缺少用户权限检查,任何用户都能操作任何数据
update = async (id: string, data: Partial<MyModel>) => {
return this.db
.update(myTable)
.set(data)
.where(eq(myTable.id, id)) // ❌ 只检查 ID没有检查 userId
.returning();
};
```
**✅ 正确示例 - 安全的实现**:
```typescript
// 安全:必须同时匹配 ID 和 userId
update = async (id: string, data: Partial<MyModel>) => {
return this.db
.update(myTable)
.set(data)
.where(
and(
eq(myTable.id, id),
eq(myTable.userId, this.userId) // ✅ 用户权限检查
)
)
.returning();
};
```
**必须进行用户权限检查的方法**
- `update()` - 更新操作
- `delete()` - 删除操作
- `findById()` - 查找特定记录
- 任何涉及特定记录的查询或修改操作
#### 3. 测试文件结构和必测场景
**基本测试结构**:
```typescript
// @vitest-environment node
describe("MyModel", () => {
describe("create", () => {
it("should create a new record");
it("should handle edge cases");
});
describe("queryAll", () => {
it("should return records for current user only");
it("should handle empty results");
});
describe("update", () => {
it("should update own records");
it("should NOT update other users records"); // 🔒 安全测试
});
describe("delete", () => {
it("should delete own records");
it("should NOT delete other users records"); // 🔒 安全测试
});
describe("user isolation", () => {
it("should enforce user data isolation"); // 🔒 核心安全测试
});
});
```
**必须测试的安全场景** 🔒:
```typescript
it("should not update records of other users", async () => {
// 创建其他用户的记录
const [otherUserRecord] = await serverDB
.insert(myTable)
.values({ userId: "other-user", data: "original" })
.returning();
// 尝试更新其他用户的记录
const result = await myModel.update(otherUserRecord.id, { data: "hacked" });
// 应该返回 undefined 或空数组(因为权限检查失败)
expect(result).toBeUndefined();
// 验证原始数据未被修改
const unchanged = await serverDB.query.myTable.findFirst({
where: eq(myTable.id, otherUserRecord.id),
});
expect(unchanged?.data).toBe("original"); // 数据应该保持不变
});
```
#### 4. Mock 外部依赖服务
如果 Model 依赖外部服务(如 FileService需要正确 Mock
**设置 Mock**:
```typescript
// 在文件顶部设置 Mock
const mockGetFullFileUrl = vi.fn();
vi.mock("@/server/services/file", () => ({
FileService: vi.fn().mockImplementation(() => ({
getFullFileUrl: mockGetFullFileUrl,
})),
}));
// 在 beforeEach 中重置和配置 Mock
beforeEach(async () => {
vi.clearAllMocks();
mockGetFullFileUrl.mockImplementation(
(url: string) => `https://example.com/${url}`
);
});
```
**验证 Mock 调用**:
```typescript
it("should process URLs through FileService", async () => {
// ... 测试逻辑
// 验证 Mock 被正确调用
expect(mockGetFullFileUrl).toHaveBeenCalledWith("expected-url");
expect(mockGetFullFileUrl).toHaveBeenCalledTimes(1);
});
```
#### 5. 数据库状态管理
**正确的数据清理模式**:
```typescript
const userId = "test-user";
const otherUserId = "other-user";
beforeEach(async () => {
// 清理用户表(级联删除相关数据)
await serverDB.delete(users);
// 创建测试用户
await serverDB.insert(users).values([{ id: userId }, { id: otherUserId }]);
});
afterEach(async () => {
// 清理测试数据
await serverDB.delete(users);
});
```
#### 6. 测试数据类型和外键约束处理 ⚠️
**必须使用 Schema 导出的类型**:
```typescript
// ✅ 正确:使用 schema 导出的类型
import { NewGenerationBatch, NewGeneration } from '../../schemas';
const testBatch: NewGenerationBatch = {
userId,
generationTopicId: 'test-topic-id',
provider: 'test-provider',
model: 'test-model',
prompt: 'Test prompt for image generation',
width: 1024,
height: 1024,
config: { /* ... */ },
};
const testGeneration: NewGeneration = {
id: 'test-gen-id',
generationBatchId: 'test-batch-id',
asyncTaskId: null, // 处理外键约束
fileId: null, // 处理外键约束
seed: 12345,
userId,
};
```
```typescript
// ❌ 错误:没有类型声明或使用错误类型
const testBatch = { // 缺少类型声明
generationTopicId: 'test-topic-id',
// ...
};
const testGeneration = { // 缺少类型声明
asyncTaskId: 'invalid-uuid', // 外键约束错误
fileId: 'non-existent-file', // 外键约束错误
// ...
};
```
**外键约束处理策略**:
1. **使用 null 值**: 对于可选的外键字段,使用 null 避免约束错误
2. **创建关联记录**: 如果需要测试关联关系,先创建被引用的记录
3. **理解约束关系**: 了解哪些字段有外键约束,避免引用不存在的记录
```typescript
// 外键约束处理示例
beforeEach(async () => {
// 清理数据库
await serverDB.delete(users);
// 创建测试用户
await serverDB.insert(users).values([{ id: userId }]);
// 如果需要测试文件关联,创建文件记录
if (needsFileAssociation) {
await serverDB.insert(files).values({
id: 'test-file-id',
userId,
name: 'test.jpg',
url: 'test-url',
size: 1024,
fileType: 'image/jpeg',
});
}
});
```
**排序测试的可预测性**:
```typescript
// ✅ 正确:使用明确的时间戳确保排序结果可预测
it('should find batches by topic id in correct order', async () => {
const oldDate = new Date('2024-01-01T10:00:00Z');
const newDate = new Date('2024-01-02T10:00:00Z');
const batch1 = { ...testBatch, prompt: 'First batch', userId, createdAt: oldDate };
const batch2 = { ...testBatch, prompt: 'Second batch', userId, createdAt: newDate };
await serverDB.insert(generationBatches).values([batch1, batch2]);
const results = await generationBatchModel.findByTopicId(testTopic.id);
expect(results[0].prompt).toBe('Second batch'); // 最新优先 (desc order)
expect(results[1].prompt).toBe('First batch');
});
```
```typescript
// ❌ 错误:依赖数据库的默认时间戳,结果不可预测
it('should find batches by topic id', async () => {
const batch1 = { ...testBatch, prompt: 'First batch', userId };
const batch2 = { ...testBatch, prompt: 'Second batch', userId };
await serverDB.insert(generationBatches).values([batch1, batch2]);
// 插入顺序和数据库时间戳可能不一致,导致测试不稳定
const results = await generationBatchModel.findByTopicId(testTopic.id);
expect(results[0].prompt).toBe('Second batch'); // 可能失败
});
```
### 常见问题和解决方案 💡
#### 问题 1权限检查缺失导致安全漏洞
**现象**: 测试失败,用户能修改其他用户的数据
**解决**: 在 Model 的 `update` 和 `delete` 方法中添加 `and(eq(table.id, id), eq(table.userId, this.userId))`
#### 问题 2Mock 未生效或验证失败
**现象**: `undefined is not a spy` 错误
**解决**: 检查 Mock 设置位置和方式,确保在测试文件顶部设置,在 `beforeEach` 中重置
#### 问题 3测试数据污染
**现象**: 测试间相互影响,结果不稳定
**解决**: 在 `beforeEach` 和 `afterEach` 中正确清理数据库状态
#### 问题 4外部依赖导致测试失败
**现象**: 因为真实的外部服务调用导致测试不稳定
**解决**: Mock 所有外部依赖,使测试更可控和快速
#### 问题 5外键约束违反导致测试失败
**现象**: `insert or update on table "xxx" violates foreign key constraint`
**解决**:
- 将可选外键字段设为 `null` 而不是无效的字符串值
- 或者先创建被引用的记录,再创建当前记录
```typescript
// ❌ 错误:无效的外键值
const testData = {
asyncTaskId: 'invalid-uuid', // 表中不存在此记录
fileId: 'non-existent-file', // 表中不存在此记录
};
// ✅ 正确:使用 null 值
const testData = {
asyncTaskId: null, // 避免外键约束
fileId: null, // 避免外键约束
};
// ✅ 或者:先创建被引用的记录
beforeEach(async () => {
const [asyncTask] = await serverDB.insert(asyncTasks).values({
id: 'valid-task-id',
status: 'pending',
type: 'generation',
}).returning();
const testData = {
asyncTaskId: asyncTask.id, // 使用有效的外键值
};
});
```
#### 问题 6排序测试结果不一致
**现象**: 相同的测试有时通过,有时失败,特别是涉及排序的测试
**解决**: 使用明确的时间戳,不要依赖数据库的默认时间戳
```typescript
// ❌ 错误:依赖插入顺序和默认时间戳
await serverDB.insert(table).values([data1, data2]); // 时间戳不可预测
// ✅ 正确:明确指定时间戳
const oldDate = new Date('2024-01-01T10:00:00Z');
const newDate = new Date('2024-01-02T10:00:00Z');
await serverDB.insert(table).values([
{ ...data1, createdAt: oldDate },
{ ...data2, createdAt: newDate },
]);
```
#### 问题 7Mock 验证失败或调用次数不匹配
**现象**: `expect(mockFunction).toHaveBeenCalledWith(...)` 失败
**解决**:
- 检查 Mock 函数的实际调用参数和期望参数是否完全匹配
- 确认 Mock 在正确的时机被重置和配置
- 使用 `toHaveBeenCalledTimes()` 验证调用次数
```typescript
// 在 beforeEach 中正确配置 Mock
beforeEach(() => {
vi.clearAllMocks(); // 重置所有 Mock
mockGetFullFileUrl.mockImplementation((url: string) => `https://example.com/${url}`);
mockTransformGeneration.mockResolvedValue({
id: 'test-id',
// ... 其他字段
});
});
// 测试中验证 Mock 调用
it('should call FileService with correct parameters', async () => {
await model.someMethod();
// 验证调用参数
expect(mockGetFullFileUrl).toHaveBeenCalledWith('expected-url');
// 验证调用次数
expect(mockGetFullFileUrl).toHaveBeenCalledTimes(1);
});
```
### Model 测试检查清单 ✅
创建 Model 测试时,请确保以下各项都已完成:
#### 🔧 基础配置
- [ ] **双环境验证** - 在客户端环境 (vitest.config.ts) 和服务端环境 (vitest.config.server.ts) 下都测试通过
- [ ] 参考了 `_template.ts` 和现有 Model 的实现模式
- [ ] **使用正确的 Schema 类型** - 测试数据使用 `NewXxx` 类型声明,如 `NewGenerationBatch`、`NewGeneration`
#### 🔒 安全测试
- [ ] **所有涉及用户数据的操作都包含用户权限检查**
- [ ] 包含了用户权限隔离的安全测试
- [ ] 测试了用户无法访问其他用户数据的场景
#### 🗃️ 数据处理
- [ ] **正确处理外键约束** - 使用 `null` 值或先创建被引用记录
- [ ] **排序测试使用明确时间戳** - 不依赖数据库默认时间,确保结果可预测
- [ ] 在 `beforeEach` 和 `afterEach` 中正确管理数据库状态
- [ ] 所有测试都能独立运行且互不干扰
#### 🎭 Mock 和外部依赖
- [ ] 正确 Mock 了外部依赖服务 (如 FileService、GenerationModel)
- [ ] 在 `beforeEach` 中重置和配置 Mock
- [ ] 验证了 Mock 服务的调用参数和次数
- [ ] 测试了外部服务错误场景的处理
#### 📋 测试覆盖
- [ ] 测试覆盖了所有主要方法 (create, query, update, delete)
- [ ] 测试了边界条件和错误场景
- [ ] 包含了空结果处理的测试
- [ ] **确认两个环境下的测试结果一致**
#### 🚨 常见问题检查
- [ ] 没有外键约束违反错误
- [ ] 排序测试结果稳定可预测
- [ ] Mock 验证无失败
- [ ] 无测试数据污染问题
### 安全警告 ⚠️
**数据库 Model 层是安全的第一道防线**。如果 Model 层缺少用户权限检查:
1. **任何用户都能访问和修改其他用户的数据**
2. **即使上层有权限检查,也可能被绕过**
3. **可能导致严重的数据泄露和安全事故**
因此,**每个涉及用户数据的 Model 方法都必须包含用户权限检查,且必须有对应的安全测试来验证这些检查的有效性**。
## 🎯 总结
修复测试时,记住以下关键点:
- **使用正确的命令**: `npx vitest run --config [config-file]`
- **理解测试意图**: 先读懂测试再修复
- **查看最近修改**: 检查相关文件的 git 修改记录,判断问题根源
- **选择正确环境**: 客户端测试用 `vitest.config.ts`,服务端用 `vitest.config.server.ts`
- **专注单一问题**: 只修复当前的测试失败
- **验证修复结果**: 确保修复后测试通过且无副作用
- **提供修复总结**: 说明错误原因和修复方法
- **Model 测试安全第一**: 必须包含用户权限检查和对应的安全测试
- **Model 双环境验证**: 必须在 PGLite 和 PostgreSQL 两个环境下都验证通过

View File

@@ -0,0 +1,19 @@
---
description:
globs: *.ts,*.tsx,*.mts
alwaysApply: false
---
TypeScript Code Style Guide:
- Avoid explicit type annotations when TypeScript can infer types.
- Avoid defining `any` type variables (e.g., `let a: number;` instead of `let a;`).
- Use the most accurate type possible (e.g., use `Record<PropertyKey, unknown>` instead of `object`).
- Prefer `interface` over `type` (e.g., define react component props).
- Use `as const satisfies XyzInterface` instead of `as const` when suitable
- import index.ts module(directory module) like `@/db/index` instead of `@/db`
- Instead of calling Date.now() multiple times, assign it to a constant once and reuse it. This ensures consistency and improves readability
- Always refactor repeated logic into a reusable function
- Don't remove meaningful code comments, be sure to keep original comments when providing applied code
- Update the code comments when needed after you modify the related code
- Please respect my prettier preferences when you provide code

View File

@@ -87,7 +87,7 @@ internal_dispatchTopic: (payload, action) => {
### 使用 Reducer 模式的场景
**适用于复杂的数据结构管理**,特别是:
适用于复杂的数据结构管理,特别是:
- 管理对象列表或映射(如 `messagesMap`, `topicMaps`
- 需要乐观更新的场景
- 状态转换逻辑复杂
@@ -125,7 +125,7 @@ export const messagesReducer = (state: ChatMessage[], payload: MessageDispatch):
### 使用简单 `set` 的场景
**适用于简单状态更新**
适用于简单状态更新:
- 切换布尔值
- 更新简单字符串/数字
- 设置单一状态字段
@@ -204,6 +204,43 @@ internal_createMessage: async (message, context) => {
},
```
### 删除操作模式(不使用乐观更新)
删除操作通常不适合乐观更新,因为:
- 删除是破坏性操作,错误恢复复杂
- 用户对删除操作的即时反馈期望较低
- 删除失败时恢复原状态会造成困惑
```typescript
// 删除操作的标准模式 - 无乐观更新
removeGenerationTopic: async (id: string) => {
const { internal_removeGenerationTopic } = get();
await internal_removeGenerationTopic(id);
},
internal_removeGenerationTopic: async (id: string) => {
// 1. 显示加载状态
get().internal_updateGenerationTopicLoading(id, true);
try {
// 2. 直接调用后端服务
await generationTopicService.deleteTopic(id);
// 3. 刷新数据获取最新状态
await get().refreshGenerationTopics();
} finally {
// 4. 确保清除加载状态(无论成功或失败)
get().internal_updateGenerationTopicLoading(id, false);
}
},
```
删除操作的特点:
- 直接调用服务,不预先更新状态
- 依赖 loading 状态提供用户反馈
- 操作完成后刷新整个列表确保一致性
- 使用 `try/finally` 确保 loading 状态总是被清理
## 加载状态管理模式
LobeChat 使用统一的加载状态管理模式:
@@ -292,27 +329,32 @@ refreshTopic: async () => {
## 命名规范总结
### Action 命名模式
- **Public Actions**: 动词形式,描述用户意图
- Public Actions: 动词形式,描述用户意图
- `createTopic`, `sendMessage`, `regenerateMessage`
- **Internal Actions**: `internal_` + 动词,描述内部操作
- Internal Actions: `internal_` + 动词,描述内部操作
- `internal_createTopic`, `internal_updateMessageContent`
- **Dispatch Methods**: `internal_dispatch` + 实体名
- Dispatch Methods: `internal_dispatch` + 实体名
- `internal_dispatchTopic`, `internal_dispatchMessage`
- **Toggle Methods**: `internal_toggle` + 状态名
- Toggle Methods: `internal_toggle` + 状态名
- `internal_toggleMessageLoading`, `internal_toggleChatLoading`
### 状态命名模式
- **ID 数组**: `[entity]LoadingIds`, `[entity]EditingIds`
- **映射结构**: `[entity]Maps`, `[entity]Map`
- **当前激活**: `active[Entity]Id`
- **初始化标记**: `[entity]sInit`
- ID 数组: `[entity]LoadingIds`, `[entity]EditingIds`
- 映射结构: `[entity]Maps`, `[entity]Map`
- 当前激活: `active[Entity]Id`
- 初始化标记: `[entity]sInit`
## 最佳实践
1. **始终实现乐观更新**:对于用户交互频繁的操作
2. **加载状态管理**:使用统一的加载状态数组管理并发操作
3. **类型安全**:为所有 action payload 定义 TypeScript 接口
4. **SWR 集成**:使用 SWR 管理数据获取和缓存失效
5. **AbortController**:为长时间运行的操作提供取消能力
1. 合理使用乐观更新:
- ✅ 适用:创建、更新操作(用户交互频繁)
- ❌ 避免:删除操作(破坏性操作,错误恢复复杂)
2. 加载状态管理:使用统一的加载状态数组管理并发操作
3. 类型安全:为所有 action payload 定义 TypeScript 接口
4. SWR 集成:使用 SWR 管理数据获取和缓存失效
5. AbortController为长时间运行的操作提供取消能力
6. 操作模式选择:
- 创建/更新:乐观更新 + 最终一致性
- 删除:加载状态 + 服务调用 + 数据刷新
这套 Action 组织模式确保了代码的一致性、可维护性,并提供了优秀的用户体验。

View File

@@ -13,10 +13,10 @@ LobeChat 的 `chat` store (`src/store/chat/`) 采用模块化的 slice 结构来
### 关键聚合文件
- **`src/store/chat/initialState.ts`**: 聚合所有 slice 的初始状态
- **`src/store/chat/store.ts`**: 定义顶层的 `ChatStore`,组合所有 slice 的 actions
- **`src/store/chat/selectors.ts`**: 统一导出所有 slice 的 selectors
- **`src/store/chat/helpers.ts`**: 提供聊天相关的辅助函数
- `src/store/chat/initialState.ts`: 聚合所有 slice 的初始状态
- `src/store/chat/store.ts`: 定义顶层的 `ChatStore`,组合所有 slice 的 actions
- `src/store/chat/selectors.ts`: 统一导出所有 slice 的 selectors
- `src/store/chat/helpers.ts`: 提供聊天相关的辅助函数
### Store 聚合模式
@@ -81,7 +81,7 @@ src/store/chat/slices/
### 文件职责说明
1. **`initialState.ts`**:
1. `initialState.ts`:
- 定义 slice 的 TypeScript 状态接口
- 提供初始状态默认值
@@ -104,7 +104,7 @@ export const initialTopicState: ChatTopicState = {
};
```
2. **`reducer.ts`** (复杂状态使用):
2. `reducer.ts` (复杂状态使用):
- 定义纯函数 reducer处理同步状态转换
- 使用 `immer` 确保不可变更新
@@ -150,10 +150,10 @@ export const topicReducer = (state: ChatTopic[] = [], payload: ChatTopicDispatch
};
```
3. **`selectors.ts`**:
3. `selectors.ts`:
- 提供状态查询和计算函数
- 供 UI 组件使用的状态订阅接口
- **重要**: 使用 `export const xxxSelectors` 模式聚合所有 selectors
- 重要: 使用 `export const xxxSelectors` 模式聚合所有 selectors
```typescript
// 典型的 selectors.ts 结构
@@ -277,22 +277,22 @@ export { aiChatSelectors } from './slices/aiChat/selectors';
## 最佳实践
1. **Slice 划分原则**:
1. Slice 划分原则:
- 按功能领域划分message, topic, aiChat 等)
- 每个 slice 管理相关的状态和操作
- 避免 slice 之间的强耦合
2. **文件命名规范**:
2. 文件命名规范:
- 使用小驼峰命名 slice 目录
- 文件名使用一致的模式action.ts, selectors.ts 等)
- 复杂 actions 时使用 actions/ 子目录
3. **状态结构设计**:
3. 状态结构设计:
- 扁平化的状态结构,避免深层嵌套
- 使用 Map 结构管理列表数据
- 分离加载状态和业务数据
4. **类型安全**:
4. 类型安全:
- 为每个 slice 定义清晰的 TypeScript 接口
- 使用 Zustand 的 StateCreator 确保类型一致性
- 在顶层聚合时保持类型安全