@lobehub/lobehub 2.0.0-next.353 → 2.0.0-next.354
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/add-provider-doc/SKILL.md +90 -0
- package/.agents/skills/add-setting-env/SKILL.md +106 -0
- package/.agents/skills/debug/SKILL.md +66 -0
- package/.agents/skills/desktop/SKILL.md +78 -0
- package/.agents/skills/desktop/references/feature-implementation.md +99 -0
- package/.agents/skills/desktop/references/local-tools.md +133 -0
- package/.agents/skills/desktop/references/menu-config.md +103 -0
- package/.agents/skills/desktop/references/window-management.md +143 -0
- package/.agents/skills/drizzle/SKILL.md +129 -0
- package/.agents/skills/drizzle/references/db-migrations.md +50 -0
- package/.agents/skills/hotkey/SKILL.md +90 -0
- package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
- package/.agents/skills/linear/SKILL.md +51 -0
- package/.agents/skills/microcopy/SKILL.md +83 -0
- package/.agents/skills/modal/SKILL.md +102 -0
- package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
- package/.agents/skills/react/SKILL.md +73 -0
- package/.agents/skills/react/references/layout-kit.md +100 -0
- package/.agents/skills/recent-data/SKILL.md +108 -0
- package/.agents/skills/testing/SKILL.md +89 -0
- package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
- package/.agents/skills/testing/references/db-model-test.md +124 -0
- package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
- package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
- package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
- package/.agents/skills/typescript/SKILL.md +52 -0
- package/.agents/skills/zustand/SKILL.md +78 -0
- package/.agents/skills/zustand/references/action-patterns.md +125 -0
- package/.agents/skills/zustand/references/slice-organization.md +125 -0
- package/AGENTS.md +42 -55
- package/CHANGELOG.md +33 -0
- package/CLAUDE.md +57 -46
- package/GEMINI.md +47 -39
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
- package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
- package/src/libs/pdfjs/index.tsx +25 -0
- package/src/store/test-coverage.md +5 -5
- package/.cursor/rules/add-provider-doc.mdc +0 -183
- package/.cursor/rules/add-setting-env.mdc +0 -175
- package/.cursor/rules/cursor-rules.mdc +0 -28
- package/.cursor/rules/db-migrations.mdc +0 -46
- package/.cursor/rules/debug-usage.mdc +0 -86
- package/.cursor/rules/desktop-controller-tests.mdc +0 -189
- package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
- package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
- package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
- package/.cursor/rules/desktop-window-management.mdc +0 -301
- package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
- package/.cursor/rules/hotkey.mdc +0 -162
- package/.cursor/rules/linear.mdc +0 -53
- package/.cursor/rules/microcopy-cn.mdc +0 -158
- package/.cursor/rules/microcopy-en.mdc +0 -148
- package/.cursor/rules/modal-imperative.mdc +0 -162
- package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
- package/.cursor/rules/project-introduce.mdc +0 -36
- package/.cursor/rules/react.mdc +0 -169
- package/.cursor/rules/recent-data-usage.mdc +0 -139
- package/.cursor/rules/rules-index.mdc +0 -44
- package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
- package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
- package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
- package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
- package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
- package/.cursor/rules/typescript.mdc +0 -55
- package/.cursor/rules/zustand-action-patterns.mdc +0 -328
- package/.cursor/rules/zustand-slice-organization.mdc +0 -308
- package/src/libs/pdfjs/pdf.worker.ts +0 -1
- package/src/libs/pdfjs/worker.ts +0 -12
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
- /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 包含添加 console.log 日志请求时
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Debug 包使用指南
|
|
8
|
-
|
|
9
|
-
本项目使用 `debug` 包进行调试日志记录。使用此规则来确保团队成员统一调试日志格式。
|
|
10
|
-
|
|
11
|
-
## 基本用法
|
|
12
|
-
|
|
13
|
-
1. 导入 debug 包:
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import debug from 'debug';
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
1. 创建一个命名空间的日志记录器:
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
// 格式: lobe:[模块]:[子模块]
|
|
23
|
-
const log = debug('lobe-[模块名]:[子模块名]');
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
1. 使用日志记录器:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
log('简单消息');
|
|
30
|
-
log('带变量的消息: %O', object);
|
|
31
|
-
log('格式化数字: %d', number);
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## 命名空间约定
|
|
35
|
-
|
|
36
|
-
- 桌面应用相关: `lobe-desktop:[模块]`
|
|
37
|
-
- 服务端相关: `lobe-server:[模块]`
|
|
38
|
-
- 客户端相关: `lobe-client:[模块]`
|
|
39
|
-
- 路由相关: `lobe-[类型]-router:[模块]`
|
|
40
|
-
|
|
41
|
-
## 格式说明符
|
|
42
|
-
|
|
43
|
-
- `%O` - 对象展开(推荐用于复杂对象)
|
|
44
|
-
- `%o` - 对象
|
|
45
|
-
- `%s` - 字符串
|
|
46
|
-
- `%d` - 数字
|
|
47
|
-
|
|
48
|
-
## 示例
|
|
49
|
-
|
|
50
|
-
查看 `src/server/routers/edge/market/index.ts` 中的使用示例:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import debug from 'debug';
|
|
54
|
-
|
|
55
|
-
const log = debug('lobe-edge-router:market');
|
|
56
|
-
|
|
57
|
-
log('getAgent input: %O', input);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## 启用调试
|
|
61
|
-
|
|
62
|
-
要在开发时启用调试输出,需设置环境变量:
|
|
63
|
-
|
|
64
|
-
### 在浏览器中
|
|
65
|
-
|
|
66
|
-
在控制台执行:
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
localStorage.debug = 'lobe-*';
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### 在 Node.js 环境中
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
DEBUG=lobe-* npm run dev
|
|
76
|
-
# 或者
|
|
77
|
-
DEBUG=lobe-* pnpm dev
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 在 Electron 应用中
|
|
81
|
-
|
|
82
|
-
可以在主进程和渲染进程启动前设置环境变量:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
process.env.DEBUG = 'lobe-*';
|
|
86
|
-
```
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 桌面端测试
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# 桌面端控制器单元测试指南
|
|
8
|
-
|
|
9
|
-
## 测试框架与目录结构
|
|
10
|
-
|
|
11
|
-
LobeChat 桌面端使用 Vitest 作为测试框架。控制器的单元测试应放置在对应控制器文件同级的 `__tests__` 目录下,并以原控制器文件名加 `.test.ts` 作为文件名。
|
|
12
|
-
|
|
13
|
-
```plaintext
|
|
14
|
-
apps/desktop/src/main/controllers/
|
|
15
|
-
├── __tests__/
|
|
16
|
-
│ ├── index.test.ts
|
|
17
|
-
│ ├── MenuCtr.test.ts
|
|
18
|
-
│ └── ...
|
|
19
|
-
├── McpCtr.ts
|
|
20
|
-
├── MenuCtr.ts
|
|
21
|
-
└── ...
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## 测试文件基本结构
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
28
|
-
|
|
29
|
-
import type { App } from '@/core/App';
|
|
30
|
-
|
|
31
|
-
import YourController from '../YourControllerName';
|
|
32
|
-
|
|
33
|
-
// 模拟依赖
|
|
34
|
-
vi.mock('依赖模块', () => ({
|
|
35
|
-
依赖函数: vi.fn(),
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
// 模拟 App 实例
|
|
39
|
-
const mockApp = {
|
|
40
|
-
// 按需模拟必要的 App 属性和方法
|
|
41
|
-
} as unknown as App;
|
|
42
|
-
|
|
43
|
-
describe('YourController', () => {
|
|
44
|
-
let controller: YourController;
|
|
45
|
-
|
|
46
|
-
beforeEach(() => {
|
|
47
|
-
vi.clearAllMocks();
|
|
48
|
-
controller = new YourController(mockApp);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
describe('方法名', () => {
|
|
52
|
-
it('测试场景描述', async () => {
|
|
53
|
-
// 准备测试数据
|
|
54
|
-
|
|
55
|
-
// 执行被测方法
|
|
56
|
-
const result = await controller.方法名(参数);
|
|
57
|
-
|
|
58
|
-
// 验证结果
|
|
59
|
-
expect(result).toMatchObject(预期结果);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## 模拟外部依赖
|
|
66
|
-
|
|
67
|
-
### 模拟模块函数
|
|
68
|
-
|
|
69
|
-
```typescript
|
|
70
|
-
const mockFunction = vi.fn();
|
|
71
|
-
|
|
72
|
-
vi.mock('module-name', () => ({
|
|
73
|
-
functionName: mockFunction,
|
|
74
|
-
}));
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### 模拟 Node.js 核心模块
|
|
78
|
-
|
|
79
|
-
例如模拟 `child_process.exec` 和 `util.promisify`:
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
// 存储模拟的 exec 实现
|
|
83
|
-
const mockExecImpl = vi.fn();
|
|
84
|
-
|
|
85
|
-
// 模拟 child_process.exec
|
|
86
|
-
vi.mock('child_process', () => ({
|
|
87
|
-
exec: vi.fn((cmd, callback) => {
|
|
88
|
-
return mockExecImpl(cmd, callback);
|
|
89
|
-
}),
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
// 模拟 util.promisify
|
|
93
|
-
vi.mock('util', () => ({
|
|
94
|
-
promisify: vi.fn((fn) => {
|
|
95
|
-
return async (cmd: string) => {
|
|
96
|
-
return new Promise((resolve, reject) => {
|
|
97
|
-
mockExecImpl(cmd, (error: Error | null, result: any) => {
|
|
98
|
-
if (error) reject(error);
|
|
99
|
-
else resolve(result);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
};
|
|
103
|
-
}),
|
|
104
|
-
}));
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## 编写有效的测试用例
|
|
108
|
-
|
|
109
|
-
### 测试分类
|
|
110
|
-
|
|
111
|
-
将测试用例分为不同类别,每个类别测试一个特定场景:
|
|
112
|
-
|
|
113
|
-
```typescript
|
|
114
|
-
// 成功场景
|
|
115
|
-
it('应该成功完成操作', async () => {});
|
|
116
|
-
|
|
117
|
-
// 边界条件
|
|
118
|
-
it('应该处理边界情况', async () => {});
|
|
119
|
-
|
|
120
|
-
// 错误处理
|
|
121
|
-
it('应该优雅地处理错误', async () => {});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### 设置测试数据
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// 模拟返回值
|
|
128
|
-
mockExecImpl.mockImplementation((cmd: string, callback: any) => {
|
|
129
|
-
if (cmd === '命令') {
|
|
130
|
-
callback(null, { stdout: '成功输出' });
|
|
131
|
-
} else {
|
|
132
|
-
callback(new Error('错误信息'), null);
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 断言
|
|
138
|
-
|
|
139
|
-
使用 Vitest 的断言函数验证结果:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// 检查基本值
|
|
143
|
-
expect(result.success).toBe(true);
|
|
144
|
-
|
|
145
|
-
// 检查对象部分匹配
|
|
146
|
-
expect(result.data).toMatchObject({
|
|
147
|
-
key: 'value',
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// 检查数组
|
|
151
|
-
expect(result.items).toHaveLength(2);
|
|
152
|
-
expect(result.items[0].name).toBe('expectedName');
|
|
153
|
-
|
|
154
|
-
// 检查函数调用
|
|
155
|
-
expect(mockFunction).toHaveBeenCalledWith(expectedArgs);
|
|
156
|
-
expect(mockFunction).toHaveBeenCalledTimes(1);
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## 最佳实践
|
|
160
|
-
|
|
161
|
-
1. **隔离测试**:确保每个测试互不影响,使用 `beforeEach` 重置模拟和状态
|
|
162
|
-
2. **全面覆盖**:测试正常流程、边界条件和错误处理
|
|
163
|
-
3. **清晰命名**:测试名称应清晰描述测试内容和预期结果
|
|
164
|
-
4. **避免测试实现细节**:测试应该关注行为而非实现细节,使代码重构不会破坏测试
|
|
165
|
-
5. **模拟外部依赖**:使用 `vi.mock()` 模拟所有外部依赖,减少测试的不确定性
|
|
166
|
-
|
|
167
|
-
## 示例:测试 IPC 事件处理方法
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
it('应该正确处理 IPC 事件', async () => {
|
|
171
|
-
// 模拟依赖
|
|
172
|
-
mockSomething.mockReturnValue({ result: 'success' });
|
|
173
|
-
|
|
174
|
-
// 调用 IPC 方法
|
|
175
|
-
const result = await controller.ipcMethodName({
|
|
176
|
-
param1: 'value1',
|
|
177
|
-
param2: 'value2',
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// 验证结果
|
|
181
|
-
expect(result).toEqual({
|
|
182
|
-
success: true,
|
|
183
|
-
data: { result: 'success' },
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// 验证依赖调用
|
|
187
|
-
expect(mockSomething).toHaveBeenCalledWith('value1', 'value2');
|
|
188
|
-
});
|
|
189
|
-
```
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 当要做 electron 相关工作时
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# 桌面端新功能实现指南
|
|
8
|
-
|
|
9
|
-
## 桌面端应用架构概述
|
|
10
|
-
|
|
11
|
-
LobeChat 桌面端基于 Electron 框架构建,采用主进程-渲染进程架构:
|
|
12
|
-
|
|
13
|
-
1. **主进程 (Main Process)**:
|
|
14
|
-
- 位置:`apps/desktop/src/main`
|
|
15
|
-
- 职责:控制应用生命周期、系统API交互、窗口管理、后台服务
|
|
16
|
-
|
|
17
|
-
2. **渲染进程 (Renderer Process)**:
|
|
18
|
-
- 复用 Web 端代码,位于 `src` 目录
|
|
19
|
-
- 通过 IPC 与主进程通信
|
|
20
|
-
|
|
21
|
-
3. **预加载脚本 (Preload)**:
|
|
22
|
-
- 位置:`apps/desktop/src/preload`
|
|
23
|
-
- 职责:安全地暴露主进程功能给渲染进程
|
|
24
|
-
|
|
25
|
-
## 添加新桌面端功能流程
|
|
26
|
-
|
|
27
|
-
### 1. 确定功能需求与设计
|
|
28
|
-
|
|
29
|
-
首先确定新功能的需求和设计,包括:
|
|
30
|
-
|
|
31
|
-
- 功能描述和用例
|
|
32
|
-
- 是否需要系统级API(如文件系统、网络等)
|
|
33
|
-
- UI/UX设计(如必要)
|
|
34
|
-
- 与现有功能的交互方式
|
|
35
|
-
|
|
36
|
-
### 2. 在主进程中实现核心功能
|
|
37
|
-
|
|
38
|
-
1. **创建控制器 (Controller)**
|
|
39
|
-
- 位置:`apps/desktop/src/main/controllers/`
|
|
40
|
-
- 示例:创建 `NewFeatureCtr.ts`
|
|
41
|
-
- 需继承 `ControllerModule`,并设置 `static readonly groupName`(例如 `static override readonly groupName = 'newFeature';`)
|
|
42
|
-
- 按 `_template.ts` 模板格式实现,并在 `apps/desktop/src/main/controllers/registry.ts` 的 `controllerIpcConstructors` 中注册,保证类型推导与自动装配
|
|
43
|
-
|
|
44
|
-
2. **定义 IPC 事件处理器**
|
|
45
|
-
- 使用 `@IpcMethod()` 装饰器暴露渲染进程可访问的通道
|
|
46
|
-
- 通道名称基于 `groupName.methodName` 自动生成,不再手动拼接字符串
|
|
47
|
-
- 处理函数可通过 `getIpcContext()` 获取 `sender`、`event` 等上下文信息,并按照需要返回结构化结果
|
|
48
|
-
|
|
49
|
-
3. **实现业务逻辑**
|
|
50
|
-
- 可能需要调用 Electron API 或 Node.js 原生模块
|
|
51
|
-
- 对于复杂功能,可以创建专门的服务类 (`services/`)
|
|
52
|
-
|
|
53
|
-
### 3. 定义 IPC 通信类型
|
|
54
|
-
|
|
55
|
-
1. **在共享类型定义中添加新类型**
|
|
56
|
-
- 位置:`packages/electron-client-ipc/src/types.ts`
|
|
57
|
-
- 添加参数类型接口(如 `NewFeatureParams`)
|
|
58
|
-
- 添加返回结果类型接口(如 `NewFeatureResult`)
|
|
59
|
-
|
|
60
|
-
### 4. 在渲染进程实现前端功能
|
|
61
|
-
|
|
62
|
-
1. **创建服务层**
|
|
63
|
-
- 位置:`src/services/electron/`
|
|
64
|
-
- 添加服务方法调用 IPC
|
|
65
|
-
- 使用 `ensureElectronIpc()` 生成的类型安全代理,避免手动拼通道名称
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// src/services/electron/newFeatureService.ts
|
|
69
|
-
import type { NewFeatureParams } from '@lobechat/electron-client-ipc';
|
|
70
|
-
|
|
71
|
-
import { ensureElectronIpc } from '@/utils/electron/ipc';
|
|
72
|
-
|
|
73
|
-
const ipc = ensureElectronIpc();
|
|
74
|
-
|
|
75
|
-
export const newFeatureService = async (params: NewFeatureParams) => {
|
|
76
|
-
return ipc.newFeature.doSomething(params);
|
|
77
|
-
};
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
2. **实现 Store Action**
|
|
81
|
-
- 位置:`src/store/`
|
|
82
|
-
- 添加状态更新逻辑和错误处理
|
|
83
|
-
|
|
84
|
-
3. **添加 UI 组件**
|
|
85
|
-
- 根据需要在适当位置添加UI组件
|
|
86
|
-
- 通过 Store 或 Service 层调用功能
|
|
87
|
-
|
|
88
|
-
### 5. 如果是新增内置工具,遵循工具实现流程
|
|
89
|
-
|
|
90
|
-
参考 `desktop-local-tools-implement.mdc` 了解更多关于添加内置工具的详细步骤。
|
|
91
|
-
|
|
92
|
-
### 6. 添加测试
|
|
93
|
-
|
|
94
|
-
1. **单元测试**
|
|
95
|
-
- 位置:`apps/desktop/src/main/controllers/__tests__/`
|
|
96
|
-
- 测试主进程组件功能
|
|
97
|
-
|
|
98
|
-
2. **集成测试**
|
|
99
|
-
- 测试 IPC 通信和功能完整流程
|
|
100
|
-
|
|
101
|
-
## 最佳实践
|
|
102
|
-
|
|
103
|
-
1. **安全性考虑**
|
|
104
|
-
- 谨慎处理用户数据和文件系统访问
|
|
105
|
-
- 适当验证和清理输入数据
|
|
106
|
-
- 限制暴露给渲染进程的API范围
|
|
107
|
-
|
|
108
|
-
2. **性能优化**
|
|
109
|
-
- 对于耗时操作,考虑使用异步方法
|
|
110
|
-
- 大型数据传输考虑分批处理
|
|
111
|
-
|
|
112
|
-
3. **用户体验**
|
|
113
|
-
- 为长时间操作添加进度指示
|
|
114
|
-
- 提供适当的错误反馈
|
|
115
|
-
- 考虑操作的可撤销性
|
|
116
|
-
|
|
117
|
-
4. **代码组织**
|
|
118
|
-
- 遵循项目现有的命名和代码风格约定
|
|
119
|
-
- 为新功能添加适当的文档和注释
|
|
120
|
-
- 功能模块化,避免过度耦合
|
|
121
|
-
|
|
122
|
-
## 示例:实现系统通知功能
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
// apps/desktop/src/main/controllers/NotificationCtr.ts
|
|
126
|
-
import type {
|
|
127
|
-
DesktopNotificationResult,
|
|
128
|
-
ShowDesktopNotificationParams,
|
|
129
|
-
} from '@lobechat/electron-client-ipc';
|
|
130
|
-
import { Notification } from 'electron';
|
|
131
|
-
|
|
132
|
-
import { ControllerModule, IpcMethod } from '@/controllers';
|
|
133
|
-
|
|
134
|
-
export default class NotificationCtr extends ControllerModule {
|
|
135
|
-
static override readonly groupName = 'notification';
|
|
136
|
-
|
|
137
|
-
@IpcMethod()
|
|
138
|
-
async showDesktopNotification(
|
|
139
|
-
params: ShowDesktopNotificationParams,
|
|
140
|
-
): Promise<DesktopNotificationResult> {
|
|
141
|
-
if (!Notification.isSupported()) {
|
|
142
|
-
return { error: 'Notifications not supported', success: false };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const notification = new Notification({ body: params.body, title: params.title });
|
|
147
|
-
notification.show();
|
|
148
|
-
return { success: true };
|
|
149
|
-
} catch (error) {
|
|
150
|
-
console.error('[NotificationCtr] Failed to show notification:', error);
|
|
151
|
-
return { error: error instanceof Error ? error.message : 'Unknown error', success: false };
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
```
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description:
|
|
3
|
-
globs:
|
|
4
|
-
alwaysApply: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
**新增桌面端工具流程:**
|
|
8
|
-
|
|
9
|
-
1. **定义工具接口 (Manifest):**
|
|
10
|
-
- **文件:** `src/tools/[tool_category]/index.ts` (例如: `src/tools/local-files/index.ts`)
|
|
11
|
-
- **操作:**
|
|
12
|
-
- 在 `ApiName` 对象(例如 `LocalFilesApiName`)中添加一个新的、唯一的 API 名称。
|
|
13
|
-
- 在 `Manifest` 对象(例如 `LocalFilesManifest`)的 `api` 数组中,新增一个对象来定义新工具的接口。
|
|
14
|
-
- **关键字段:**
|
|
15
|
-
- `name`: 使用上一步定义的 API 名称。
|
|
16
|
-
- `description`: 清晰描述工具的功能,供 Agent 理解和向用户展示。
|
|
17
|
-
- `parameters`: 使用 JSON Schema 定义工具所需的输入参数。
|
|
18
|
-
- `type`: 通常是 'object'。
|
|
19
|
-
- `properties`: 定义每个参数的名称、`description`、`type` (string, number, boolean, array, etc.),使用英文。
|
|
20
|
-
- `required`: 一个字符串数组,列出必须提供的参数名称。
|
|
21
|
-
|
|
22
|
-
2. **定义相关类型:**
|
|
23
|
-
- **文件 1:** `packages/electron-client-ipc/src/types.ts` (或类似的共享 IPC 类型文件)
|
|
24
|
-
- **操作:** 定义传递给 IPC 事件的参数类型接口 (例如: `RenameLocalFileParams`, `MoveLocalFileParams`)。确保与 Manifest 中定义的 `parameters` 一致。
|
|
25
|
-
- **文件 2:** `src/tools/[tool_category]/type.ts` (例如: `src/tools/local-files/type.ts`)
|
|
26
|
-
- **操作:** 定义此工具执行后,存储在前端 Zustand Store 中的状态类型接口 (例如: `LocalRenameFileState`, `LocalMoveFileState`)。这通常包含操作结果(成功/失败)、错误信息以及相关数据(如旧路径、新路径等)。
|
|
27
|
-
|
|
28
|
-
3. **实现前端状态管理 (Store Action):**
|
|
29
|
-
- **文件:** `src/store/chat/slices/builtinTool/actions/[tool_category].ts` (例如: `src/store/chat/slices/builtinTool/actions/localFile.ts`)
|
|
30
|
-
- **操作:**
|
|
31
|
-
- 导入在步骤 2 中定义的 IPC 参数类型和状态类型。
|
|
32
|
-
- 在 Action 接口 (例如: `LocalFileAction`) 中添加新 Action 的方法签名,使用对应的 IPC 参数类型。
|
|
33
|
-
- 在 `createSlice` (例如: `localFileSlice`) 中实现该 Action 方法:
|
|
34
|
-
- 接收 `id` (消息 ID) 和 `params` (符合 IPC 参数类型)。
|
|
35
|
-
- 设置加载状态 (`toggleLocalFileLoading(id, true)`)。
|
|
36
|
-
- 调用对应的 `Service` 层方法 (见步骤 4),传递 `params`。
|
|
37
|
-
- 使用 `try...catch` 处理 `Service` 调用可能发生的错误。
|
|
38
|
-
- **成功时:**
|
|
39
|
-
- 调用 `updatePluginState(id, {...})` 更新插件状态,使用步骤 2 中定义的状态类型。
|
|
40
|
-
- 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,通常包含成功确认信息。
|
|
41
|
-
- **失败时:**
|
|
42
|
-
- 记录错误 (`console.error`)。
|
|
43
|
-
- 调用 `updatePluginState(id, {...})` 更新插件状态,包含错误信息。
|
|
44
|
-
- 调用 `internal_updateMessagePluginError(id, {...})` 设置消息的错误状态。
|
|
45
|
-
- 调用 `internal_updateMessageContent(id, JSON.stringify({...}))` 更新消息内容,包含错误信息。
|
|
46
|
-
- 在 `finally` 块中取消加载状态 (`toggleLocalFileLoading(id, false)`)。
|
|
47
|
-
- 返回操作是否成功 (`boolean`)。
|
|
48
|
-
|
|
49
|
-
4. **实现 Service 层 (调用 IPC):**
|
|
50
|
-
- **文件:** `src/services/electron/[tool_category]Service.ts` (例如: `src/services/electron/localFileService.ts`)
|
|
51
|
-
- **操作:**
|
|
52
|
-
- 导入在步骤 2 中定义的 IPC 参数类型。
|
|
53
|
-
- 添加一个新的 `async` 方法,方法名通常与 Action 名称对应 (例如: `renameLocalFile`)。
|
|
54
|
-
- 方法接收 `params` (符合 IPC 参数类型)。
|
|
55
|
-
- 通过 `ensureElectronIpc()` 获取 IPC 代理 (`const ipc = ensureElectronIpc();`),调用与 Manifest 中 `name` 字段匹配的链式方法,并将 `params` 传递过去。
|
|
56
|
-
- 定义方法的返回类型,通常是 `Promise<{ success: boolean; error?: string }>`,与后端 Controller 返回的结构一致。
|
|
57
|
-
|
|
58
|
-
5. **实现后端逻辑 (Controller / IPC Handler):**
|
|
59
|
-
- **文件:** `apps/desktop/src/main/controllers/[ToolName]Ctr.ts` (例如: `apps/desktop/src/main/controllers/LocalFileCtr.ts`)
|
|
60
|
-
- **操作:**
|
|
61
|
-
- 导入 Node.js 相关模块 (`fs`, `path` 等) 和 IPC 相关依赖 (`ControllerModule`, `IpcMethod`、参数类型等)。
|
|
62
|
-
- 添加一个新的 `async` 方法,方法名通常以 `handle` 开头 (例如: `handleRenameFile`)。
|
|
63
|
-
- 使用 `@IpcMethod()` 装饰器将此方法注册为对应 IPC 事件的处理器,确保方法名与 Manifest 中的 `name` 以及 Service 层的链式调用一致。
|
|
64
|
-
- 方法的参数应解构自 Service 层传递过来的对象,类型与步骤 2 中定义的 IPC 参数类型匹配。
|
|
65
|
-
- 实现核心业务逻辑:
|
|
66
|
-
- 进行必要的输入验证。
|
|
67
|
-
- 执行文件系统操作或其他后端任务 (例如: `fs.promises.rename`)。
|
|
68
|
-
- 使用 `try...catch` 捕获执行过程中的错误。
|
|
69
|
-
- 处理特定错误码 (`error.code`) 以提供更友好的错误消息。
|
|
70
|
-
- 返回一个包含 `success` (boolean) 和可选 `error` (string) 字段的对象。
|
|
71
|
-
|
|
72
|
-
6. **更新 Agent 文档 (System Role):**
|
|
73
|
-
- **文件:** `src/tools/[tool_category]/systemRole.ts` (例如: `src/tools/local-files/systemRole.ts`)
|
|
74
|
-
- **操作:**
|
|
75
|
-
- 在 `<core_capabilities>` 部分添加新工具的简要描述。
|
|
76
|
-
- 如果需要,更新 `<workflow>`。
|
|
77
|
-
- 在 `<tool_usage_guidelines>` 部分为新工具添加详细的使用说明,解释其参数、用途和预期行为。
|
|
78
|
-
- 如有必要,更新 `<security_considerations>`。
|
|
79
|
-
- 如有必要(例如工具返回了新的数据结构或路径),更新 `<response_format>` 中的示例。
|
|
80
|
-
|
|
81
|
-
通过遵循这些步骤,可以系统地将新的桌面端工具集成到 LobeChat 的插件系统中。
|