@lobehub/lobehub 2.0.0-next.241 → 2.0.0-next.243

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 (90) hide show
  1. package/.cursor/rules/desktop-feature-implementation.mdc +2 -2
  2. package/.cursor/rules/desktop-local-tools-implement.mdc +2 -2
  3. package/.github/workflows/test.yml +13 -5
  4. package/CHANGELOG.md +52 -0
  5. package/apps/desktop/Development.md +1 -6
  6. package/apps/desktop/README.md +2 -17
  7. package/apps/desktop/README.zh-CN.md +1 -15
  8. package/apps/desktop/build/Icon-beta.Assets.car +0 -0
  9. package/apps/desktop/build/Icon-beta.icns +0 -0
  10. package/apps/desktop/build/icon-beta.ico +0 -0
  11. package/apps/desktop/build/icon-beta.png +0 -0
  12. package/apps/desktop/src/main/controllers/index.ts +1 -1
  13. package/apps/desktop/src/main/controllers/registry.ts +0 -9
  14. package/apps/desktop/src/main/core/App.ts +1 -11
  15. package/apps/desktop/src/main/core/browser/Browser.ts +278 -457
  16. package/apps/desktop/src/main/core/browser/WindowStateManager.ts +180 -0
  17. package/apps/desktop/src/main/core/browser/WindowThemeManager.ts +167 -0
  18. package/apps/desktop/src/main/core/browser/__tests__/WindowStateManager.test.ts +237 -0
  19. package/apps/desktop/src/main/core/browser/__tests__/WindowThemeManager.test.ts +240 -0
  20. package/apps/desktop/src/main/exports.d.ts +1 -1
  21. package/apps/desktop/src/main/exports.ts +1 -1
  22. package/apps/desktop/src/main/utils/__tests__/http-headers.test.ts +131 -0
  23. package/apps/desktop/src/main/utils/http-headers.ts +61 -0
  24. package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +1 -22
  25. package/apps/desktop/src/main/utils/ipc/base.ts +0 -20
  26. package/apps/desktop/src/main/utils/ipc/index.ts +1 -9
  27. package/changelog/v1.json +10 -0
  28. package/locales/ar/models.json +48 -7
  29. package/locales/ar/plugin.json +9 -0
  30. package/locales/ar/providers.json +1 -0
  31. package/locales/bg-BG/models.json +35 -7
  32. package/locales/bg-BG/plugin.json +9 -0
  33. package/locales/bg-BG/providers.json +1 -0
  34. package/locales/de-DE/models.json +26 -6
  35. package/locales/de-DE/plugin.json +9 -0
  36. package/locales/de-DE/providers.json +1 -0
  37. package/locales/en-US/models.json +10 -10
  38. package/locales/en-US/oauth.json +0 -1
  39. package/locales/en-US/providers.json +1 -0
  40. package/locales/en-US/subscription.json +2 -2
  41. package/locales/es-ES/models.json +42 -7
  42. package/locales/es-ES/plugin.json +9 -0
  43. package/locales/es-ES/providers.json +1 -0
  44. package/locales/fa-IR/models.json +52 -10
  45. package/locales/fa-IR/plugin.json +9 -0
  46. package/locales/fa-IR/providers.json +1 -0
  47. package/locales/fr-FR/models.json +36 -7
  48. package/locales/fr-FR/plugin.json +9 -0
  49. package/locales/fr-FR/providers.json +1 -0
  50. package/locales/it-IT/models.json +42 -7
  51. package/locales/it-IT/plugin.json +9 -0
  52. package/locales/it-IT/providers.json +1 -0
  53. package/locales/ja-JP/models.json +35 -6
  54. package/locales/ja-JP/plugin.json +9 -0
  55. package/locales/ja-JP/providers.json +1 -0
  56. package/locales/ko-KR/models.json +28 -7
  57. package/locales/ko-KR/plugin.json +9 -0
  58. package/locales/ko-KR/providers.json +1 -0
  59. package/locales/nl-NL/models.json +35 -6
  60. package/locales/nl-NL/plugin.json +9 -0
  61. package/locales/nl-NL/providers.json +1 -0
  62. package/locales/pl-PL/models.json +36 -7
  63. package/locales/pl-PL/plugin.json +9 -0
  64. package/locales/pl-PL/providers.json +1 -0
  65. package/locales/pt-BR/models.json +35 -6
  66. package/locales/pt-BR/plugin.json +9 -0
  67. package/locales/pt-BR/providers.json +1 -0
  68. package/locales/ru-RU/models.json +35 -7
  69. package/locales/ru-RU/plugin.json +9 -0
  70. package/locales/ru-RU/providers.json +1 -0
  71. package/locales/tr-TR/models.json +5 -7
  72. package/locales/tr-TR/plugin.json +9 -0
  73. package/locales/tr-TR/providers.json +1 -0
  74. package/locales/vi-VN/models.json +5 -5
  75. package/locales/vi-VN/plugin.json +9 -0
  76. package/locales/vi-VN/providers.json +1 -0
  77. package/locales/zh-CN/models.json +48 -6
  78. package/locales/zh-CN/oauth.json +0 -1
  79. package/locales/zh-CN/providers.json +1 -0
  80. package/locales/zh-CN/subscription.json +1 -1
  81. package/locales/zh-TW/models.json +10 -10
  82. package/locales/zh-TW/plugin.json +9 -0
  83. package/locales/zh-TW/providers.json +1 -0
  84. package/package.json +1 -1
  85. package/src/features/ChatInput/InputEditor/Placeholder.tsx +4 -1
  86. package/src/locales/default/subscription.ts +2 -3
  87. package/src/server/services/memory/userMemory/extract.ts +46 -6
  88. package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +0 -33
  89. package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +0 -55
  90. package/src/server/modules/ElectronIPCClient/index.ts +0 -92
