@lobehub/lobehub 2.0.0-next.141 → 2.0.0-next.143

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +2 -0
  3. package/apps/desktop/package.json +1 -0
  4. package/apps/desktop/src/main/controllers/__tests__/McpInstallCtr.test.ts +286 -0
  5. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +347 -0
  6. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +645 -0
  7. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +372 -0
  8. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +276 -0
  9. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +171 -0
  10. package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +573 -0
  11. package/apps/desktop/src/main/core/browser/__tests__/BrowserManager.test.ts +415 -0
  12. package/apps/desktop/src/main/core/infrastructure/__tests__/I18nManager.test.ts +353 -0
  13. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +156 -0
  14. package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +348 -0
  15. package/apps/desktop/src/main/core/infrastructure/__tests__/StaticFileServerManager.test.ts +481 -0
  16. package/apps/desktop/src/main/core/infrastructure/__tests__/StoreManager.test.ts +164 -0
  17. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +513 -0
  18. package/apps/desktop/src/main/core/ui/__tests__/MenuManager.test.ts +320 -0
  19. package/apps/desktop/src/main/core/ui/__tests__/Tray.test.ts +518 -0
  20. package/apps/desktop/src/main/core/ui/__tests__/TrayManager.test.ts +360 -0
  21. package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.test.ts +49 -0
  22. package/apps/desktop/src/main/menus/impls/linux.test.ts +552 -0
  23. package/apps/desktop/src/main/menus/impls/macOS.test.ts +464 -0
  24. package/apps/desktop/src/main/menus/impls/windows.test.ts +429 -0
  25. package/apps/desktop/src/main/modules/fileSearch/__tests__/macOS.integration.test.ts +2 -2
  26. package/apps/desktop/src/main/services/__tests__/fileSearchSrv.test.ts +402 -0
  27. package/apps/desktop/src/main/utils/__tests__/file-system.test.ts +91 -0
  28. package/apps/desktop/src/main/utils/__tests__/logger.test.ts +229 -0
  29. package/apps/desktop/src/preload/electronApi.test.ts +142 -0
  30. package/apps/desktop/src/preload/invoke.test.ts +145 -0
  31. package/apps/desktop/src/preload/routeInterceptor.test.ts +374 -0
  32. package/apps/desktop/src/preload/streamer.test.ts +365 -0
  33. package/apps/desktop/vitest.config.mts +1 -0
  34. package/changelog/v1.json +18 -0
  35. package/docs/self-hosting/environment-variables/model-provider.mdx +31 -0
  36. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +30 -0
  37. package/locales/ar/marketAuth.json +13 -0
  38. package/locales/bg-BG/marketAuth.json +13 -0
  39. package/locales/de-DE/marketAuth.json +13 -0
  40. package/locales/en-US/marketAuth.json +13 -0
  41. package/locales/es-ES/marketAuth.json +13 -0
  42. package/locales/fa-IR/marketAuth.json +13 -0
  43. package/locales/fr-FR/marketAuth.json +13 -0
  44. package/locales/it-IT/marketAuth.json +13 -0
  45. package/locales/ja-JP/marketAuth.json +13 -0
  46. package/locales/ko-KR/marketAuth.json +13 -0
  47. package/locales/nl-NL/marketAuth.json +13 -0
  48. package/locales/pl-PL/marketAuth.json +13 -0
  49. package/locales/pt-BR/marketAuth.json +13 -0
  50. package/locales/ru-RU/marketAuth.json +13 -0
  51. package/locales/tr-TR/marketAuth.json +13 -0
  52. package/locales/vi-VN/marketAuth.json +13 -0
  53. package/locales/zh-CN/marketAuth.json +13 -0
  54. package/locales/zh-TW/marketAuth.json +13 -0
  55. package/package.json +1 -1
  56. package/packages/database/src/models/user.ts +2 -0
  57. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +6 -3
  58. package/packages/types/src/discover/mcp.ts +2 -1
  59. package/packages/types/src/tool/plugin.ts +2 -1
  60. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +0 -2
  61. package/src/app/[variants]/(main)/discover/(detail)/mcp/features/Sidebar/ActionButton/index.tsx +33 -7
  62. package/src/config/modelProviders/vertexai.ts +1 -1
  63. package/src/envs/llm.ts +4 -0
  64. package/src/features/PluginStore/McpList/List/Action.tsx +20 -1
  65. package/src/layout/AuthProvider/MarketAuth/MarketAuthConfirmModal.tsx +158 -0
  66. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +130 -14
  67. package/src/libs/mcp/types.ts +8 -0
  68. package/src/locales/default/marketAuth.ts +13 -0
  69. package/src/server/modules/ModelRuntime/index.ts +4 -4
  70. package/src/server/routers/lambda/market/index.ts +85 -2
  71. package/src/server/services/discover/index.ts +45 -4
  72. package/src/services/discover.ts +1 -1
  73. package/src/services/mcp.ts +18 -3
  74. package/src/store/tool/slices/mcpStore/action.test.ts +141 -0
  75. package/src/store/tool/slices/mcpStore/action.ts +153 -11
