@lobehub/lobehub 2.0.0-next.302 → 2.0.0-next.304
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/CHANGELOG.md +50 -0
- package/apps/desktop/src/common/routes.ts +8 -8
- package/apps/desktop/src/main/const/dir.ts +2 -2
- package/apps/desktop/src/main/const/env.ts +4 -4
- package/apps/desktop/src/main/const/store.ts +3 -3
- package/apps/desktop/src/main/controllers/AuthCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/McpInstallCtr.ts +8 -8
- package/apps/desktop/src/main/controllers/NetworkProxyCtr.ts +9 -9
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +8 -8
- package/apps/desktop/src/main/core/App.ts +9 -9
- package/apps/desktop/src/main/core/infrastructure/StaticFileServerManager.ts +2 -2
- package/apps/desktop/src/main/core/ui/ShortcutManager.ts +10 -10
- package/apps/desktop/src/main/core/ui/TrayManager.ts +12 -12
- package/apps/desktop/src/main/locales/resources.ts +4 -4
- package/apps/desktop/src/main/menus/impls/macOS.ts +1 -1
- package/apps/desktop/src/main/menus/types.ts +5 -5
- package/apps/desktop/src/main/modules/updater/configs.ts +10 -10
- package/apps/desktop/src/main/modules/updater/utils.ts +9 -9
- package/apps/desktop/src/main/services/fileSrv.ts +62 -62
- package/apps/desktop/src/main/shortcuts/config.ts +3 -3
- package/apps/desktop/src/main/types/protocol.ts +12 -12
- package/apps/desktop/src/main/utils/file-system.ts +2 -2
- package/apps/desktop/src/main/utils/logger.ts +5 -5
- package/apps/desktop/src/main/utils/protocol.ts +32 -32
- package/changelog/v1.json +18 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/discover.json +4 -4
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +1 -1
- package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +78 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/{ExecuteTasks → ExecuteAgentTasks}/index.tsx +1 -5
- package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +4 -2
- package/packages/database/src/schemas/relations.ts +4 -4
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +3 -9
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +2 -6
- package/src/features/Conversation/Messages/Assistant/index.tsx +1 -1
- package/src/features/Conversation/Messages/AssistantGroup/components/MessageContent.tsx +3 -3
- package/src/features/Conversation/Messages/Supervisor/components/MessageContent.tsx +2 -2
- package/src/features/Conversation/Messages/components/ContentLoading.tsx +5 -3
- package/src/locales/default/plugin.ts +1 -0
- package/src/store/chat/slices/operation/__tests__/selectors.test.ts +165 -0
- package/src/store/chat/slices/operation/selectors.ts +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.304](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.303...v2.0.0-next.304)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-18**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Improve auto scroll and loading hint.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Improve auto scroll and loading hint, closes [#11579](https://github.com/lobehub/lobe-chat/issues/11579) ([277b42d](https://github.com/lobehub/lobe-chat/commit/277b42d))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.303](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.302...v2.0.0-next.303)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2026-01-18**</sup>
|
|
33
|
+
|
|
34
|
+
#### 💄 Styles
|
|
35
|
+
|
|
36
|
+
- **misc**: Improve operation hint and fix scroll issue.
|
|
37
|
+
|
|
38
|
+
<br/>
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
42
|
+
|
|
43
|
+
#### Styles
|
|
44
|
+
|
|
45
|
+
- **misc**: Improve operation hint and fix scroll issue, closes [#11573](https://github.com/lobehub/lobe-chat/issues/11573) ([8505d14](https://github.com/lobehub/lobe-chat/commit/8505d14))
|
|
46
|
+
|
|
47
|
+
</details>
|
|
48
|
+
|
|
49
|
+
<div align="right">
|
|
50
|
+
|
|
51
|
+
[](#readme-top)
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
5
55
|
## [Version 2.0.0-next.302](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.301...v2.0.0-next.302)
|
|
6
56
|
|
|
7
57
|
<sup>Released on **2026-01-17**</sup>
|
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Route interception type, describing the mapping between intercepted routes and target windows
|
|
3
3
|
*/
|
|
4
4
|
export interface RouteInterceptConfig {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Whether to always open in a new window, even if target window already exists
|
|
7
7
|
*/
|
|
8
8
|
alwaysOpenNew?: boolean;
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Description
|
|
12
12
|
*/
|
|
13
13
|
description: string;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Whether interception is enabled
|
|
17
17
|
*/
|
|
18
18
|
enabled: boolean;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
21
|
+
* Route pattern prefix, e.g., '/settings'
|
|
22
22
|
*/
|
|
23
23
|
pathPrefix: string;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Target window identifier
|
|
27
27
|
*/
|
|
28
28
|
targetWindow: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Intercepted route configuration list
|
|
33
|
+
* Defines all routes that require special handling
|
|
34
34
|
*/
|
|
35
35
|
export const interceptRoutes: RouteInterceptConfig[] = [
|
|
36
36
|
{
|
|
@@ -25,9 +25,9 @@ export const appStorageDir = join(userDataDir, 'lobehub-storage');
|
|
|
25
25
|
|
|
26
26
|
// ------ Application storage directory ---- //
|
|
27
27
|
|
|
28
|
-
//
|
|
28
|
+
// Local storage files (simulating S3)
|
|
29
29
|
export const FILE_STORAGE_DIR = 'file-storage';
|
|
30
|
-
// Plugin
|
|
30
|
+
// Plugin installation directory
|
|
31
31
|
export const INSTALL_PLUGINS_DIR = 'plugins';
|
|
32
32
|
|
|
33
33
|
// Desktop file service
|
|
@@ -13,18 +13,18 @@ export const isLinux = linux();
|
|
|
13
13
|
|
|
14
14
|
function getIsWindows11() {
|
|
15
15
|
if (!isWindows) return false;
|
|
16
|
-
//
|
|
16
|
+
// Get OS version (e.g., "10.0.22621")
|
|
17
17
|
const release = os.release();
|
|
18
18
|
const parts = release.split('.');
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Major and minor version
|
|
21
21
|
const majorVersion = parseInt(parts[0], 10);
|
|
22
22
|
const minorVersion = parseInt(parts[1], 10);
|
|
23
23
|
|
|
24
|
-
//
|
|
24
|
+
// Build number is the third part
|
|
25
25
|
const buildNumber = parseInt(parts[2], 10);
|
|
26
26
|
|
|
27
|
-
// Windows 11
|
|
27
|
+
// Windows 11 build numbers start from 22000
|
|
28
28
|
return majorVersion === 10 && minorVersion === 0 && buildNumber >= 22_000;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Application settings storage related constants
|
|
3
3
|
*/
|
|
4
4
|
import { NetworkProxySettings } from '@lobechat/electron-client-ipc';
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
|
|
|
8
8
|
import { ElectronMainStore } from '@/types/store';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Storage name
|
|
12
12
|
*/
|
|
13
13
|
export const STORE_NAME = 'lobehub-settings';
|
|
14
14
|
|
|
@@ -22,7 +22,7 @@ export const defaultProxySettings: NetworkProxySettings = {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Storage default values
|
|
26
26
|
*/
|
|
27
27
|
export const STORE_DEFAULTS: ElectronMainStore = {
|
|
28
28
|
dataSyncConfig: { storageMode: 'cloud' },
|
|
@@ -8,23 +8,23 @@ const logger = createLogger('controllers:McpInstallCtr');
|
|
|
8
8
|
const protocolHandler = createProtocolHandler('plugin');
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Validate MCP Schema object structure
|
|
12
12
|
*/
|
|
13
13
|
function validateMcpSchema(schema: any): schema is McpSchema {
|
|
14
14
|
if (!schema || typeof schema !== 'object') return false;
|
|
15
15
|
|
|
16
|
-
//
|
|
16
|
+
// Required field validation
|
|
17
17
|
if (typeof schema.identifier !== 'string' || !schema.identifier) return false;
|
|
18
18
|
if (typeof schema.name !== 'string' || !schema.name) return false;
|
|
19
19
|
if (typeof schema.author !== 'string' || !schema.author) return false;
|
|
20
20
|
if (typeof schema.description !== 'string' || !schema.description) return false;
|
|
21
21
|
if (typeof schema.version !== 'string' || !schema.version) return false;
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Optional field validation
|
|
24
24
|
if (schema.homepage !== undefined && typeof schema.homepage !== 'string') return false;
|
|
25
25
|
if (schema.icon !== undefined && typeof schema.icon !== 'string') return false;
|
|
26
26
|
|
|
27
|
-
// config
|
|
27
|
+
// config field validation
|
|
28
28
|
if (!schema.config || typeof schema.config !== 'object') return false;
|
|
29
29
|
const config = schema.config;
|
|
30
30
|
|
|
@@ -35,13 +35,13 @@ function validateMcpSchema(schema: any): schema is McpSchema {
|
|
|
35
35
|
} else if (config.type === 'http') {
|
|
36
36
|
if (typeof config.url !== 'string' || !config.url) return false;
|
|
37
37
|
try {
|
|
38
|
-
new URL(config.url); //
|
|
38
|
+
new URL(config.url); // Validate URL format
|
|
39
39
|
} catch {
|
|
40
40
|
return false;
|
|
41
41
|
}
|
|
42
42
|
if (config.headers !== undefined && typeof config.headers !== 'object') return false;
|
|
43
43
|
} else {
|
|
44
|
-
return false; //
|
|
44
|
+
return false; // Unknown config type
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
return true;
|
|
@@ -54,8 +54,8 @@ interface McpInstallParams {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
|
-
* MCP
|
|
58
|
-
*
|
|
57
|
+
* MCP plugin installation controller
|
|
58
|
+
* Responsible for handling MCP plugin installation process
|
|
59
59
|
*/
|
|
60
60
|
export default class McpInstallController extends ControllerModule {
|
|
61
61
|
/**
|
|
@@ -16,13 +16,13 @@ import { ControllerModule, IpcMethod } from './index';
|
|
|
16
16
|
const logger = createLogger('controllers:NetworkProxyCtr');
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Network proxy controller
|
|
20
|
+
* Handle desktop application network proxy related functions
|
|
21
21
|
*/
|
|
22
22
|
export default class NetworkProxyCtr extends ControllerModule {
|
|
23
23
|
static override readonly groupName = 'networkProxy';
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Get proxy settings
|
|
26
26
|
*/
|
|
27
27
|
@IpcMethod()
|
|
28
28
|
async getDesktopSettings(): Promise<NetworkProxySettings> {
|
|
@@ -43,18 +43,18 @@ export default class NetworkProxyCtr extends ControllerModule {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Set proxy configuration
|
|
47
47
|
*/
|
|
48
48
|
@IpcMethod()
|
|
49
49
|
async setProxySettings(config: Partial<NetworkProxySettings>): Promise<void> {
|
|
50
50
|
try {
|
|
51
|
-
//
|
|
51
|
+
// Get current configuration
|
|
52
52
|
const currentConfig = this.app.storeManager.get(
|
|
53
53
|
'networkProxy',
|
|
54
54
|
defaultProxySettings,
|
|
55
55
|
) as NetworkProxySettings;
|
|
56
56
|
|
|
57
|
-
//
|
|
57
|
+
// Merge configuration and validate
|
|
58
58
|
const newConfig = merge({}, currentConfig, config);
|
|
59
59
|
|
|
60
60
|
const validation = ProxyConfigValidator.validate(newConfig);
|
|
@@ -69,10 +69,10 @@ export default class NetworkProxyCtr extends ControllerModule {
|
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// Apply proxy settings
|
|
73
73
|
await ProxyDispatcherManager.applyProxySettings(newConfig);
|
|
74
74
|
|
|
75
|
-
//
|
|
75
|
+
// Save to storage
|
|
76
76
|
this.app.storeManager.set('networkProxy', newConfig);
|
|
77
77
|
|
|
78
78
|
logger.info('Proxy settings updated successfully', {
|
|
@@ -149,7 +149,7 @@ export default class NetworkProxyCtr extends ControllerModule {
|
|
|
149
149
|
return;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
//
|
|
152
|
+
// Apply proxy settings
|
|
153
153
|
await ProxyDispatcherManager.applyProxySettings(networkProxy);
|
|
154
154
|
|
|
155
155
|
logger.info('Initial proxy settings applied successfully', {
|
|
@@ -47,7 +47,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Handle IPC calls for streaming requests
|
|
51
51
|
*/
|
|
52
52
|
private handleStreamRequest = async (event: IpcMainEvent, args: ProxyTRPCStreamRequestParams) => {
|
|
53
53
|
const { requestId } = args;
|
|
@@ -79,7 +79,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
//
|
|
82
|
+
// Call new streaming forwarding method
|
|
83
83
|
await this.forwardStreamRequest(event.sender, {
|
|
84
84
|
...args,
|
|
85
85
|
accessToken: token,
|
|
@@ -95,7 +95,7 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
98
|
+
* Execute actual streaming request forwarding
|
|
99
99
|
*/
|
|
100
100
|
private async forwardStreamRequest(
|
|
101
101
|
sender: WebContents,
|
|
@@ -123,14 +123,14 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
|
|
123
123
|
const clientReq = requester.request(requestOptions, (clientRes: IncomingMessage) => {
|
|
124
124
|
logger.debug(`${logPrefix} Received response with status ${clientRes.statusCode}`);
|
|
125
125
|
|
|
126
|
-
//
|
|
126
|
+
// Add debug information
|
|
127
127
|
logger.debug(`${logPrefix} Response details:`, {
|
|
128
128
|
headers: clientRes.headers,
|
|
129
129
|
statusCode: clientRes.statusCode,
|
|
130
130
|
statusMessage: clientRes.statusMessage,
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
-
// 1.
|
|
133
|
+
// 1. Immediately send response headers and status code
|
|
134
134
|
const responseData = {
|
|
135
135
|
headers: clientRes.headers || {},
|
|
136
136
|
status: clientRes.statusCode || 500,
|
|
@@ -140,21 +140,21 @@ export default class RemoteServerSyncCtr extends ControllerModule {
|
|
|
140
140
|
logger.debug(`${logPrefix} Sending response data:`, responseData);
|
|
141
141
|
sender.send(`stream:response:${requestId}`, responseData);
|
|
142
142
|
|
|
143
|
-
// 2.
|
|
143
|
+
// 2. Listen for data chunks and forward
|
|
144
144
|
clientRes.on('data', (chunk: Buffer) => {
|
|
145
145
|
if (sender.isDestroyed()) return;
|
|
146
146
|
logger.debug(`${logPrefix} Received data chunk, size: ${chunk.length}. Forwarding...`);
|
|
147
147
|
sender.send(`stream:data:${requestId}`, chunk);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
// 3.
|
|
150
|
+
// 3. Listen for end signal and forward
|
|
151
151
|
clientRes.on('end', () => {
|
|
152
152
|
logger.debug(`${logPrefix} Stream ended. Forwarding end signal...`);
|
|
153
153
|
if (sender.isDestroyed()) return;
|
|
154
154
|
sender.send(`stream:end:${requestId}`);
|
|
155
155
|
});
|
|
156
156
|
|
|
157
|
-
// 4.
|
|
157
|
+
// 4. Listen for response stream errors and forward
|
|
158
158
|
clientRes.on('error', (error) => {
|
|
159
159
|
logger.error(`${logPrefix} Error reading response stream:`, error);
|
|
160
160
|
if (sender.isDestroyed()) return;
|
|
@@ -127,7 +127,7 @@ export class App {
|
|
|
127
127
|
// initialize protocol handlers
|
|
128
128
|
this.protocolManager.initialize();
|
|
129
129
|
|
|
130
|
-
//
|
|
130
|
+
// Unified handling of before-quit event
|
|
131
131
|
app.on('before-quit', this.handleBeforeQuit);
|
|
132
132
|
|
|
133
133
|
// Initialize theme mode from store
|
|
@@ -224,10 +224,10 @@ export class App {
|
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
226
|
* Handle protocol request by dispatching to registered handlers
|
|
227
|
-
* @param urlType
|
|
228
|
-
* @param action
|
|
229
|
-
* @param data
|
|
230
|
-
* @returns
|
|
227
|
+
* @param urlType Protocol URL type (e.g., 'plugin')
|
|
228
|
+
* @param action Action type (e.g., 'install')
|
|
229
|
+
* @param data Parsed protocol data
|
|
230
|
+
* @returns Whether successfully handled
|
|
231
231
|
*/
|
|
232
232
|
async handleProtocolRequest(urlType: string, action: string, data: any): Promise<boolean> {
|
|
233
233
|
const key = `${urlType}:${action}`;
|
|
@@ -241,7 +241,7 @@ export class App {
|
|
|
241
241
|
try {
|
|
242
242
|
logger.debug(`Dispatching protocol request ${key} to controller`);
|
|
243
243
|
const result = await handler.controller[handler.methodName](data);
|
|
244
|
-
return result !== false; //
|
|
244
|
+
return result !== false; // Assume controller returning false indicates handling failure
|
|
245
245
|
} catch (error) {
|
|
246
246
|
logger.error(`Error handling protocol request ${key}:`, error);
|
|
247
247
|
return false;
|
|
@@ -385,17 +385,17 @@ export class App {
|
|
|
385
385
|
this.ipcServer = new ElectronIPCServer(name, ipcServerEvents);
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
//
|
|
388
|
+
// Add before-quit handler function
|
|
389
389
|
private handleBeforeQuit = () => {
|
|
390
390
|
logger.info('Application is preparing to quit');
|
|
391
391
|
this.isQuiting = true;
|
|
392
392
|
|
|
393
|
-
//
|
|
393
|
+
// Destroy tray
|
|
394
394
|
if (process.platform === 'win32') {
|
|
395
395
|
this.trayManager.destroyAll();
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
-
//
|
|
398
|
+
// Execute cleanup operations
|
|
399
399
|
this.staticFileServerManager.destroy();
|
|
400
400
|
};
|
|
401
401
|
}
|
|
@@ -160,7 +160,7 @@ export class StaticFileServerManager {
|
|
|
160
160
|
logger.debug(`Request method: ${req.method}`);
|
|
161
161
|
logger.debug(`Request headers: ${JSON.stringify(req.headers)}`);
|
|
162
162
|
|
|
163
|
-
//
|
|
163
|
+
// 提取File path:从 /desktop-file/path/to/file.png 中提取相对路径
|
|
164
164
|
let filePath = decodeURIComponent(url.pathname.slice(1)); // 移除开头的 /
|
|
165
165
|
logger.debug(`Initial file path after decode: ${filePath}`);
|
|
166
166
|
|
|
@@ -243,7 +243,7 @@ export class StaticFileServerManager {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
/**
|
|
246
|
-
*
|
|
246
|
+
* Get file server domain
|
|
247
247
|
*/
|
|
248
248
|
getFileServerDomain(): string {
|
|
249
249
|
if (!this.isInitialized || !this.serverPort) {
|
|
@@ -77,25 +77,25 @@ export class ShortcutManager {
|
|
|
77
77
|
try {
|
|
78
78
|
logger.debug(`Updating shortcut ${id} to ${accelerator}`);
|
|
79
79
|
|
|
80
|
-
// 1.
|
|
80
|
+
// 1. Check if ID is valid
|
|
81
81
|
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
|
|
82
82
|
logger.error(`Invalid shortcut ID: ${id}`);
|
|
83
83
|
return { errorType: 'INVALID_ID', success: false };
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// 2.
|
|
86
|
+
// 2. Basic format validation
|
|
87
87
|
if (!accelerator || typeof accelerator !== 'string' || accelerator.trim() === '') {
|
|
88
88
|
logger.error(`Invalid accelerator format: ${accelerator}`);
|
|
89
89
|
return { errorType: 'INVALID_FORMAT', success: false };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
//
|
|
92
|
+
// Convert frontend format to Electron format
|
|
93
93
|
const convertedAccelerator = this.convertAcceleratorFormat(accelerator.trim());
|
|
94
94
|
const cleanAccelerator = convertedAccelerator.toLowerCase();
|
|
95
95
|
|
|
96
96
|
logger.debug(`Converted accelerator from ${accelerator} to ${convertedAccelerator}`);
|
|
97
97
|
|
|
98
|
-
// 3.
|
|
98
|
+
// 3. Check if contains + sign (modifier key format)
|
|
99
99
|
if (!cleanAccelerator.includes('+')) {
|
|
100
100
|
logger.error(
|
|
101
101
|
`Invalid accelerator format: ${cleanAccelerator}. Must contain modifier keys like 'CommandOrControl+E'`,
|
|
@@ -103,7 +103,7 @@ export class ShortcutManager {
|
|
|
103
103
|
return { errorType: 'INVALID_FORMAT', success: false };
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// 4.
|
|
106
|
+
// 4. Check if has basic modifier keys
|
|
107
107
|
const hasModifier = ['CommandOrControl', 'Command', 'Ctrl', 'Alt', 'Shift'].some((modifier) =>
|
|
108
108
|
cleanAccelerator.includes(modifier.toLowerCase()),
|
|
109
109
|
);
|
|
@@ -113,7 +113,7 @@ export class ShortcutManager {
|
|
|
113
113
|
return { errorType: 'NO_MODIFIER', success: false };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// 5.
|
|
116
|
+
// 5. Check for conflicts
|
|
117
117
|
for (const [existingId, existingAccelerator] of Object.entries(this.shortcutsConfig)) {
|
|
118
118
|
if (
|
|
119
119
|
existingId !== id &&
|
|
@@ -125,7 +125,7 @@ export class ShortcutManager {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// 6.
|
|
128
|
+
// 6. Try test registration (check if occupied by system)
|
|
129
129
|
const testSuccess = globalShortcut.register(convertedAccelerator, () => {});
|
|
130
130
|
if (!testSuccess) {
|
|
131
131
|
logger.error(
|
|
@@ -133,11 +133,11 @@ export class ShortcutManager {
|
|
|
133
133
|
);
|
|
134
134
|
return { errorType: 'SYSTEM_OCCUPIED', success: false };
|
|
135
135
|
} else {
|
|
136
|
-
//
|
|
136
|
+
// Test successful, immediately unregister
|
|
137
137
|
globalShortcut.unregister(convertedAccelerator);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// 7.
|
|
140
|
+
// 7. Update configuration
|
|
141
141
|
this.shortcutsConfig[id] = convertedAccelerator;
|
|
142
142
|
|
|
143
143
|
this.saveShortcutsConfig();
|
|
@@ -285,7 +285,7 @@ export class ShortcutManager {
|
|
|
285
285
|
Object.entries(this.shortcutsConfig).forEach(([id, accelerator]) => {
|
|
286
286
|
logger.debug(`Registering shortcut '${id}' with ${accelerator}`);
|
|
287
287
|
|
|
288
|
-
//
|
|
288
|
+
// Only register shortcuts that exist in DEFAULT_SHORTCUTS_CONFIG
|
|
289
289
|
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
|
|
290
290
|
logger.debug(`Skipping shortcut '${id}' - not found in DEFAULT_SHORTCUTS_CONFIG`);
|
|
291
291
|
return;
|
|
@@ -8,11 +8,11 @@ import { createLogger } from '@/utils/logger';
|
|
|
8
8
|
import type { App } from '../App';
|
|
9
9
|
import { Tray, TrayOptions } from './Tray';
|
|
10
10
|
|
|
11
|
-
//
|
|
11
|
+
// Create logger
|
|
12
12
|
const logger = createLogger('core:TrayManager');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Tray identifier type
|
|
16
16
|
*/
|
|
17
17
|
export type TrayIdentifiers = 'main';
|
|
18
18
|
|
|
@@ -20,41 +20,41 @@ export class TrayManager {
|
|
|
20
20
|
app: App;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Store all tray instances
|
|
24
24
|
*/
|
|
25
25
|
trays: Map<TrayIdentifiers, Tray> = new Map();
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @param app
|
|
28
|
+
* Constructor
|
|
29
|
+
* @param app Application instance
|
|
30
30
|
*/
|
|
31
31
|
constructor(app: App) {
|
|
32
|
-
logger.debug('
|
|
32
|
+
logger.debug('Initialize TrayManager');
|
|
33
33
|
this.app = app;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* Initialize all trays
|
|
38
38
|
*/
|
|
39
39
|
initializeTrays() {
|
|
40
|
-
logger.debug('
|
|
40
|
+
logger.debug('Initialize application tray');
|
|
41
41
|
|
|
42
|
-
//
|
|
42
|
+
// Initialize main tray
|
|
43
43
|
this.initializeMainTray();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
47
|
+
* Get main tray
|
|
48
48
|
*/
|
|
49
49
|
getMainTray() {
|
|
50
50
|
return this.retrieveByIdentifier('main');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Initialize main tray
|
|
55
55
|
*/
|
|
56
56
|
initializeMainTray() {
|
|
57
|
-
logger.debug('
|
|
57
|
+
logger.debug('Initialize main tray');
|
|
58
58
|
return this.retrieveOrInitialize({
|
|
59
59
|
iconPath: isMac
|
|
60
60
|
? nativeTheme.shouldUseDarkColorsForSystemIntegratedUI
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Normalize language code
|
|
3
3
|
*/
|
|
4
4
|
export const normalizeLocale = (locale: string) => {
|
|
5
5
|
return locale.toLowerCase().replace('_', '-');
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Load translation resources on demand
|
|
10
10
|
*/
|
|
11
11
|
export const loadResources = async (lng: string, ns: string) => {
|
|
12
12
|
// All en-* locales fallback to 'en' and use default TypeScript files
|
|
@@ -16,7 +16,7 @@ export const loadResources = async (lng: string, ns: string) => {
|
|
|
16
16
|
|
|
17
17
|
return content;
|
|
18
18
|
} catch (error) {
|
|
19
|
-
console.error(`[I18n]
|
|
19
|
+
console.error(`[I18n] Unable to load translation file: ${ns}`, error);
|
|
20
20
|
return {};
|
|
21
21
|
}
|
|
22
22
|
}
|
|
@@ -26,7 +26,7 @@ export const loadResources = async (lng: string, ns: string) => {
|
|
|
26
26
|
|
|
27
27
|
return content;
|
|
28
28
|
} catch (error) {
|
|
29
|
-
console.error(
|
|
29
|
+
console.error(`Unable to load translation file: ${lng} - ${ns}`, error);
|
|
30
30
|
return {};
|
|
31
31
|
}
|
|
32
32
|
};
|
|
@@ -47,7 +47,7 @@ export class MacOSMenu extends BaseMenuPlatform implements IMenuPlatform {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
refresh(options?: MenuOptions): void {
|
|
50
|
-
//
|
|
50
|
+
// 重建Application menu
|
|
51
51
|
this.buildAndSetAppMenu(options);
|
|
52
52
|
// 如果托盘菜单存在,也重建它(如果需要动态更新)
|
|
53
53
|
// this.trayMenu = this.buildTrayMenu();
|
|
@@ -2,27 +2,27 @@ import { Menu } from 'electron';
|
|
|
2
2
|
|
|
3
3
|
export interface MenuOptions {
|
|
4
4
|
showDevItems?: boolean;
|
|
5
|
-
//
|
|
5
|
+
// Other possible configuration items
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export interface IMenuPlatform {
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Build and set application menu
|
|
11
11
|
*/
|
|
12
12
|
buildAndSetAppMenu(options?: MenuOptions): Menu;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Build context menu
|
|
16
16
|
*/
|
|
17
17
|
buildContextMenu(type: string, data?: any): Menu;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Build tray menu
|
|
21
21
|
*/
|
|
22
22
|
buildTrayMenu(): Menu;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* Refresh menu
|
|
26
26
|
*/
|
|
27
27
|
refresh(options?: MenuOptions): void;
|
|
28
28
|
}
|