@lobehub/lobehub 2.0.0-next.303 → 2.0.0-next.305
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/.github/workflows/manual-build-desktop.yml +11 -1
- package/CHANGELOG.md +50 -0
- package/apps/desktop/.i18nrc.js +3 -3
- package/apps/desktop/electron.vite.config.ts +0 -2
- package/apps/desktop/resources/locales/ar/dialog.json +5 -1
- package/apps/desktop/resources/locales/ar/menu.json +16 -0
- package/apps/desktop/resources/locales/bg-BG/dialog.json +5 -1
- package/apps/desktop/resources/locales/bg-BG/menu.json +16 -0
- package/apps/desktop/resources/locales/de-DE/dialog.json +5 -1
- package/apps/desktop/resources/locales/de-DE/menu.json +16 -0
- package/apps/desktop/resources/locales/en/common.json +26 -0
- package/apps/desktop/resources/locales/en/dialog.json +27 -0
- package/apps/desktop/resources/locales/en/menu.json +73 -0
- package/apps/desktop/resources/locales/es-ES/dialog.json +5 -1
- package/apps/desktop/resources/locales/es-ES/menu.json +16 -0
- package/apps/desktop/resources/locales/fa-IR/dialog.json +5 -1
- package/apps/desktop/resources/locales/fa-IR/menu.json +16 -0
- package/apps/desktop/resources/locales/fr-FR/dialog.json +5 -1
- package/apps/desktop/resources/locales/fr-FR/menu.json +16 -0
- package/apps/desktop/resources/locales/it-IT/dialog.json +5 -1
- package/apps/desktop/resources/locales/it-IT/menu.json +16 -0
- package/apps/desktop/resources/locales/ja-JP/dialog.json +5 -1
- package/apps/desktop/resources/locales/ja-JP/menu.json +16 -0
- package/apps/desktop/resources/locales/ko-KR/dialog.json +5 -1
- package/apps/desktop/resources/locales/ko-KR/menu.json +16 -0
- package/apps/desktop/resources/locales/nl-NL/dialog.json +5 -1
- package/apps/desktop/resources/locales/nl-NL/menu.json +16 -0
- package/apps/desktop/resources/locales/pl-PL/dialog.json +5 -1
- package/apps/desktop/resources/locales/pl-PL/menu.json +16 -0
- package/apps/desktop/resources/locales/pt-BR/dialog.json +5 -1
- package/apps/desktop/resources/locales/pt-BR/menu.json +16 -0
- package/apps/desktop/resources/locales/ru-RU/dialog.json +5 -1
- package/apps/desktop/resources/locales/ru-RU/menu.json +16 -0
- package/apps/desktop/resources/locales/tr-TR/dialog.json +5 -1
- package/apps/desktop/resources/locales/tr-TR/menu.json +16 -0
- package/apps/desktop/resources/locales/vi-VN/dialog.json +5 -1
- package/apps/desktop/resources/locales/vi-VN/menu.json +16 -0
- package/apps/desktop/resources/locales/zh-TW/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-TW/menu.json +16 -0
- package/apps/desktop/scripts/update-test/README.md +15 -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/BackendProxyProtocolManager.ts +7 -6
- package/apps/desktop/src/main/core/infrastructure/StaticFileServerManager.ts +2 -2
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +38 -5
- 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 +4 -4
- package/apps/desktop/src/main/utils/protocol.ts +32 -32
- package/changelog/v1.json +14 -0
- package/locales/en-US/auth.json +5 -0
- package/locales/en-US/plugin.json +1 -0
- package/locales/zh-CN/auth.json +5 -0
- package/locales/zh-CN/discover.json +4 -4
- package/locales/zh-CN/plugin.json +1 -0
- package/package.json +6 -5
- package/packages/builtin-tool-agent-builder/src/ExecutionRuntime/index.ts +362 -30
- package/packages/builtin-tool-agent-builder/src/client/Intervention/InstallPlugin.tsx +28 -4
- 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/scripts/electronWorkflow/buildDesktopChannel.ts +135 -0
- package/src/app/[variants]/(main)/_layout/index.tsx +2 -0
- package/src/features/Conversation/ChatList/components/AutoScroll.tsx +3 -9
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +2 -6
- package/src/features/DesktopNavigationBridge/index.tsx +0 -9
- package/src/features/Electron/AuthRequiredModal/index.tsx +151 -0
- package/src/locales/default/auth.ts +6 -0
- package/src/locales/default/plugin.ts +1 -0
- package/src/utils/errorResponse.ts +21 -1
|
@@ -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
|
}
|
|
@@ -163,13 +163,14 @@ export class BackendProxyProtocolManager {
|
|
|
163
163
|
responseHeaders.set('Access-Control-Allow-Headers', '*');
|
|
164
164
|
responseHeaders.set('X-Src-Url', rewrittenUrl);
|
|
165
165
|
|
|
166
|
-
// Handle 401 Unauthorized: notify authorization required
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// 2. Token has expired
|
|
170
|
-
// 3. Token has been revoked server-side
|
|
166
|
+
// Handle 401 Unauthorized: only notify authorization required for real auth failures
|
|
167
|
+
// The server sets X-Auth-Required header for real authentication failures (e.g., token expired)
|
|
168
|
+
// Other 401 errors (e.g., invalid API keys) should not trigger re-authentication
|
|
171
169
|
if (upstreamResponse.status === 401) {
|
|
172
|
-
|
|
170
|
+
const authRequired = upstreamResponse.headers.get('X-Auth-Required') === 'true';
|
|
171
|
+
if (authRequired) {
|
|
172
|
+
this.notifyAuthorizationRequired();
|
|
173
|
+
}
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
return new Response(upstreamResponse.body, {
|
|
@@ -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) {
|
|
@@ -113,6 +113,26 @@ export class UpdaterManager {
|
|
|
113
113
|
|
|
114
114
|
logger.info(`${manual ? 'Manually checking' : 'Auto checking'} for updates...`);
|
|
115
115
|
|
|
116
|
+
// Log detailed updater configuration for debugging
|
|
117
|
+
const inferredChannel =
|
|
118
|
+
autoUpdater.channel ||
|
|
119
|
+
(autoUpdater.currentVersion?.prerelease?.[0]
|
|
120
|
+
? String(autoUpdater.currentVersion.prerelease[0])
|
|
121
|
+
: null);
|
|
122
|
+
|
|
123
|
+
logger.info('[Updater Config] Channel:', autoUpdater.channel);
|
|
124
|
+
logger.info('[Updater Config] inferredChannel:', inferredChannel);
|
|
125
|
+
logger.info('[Updater Config] allowPrerelease:', autoUpdater.allowPrerelease);
|
|
126
|
+
logger.info('[Updater Config] currentVersion:', autoUpdater.currentVersion?.version);
|
|
127
|
+
logger.info('[Updater Config] allowDowngrade:', autoUpdater.allowDowngrade);
|
|
128
|
+
logger.info('[Updater Config] autoDownload:', autoUpdater.autoDownload);
|
|
129
|
+
logger.info('[Updater Config] forceDevUpdateConfig:', autoUpdater.forceDevUpdateConfig);
|
|
130
|
+
logger.info('[Updater Config] Build channel from config:', channel);
|
|
131
|
+
logger.info('[Updater Config] isStableChannel:', isStableChannel);
|
|
132
|
+
logger.info('[Updater Config] UPDATE_SERVER_URL:', UPDATE_SERVER_URL || '(not set)');
|
|
133
|
+
logger.info('[Updater Config] usingFallbackProvider:', this.usingFallbackProvider);
|
|
134
|
+
logger.info('[Updater Config] GitHub config:', JSON.stringify(githubConfig));
|
|
135
|
+
|
|
116
136
|
// If manual check, notify renderer process about check start
|
|
117
137
|
if (manual) {
|
|
118
138
|
this.mainWindow.broadcast('manualUpdateCheckStart');
|
|
@@ -322,7 +342,7 @@ export class UpdaterManager {
|
|
|
322
342
|
/**
|
|
323
343
|
* Configure update provider based on channel
|
|
324
344
|
* - Stable channel + UPDATE_SERVER_URL: Use generic HTTP provider (S3) as primary, channel=stable
|
|
325
|
-
* - Other channels (beta/nightly) or no S3: Use GitHub provider, channel
|
|
345
|
+
* - Other channels (beta/nightly) or no S3: Use GitHub provider, channel unset (defaults to latest)
|
|
326
346
|
*
|
|
327
347
|
* Important: S3 has stable-mac.yml, GitHub has latest-mac.yml
|
|
328
348
|
*/
|
|
@@ -340,12 +360,15 @@ export class UpdaterManager {
|
|
|
340
360
|
url: UPDATE_SERVER_URL,
|
|
341
361
|
});
|
|
342
362
|
} else {
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
|
|
363
|
+
// GitHub provider:
|
|
364
|
+
// - stable: use default latest-mac.yml (GitHub uploads latest* only)
|
|
365
|
+
// - beta/nightly: leave channel unset so prerelease matching uses tag (e.g. next)
|
|
346
366
|
const reason = this.usingFallbackProvider ? '(fallback from S3)' : '';
|
|
347
367
|
logger.info(`Configuring GitHub provider for ${channel} channel ${reason}`);
|
|
348
|
-
|
|
368
|
+
if (autoUpdater.channel !== null) {
|
|
369
|
+
autoUpdater.channel = null;
|
|
370
|
+
}
|
|
371
|
+
logger.info('Channel left unset (defaults to latest-mac.yml for GitHub)');
|
|
349
372
|
|
|
350
373
|
// For beta/nightly channels, we need prerelease versions
|
|
351
374
|
const needPrerelease = channel !== 'stable';
|
|
@@ -406,6 +429,8 @@ export class UpdaterManager {
|
|
|
406
429
|
|
|
407
430
|
autoUpdater.on('checking-for-update', () => {
|
|
408
431
|
logger.info('[Updater] Checking for update...');
|
|
432
|
+
logger.info('[Updater] Current channel:', autoUpdater.channel);
|
|
433
|
+
logger.info('[Updater] Current allowPrerelease:', autoUpdater.allowPrerelease);
|
|
409
434
|
});
|
|
410
435
|
|
|
411
436
|
autoUpdater.on('update-available', (info) => {
|
|
@@ -437,6 +462,14 @@ export class UpdaterManager {
|
|
|
437
462
|
|
|
438
463
|
autoUpdater.on('error', async (err) => {
|
|
439
464
|
logger.error('Error in auto-updater:', err);
|
|
465
|
+
// Log configuration state when error occurs for debugging
|
|
466
|
+
logger.error('[Updater Error Context] Channel:', autoUpdater.channel);
|
|
467
|
+
logger.error('[Updater Error Context] allowPrerelease:', autoUpdater.allowPrerelease);
|
|
468
|
+
logger.error('[Updater Error Context] Build channel from config:', channel);
|
|
469
|
+
logger.error('[Updater Error Context] isStableChannel:', isStableChannel);
|
|
470
|
+
logger.error('[Updater Error Context] UPDATE_SERVER_URL:', UPDATE_SERVER_URL || '(not set)');
|
|
471
|
+
logger.error('[Updater Error Context] usingFallbackProvider:', this.usingFallbackProvider);
|
|
472
|
+
logger.error('[Updater Error Context] GitHub config:', JSON.stringify(githubConfig));
|
|
440
473
|
|
|
441
474
|
// Try fallback to GitHub if S3 failed
|
|
442
475
|
if (!this.usingFallbackProvider && isStableChannel && UPDATE_SERVER_URL) {
|
|
@@ -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
|
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import { isDev } from '@/const/env';
|
|
2
2
|
import { getDesktopEnv } from '@/env';
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// Update channel (stable, beta, alpha, etc.)
|
|
5
5
|
export const UPDATE_CHANNEL = getDesktopEnv().UPDATE_CHANNEL || 'stable';
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// Determine if stable channel
|
|
8
8
|
export const isStableChannel = UPDATE_CHANNEL === 'stable' || !UPDATE_CHANNEL;
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// Custom update server URL (for stable channel)
|
|
11
11
|
// e.g., https://releases.lobehub.com/stable
|
|
12
12
|
export const UPDATE_SERVER_URL = getDesktopEnv().UPDATE_SERVER_URL;
|
|
13
13
|
|
|
14
|
-
// GitHub
|
|
14
|
+
// GitHub configuration (for beta/nightly channels, or as fallback)
|
|
15
15
|
export const githubConfig = {
|
|
16
16
|
owner: 'lobehub',
|
|
17
17
|
repo: 'lobe-chat',
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
export const updaterConfig = {
|
|
21
|
-
//
|
|
21
|
+
// 应用Update configuration
|
|
22
22
|
app: {
|
|
23
|
-
//
|
|
23
|
+
// Whether to auto-check for updates
|
|
24
24
|
autoCheckUpdate: true,
|
|
25
|
-
//
|
|
25
|
+
// Whether to auto-download updates
|
|
26
26
|
autoDownloadUpdate: true,
|
|
27
|
-
//
|
|
28
|
-
checkUpdateInterval: 60 * 60 * 1000, // 1
|
|
27
|
+
// Update check interval (milliseconds)
|
|
28
|
+
checkUpdateInterval: 60 * 60 * 1000, // 1 hour
|
|
29
29
|
},
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Whether to enable application updates
|
|
32
32
|
enableAppUpdate: !isDev,
|
|
33
33
|
|
|
34
34
|
// 是否启用渲染层热更新
|