@@ -132,10 +132,9 @@ export default {
132
132
  'Your {{plan}} computing credits have been exhausted. Upgrade now to get more credits.',
133
133
  'limitation.limited.descUltimate':
134
134
  'Your {{plan}} computing credits have been exhausted. Please top up credits to continue.',
135
- 'limitation.limited.referralTip':
136
- 'Invite new users to register, and you and your friend will each receive {{reward}}M credits',
135
+ 'limitation.limited.referralTip': 'Invite friends, both get {{reward}}M',
137
136
  'limitation.limited.title': 'Computing Credits Exhausted',
138
- 'limitation.limited.topup': 'Top Up Credits',
137
+ 'limitation.limited.topup': 'Top-Up Credits',
139
138
  'limitation.limited.upgrade': 'Upgrade to Higher Plan',
140
139
  'limitation.providers.lock.addNew': 'Subscribe now to create custom AI providers',
141
140
  'limitation.providers.lock.enableProvider': 'Subscribe now to enable this AI provider',
@@ -66,6 +66,7 @@ import type { GlobalMemoryLayer } from '@/types/serverConfig';
66
66
  import type { UserKeyVaults } from '@/types/user/settings';
67
67
  import { LayersEnum, type MergeStrategyEnum, TypesEnum, MemorySourceType } from '@/types/userMemory';
68
68
  import { encodeAsync } from '@/utils/tokenizer';
69
+ import debug from 'debug';
69
70
 
70
71
  const SOURCE_ALIAS_MAP: Record<string, MemorySourceType> = {
71
72
  benchmark_locomo: MemorySourceType.BenchmarkLocomo,
@@ -262,19 +263,58 @@ const resolveLayerModels = (
262
263
  preference: layers?.preference ?? fallback.preference,
263
264
  });
264
265
 
265
- const initRuntimeForAgent = async (agent: MemoryAgentConfig, keyVaults?: UserKeyVaults) => {
266
+ const maskSecret = (value?: string) => {
267
+ if (!value) return 'undefined';
268
+ if (value.length <= 8) return `${value[0]}***${value.at(-1)}`;
269
+
270
+ return `${value.slice(0, 6)}***${value.slice(-4)}`;
271
+ };
272
+
273
+ const resolveRuntimeAgentConfig = (agent: MemoryAgentConfig, keyVaults?: UserKeyVaults) => {
266
274
  const provider = agent.provider || 'openai';
267
275
  const { apiKey: userApiKey, baseURL: userBaseURL } = extractCredentialsFromVault(
268
276
  provider,
269
277
  keyVaults,
270
278
  );
271
279
 
272
- const apiKey = userApiKey || agent.apiKey;
273
- if (!apiKey) throw new Error(`Missing API key for ${provider} memory extraction runtime`);
280
+ // Only use the user baseURL if we are also using their API key; otherwise fall back entirely
281
+ // to system config to avoid mixing credentials.
282
+ const useUserCredential = !!userApiKey;
283
+ const apiKey = useUserCredential ? userApiKey : agent.apiKey;
284
+ const baseURL = useUserCredential ? userBaseURL || agent.baseURL : agent.baseURL;
285
+ const source = useUserCredential ? 'user-keyvault' : 'system-config';
286
+
287
+ return { apiKey, baseURL, provider, source };
288
+ };
289
+
290
+ const logRuntime = debug('lobe-server:memory:user-memory:runtime');
291
+
292
+ const debugRuntimeInit = (
293
+ agent: MemoryAgentConfig,
294
+ resolved: ReturnType<typeof resolveRuntimeAgentConfig>,
295
+ ) => {
296
+ if (!logRuntime.enabled) return;
297
+ logRuntime('init runtime', {
298
+ agentModel: agent.model,
299
+ agentProvider: agent.provider || 'openai',
300
+ apiKey: maskSecret(resolved.apiKey),
301
+ baseURL: resolved.baseURL,
302
+ provider: resolved.provider,
303
+ source: resolved.source,
304
+ });
305
+ };
306
+
307
+ const initRuntimeForAgent = async (agent: MemoryAgentConfig, keyVaults?: UserKeyVaults) => {
308
+ const resolved = resolveRuntimeAgentConfig(agent, keyVaults);
309
+ debugRuntimeInit(agent, resolved);
310
+
311
+ if (!resolved.apiKey) {
312
+ throw new Error(`Missing API key for ${resolved.provider} memory extraction runtime`);
313
+ }
274
314
 
275
- return ModelRuntime.initializeWithProvider(provider, {
276
- apiKey,
277
- baseURL: userBaseURL || agent.baseURL,
315
+ return ModelRuntime.initializeWithProvider(resolved.provider, {
316
+ apiKey: resolved.apiKey,
317
+ baseURL: resolved.baseURL,
278
318
  });
279
319
  };
280
320
 
@@ -1,33 +0,0 @@
1
- import { CreateFileParams } from '@lobechat/electron-server-ipc';
2
-
3
- import FileService from '@/services/fileSrv';
4
-
5
- import { ControllerModule, IpcServerMethod } from './index';
6
-
7
- export default class UploadFileServerCtr extends ControllerModule {
8
- static override readonly groupName = 'upload';
9
-
10
- private get fileService() {
11
- return this.app.getService(FileService);
12
- }
13
-
14
- @IpcServerMethod()
15
- async getFileUrlById(id: string) {
16
- return this.fileService.getFilePath(id);
17
- }
18
-
19
- @IpcServerMethod()
20
- async getFileHTTPURL(path: string) {
21
- return this.fileService.getFileHTTPURL(path);
22
- }
23
-
24
- @IpcServerMethod()
25
- async deleteFiles(paths: string[]) {
26
- return this.fileService.deleteFiles(paths);
27
- }
28
-
29
- @IpcServerMethod()
30
- async createFile(params: CreateFileParams) {
31
- return this.fileService.uploadFile(params);
32
- }
33
- }
@@ -1,55 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import type { App } from '@/core/App';
4
-
5
- import UploadFileServerCtr from '../UploadFileServerCtr';
6
-
7
- vi.mock('@/services/fileSrv', () => ({
8
- default: class MockFileService {},
9
- }));
10
-
11
- const mockFileService = {
12
- getFileHTTPURL: vi.fn(),
13
- getFilePath: vi.fn(),
14
- deleteFiles: vi.fn(),
15
- uploadFile: vi.fn(),
16
- };
17
-
18
- const mockApp = {
19
- getService: vi.fn(() => mockFileService),
20
- } as unknown as App;
21
-
22
- describe('UploadFileServerCtr', () => {
23
- let controller: UploadFileServerCtr;
24
-
25
- beforeEach(() => {
26
- vi.clearAllMocks();
27
- controller = new UploadFileServerCtr(mockApp);
28
- });
29
-
30
- it('gets file path by id', async () => {
31
- mockFileService.getFilePath.mockResolvedValue('path');
32
- await expect(controller.getFileUrlById('id')).resolves.toBe('path');
33
- expect(mockFileService.getFilePath).toHaveBeenCalledWith('id');
34
- });
35
-
36
- it('gets HTTP URL', async () => {
37
- mockFileService.getFileHTTPURL.mockResolvedValue('url');
38
- await expect(controller.getFileHTTPURL('/path')).resolves.toBe('url');
39
- expect(mockFileService.getFileHTTPURL).toHaveBeenCalledWith('/path');
40
- });
41
-
42
- it('deletes files', async () => {
43
- mockFileService.deleteFiles.mockResolvedValue(undefined);
44
- await controller.deleteFiles(['a']);
45
- expect(mockFileService.deleteFiles).toHaveBeenCalledWith(['a']);
46
- });
47
-
48
- it('creates files via upload service', async () => {
49
- const params = { filename: 'file' } as any;
50
- mockFileService.uploadFile.mockResolvedValue({ success: true });
51
-
52
- await expect(controller.createFile(params)).resolves.toEqual({ success: true });
53
- expect(mockFileService.uploadFile).toHaveBeenCalledWith(params);
54
- });
55
- });
@@ -1,92 +0,0 @@
1
- import { type CreateFileParams, ElectronIpcClient, type FileMetadata } from '@lobechat/electron-server-ipc';
2
- import type { DesktopServerIpcServices } from '@lobehub/desktop-ipc-typings';
3
-
4
- import packageJSON from '@/../apps/desktop/package.json';
5
-
6
- const createServerInvokeProxy = <IpcServices>(
7
- invoke: (channel: string, payload?: unknown) => Promise<unknown>,
8
- ): IpcServices =>
9
- new Proxy(
10
- {},
11
- {
12
- get(_target, groupKey) {
13
- if (typeof groupKey !== 'string') return undefined;
14
-
15
- return new Proxy(
16
- {},
17
- {
18
- get(_methodTarget, methodKey) {
19
- if (typeof methodKey !== 'string') return undefined;
20
-
21
- const channel = `${groupKey}.${methodKey}`;
22
- return (payload?: unknown) =>
23
- payload === undefined ? invoke(channel) : invoke(channel, payload);
24
- },
25
- },
26
- );
27
- },
28
- },
29
- ) as IpcServices;
30
-
31
- class LobeHubElectronIpcClient extends ElectronIpcClient {
32
- private _services: DesktopServerIpcServices | null = null;
33
-
34
- private ensureServices(): DesktopServerIpcServices {
35
- if (this._services) return this._services;
36
-
37
- this._services = createServerInvokeProxy<DesktopServerIpcServices>((channel, payload) =>
38
- payload === undefined ? this.sendRequest(channel) : this.sendRequest(channel, payload),
39
- );
40
-
41
- return this.services;
42
- }
43
-
44
- private get ipc() {
45
- return this.ensureServices();
46
- }
47
-
48
- public get services(): DesktopServerIpcServices {
49
- return this.ipc;
50
- }
51
-
52
- getDatabasePath = async (): Promise<string> => {
53
- return this.ipc.system.getDatabasePath();
54
- };
55
-
56
- getUserDataPath = async (): Promise<string> => {
57
- return this.ipc.system.getUserDataPath();
58
- };
59
-
60
- getDatabaseSchemaHash = async () => {
61
- return this.ipc.system.getDatabaseSchemaHash();
62
- };
63
-
64
- setDatabaseSchemaHash = async (hash: string | undefined) => {
65
- if (!hash) return;
66
-
67
- return this.ipc.system.setDatabaseSchemaHash(hash);
68
- };
69
-
70
- getFilePathById = async (id: string) => {
71
- return this.ipc.upload.getFileUrlById(id);
72
- };
73
-
74
- getFileHTTPURL = async (path: string) => {
75
- return this.ipc.upload.getFileHTTPURL(path);
76
- };
77
-
78
- deleteFiles = async (paths: string[]) => {
79
- return this.ipc.upload.deleteFiles(paths);
80
- };
81
-
82
- createFile = async (params: CreateFileParams) => {
83
- return this.ipc.upload.createFile(params) as Promise<{
84
- metadata: FileMetadata;
85
- success: boolean;
86
- }>;
87
- };
88
- }
89
-
90
- export const electronIpcClient = new LobeHubElectronIpcClient(packageJSON.name);
91
-
92
- export const ensureElectronServerIpc = (): DesktopServerIpcServices => electronIpcClient.services;