@lobehub/lobehub 2.0.0-next.140 → 2.0.0-next.142

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 (30) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/Dockerfile +2 -0
  3. package/apps/desktop/src/main/controllers/__tests__/McpInstallCtr.test.ts +286 -0
  4. package/apps/desktop/src/main/controllers/__tests__/NotificationCtr.test.ts +347 -0
  5. package/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +645 -0
  6. package/apps/desktop/src/main/controllers/__tests__/RemoteServerSyncCtr.test.ts +372 -0
  7. package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +276 -0
  8. package/apps/desktop/src/main/controllers/__tests__/UploadFileCtr.test.ts +171 -0
  9. package/apps/desktop/src/main/core/browser/__tests__/Browser.test.ts +573 -0
  10. package/apps/desktop/src/main/core/browser/__tests__/BrowserManager.test.ts +415 -0
  11. package/apps/desktop/src/main/core/infrastructure/__tests__/I18nManager.test.ts +353 -0
  12. package/apps/desktop/src/main/core/infrastructure/__tests__/IoCContainer.test.ts +156 -0
  13. package/apps/desktop/src/main/core/infrastructure/__tests__/ProtocolManager.test.ts +348 -0
  14. package/apps/desktop/src/main/core/infrastructure/__tests__/StaticFileServerManager.test.ts +481 -0
  15. package/apps/desktop/src/main/core/infrastructure/__tests__/StoreManager.test.ts +164 -0
  16. package/apps/desktop/src/main/core/infrastructure/__tests__/UpdaterManager.test.ts +513 -0
  17. package/changelog/v1.json +18 -0
  18. package/docs/development/database-schema.dbml +1 -2
  19. package/docs/self-hosting/environment-variables/model-provider.mdx +31 -0
  20. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +30 -0
  21. package/package.json +1 -1
  22. package/packages/database/migrations/0055_rename_phone_number_to_phone.sql +4 -0
  23. package/packages/database/migrations/meta/0055_snapshot.json +8396 -0
  24. package/packages/database/migrations/meta/_journal.json +7 -0
  25. package/packages/database/src/core/migrations.json +11 -0
  26. package/packages/database/src/schemas/user.ts +1 -2
  27. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +6 -3
  28. package/src/config/modelProviders/vertexai.ts +1 -1
  29. package/src/envs/llm.ts +4 -0
  30. package/src/server/modules/ModelRuntime/index.ts +4 -4
