@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.
- package/.cursor/rules/desktop-feature-implementation.mdc +2 -2
- package/.cursor/rules/desktop-local-tools-implement.mdc +2 -2
- package/.github/workflows/test.yml +13 -5
- package/CHANGELOG.md +52 -0
- package/apps/desktop/Development.md +1 -6
- package/apps/desktop/README.md +2 -17
- package/apps/desktop/README.zh-CN.md +1 -15
- package/apps/desktop/build/Icon-beta.Assets.car +0 -0
- package/apps/desktop/build/Icon-beta.icns +0 -0
- package/apps/desktop/build/icon-beta.ico +0 -0
- package/apps/desktop/build/icon-beta.png +0 -0
- package/apps/desktop/src/main/controllers/index.ts +1 -1
- package/apps/desktop/src/main/controllers/registry.ts +0 -9
- package/apps/desktop/src/main/core/App.ts +1 -11
- package/apps/desktop/src/main/core/browser/Browser.ts +278 -457
- package/apps/desktop/src/main/core/browser/WindowStateManager.ts +180 -0
- package/apps/desktop/src/main/core/browser/WindowThemeManager.ts +167 -0
- package/apps/desktop/src/main/core/browser/__tests__/WindowStateManager.test.ts +237 -0
- package/apps/desktop/src/main/core/browser/__tests__/WindowThemeManager.test.ts +240 -0
- package/apps/desktop/src/main/exports.d.ts +1 -1
- package/apps/desktop/src/main/exports.ts +1 -1
- package/apps/desktop/src/main/utils/__tests__/http-headers.test.ts +131 -0
- package/apps/desktop/src/main/utils/http-headers.ts +61 -0
- package/apps/desktop/src/main/utils/ipc/__tests__/base.test.ts +1 -22
- package/apps/desktop/src/main/utils/ipc/base.ts +0 -20
- package/apps/desktop/src/main/utils/ipc/index.ts +1 -9
- package/changelog/v1.json +10 -0
- package/locales/ar/models.json +48 -7
- package/locales/ar/plugin.json +9 -0
- package/locales/ar/providers.json +1 -0
- package/locales/bg-BG/models.json +35 -7
- package/locales/bg-BG/plugin.json +9 -0
- package/locales/bg-BG/providers.json +1 -0
- package/locales/de-DE/models.json +26 -6
- package/locales/de-DE/plugin.json +9 -0
- package/locales/de-DE/providers.json +1 -0
- package/locales/en-US/models.json +10 -10
- package/locales/en-US/oauth.json +0 -1
- package/locales/en-US/providers.json +1 -0
- package/locales/en-US/subscription.json +2 -2
- package/locales/es-ES/models.json +42 -7
- package/locales/es-ES/plugin.json +9 -0
- package/locales/es-ES/providers.json +1 -0
- package/locales/fa-IR/models.json +52 -10
- package/locales/fa-IR/plugin.json +9 -0
- package/locales/fa-IR/providers.json +1 -0
- package/locales/fr-FR/models.json +36 -7
- package/locales/fr-FR/plugin.json +9 -0
- package/locales/fr-FR/providers.json +1 -0
- package/locales/it-IT/models.json +42 -7
- package/locales/it-IT/plugin.json +9 -0
- package/locales/it-IT/providers.json +1 -0
- package/locales/ja-JP/models.json +35 -6
- package/locales/ja-JP/plugin.json +9 -0
- package/locales/ja-JP/providers.json +1 -0
- package/locales/ko-KR/models.json +28 -7
- package/locales/ko-KR/plugin.json +9 -0
- package/locales/ko-KR/providers.json +1 -0
- package/locales/nl-NL/models.json +35 -6
- package/locales/nl-NL/plugin.json +9 -0
- package/locales/nl-NL/providers.json +1 -0
- package/locales/pl-PL/models.json +36 -7
- package/locales/pl-PL/plugin.json +9 -0
- package/locales/pl-PL/providers.json +1 -0
- package/locales/pt-BR/models.json +35 -6
- package/locales/pt-BR/plugin.json +9 -0
- package/locales/pt-BR/providers.json +1 -0
- package/locales/ru-RU/models.json +35 -7
- package/locales/ru-RU/plugin.json +9 -0
- package/locales/ru-RU/providers.json +1 -0
- package/locales/tr-TR/models.json +5 -7
- package/locales/tr-TR/plugin.json +9 -0
- package/locales/tr-TR/providers.json +1 -0
- package/locales/vi-VN/models.json +5 -5
- package/locales/vi-VN/plugin.json +9 -0
- package/locales/vi-VN/providers.json +1 -0
- package/locales/zh-CN/models.json +48 -6
- package/locales/zh-CN/oauth.json +0 -1
- package/locales/zh-CN/providers.json +1 -0
- package/locales/zh-CN/subscription.json +1 -1
- package/locales/zh-TW/models.json +10 -10
- package/locales/zh-TW/plugin.json +9 -0
- package/locales/zh-TW/providers.json +1 -0
- package/package.json +1 -1
- package/src/features/ChatInput/InputEditor/Placeholder.tsx +4 -1
- package/src/locales/default/subscription.ts +2 -3
- package/src/server/services/memory/userMemory/extract.ts +46 -6
- package/apps/desktop/src/main/controllers/UploadFileServerCtr.ts +0 -33
- package/apps/desktop/src/main/controllers/__tests__/UploadFileServerCtr.test.ts +0 -55
- 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
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
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:
|
|
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;
|