@@ -0,0 +1,415 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import type { App as AppCore } from '../../App';
4
+ import { BrowserManager } from '../BrowserManager';
5
+
6
+ // Use vi.hoisted to define mocks before hoisting
7
+ const { MockBrowser, mockAppBrowsers, mockWindowTemplates } = vi.hoisted(() => {
8
+ const createMockBrowserWindow = () => ({
9
+ isMaximized: vi.fn().mockReturnValue(false),
10
+ maximize: vi.fn(),
11
+ minimize: vi.fn(),
12
+ on: vi.fn(),
13
+ unmaximize: vi.fn(),
14
+ webContents: { id: Math.random() },
15
+ });
16
+
17
+ const MockBrowser = vi.fn().mockImplementation((options: any) => {
18
+ const browserWindow = createMockBrowserWindow();
19
+ return {
20
+ broadcast: vi.fn(),
21
+ browserWindow,
22
+ close: vi.fn(),
23
+ handleAppThemeChange: vi.fn(),
24
+ hide: vi.fn(),
25
+ identifier: options.identifier,
26
+ loadUrl: vi.fn().mockResolvedValue(undefined),
27
+ options,
28
+ show: vi.fn(),
29
+ webContents: browserWindow.webContents,
30
+ };
31
+ });
32
+
33
+ return {
34
+ MockBrowser,
35
+ mockAppBrowsers: {
36
+ chat: {
37
+ identifier: 'chat',
38
+ keepAlive: true,
39
+ path: '/chat',
40
+ },
41
+ settings: {
42
+ identifier: 'settings',
43
+ keepAlive: false,
44
+ path: '/settings',
45
+ },
46
+ },
47
+ mockWindowTemplates: {
48
+ popup: {
49
+ baseIdentifier: 'popup',
50
+ height: 400,
51
+ width: 600,
52
+ },
53
+ },
54
+ };
55
+ });
56
+
57
+ // Mock Browser class
58
+ vi.mock('../Browser', () => ({
59
+ default: MockBrowser,
60
+ }));
61
+
62
+ // Mock appBrowsers config
63
+ vi.mock('../../../appBrowsers', () => ({
64
+ appBrowsers: mockAppBrowsers,
65
+ windowTemplates: mockWindowTemplates,
66
+ }));
67
+
68
+ // Mock logger
69
+ vi.mock('@/utils/logger', () => ({
70
+ createLogger: () => ({
71
+ debug: vi.fn(),
72
+ error: vi.fn(),
73
+ info: vi.fn(),
74
+ warn: vi.fn(),
75
+ }),
76
+ }));
77
+
78
+ describe('BrowserManager', () => {
79
+ let manager: BrowserManager;
80
+ let mockApp: AppCore;
81
+
82
+ beforeEach(() => {
83
+ vi.clearAllMocks();
84
+
85
+ // Reset MockBrowser
86
+ MockBrowser.mockClear();
87
+
88
+ // Create mock App
89
+ mockApp = {} as unknown as AppCore;
90
+
91
+ manager = new BrowserManager(mockApp);
92
+ });
93
+
94
+ describe('constructor', () => {
95
+ it('should initialize with empty browsers Map', () => {
96
+ expect(manager.browsers.size).toBe(0);
97
+ });
98
+
99
+ it('should store app reference', () => {
100
+ expect(manager.app).toBe(mockApp);
101
+ });
102
+ });
103
+
104
+ describe('getMainWindow', () => {
105
+ it('should return chat window', () => {
106
+ const mainWindow = manager.getMainWindow();
107
+
108
+ expect(mainWindow.identifier).toBe('chat');
109
+ });
110
+ });
111
+
112
+ describe('showMainWindow', () => {
113
+ it('should show the main window', () => {
114
+ manager.showMainWindow();
115
+
116
+ const chatBrowser = manager.browsers.get('chat');
117
+ expect(chatBrowser?.show).toHaveBeenCalled();
118
+ });
119
+ });
120
+
121
+ describe('retrieveByIdentifier', () => {
122
+ it('should return existing browser', () => {
123
+ // First call creates the browser
124
+ const browser1 = manager.retrieveByIdentifier('chat');
125
+ // Second call should return same instance
126
+ const browser2 = manager.retrieveByIdentifier('chat');
127
+
128
+ expect(browser1).toBe(browser2);
129
+ expect(MockBrowser).toHaveBeenCalledTimes(1);
130
+ });
131
+
132
+ it('should create static browser when not exists', () => {
133
+ const browser = manager.retrieveByIdentifier('chat');
134
+
135
+ expect(MockBrowser).toHaveBeenCalledWith(mockAppBrowsers.chat, mockApp);
136
+ expect(browser.identifier).toBe('chat');
137
+ });
138
+
139
+ it('should throw error for non-static browser that does not exist', () => {
140
+ expect(() => manager.retrieveByIdentifier('non-existent')).toThrow(
141
+ 'Browser non-existent not found and is not a static browser',
142
+ );
143
+ });
144
+ });
145
+
146
+ describe('createMultiInstanceWindow', () => {
147
+ it('should create window from template', () => {
148
+ const result = manager.createMultiInstanceWindow('popup' as any, '/popup/path');
149
+
150
+ expect(result.browser).toBeDefined();
151
+ expect(result.identifier).toMatch(/^popup_/);
152
+ expect(MockBrowser).toHaveBeenCalledWith(
153
+ expect.objectContaining({
154
+ baseIdentifier: 'popup',
155
+ height: 400,
156
+ path: '/popup/path',
157
+ width: 600,
158
+ }),
159
+ mockApp,
160
+ );
161
+ });
162
+
163
+ it('should use provided uniqueId', () => {
164
+ const result = manager.createMultiInstanceWindow(
165
+ 'popup' as any,
166
+ '/popup/path',
167
+ 'my-custom-id',
168
+ );
169
+
170
+ expect(result.identifier).toBe('my-custom-id');
171
+ });
172
+
173
+ it('should throw error for non-existent template', () => {
174
+ expect(() => manager.createMultiInstanceWindow('nonexistent' as any, '/path')).toThrow(
175
+ 'Window template nonexistent not found',
176
+ );
177
+ });
178
+
179
+ it('should generate unique identifier when not provided', () => {
180
+ const result1 = manager.createMultiInstanceWindow('popup' as any, '/path1');
181
+ const result2 = manager.createMultiInstanceWindow('popup' as any, '/path2');
182
+
183
+ expect(result1.identifier).not.toBe(result2.identifier);
184
+ });
185
+ });
186
+
187
+ describe('getWindowsByTemplate', () => {
188
+ it('should return windows matching template prefix', () => {
189
+ manager.createMultiInstanceWindow('popup' as any, '/path1', 'popup_1');
190
+ manager.createMultiInstanceWindow('popup' as any, '/path2', 'popup_2');
191
+ manager.retrieveByIdentifier('chat'); // This should not be included
192
+
193
+ const popupWindows = manager.getWindowsByTemplate('popup');
194
+
195
+ expect(popupWindows).toContain('popup_1');
196
+ expect(popupWindows).toContain('popup_2');
197
+ expect(popupWindows).not.toContain('chat');
198
+ });
199
+
200
+ it('should return empty array when no matching windows', () => {
201
+ const windows = manager.getWindowsByTemplate('nonexistent');
202
+
203
+ expect(windows).toEqual([]);
204
+ });
205
+ });
206
+
207
+ describe('closeWindowsByTemplate', () => {
208
+ it('should close all windows matching template', () => {
209
+ const { browser: browser1 } = manager.createMultiInstanceWindow(
210
+ 'popup' as any,
211
+ '/path1',
212
+ 'popup_1',
213
+ );
214
+ const { browser: browser2 } = manager.createMultiInstanceWindow(
215
+ 'popup' as any,
216
+ '/path2',
217
+ 'popup_2',
218
+ );
219
+
220
+ manager.closeWindowsByTemplate('popup');
221
+
222
+ expect(browser1.close).toHaveBeenCalled();
223
+ expect(browser2.close).toHaveBeenCalled();
224
+ });
225
+ });
226
+
227
+ describe('initializeBrowsers', () => {
228
+ it('should initialize keepAlive browsers', () => {
229
+ manager.initializeBrowsers();
230
+
231
+ // chat has keepAlive: true, settings has keepAlive: false
232
+ expect(manager.browsers.has('chat')).toBe(true);
233
+ expect(manager.browsers.has('settings')).toBe(false);
234
+ });
235
+ });
236
+
237
+ describe('broadcastToAllWindows', () => {
238
+ it('should broadcast to all browsers', () => {
239
+ manager.retrieveByIdentifier('chat');
240
+ manager.retrieveByIdentifier('settings');
241
+
242
+ manager.broadcastToAllWindows('updateAvailable' as any, { version: '1.0.0' } as any);
243
+
244
+ const chatBrowser = manager.browsers.get('chat');
245
+ const settingsBrowser = manager.browsers.get('settings');
246
+
247
+ expect(chatBrowser?.broadcast).toHaveBeenCalledWith('updateAvailable', { version: '1.0.0' });
248
+ expect(settingsBrowser?.broadcast).toHaveBeenCalledWith('updateAvailable', {
249
+ version: '1.0.0',
250
+ });
251
+ });
252
+ });
253
+
254
+ describe('broadcastToWindow', () => {
255
+ it('should broadcast to specific window', () => {
256
+ manager.retrieveByIdentifier('chat');
257
+ manager.retrieveByIdentifier('settings');
258
+
259
+ const chatBrowser = manager.browsers.get('chat');
260
+ const settingsBrowser = manager.browsers.get('settings');
261
+
262
+ manager.broadcastToWindow('chat', 'updateAvailable' as any, { version: '1.0.0' } as any);
263
+
264
+ expect(chatBrowser?.broadcast).toHaveBeenCalledWith('updateAvailable', { version: '1.0.0' });
265
+ expect(settingsBrowser?.broadcast).not.toHaveBeenCalled();
266
+ });
267
+
268
+ it('should safely handle non-existent window', () => {
269
+ expect(() =>
270
+ manager.broadcastToWindow('nonexistent', 'updateAvailable' as any, {} as any),
271
+ ).not.toThrow();
272
+ });
273
+ });
274
+
275
+ describe('redirectToPage', () => {
276
+ it('should load URL and show window', async () => {
277
+ const browser = await manager.redirectToPage('chat', 'agent');
278
+
279
+ expect(browser.hide).toHaveBeenCalled();
280
+ expect(browser.loadUrl).toHaveBeenCalledWith('/chat/agent');
281
+ expect(browser.show).toHaveBeenCalled();
282
+ });
283
+
284
+ it('should handle subPath correctly', async () => {
285
+ const browser = await manager.redirectToPage('chat', 'settings/profile');
286
+
287
+ expect(browser.loadUrl).toHaveBeenCalledWith('/chat/settings/profile');
288
+ });
289
+
290
+ it('should handle search parameters', async () => {
291
+ const browser = await manager.redirectToPage('chat', 'agent', 'id=123');
292
+
293
+ expect(browser.loadUrl).toHaveBeenCalledWith('/chat/agent?id=123');
294
+ });
295
+
296
+ it('should handle search parameters starting with ?', async () => {
297
+ const browser = await manager.redirectToPage('chat', undefined, '?id=123');
298
+
299
+ expect(browser.loadUrl).toHaveBeenCalledWith('/chat?id=123');
300
+ });
301
+
302
+ it('should handle no subPath', async () => {
303
+ const browser = await manager.redirectToPage('chat');
304
+
305
+ expect(browser.loadUrl).toHaveBeenCalledWith('/chat');
306
+ });
307
+
308
+ it('should throw error on failure', async () => {
309
+ const mockError = new Error('Load failed');
310
+ MockBrowser.mockImplementationOnce((options: any) => ({
311
+ broadcast: vi.fn(),
312
+ browserWindow: { on: vi.fn(), webContents: { id: 1 } },
313
+ close: vi.fn(),
314
+ handleAppThemeChange: vi.fn(),
315
+ hide: vi.fn(),
316
+ identifier: options.identifier,
317
+ loadUrl: vi.fn().mockRejectedValue(mockError),
318
+ options: { path: '/chat' },
319
+ show: vi.fn(),
320
+ webContents: { id: 1 },
321
+ }));
322
+
323
+ // Clear the browser cache
324
+ manager.browsers.clear();
325
+
326
+ await expect(manager.redirectToPage('chat', 'agent')).rejects.toThrow('Load failed');
327
+ });
328
+ });
329
+
330
+ describe('window operations', () => {
331
+ describe('closeWindow', () => {
332
+ it('should close specified window', () => {
333
+ manager.retrieveByIdentifier('chat');
334
+
335
+ manager.closeWindow('chat');
336
+
337
+ const browser = manager.browsers.get('chat');
338
+ expect(browser?.close).toHaveBeenCalled();
339
+ });
340
+
341
+ it('should safely handle non-existent window', () => {
342
+ expect(() => manager.closeWindow('nonexistent')).not.toThrow();
343
+ });
344
+ });
345
+
346
+ describe('minimizeWindow', () => {
347
+ it('should minimize specified window', () => {
348
+ manager.retrieveByIdentifier('chat');
349
+
350
+ manager.minimizeWindow('chat');
351
+
352
+ const browser = manager.browsers.get('chat');
353
+ expect(browser?.browserWindow.minimize).toHaveBeenCalled();
354
+ });
355
+ });
356
+
357
+ describe('maximizeWindow', () => {
358
+ it('should maximize when not maximized', () => {
359
+ manager.retrieveByIdentifier('chat');
360
+ const browser = manager.browsers.get('chat');
361
+ browser!.browserWindow.isMaximized = vi.fn().mockReturnValue(false);
362
+
363
+ manager.maximizeWindow('chat');
364
+
365
+ expect(browser?.browserWindow.maximize).toHaveBeenCalled();
366
+ expect(browser?.browserWindow.unmaximize).not.toHaveBeenCalled();
367
+ });
368
+
369
+ it('should unmaximize when already maximized', () => {
370
+ manager.retrieveByIdentifier('chat');
371
+ const browser = manager.browsers.get('chat');
372
+ browser!.browserWindow.isMaximized = vi.fn().mockReturnValue(true);
373
+
374
+ manager.maximizeWindow('chat');
375
+
376
+ expect(browser?.browserWindow.unmaximize).toHaveBeenCalled();
377
+ expect(browser?.browserWindow.maximize).not.toHaveBeenCalled();
378
+ });
379
+ });
380
+ });
381
+
382
+ describe('getIdentifierByWebContents', () => {
383
+ it('should return identifier for known webContents', () => {
384
+ const browser = manager.retrieveByIdentifier('chat');
385
+ const webContents = browser.browserWindow.webContents;
386
+
387
+ const identifier = manager.getIdentifierByWebContents(webContents as any);
388
+
389
+ expect(identifier).toBe('chat');
390
+ });
391
+
392
+ it('should return null for unknown webContents', () => {
393
+ const unknownWebContents = { id: 999 };
394
+
395
+ const identifier = manager.getIdentifierByWebContents(unknownWebContents as any);
396
+
397
+ expect(identifier).toBeNull();
398
+ });
399
+ });
400
+
401
+ describe('handleAppThemeChange', () => {
402
+ it('should notify all browsers of theme change', () => {
403
+ manager.retrieveByIdentifier('chat');
404
+ manager.retrieveByIdentifier('settings');
405
+
406
+ manager.handleAppThemeChange();
407
+
408
+ const chatBrowser = manager.browsers.get('chat');
409
+ const settingsBrowser = manager.browsers.get('settings');
410
+
411
+ expect(chatBrowser?.handleAppThemeChange).toHaveBeenCalled();
412
+ expect(settingsBrowser?.handleAppThemeChange).toHaveBeenCalled();
413
+ });
414
+ });
415
+ });