@@ -0,0 +1,513 @@
1
+ import { autoUpdater } from 'electron-updater';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import type { App as AppCore } from '../../App';
5
+ import { UpdaterManager } from '../UpdaterManager';
6
+
7
+ // Use vi.hoisted to ensure mocks work with require()
8
+ const { mockGetAllWindows, mockReleaseSingleInstanceLock } = vi.hoisted(() => ({
9
+ mockGetAllWindows: vi.fn().mockReturnValue([]),
10
+ mockReleaseSingleInstanceLock: vi.fn(),
11
+ }));
12
+
13
+ // Mock electron-log
14
+ vi.mock('electron-log', () => ({
15
+ default: {
16
+ transports: {
17
+ file: {
18
+ level: 'info',
19
+ getFile: vi.fn().mockReturnValue({ path: '/mock/log/path' }),
20
+ },
21
+ },
22
+ },
23
+ }));
24
+
25
+ // Mock electron-updater
26
+ vi.mock('electron-updater', () => ({
27
+ autoUpdater: {
28
+ allowDowngrade: false,
29
+ allowPrerelease: false,
30
+ autoDownload: false,
31
+ autoInstallOnAppQuit: false,
32
+ channel: 'stable',
33
+ checkForUpdates: vi.fn(),
34
+ downloadUpdate: vi.fn(),
35
+ forceDevUpdateConfig: false,
36
+ logger: null as any,
37
+ on: vi.fn(),
38
+ quitAndInstall: vi.fn(),
39
+ },
40
+ }));
41
+
42
+ // Mock electron - uses hoisted functions for require() compatibility
43
+ vi.mock('electron', () => ({
44
+ BrowserWindow: {
45
+ getAllWindows: mockGetAllWindows,
46
+ },
47
+ app: {
48
+ releaseSingleInstanceLock: mockReleaseSingleInstanceLock,
49
+ },
50
+ }));
51
+
52
+ // Mock logger
53
+ vi.mock('@/utils/logger', () => ({
54
+ createLogger: () => ({
55
+ debug: vi.fn(),
56
+ error: vi.fn(),
57
+ info: vi.fn(),
58
+ warn: vi.fn(),
59
+ }),
60
+ }));
61
+
62
+ // Mock updater configs
63
+ vi.mock('@/modules/updater/configs', () => ({
64
+ UPDATE_CHANNEL: 'stable',
65
+ updaterConfig: {
66
+ app: {
67
+ autoCheckUpdate: false,
68
+ autoDownloadUpdate: true,
69
+ checkUpdateInterval: 60 * 60 * 1000,
70
+ },
71
+ enableAppUpdate: true,
72
+ enableRenderHotUpdate: true,
73
+ },
74
+ }));
75
+
76
+ // Mock isDev
77
+ vi.mock('@/const/env', () => ({
78
+ isDev: false,
79
+ }));
80
+
81
+ describe('UpdaterManager', () => {
82
+ let updaterManager: UpdaterManager;
83
+ let mockApp: AppCore;
84
+ let mockBroadcast: ReturnType<typeof vi.fn>;
85
+ let registeredEvents: Map<string, (...args: any[]) => void>;
86
+
87
+ beforeEach(() => {
88
+ vi.clearAllMocks();
89
+ vi.useFakeTimers();
90
+
91
+ // Reset autoUpdater state
92
+ (autoUpdater as any).autoDownload = false;
93
+ (autoUpdater as any).autoInstallOnAppQuit = false;
94
+ (autoUpdater as any).channel = 'stable';
95
+ (autoUpdater as any).allowPrerelease = false;
96
+ (autoUpdater as any).allowDowngrade = false;
97
+ (autoUpdater as any).forceDevUpdateConfig = false;
98
+
99
+ // Capture registered events
100
+ registeredEvents = new Map();
101
+ vi.mocked(autoUpdater.on).mockImplementation((event: string, handler: any) => {
102
+ registeredEvents.set(event, handler);
103
+ return autoUpdater;
104
+ });
105
+
106
+ // Mock broadcast function
107
+ mockBroadcast = vi.fn();
108
+
109
+ // Create mock App
110
+ mockApp = {
111
+ browserManager: {
112
+ getMainWindow: vi.fn().mockReturnValue({
113
+ broadcast: mockBroadcast,
114
+ }),
115
+ },
116
+ isQuiting: false,
117
+ } as unknown as AppCore;
118
+
119
+ updaterManager = new UpdaterManager(mockApp);
120
+ });
121
+
122
+ afterEach(() => {
123
+ vi.useRealTimers();
124
+ });
125
+
126
+ describe('constructor', () => {
127
+ it('should set up electron-log for autoUpdater', () => {
128
+ expect(autoUpdater.logger).not.toBeNull();
129
+ });
130
+ });
131
+
132
+ describe('initialize', () => {
133
+ it('should configure autoUpdater properties', async () => {
134
+ await updaterManager.initialize();
135
+
136
+ expect(autoUpdater.autoDownload).toBe(false);
137
+ expect(autoUpdater.autoInstallOnAppQuit).toBe(false);
138
+ expect(autoUpdater.channel).toBe('stable');
139
+ expect(autoUpdater.allowPrerelease).toBe(false);
140
+ expect(autoUpdater.allowDowngrade).toBe(false);
141
+ });
142
+
143
+ it('should register all event listeners', async () => {
144
+ await updaterManager.initialize();
145
+
146
+ expect(autoUpdater.on).toHaveBeenCalledWith('checking-for-update', expect.any(Function));
147
+ expect(autoUpdater.on).toHaveBeenCalledWith('update-available', expect.any(Function));
148
+ expect(autoUpdater.on).toHaveBeenCalledWith('update-not-available', expect.any(Function));
149
+ expect(autoUpdater.on).toHaveBeenCalledWith('error', expect.any(Function));
150
+ expect(autoUpdater.on).toHaveBeenCalledWith('download-progress', expect.any(Function));
151
+ expect(autoUpdater.on).toHaveBeenCalledWith('update-downloaded', expect.any(Function));
152
+ });
153
+ });
154
+
155
+ describe('checkForUpdates', () => {
156
+ beforeEach(async () => {
157
+ await updaterManager.initialize();
158
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
159
+ });
160
+
161
+ it('should call autoUpdater.checkForUpdates', async () => {
162
+ await updaterManager.checkForUpdates();
163
+
164
+ expect(autoUpdater.checkForUpdates).toHaveBeenCalled();
165
+ });
166
+
167
+ it('should broadcast manualUpdateCheckStart when manual check', async () => {
168
+ await updaterManager.checkForUpdates({ manual: true });
169
+
170
+ expect(mockBroadcast).toHaveBeenCalledWith('manualUpdateCheckStart');
171
+ });
172
+
173
+ it('should not broadcast when auto check', async () => {
174
+ await updaterManager.checkForUpdates({ manual: false });
175
+
176
+ expect(mockBroadcast).not.toHaveBeenCalledWith('manualUpdateCheckStart');
177
+ });
178
+
179
+ it('should ignore duplicate check requests while checking', async () => {
180
+ // Start first check but don't resolve
181
+ vi.mocked(autoUpdater.checkForUpdates).mockImplementation(
182
+ () => new Promise((resolve) => setTimeout(resolve, 1000)) as any,
183
+ );
184
+
185
+ const firstCheck = updaterManager.checkForUpdates();
186
+ const secondCheck = updaterManager.checkForUpdates();
187
+
188
+ await vi.advanceTimersByTimeAsync(1000);
189
+ await Promise.all([firstCheck, secondCheck]);
190
+
191
+ expect(autoUpdater.checkForUpdates).toHaveBeenCalledTimes(1);
192
+ });
193
+
194
+ it('should broadcast updateError when check fails during manual check', async () => {
195
+ const error = new Error('Network error');
196
+ vi.mocked(autoUpdater.checkForUpdates).mockRejectedValue(error);
197
+
198
+ await updaterManager.checkForUpdates({ manual: true });
199
+
200
+ expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Network error');
201
+ });
202
+ });
203
+
204
+ describe('downloadUpdate', () => {
205
+ beforeEach(async () => {
206
+ await updaterManager.initialize();
207
+ vi.mocked(autoUpdater.downloadUpdate).mockResolvedValue([] as any);
208
+
209
+ // Simulate update available
210
+ const updateAvailableHandler = registeredEvents.get('update-available');
211
+ updateAvailableHandler?.({ version: '2.0.0' });
212
+ });
213
+
214
+ it('should call autoUpdater.downloadUpdate', async () => {
215
+ await updaterManager.downloadUpdate();
216
+
217
+ expect(autoUpdater.downloadUpdate).toHaveBeenCalled();
218
+ });
219
+
220
+ it('should ignore download request when no update available', async () => {
221
+ // Create fresh manager without update available
222
+ const freshManager = new UpdaterManager(mockApp);
223
+ await freshManager.initialize();
224
+
225
+ await freshManager.downloadUpdate();
226
+
227
+ // Reset call count since downloadUpdate might have been called in beforeEach
228
+ vi.mocked(autoUpdater.downloadUpdate).mockClear();
229
+ await freshManager.downloadUpdate();
230
+
231
+ // downloadUpdate should not be called on autoUpdater for fresh manager
232
+ expect(autoUpdater.downloadUpdate).not.toHaveBeenCalled();
233
+ });
234
+
235
+ it('should ignore duplicate download requests while downloading', async () => {
236
+ vi.mocked(autoUpdater.downloadUpdate).mockImplementation(
237
+ () => new Promise((resolve) => setTimeout(resolve, 1000)) as any,
238
+ );
239
+
240
+ const firstDownload = updaterManager.downloadUpdate();
241
+ const secondDownload = updaterManager.downloadUpdate();
242
+
243
+ await vi.advanceTimersByTimeAsync(1000);
244
+ await Promise.all([firstDownload, secondDownload]);
245
+
246
+ expect(autoUpdater.downloadUpdate).toHaveBeenCalledTimes(1);
247
+ });
248
+
249
+ it('should broadcast updateDownloadStart when isManualCheck is true', async () => {
250
+ // Create a fresh manager to avoid state pollution from beforeEach
251
+ const freshManager = new UpdaterManager(mockApp);
252
+
253
+ // Setup fresh event capture
254
+ const freshEvents = new Map<string, (...args: any[]) => void>();
255
+ vi.mocked(autoUpdater.on).mockImplementation((event: string, handler: any) => {
256
+ freshEvents.set(event, handler);
257
+ return autoUpdater;
258
+ });
259
+ await freshManager.initialize();
260
+
261
+ // Trigger a manual check to set isManualCheck = true
262
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
263
+ await freshManager.checkForUpdates({ manual: true });
264
+
265
+ // Manually set updateAvailable without triggering auto-download
266
+ // Access private property to set state
267
+ (freshManager as any).updateAvailable = true;
268
+
269
+ // Clear previous broadcast calls
270
+ mockBroadcast.mockClear();
271
+
272
+ // Now download should broadcast updateDownloadStart because isManualCheck is true
273
+ vi.mocked(autoUpdater.downloadUpdate).mockResolvedValue([] as any);
274
+ await freshManager.downloadUpdate();
275
+
276
+ expect(mockBroadcast).toHaveBeenCalledWith('updateDownloadStart');
277
+ });
278
+
279
+ it('should broadcast updateError when download fails with isManualCheck true', async () => {
280
+ // Create a fresh manager to avoid state pollution from beforeEach
281
+ const freshManager = new UpdaterManager(mockApp);
282
+
283
+ // Setup fresh event capture
284
+ const freshEvents = new Map<string, (...args: any[]) => void>();
285
+ vi.mocked(autoUpdater.on).mockImplementation((event: string, handler: any) => {
286
+ freshEvents.set(event, handler);
287
+ return autoUpdater;
288
+ });
289
+ await freshManager.initialize();
290
+
291
+ // Trigger a manual check to set isManualCheck = true
292
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
293
+ await freshManager.checkForUpdates({ manual: true });
294
+
295
+ // Manually set updateAvailable without triggering auto-download
296
+ (freshManager as any).updateAvailable = true;
297
+
298
+ // Clear previous broadcast calls
299
+ mockBroadcast.mockClear();
300
+
301
+ // Setup error
302
+ const error = new Error('Download failed');
303
+ vi.mocked(autoUpdater.downloadUpdate).mockRejectedValue(error);
304
+
305
+ await freshManager.downloadUpdate();
306
+
307
+ expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Download failed');
308
+ });
309
+ });
310
+
311
+ describe('installNow', () => {
312
+ // Note: installNow uses require('electron') which is difficult to mock in vitest.
313
+ // These tests are skipped because vi.mock doesn't work with dynamic require().
314
+ // The functionality should be tested in integration tests or E2E tests.
315
+
316
+ it.skip('should set app.isQuiting to true', () => {
317
+ updaterManager.installNow();
318
+ expect(mockApp.isQuiting).toBe(true);
319
+ });
320
+
321
+ it.skip('should close all windows', () => {
322
+ const mockWindow1 = { close: vi.fn(), isDestroyed: vi.fn().mockReturnValue(false) };
323
+ const mockWindow2 = { close: vi.fn(), isDestroyed: vi.fn().mockReturnValue(false) };
324
+ mockGetAllWindows.mockReturnValue([mockWindow1, mockWindow2]);
325
+ updaterManager.installNow();
326
+ expect(mockWindow1.close).toHaveBeenCalled();
327
+ expect(mockWindow2.close).toHaveBeenCalled();
328
+ });
329
+
330
+ it.skip('should not close destroyed windows', () => {
331
+ const mockWindow = { close: vi.fn(), isDestroyed: vi.fn().mockReturnValue(true) };
332
+ mockGetAllWindows.mockReturnValue([mockWindow]);
333
+ updaterManager.installNow();
334
+ expect(mockWindow.close).not.toHaveBeenCalled();
335
+ });
336
+
337
+ it.skip('should release single instance lock', () => {
338
+ updaterManager.installNow();
339
+ expect(mockReleaseSingleInstanceLock).toHaveBeenCalled();
340
+ });
341
+
342
+ it.skip('should call quitAndInstall with correct parameters after delay', async () => {
343
+ updaterManager.installNow();
344
+ expect(autoUpdater.quitAndInstall).not.toHaveBeenCalled();
345
+ await vi.advanceTimersByTimeAsync(100);
346
+ expect(autoUpdater.quitAndInstall).toHaveBeenCalledWith(true, true);
347
+ });
348
+ });
349
+
350
+ describe('installLater', () => {
351
+ it('should set autoInstallOnAppQuit to true', () => {
352
+ updaterManager.installLater();
353
+
354
+ expect(autoUpdater.autoInstallOnAppQuit).toBe(true);
355
+ });
356
+
357
+ it('should broadcast updateWillInstallLater', () => {
358
+ updaterManager.installLater();
359
+
360
+ expect(mockBroadcast).toHaveBeenCalledWith('updateWillInstallLater');
361
+ });
362
+ });
363
+
364
+ describe('event handlers', () => {
365
+ beforeEach(async () => {
366
+ await updaterManager.initialize();
367
+ });
368
+
369
+ describe('update-available', () => {
370
+ it('should broadcast manualUpdateAvailable when manual check', async () => {
371
+ // Trigger manual check first
372
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
373
+ await updaterManager.checkForUpdates({ manual: true });
374
+
375
+ const updateInfo = { version: '2.0.0' };
376
+ const handler = registeredEvents.get('update-available');
377
+ handler?.(updateInfo);
378
+
379
+ expect(mockBroadcast).toHaveBeenCalledWith('manualUpdateAvailable', updateInfo);
380
+ });
381
+
382
+ it('should auto download when auto check finds update', async () => {
383
+ // Trigger auto check first
384
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
385
+ await updaterManager.checkForUpdates({ manual: false });
386
+
387
+ vi.mocked(autoUpdater.downloadUpdate).mockResolvedValue([] as any);
388
+
389
+ const handler = registeredEvents.get('update-available');
390
+ handler?.({ version: '2.0.0' });
391
+
392
+ expect(autoUpdater.downloadUpdate).toHaveBeenCalled();
393
+ });
394
+ });
395
+
396
+ describe('update-not-available', () => {
397
+ it('should broadcast manualUpdateNotAvailable when manual check', async () => {
398
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
399
+ await updaterManager.checkForUpdates({ manual: true });
400
+
401
+ const info = { version: '1.0.0' };
402
+ const handler = registeredEvents.get('update-not-available');
403
+ handler?.(info);
404
+
405
+ expect(mockBroadcast).toHaveBeenCalledWith('manualUpdateNotAvailable', info);
406
+ });
407
+
408
+ it('should not broadcast when auto check', async () => {
409
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
410
+ await updaterManager.checkForUpdates({ manual: false });
411
+
412
+ const handler = registeredEvents.get('update-not-available');
413
+ handler?.({ version: '1.0.0' });
414
+
415
+ expect(mockBroadcast).not.toHaveBeenCalledWith(
416
+ 'manualUpdateNotAvailable',
417
+ expect.anything(),
418
+ );
419
+ });
420
+ });
421
+
422
+ describe('download-progress', () => {
423
+ it('should broadcast progress when manual check', async () => {
424
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
425
+ await updaterManager.checkForUpdates({ manual: true });
426
+
427
+ const progressObj = {
428
+ bytesPerSecond: 1024,
429
+ percent: 50,
430
+ total: 1024 * 1024,
431
+ transferred: 512 * 1024,
432
+ };
433
+ const handler = registeredEvents.get('download-progress');
434
+ handler?.(progressObj);
435
+
436
+ expect(mockBroadcast).toHaveBeenCalledWith('updateDownloadProgress', progressObj);
437
+ });
438
+ });
439
+
440
+ describe('update-downloaded', () => {
441
+ it('should broadcast updateDownloaded', async () => {
442
+ await updaterManager.initialize();
443
+
444
+ const info = { version: '2.0.0' };
445
+ const handler = registeredEvents.get('update-downloaded');
446
+ handler?.(info);
447
+
448
+ expect(mockBroadcast).toHaveBeenCalledWith('updateDownloaded', info);
449
+ });
450
+ });
451
+
452
+ describe('error', () => {
453
+ it('should broadcast updateError when manual check', async () => {
454
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
455
+ await updaterManager.checkForUpdates({ manual: true });
456
+
457
+ const error = new Error('Update error');
458
+ const handler = registeredEvents.get('error');
459
+ handler?.(error);
460
+
461
+ expect(mockBroadcast).toHaveBeenCalledWith('updateError', 'Update error');
462
+ });
463
+
464
+ it('should not broadcast when auto check', async () => {
465
+ vi.mocked(autoUpdater.checkForUpdates).mockResolvedValue({} as any);
466
+ await updaterManager.checkForUpdates({ manual: false });
467
+
468
+ const error = new Error('Update error');
469
+ const handler = registeredEvents.get('error');
470
+ handler?.(error);
471
+
472
+ expect(mockBroadcast).not.toHaveBeenCalledWith('updateError', expect.anything());
473
+ });
474
+ });
475
+ });
476
+
477
+ describe('simulation methods (dev mode)', () => {
478
+ it('simulateUpdateAvailable should do nothing when not in dev mode', () => {
479
+ // Current mock has isDev = false
480
+ updaterManager.simulateUpdateAvailable();
481
+
482
+ // Should not broadcast anything since isDev is false
483
+ expect(mockBroadcast).not.toHaveBeenCalledWith(
484
+ 'manualUpdateAvailable',
485
+ expect.objectContaining({ version: '1.0.0' }),
486
+ );
487
+ });
488
+
489
+ it('simulateUpdateDownloaded should do nothing when not in dev mode', () => {
490
+ updaterManager.simulateUpdateDownloaded();
491
+
492
+ expect(mockBroadcast).not.toHaveBeenCalledWith(
493
+ 'updateDownloaded',
494
+ expect.objectContaining({ version: '1.0.0' }),
495
+ );
496
+ });
497
+
498
+ it('simulateDownloadProgress should do nothing when not in dev mode', () => {
499
+ updaterManager.simulateDownloadProgress();
500
+
501
+ expect(mockBroadcast).not.toHaveBeenCalledWith('updateDownloadStart');
502
+ });
503
+ });
504
+
505
+ describe('mainWindow getter', () => {
506
+ it('should return main window from browserManager', () => {
507
+ const mainWindow = updaterManager['mainWindow'];
508
+
509
+ expect(mockApp.browserManager.getMainWindow).toHaveBeenCalled();
510
+ expect(mainWindow.broadcast).toBe(mockBroadcast);
511
+ });
512
+ });
513
+ });
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Remove internal apiMode param from chat completion API requests."
6
+ ]
7
+ },
8
+ "date": "2025-12-01",
9
+ "version": "2.0.0-next.142"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Drop user.phoneNumber and reuse user.phone."
15
+ ]
16
+ },
17
+ "date": "2025-12-01",
18
+ "version": "2.0.0-next.141"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "features": [
@@ -1044,7 +1044,7 @@ table users {
1044
1044
  username text [unique]
1045
1045
  email text [unique]
1046
1046
  avatar text
1047
- phone text
1047
+ phone text [unique]
1048
1048
  first_name text
1049
1049
  last_name text
1050
1050
  full_name text
@@ -1058,7 +1058,6 @@ table users {
1058
1058
  ban_reason text
1059
1059
  ban_expires "timestamp with time zone"
1060
1060
  two_factor_enabled boolean [default: false]
1061
- phone_number text [unique]
1062
1061
  phone_number_verified boolean
1063
1062
  accessed_at "timestamp with time zone" [not null, default: `now()`]
1064
1063
  created_at "timestamp with time zone" [not null, default: `now()`]
@@ -121,6 +121,37 @@ If you need to use Azure OpenAI to provide model services, you can refer to the
121
121
  - Default: `-`
122
122
  - Example: `-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`
123
123
 
124
+ ## Vertex AI
125
+
126
+ ### `VERTEXAI_CREDENTIALS`
127
+
128
+ - Type: Required
129
+ - Description: A JSON string of your Google Cloud service account key, you can get the key from [here](/docs/usage/providers/vertexai).
130
+ - Default: -
131
+ - Example: `{"type": "service_account", "project_id": "your-gcp-project-id", ...}`
132
+
133
+ ### `VERTEXAI_PROJECT`
134
+
135
+ - Type: Optional
136
+ - Description: Your Google Cloud project ID. If not set, it will be obtained from the `project_id` field in `VERTEXAI_CREDENTIALS`.
137
+ - Default: -
138
+ - Example: `your-gcp-project-id`
139
+
140
+ ### `VERTEXAI_LOCATION`
141
+
142
+ - Type: Optional
143
+ - Description: The region where your Vertex AI model is located.
144
+ - Default: `global`
145
+ - Example: `us-central1`
146
+
147
+
148
+ ### `VERTEXAI_MODEL_LIST`
149
+
150
+ - Type: Optional
151
+ - Description: Used to control the model list, use `+` to add a model, use `-` to hide a model, use `model_name=display_name` to customize the display name of a model, separated by commas. Definition syntax rules see [model-list][model-list]
152
+ - Default: `-`
153
+ - Example: `-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`
154
+
124
155
  ## Anthropic AI
125
156
 
126
157
  ### `ANTHROPIC_API_KEY`
@@ -119,6 +119,36 @@ LobeChat 在部署时提供了丰富的模型服务商相关的环境变量,
119
119
  - 默认值:`-`
120
120
  - 示例:`-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`
121
121
 
122
+ ## Vertex AI
123
+
124
+ ### `VERTEXAI_CREDENTIALS`
125
+
126
+ - 类型:必选
127
+ - 描述:Google Cloud 服务账号密钥的 JSON 字符串。用于认证和授权访问 Vertex AI 服务,获取方法请参考 [这里](/zh/docs/usage/providers/vertexai)
128
+ - 默认值:-
129
+ - 示例:`{"type": "service_account", "project_id": "your-gcp-project-id", ...}`
130
+
131
+ ### `VERTEXAI_PROJECT`
132
+
133
+ - 类型:可选
134
+ - 描述:你的 Google Cloud 项目 ID。如果未设置,将从 `VERTEXAI_CREDENTIALS` 中的 `project_id` 字段获取。
135
+ - 默认值:-
136
+ - 示例:`your-gcp-project-id`
137
+
138
+ ### `VERTEXAI_LOCATION`
139
+
140
+ - 类型:可选
141
+ - 描述:你的 Vertex AI 模型所在的区域。
142
+ - 默认值:`global`
143
+ - 示例:`us-central1`
144
+
145
+ ### `VERTEXAI_MODEL_LIST`
146
+
147
+ - 类型:可选
148
+ - 描述:用来控制模型列表,使用 `+` 增加一个模型,使用 `-` 来隐藏一个模型,使用 `模型名=展示名<扩展配置>` 来自定义模型的展示名,用英文逗号隔开。模型定义语法规则见 [模型列表][model-list]
149
+ - 默认值:`-`
150
+ - 示例:`-all,+gemini-1.5-flash-latest,+gemini-1.5-pro-latest`
151
+
122
152
  ## Anthropic AI
123
153
 
124
154
  ### `ANTHROPIC_API_KEY`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.140",
3
+ "version": "2.0.0-next.142",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -0,0 +1,4 @@
1
+ ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "users_phone_number_unique";--> statement-breakpoint
2
+ ALTER TABLE "users" DROP COLUMN IF EXISTS "phone_number";--> statement-breakpoint
3
+ ALTER TABLE "users" DROP CONSTRAINT IF EXISTS "users_phone_unique";--> statement-breakpoint
4
+ ALTER TABLE "users" ADD CONSTRAINT "users_phone_unique" UNIQUE("phone");