@lobehub/chat 1.99.6 → 1.100.1
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/.cursor/rules/testing-guide/testing-guide.mdc +173 -0
- package/.github/workflows/desktop-pr-build.yml +3 -3
- package/.github/workflows/release-desktop-beta.yml +3 -3
- package/CHANGELOG.md +50 -0
- package/apps/desktop/package.json +5 -2
- package/apps/desktop/src/main/controllers/AuthCtr.ts +310 -111
- package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +50 -3
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +188 -23
- package/apps/desktop/src/main/controllers/__tests__/NetworkProxyCtr.test.ts +37 -18
- package/apps/desktop/src/main/types/store.ts +1 -0
- package/apps/desktop/src/preload/electronApi.ts +2 -1
- package/apps/desktop/src/preload/streamer.ts +58 -0
- package/changelog/v1.json +18 -0
- package/docs/development/database-schema.dbml +9 -0
- package/docs/self-hosting/environment-variables/model-provider.mdx +25 -0
- package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +25 -0
- package/docs/self-hosting/faq/vercel-ai-image-timeout.mdx +65 -0
- package/docs/self-hosting/faq/vercel-ai-image-timeout.zh-CN.mdx +63 -0
- package/docs/usage/providers/fal.mdx +6 -6
- package/docs/usage/providers/fal.zh-CN.mdx +6 -6
- package/locales/ar/electron.json +3 -0
- package/locales/ar/oauth.json +8 -4
- package/locales/bg-BG/electron.json +3 -0
- package/locales/bg-BG/oauth.json +8 -4
- package/locales/de-DE/electron.json +3 -0
- package/locales/de-DE/oauth.json +9 -5
- package/locales/en-US/electron.json +3 -0
- package/locales/en-US/oauth.json +8 -4
- package/locales/es-ES/electron.json +3 -0
- package/locales/es-ES/oauth.json +9 -5
- package/locales/fa-IR/electron.json +3 -0
- package/locales/fa-IR/oauth.json +8 -4
- package/locales/fr-FR/electron.json +3 -0
- package/locales/fr-FR/oauth.json +8 -4
- package/locales/it-IT/electron.json +3 -0
- package/locales/it-IT/oauth.json +9 -5
- package/locales/ja-JP/electron.json +3 -0
- package/locales/ja-JP/oauth.json +8 -4
- package/locales/ko-KR/electron.json +3 -0
- package/locales/ko-KR/oauth.json +8 -4
- package/locales/nl-NL/electron.json +3 -0
- package/locales/nl-NL/oauth.json +9 -5
- package/locales/pl-PL/electron.json +3 -0
- package/locales/pl-PL/oauth.json +8 -4
- package/locales/pt-BR/electron.json +3 -0
- package/locales/pt-BR/oauth.json +8 -4
- package/locales/ru-RU/electron.json +3 -0
- package/locales/ru-RU/oauth.json +8 -4
- package/locales/tr-TR/electron.json +3 -0
- package/locales/tr-TR/oauth.json +8 -4
- package/locales/vi-VN/electron.json +3 -0
- package/locales/vi-VN/oauth.json +9 -5
- package/locales/zh-CN/electron.json +3 -0
- package/locales/zh-CN/oauth.json +8 -4
- package/locales/zh-TW/electron.json +3 -0
- package/locales/zh-TW/oauth.json +8 -4
- package/package.json +3 -3
- package/packages/electron-client-ipc/src/dispatch.ts +14 -2
- package/packages/electron-client-ipc/src/index.ts +1 -0
- package/packages/electron-client-ipc/src/streamInvoke.ts +62 -0
- package/packages/electron-client-ipc/src/types/proxyTRPCRequest.ts +5 -0
- package/packages/electron-client-ipc/src/utils/headers.ts +27 -0
- package/packages/electron-client-ipc/src/utils/request.ts +28 -0
- package/src/app/(backend)/oidc/callback/desktop/route.ts +58 -0
- package/src/app/(backend)/oidc/handoff/route.ts +46 -0
- package/src/app/[variants]/oauth/callback/error/page.tsx +55 -0
- package/src/app/[variants]/oauth/callback/layout.tsx +12 -0
- package/src/app/[variants]/oauth/callback/loading.tsx +3 -0
- package/src/app/[variants]/oauth/{consent/[uid] → callback}/success/page.tsx +10 -1
- package/src/app/[variants]/oauth/consent/[uid]/Consent.tsx +7 -1
- package/src/database/client/migrations.json +8 -0
- package/src/database/migrations/0028_oauth_handoffs.sql +8 -0
- package/src/database/migrations/meta/0028_snapshot.json +6055 -0
- package/src/database/migrations/meta/_journal.json +7 -0
- package/src/database/models/oauthHandoff.ts +94 -0
- package/src/database/repositories/tableViewer/index.test.ts +1 -1
- package/src/database/schemas/oidc.ts +46 -0
- package/src/features/ElectronTitlebar/Connection/Waiting.tsx +59 -115
- package/src/features/ElectronTitlebar/Connection/WaitingAnim.tsx +114 -0
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.test.ts +1 -1
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +2 -1
- package/src/libs/oidc-provider/config.ts +16 -17
- package/src/libs/oidc-provider/jwt.ts +135 -0
- package/src/libs/oidc-provider/provider.ts +22 -38
- package/src/libs/trpc/client/async.ts +1 -2
- package/src/libs/trpc/client/edge.ts +1 -2
- package/src/libs/trpc/client/lambda.ts +1 -1
- package/src/libs/trpc/client/tools.ts +1 -2
- package/src/libs/trpc/lambda/context.ts +9 -16
- package/src/locales/default/electron.ts +3 -0
- package/src/locales/default/oauth.ts +8 -4
- package/src/middleware.ts +10 -4
- package/src/server/globalConfig/genServerAiProviderConfig.test.ts +235 -0
- package/src/server/globalConfig/genServerAiProviderConfig.ts +9 -10
- package/src/server/services/oidc/index.ts +0 -71
- package/src/services/chat.ts +5 -1
- package/src/services/electron/remoteServer.ts +0 -7
- package/src/store/aiInfra/slices/aiProvider/action.ts +2 -1
- package/src/{libs/trpc/client/helpers → utils/electron}/desktopRemoteRPCFetch.ts +22 -7
- package/src/utils/getFallbackModelProperty.test.ts +193 -0
- package/src/utils/getFallbackModelProperty.ts +36 -0
- package/src/utils/parseModels.test.ts +150 -48
- package/src/utils/parseModels.ts +26 -11
- package/src/utils/server/auth.ts +22 -0
- package/src/app/[variants]/oauth/consent/[uid]/failed/page.tsx +0 -36
- package/src/app/[variants]/oauth/handoff/Client.tsx +0 -98
- package/src/app/[variants]/oauth/handoff/page.tsx +0 -13
@@ -214,6 +214,176 @@ describe('<UserProfile />', () => {
|
|
214
214
|
**修复方法**: 更新了测试文件中的 mock 数据结构,使其与最新的 API 响应格式保持一致。具体修改了 `user.test.ts` 中的 `mockUserData` 对象结构。
|
215
215
|
```
|
216
216
|
|
217
|
+
## 🎯 测试编写最佳实践
|
218
|
+
|
219
|
+
### Mock 数据策略:追求"低成本的真实性" 📋
|
220
|
+
|
221
|
+
**核心原则**: 测试数据应默认追求真实性,只有在引入"高昂的测试成本"时才进行简化。
|
222
|
+
|
223
|
+
#### 什么是"高昂的测试成本"?
|
224
|
+
|
225
|
+
"高成本"指的是测试中引入了外部依赖,使测试变慢、不稳定或复杂:
|
226
|
+
|
227
|
+
- **文件 I/O 操作**:读写硬盘文件
|
228
|
+
- **网络请求**:HTTP 调用、数据库连接
|
229
|
+
- **系统调用**:获取系统时间、环境变量等
|
230
|
+
|
231
|
+
#### ✅ 推荐做法:Mock 依赖,保留真实数据
|
232
|
+
|
233
|
+
```typescript
|
234
|
+
// ✅ 好的做法:Mock I/O 操作,但使用真实的文件内容格式
|
235
|
+
describe('parseContentType', () => {
|
236
|
+
beforeEach(() => {
|
237
|
+
// Mock 文件读取操作(避免真实 I/O)
|
238
|
+
vi.spyOn(fs, 'readFileSync').mockImplementation((path) => {
|
239
|
+
// 但返回真实的文件内容格式
|
240
|
+
if (path.includes('.pdf')) return '%PDF-1.4\n%âãÏÓ'; // 真实 PDF 文件头
|
241
|
+
if (path.includes('.png')) return '\x89PNG\r\n\x1a\n'; // 真实 PNG 文件头
|
242
|
+
return '';
|
243
|
+
});
|
244
|
+
});
|
245
|
+
|
246
|
+
it('should detect PDF content type correctly', () => {
|
247
|
+
const result = parseContentType('/path/to/file.pdf');
|
248
|
+
expect(result).toBe('application/pdf');
|
249
|
+
});
|
250
|
+
});
|
251
|
+
|
252
|
+
// ❌ 过度简化:使用不真实的数据
|
253
|
+
describe('parseContentType', () => {
|
254
|
+
it('should detect PDF content type correctly', () => {
|
255
|
+
// 这种简化数据没有测试价值
|
256
|
+
const result = parseContentType('fake-pdf-content');
|
257
|
+
expect(result).toBe('application/pdf');
|
258
|
+
});
|
259
|
+
});
|
260
|
+
```
|
261
|
+
|
262
|
+
#### 🎯 真实标识符的价值
|
263
|
+
|
264
|
+
```typescript
|
265
|
+
// ✅ 使用真实的提供商标识符
|
266
|
+
it('should parse OpenAI model list correctly', () => {
|
267
|
+
const result = parseModelString('openai', '+gpt-4,+gpt-3.5-turbo');
|
268
|
+
expect(result.add).toHaveLength(2);
|
269
|
+
expect(result.add[0].id).toBe('gpt-4');
|
270
|
+
});
|
271
|
+
|
272
|
+
// ❌ 使用占位符标识符(价值较低)
|
273
|
+
it('should parse model list correctly', () => {
|
274
|
+
const result = parseModelString('test-provider', '+model1,+model2');
|
275
|
+
expect(result.add).toHaveLength(2);
|
276
|
+
// 这种测试对理解真实场景帮助不大
|
277
|
+
});
|
278
|
+
```
|
279
|
+
|
280
|
+
### 错误处理测试:测试"行为"而非"文本" ⚠️
|
281
|
+
|
282
|
+
**核心原则**: 测试应该验证程序在错误发生时的行为是可预测的,而不是验证易变的错误信息文本。
|
283
|
+
|
284
|
+
#### ✅ 推荐的错误测试方式
|
285
|
+
|
286
|
+
```typescript
|
287
|
+
// ✅ 测试是否抛出错误
|
288
|
+
it('should throw error when invalid input provided', () => {
|
289
|
+
expect(() => processInput(null)).toThrow();
|
290
|
+
});
|
291
|
+
|
292
|
+
// ✅ 测试错误类型(最推荐)
|
293
|
+
it('should throw ValidationError for invalid data', () => {
|
294
|
+
expect(() => validateUser({})).toThrow(ValidationError);
|
295
|
+
});
|
296
|
+
|
297
|
+
// ✅ 测试错误属性而非消息文本
|
298
|
+
it('should throw error with correct error code', () => {
|
299
|
+
expect(() => processPayment({})).toThrow(
|
300
|
+
expect.objectContaining({
|
301
|
+
code: 'INVALID_PAYMENT_DATA',
|
302
|
+
statusCode: 400,
|
303
|
+
}),
|
304
|
+
);
|
305
|
+
});
|
306
|
+
```
|
307
|
+
|
308
|
+
#### ❌ 应避免的做法
|
309
|
+
|
310
|
+
```typescript
|
311
|
+
// ❌ 过度依赖具体错误信息文本
|
312
|
+
it('should throw specific error message', () => {
|
313
|
+
expect(() => processUser({})).toThrow('用户数据不能为空,请检查输入参数');
|
314
|
+
// 这种测试很脆弱,错误文案稍有修改就会失败
|
315
|
+
});
|
316
|
+
```
|
317
|
+
|
318
|
+
#### 🎯 例外情况:何时可以测试错误信息
|
319
|
+
|
320
|
+
```typescript
|
321
|
+
// ✅ 测试标准 API 错误(这是契约的一部分)
|
322
|
+
it('should return proper HTTP error for API', () => {
|
323
|
+
expect(response.statusCode).toBe(400);
|
324
|
+
expect(response.error).toBe('Bad Request');
|
325
|
+
});
|
326
|
+
|
327
|
+
// ✅ 测试错误信息的关键部分(使用正则)
|
328
|
+
it('should include field name in validation error', () => {
|
329
|
+
expect(() => validateField('email', '')).toThrow(/email/i);
|
330
|
+
});
|
331
|
+
```
|
332
|
+
|
333
|
+
### 疑难解答:警惕模块污染 🚨
|
334
|
+
|
335
|
+
**识别信号**: 当你的测试出现以下"灵异"现象时,优先怀疑模块污染:
|
336
|
+
|
337
|
+
- 单独运行某个测试通过,但和其他测试一起运行就失败
|
338
|
+
- 测试的执行顺序影响结果
|
339
|
+
- Mock 设置看起来正确,但实际使用的是旧的 Mock 版本
|
340
|
+
|
341
|
+
#### 典型场景:动态 Mock 同一模块
|
342
|
+
|
343
|
+
```typescript
|
344
|
+
// ❌ 容易出现模块污染的写法
|
345
|
+
describe('ConfigService', () => {
|
346
|
+
it('should work in development mode', async () => {
|
347
|
+
vi.doMock('./config', () => ({ isDev: true }));
|
348
|
+
const { getSettings } = await import('./configService'); // 第一次加载
|
349
|
+
expect(getSettings().debugMode).toBe(true);
|
350
|
+
});
|
351
|
+
|
352
|
+
it('should work in production mode', async () => {
|
353
|
+
vi.doMock('./config', () => ({ isDev: false }));
|
354
|
+
const { getSettings } = await import('./configService'); // 可能使用缓存的旧版本!
|
355
|
+
expect(getSettings().debugMode).toBe(false); // ❌ 可能失败
|
356
|
+
});
|
357
|
+
});
|
358
|
+
|
359
|
+
// ✅ 使用 resetModules 解决模块污染
|
360
|
+
describe('ConfigService', () => {
|
361
|
+
beforeEach(() => {
|
362
|
+
vi.resetModules(); // 清除模块缓存,确保每个测试都是干净的环境
|
363
|
+
});
|
364
|
+
|
365
|
+
it('should work in development mode', async () => {
|
366
|
+
vi.doMock('./config', () => ({ isDev: true }));
|
367
|
+
const { getSettings } = await import('./configService');
|
368
|
+
expect(getSettings().debugMode).toBe(true);
|
369
|
+
});
|
370
|
+
|
371
|
+
it('should work in production mode', async () => {
|
372
|
+
vi.doMock('./config', () => ({ isDev: false }));
|
373
|
+
const { getSettings } = await import('./configService');
|
374
|
+
expect(getSettings().debugMode).toBe(false); // ✅ 测试通过
|
375
|
+
});
|
376
|
+
});
|
377
|
+
```
|
378
|
+
|
379
|
+
#### 🔧 排查和解决步骤
|
380
|
+
|
381
|
+
1. **识别问题**: 测试失败时,首先问自己:"是否有多个测试在 Mock 同一个模块?"
|
382
|
+
2. **添加隔离**: 在 `beforeEach` 中添加 `vi.resetModules()`
|
383
|
+
3. **验证修复**: 重新运行测试,确认问题解决
|
384
|
+
|
385
|
+
**记住**: `vi.resetModules()` 是解决测试"灵异"失败的终极武器,当常规调试方法都无效时,它往往能一针见血地解决问题。
|
386
|
+
|
217
387
|
## 📂 测试文件组织
|
218
388
|
|
219
389
|
### 文件命名约定
|
@@ -320,4 +490,7 @@ git show HEAD -- path/to/component.ts | cat # 查看最新提交的修改
|
|
320
490
|
- **修复原则**: 失败1-2次后寻求帮助,测试命名关注行为而非实现细节
|
321
491
|
- **调试流程**: 复现 → 分析 → 假设 → 修复 → 验证 → 总结
|
322
492
|
- **文件组织**: 优先在现有 `describe` 块中添加测试,避免创建冗余顶级块
|
493
|
+
- **数据策略**: 默认追求真实性,只有高成本(I/O、网络等)时才简化
|
494
|
+
- **错误测试**: 测试错误类型和行为,避免依赖具体的错误信息文本
|
495
|
+
- **模块污染**: 测试"灵异"失败时,优先怀疑模块污染,使用 `vi.resetModules()` 解决
|
323
496
|
- **安全要求**: Model 测试必须包含权限检查,并在双环境下验证通过
|
@@ -155,9 +155,9 @@ jobs:
|
|
155
155
|
KEY_VAULTS_SECRET: 'oLXWIiR/AKF+rWaqy9lHkrYgzpATbW3CtJp3UfkVgpE='
|
156
156
|
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_PROJECT_ID }}
|
157
157
|
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_NIGHTLY_DESKTOP_BASE_URL }}
|
158
|
-
# 将 TEMP 和 TMP 目录设置到
|
159
|
-
TEMP:
|
160
|
-
TMP:
|
158
|
+
# 将 TEMP 和 TMP 目录设置到 C 盘
|
159
|
+
TEMP: C:\temp
|
160
|
+
TMP: C:\temp
|
161
161
|
|
162
162
|
# Linux 平台构建处理
|
163
163
|
- name: Build artifact on Linux
|
@@ -139,9 +139,9 @@ jobs:
|
|
139
139
|
NEXT_PUBLIC_DESKTOP_PROJECT_ID: ${{ secrets.UMAMI_BETA_DESKTOP_PROJECT_ID }}
|
140
140
|
NEXT_PUBLIC_DESKTOP_UMAMI_BASE_URL: ${{ secrets.UMAMI_BETA_DESKTOP_BASE_URL }}
|
141
141
|
|
142
|
-
# 将 TEMP 和 TMP 目录设置到
|
143
|
-
TEMP:
|
144
|
-
TMP:
|
142
|
+
# 将 TEMP 和 TMP 目录设置到 C 盘
|
143
|
+
TEMP: C:\temp
|
144
|
+
TMP: C:\temp
|
145
145
|
|
146
146
|
# Linux 平台构建处理
|
147
147
|
- name: Build artifact on Linux
|
package/CHANGELOG.md
CHANGED
@@ -2,6 +2,56 @@
|
|
2
2
|
|
3
3
|
# Changelog
|
4
4
|
|
5
|
+
### [Version 1.100.1](https://github.com/lobehub/lobe-chat/compare/v1.100.0...v1.100.1)
|
6
|
+
|
7
|
+
<sup>Released on **2025-07-17**</sup>
|
8
|
+
|
9
|
+
#### 🐛 Bug Fixes
|
10
|
+
|
11
|
+
- **misc**: Use server env config image models.
|
12
|
+
|
13
|
+
<br/>
|
14
|
+
|
15
|
+
<details>
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
17
|
+
|
18
|
+
#### What's fixed
|
19
|
+
|
20
|
+
- **misc**: Use server env config image models, closes [#8478](https://github.com/lobehub/lobe-chat/issues/8478) ([768ee2b](https://github.com/lobehub/lobe-chat/commit/768ee2b))
|
21
|
+
|
22
|
+
</details>
|
23
|
+
|
24
|
+
<div align="right">
|
25
|
+
|
26
|
+
[](#readme-top)
|
27
|
+
|
28
|
+
</div>
|
29
|
+
|
30
|
+
## [Version 1.100.0](https://github.com/lobehub/lobe-chat/compare/v1.99.6...v1.100.0)
|
31
|
+
|
32
|
+
<sup>Released on **2025-07-17**</sup>
|
33
|
+
|
34
|
+
#### ✨ Features
|
35
|
+
|
36
|
+
- **misc**: Refactor desktop oauth and use JWTs token to support remote chat.
|
37
|
+
|
38
|
+
<br/>
|
39
|
+
|
40
|
+
<details>
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
42
|
+
|
43
|
+
#### What's improved
|
44
|
+
|
45
|
+
- **misc**: Refactor desktop oauth and use JWTs token to support remote chat, closes [#8446](https://github.com/lobehub/lobe-chat/issues/8446) ([054ca5f](https://github.com/lobehub/lobe-chat/commit/054ca5f))
|
46
|
+
|
47
|
+
</details>
|
48
|
+
|
49
|
+
<div align="right">
|
50
|
+
|
51
|
+
[](#readme-top)
|
52
|
+
|
53
|
+
</div>
|
54
|
+
|
5
55
|
### [Version 1.99.6](https://github.com/lobehub/lobe-chat/compare/v1.99.5...v1.99.6)
|
6
56
|
|
7
57
|
<sup>Released on **2025-07-16**</sup>
|
@@ -25,7 +25,7 @@
|
|
25
25
|
"lint": "eslint --cache ",
|
26
26
|
"pg-server": "bun run scripts/pglite-server.ts",
|
27
27
|
"start": "electron-vite preview",
|
28
|
-
"test": "
|
28
|
+
"test": "vitest --run",
|
29
29
|
"typecheck": "tsgo --noEmit -p tsconfig.json"
|
30
30
|
},
|
31
31
|
"dependencies": {
|
@@ -58,6 +58,8 @@
|
|
58
58
|
"electron-vite": "^3.0.0",
|
59
59
|
"execa": "^9.5.2",
|
60
60
|
"fix-path": "^4.0.0",
|
61
|
+
"http-proxy-agent": "^7.0.2",
|
62
|
+
"https-proxy-agent": "^7.0.6",
|
61
63
|
"just-diff": "^6.0.2",
|
62
64
|
"lodash": "^4.17.21",
|
63
65
|
"lodash-es": "^4.17.21",
|
@@ -67,7 +69,8 @@
|
|
67
69
|
"tsx": "^4.19.3",
|
68
70
|
"typescript": "^5.7.3",
|
69
71
|
"undici": "^7.9.0",
|
70
|
-
"vite": "^6.
|
72
|
+
"vite": "^6.3.5",
|
73
|
+
"vitest": "^3.2.4"
|
71
74
|
},
|
72
75
|
"pnpm": {
|
73
76
|
"onlyBuiltDependencies": [
|