@lobehub/chat 1.103.1 → 1.104.0
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 +60 -0
- package/apps/desktop/build/icon-beta.ico +0 -0
- package/apps/desktop/build/icon-dev.ico +0 -0
- package/apps/desktop/build/icon-nightly.ico +0 -0
- package/apps/desktop/build/icon.ico +0 -0
- package/apps/desktop/electron.vite.config.ts +4 -2
- package/apps/desktop/package.json +1 -0
- package/apps/desktop/src/main/appBrowsers.ts +2 -2
- package/apps/desktop/src/main/const/env.ts +5 -4
- package/apps/desktop/src/main/const/store.ts +1 -0
- package/apps/desktop/src/main/const/theme.ts +11 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +1 -1
- package/apps/desktop/src/main/controllers/NotificationCtr.ts +2 -4
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +9 -1
- package/apps/desktop/src/main/controllers/SystemCtr.ts +4 -0
- package/apps/desktop/src/main/controllers/TrayMenuCtr.ts +5 -9
- package/apps/desktop/src/main/controllers/__tests__/ShortcutCtr.test.ts +14 -11
- package/apps/desktop/src/main/controllers/index.ts +1 -1
- package/apps/desktop/src/main/core/App.ts +9 -10
- package/apps/desktop/src/main/core/{Browser.ts → browser/Browser.ts} +129 -88
- package/apps/desktop/src/main/core/{BrowserManager.ts → browser/BrowserManager.ts} +13 -3
- package/apps/desktop/src/main/core/{StaticFileServerManager.ts → infrastructure/StaticFileServerManager.ts} +13 -7
- package/apps/desktop/src/main/core/{StoreManager.ts → infrastructure/StoreManager.ts} +1 -1
- package/apps/desktop/src/main/core/{UpdaterManager.ts → infrastructure/UpdaterManager.ts} +1 -1
- package/apps/desktop/src/main/core/{MenuManager.ts → ui/MenuManager.ts} +2 -2
- package/apps/desktop/src/main/core/{ShortcutManager.ts → ui/ShortcutManager.ts} +78 -6
- package/apps/desktop/src/main/core/{Tray.ts → ui/Tray.ts} +61 -59
- package/apps/desktop/src/main/core/{TrayManager.ts → ui/TrayManager.ts} +5 -5
- package/apps/desktop/src/main/shortcuts/config.ts +4 -2
- package/apps/desktop/src/main/types/store.ts +1 -0
- package/changelog/v1.json +21 -0
- package/docs/development/basic/add-new-image-model.mdx +162 -0
- package/docs/development/basic/add-new-image-model.zh-CN.mdx +162 -0
- package/docs/usage/providers/fal.mdx +1 -1
- package/docs/usage/providers/fal.zh-CN.mdx +1 -1
- package/locales/ar/hotkey.json +10 -4
- package/locales/ar/setting.json +12 -1
- package/locales/bg-BG/hotkey.json +10 -4
- package/locales/bg-BG/setting.json +12 -1
- package/locales/de-DE/hotkey.json +10 -4
- package/locales/de-DE/setting.json +12 -1
- package/locales/en-US/hotkey.json +10 -4
- package/locales/en-US/setting.json +12 -1
- package/locales/es-ES/hotkey.json +10 -4
- package/locales/es-ES/setting.json +12 -1
- package/locales/fa-IR/hotkey.json +10 -4
- package/locales/fa-IR/setting.json +12 -1
- package/locales/fr-FR/hotkey.json +10 -4
- package/locales/fr-FR/setting.json +12 -1
- package/locales/it-IT/hotkey.json +10 -4
- package/locales/it-IT/setting.json +12 -1
- package/locales/ja-JP/hotkey.json +10 -4
- package/locales/ja-JP/setting.json +12 -1
- package/locales/ko-KR/hotkey.json +10 -4
- package/locales/ko-KR/setting.json +12 -1
- package/locales/nl-NL/hotkey.json +10 -4
- package/locales/nl-NL/setting.json +12 -1
- package/locales/pl-PL/hotkey.json +10 -4
- package/locales/pl-PL/setting.json +12 -1
- package/locales/pt-BR/hotkey.json +10 -4
- package/locales/pt-BR/setting.json +12 -1
- package/locales/ru-RU/hotkey.json +10 -4
- package/locales/ru-RU/setting.json +12 -1
- package/locales/tr-TR/hotkey.json +10 -4
- package/locales/tr-TR/setting.json +12 -1
- package/locales/vi-VN/hotkey.json +10 -4
- package/locales/vi-VN/setting.json +12 -1
- package/locales/zh-CN/hotkey.json +10 -4
- package/locales/zh-CN/setting.json +12 -1
- package/locales/zh-TW/hotkey.json +10 -4
- package/locales/zh-TW/setting.json +12 -1
- package/package.json +66 -66
- package/packages/electron-client-ipc/src/events/shortcut.ts +3 -1
- package/packages/electron-client-ipc/src/types/shortcut.ts +11 -0
- package/src/app/[variants]/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +3 -1
- package/src/app/[variants]/(main)/chat/(workspace)/features/AgentSettings/index.tsx +4 -2
- package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
- package/src/app/[variants]/(main)/image/features/GenerationFeed/BatchItem.tsx +39 -3
- package/src/app/[variants]/(main)/image/features/GenerationFeed/ReferenceImages.tsx +122 -0
- package/src/app/[variants]/(main)/settings/hotkey/features/Conversation.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/features/Desktop.tsx +92 -0
- package/src/app/[variants]/(main)/settings/hotkey/features/Essential.tsx +3 -11
- package/src/app/[variants]/(main)/settings/hotkey/page.tsx +3 -0
- package/src/config/aiModels/fal.ts +31 -7
- package/src/config/aiModels/openai.ts +10 -1
- package/src/const/desktop.ts +9 -0
- package/src/const/hotkeys.ts +20 -16
- package/src/features/ElectronTitlebar/WinControl/index.tsx +85 -90
- package/src/features/ElectronTitlebar/hooks/useWatchThemeUpdate.ts +10 -5
- package/src/features/ImageTopicPanel/index.tsx +0 -1
- package/src/features/PluginDevModal/index.tsx +3 -1
- package/src/features/User/UserPanel/useMenu.tsx +2 -2
- package/src/features/User/__tests__/UserAvatar.test.tsx +5 -4
- package/src/libs/model-runtime/fal/index.ts +1 -1
- package/src/libs/model-runtime/types/image.ts +1 -1
- package/src/libs/model-runtime/utils/openaiCompatibleFactory/index.ts +1 -1
- package/src/libs/model-runtime/utils/response.ts +2 -0
- package/src/libs/model-runtime/utils/streams/google-ai.test.ts +46 -0
- package/src/libs/model-runtime/utils/streams/google-ai.ts +4 -4
- package/src/libs/model-runtime/utils/streams/vertex-ai.ts +6 -8
- package/src/libs/standard-parameters/{meta-schema.test.ts → index.test.ts} +1 -1
- package/src/libs/standard-parameters/index.ts +152 -1
- package/src/locales/default/hotkey.ts +13 -5
- package/src/locales/default/setting.ts +11 -0
- package/src/server/ld.test.ts +4 -3
- package/src/server/routers/async/image.ts +1 -1
- package/src/services/__tests__/chat.test.ts +3 -4
- package/src/services/electron/settings.ts +19 -1
- package/src/store/chat/slices/message/selectors.test.ts +2 -3
- package/src/store/chat/slices/plugin/action.test.ts +2 -1
- package/src/store/electron/actions/settings.ts +42 -1
- package/src/store/electron/initialState.ts +9 -1
- package/src/store/electron/selectors/__tests__/desktopState.test.ts +6 -17
- package/src/store/electron/selectors/hotkey.ts +11 -0
- package/src/store/electron/selectors/index.ts +1 -0
- package/src/store/image/slices/generationConfig/action.test.ts +2 -2
- package/src/store/image/slices/generationConfig/action.ts +1 -1
- package/src/store/image/slices/generationConfig/hooks.test.ts +2 -2
- package/src/store/image/slices/generationConfig/hooks.ts +1 -4
- package/src/store/image/slices/generationConfig/initialState.ts +2 -2
- package/src/store/image/slices/generationConfig/selectors.test.ts +2 -2
- package/src/store/image/slices/generationConfig/selectors.ts +1 -1
- package/src/store/user/slices/auth/selectors.test.ts +3 -2
- package/src/types/generation/index.ts +1 -0
- package/src/types/hotkey.ts +18 -4
- package/docs/development/basic/add-new-ai-image-model.mdx +0 -36
- package/docs/development/basic/add-new-ai-image-model.zh-CN.mdx +0 -0
- package/src/config/paramsSchemas/fal/flux-kontext-dev.ts +0 -8
- package/src/config/paramsSchemas/fal/flux-pro-kontext.ts +0 -11
- package/src/config/paramsSchemas/fal/flux-schnell.ts +0 -9
- package/src/config/paramsSchemas/fal/imagen4.ts +0 -10
- package/src/config/paramsSchemas/openai/gpt-image-1.ts +0 -10
- package/src/libs/standard-parameters/meta-schema.ts +0 -147
- /package/apps/desktop/src/main/core/{I18nManager.ts → infrastructure/I18nManager.ts} +0 -0
- /package/apps/desktop/src/main/core/{IoCContainer.ts → infrastructure/IoCContainer.ts} +0 -0
@@ -6,13 +6,21 @@ import {
|
|
6
6
|
nativeTheme,
|
7
7
|
screen,
|
8
8
|
} from 'electron';
|
9
|
-
import os from 'node:os';
|
10
9
|
import { join } from 'node:path';
|
11
10
|
|
11
|
+
import { buildDir, preloadDir, resourcesDir } from '@/const/dir';
|
12
|
+
import { isDev, isWindows } from '@/const/env';
|
13
|
+
import {
|
14
|
+
BACKGROUND_DARK,
|
15
|
+
BACKGROUND_LIGHT,
|
16
|
+
SYMBOL_COLOR_DARK,
|
17
|
+
SYMBOL_COLOR_LIGHT,
|
18
|
+
THEME_CHANGE_DELAY,
|
19
|
+
TITLE_BAR_HEIGHT,
|
20
|
+
} from '@/const/theme';
|
12
21
|
import { createLogger } from '@/utils/logger';
|
13
22
|
|
14
|
-
import {
|
15
|
-
import type { App } from './App';
|
23
|
+
import type { App } from '../App';
|
16
24
|
|
17
25
|
// Create logger
|
18
26
|
const logger = createLogger('core:Browser');
|
@@ -20,9 +28,6 @@ const logger = createLogger('core:Browser');
|
|
20
28
|
export interface BrowserWindowOpts extends BrowserWindowConstructorOptions {
|
21
29
|
devTools?: boolean;
|
22
30
|
height?: number;
|
23
|
-
/**
|
24
|
-
* URL
|
25
|
-
*/
|
26
31
|
identifier: string;
|
27
32
|
keepAlive?: boolean;
|
28
33
|
parentIdentifier?: string;
|
@@ -34,38 +39,18 @@ export interface BrowserWindowOpts extends BrowserWindowConstructorOptions {
|
|
34
39
|
|
35
40
|
export default class Browser {
|
36
41
|
private app: App;
|
37
|
-
|
38
|
-
/**
|
39
|
-
* Internal electron window
|
40
|
-
*/
|
41
42
|
private _browserWindow?: BrowserWindow;
|
42
|
-
|
43
|
+
private themeListenerSetup = false;
|
43
44
|
private stopInterceptHandler;
|
44
|
-
/**
|
45
|
-
* Identifier
|
46
|
-
*/
|
47
45
|
identifier: string;
|
48
|
-
|
49
|
-
/**
|
50
|
-
* Options at creation
|
51
|
-
*/
|
52
46
|
options: BrowserWindowOpts;
|
53
|
-
|
54
|
-
/**
|
55
|
-
* Key for storing window state in storeManager
|
56
|
-
*/
|
57
47
|
private readonly windowStateKey: string;
|
58
48
|
|
59
|
-
/**
|
60
|
-
* Method to expose window externally
|
61
|
-
*/
|
62
49
|
get browserWindow() {
|
63
50
|
return this.retrieveOrInitialize();
|
64
51
|
}
|
65
|
-
|
66
52
|
get webContents() {
|
67
53
|
if (this._browserWindow.isDestroyed()) return null;
|
68
|
-
|
69
54
|
return this._browserWindow.webContents;
|
70
55
|
}
|
71
56
|
|
@@ -86,6 +71,101 @@ export default class Browser {
|
|
86
71
|
this.retrieveOrInitialize();
|
87
72
|
}
|
88
73
|
|
74
|
+
/**
|
75
|
+
* Get platform-specific theme configuration for window creation
|
76
|
+
*/
|
77
|
+
private getPlatformThemeConfig(isDarkMode?: boolean): Record<string, any> {
|
78
|
+
const darkMode = isDarkMode ?? nativeTheme.shouldUseDarkColors;
|
79
|
+
|
80
|
+
if (isWindows) {
|
81
|
+
return this.getWindowsThemeConfig(darkMode);
|
82
|
+
}
|
83
|
+
|
84
|
+
return {};
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Get Windows-specific theme configuration
|
89
|
+
*/
|
90
|
+
private getWindowsThemeConfig(isDarkMode: boolean) {
|
91
|
+
return {
|
92
|
+
backgroundColor: isDarkMode ? BACKGROUND_DARK : BACKGROUND_LIGHT,
|
93
|
+
icon: isDev ? join(buildDir, 'icon-dev.ico') : undefined,
|
94
|
+
titleBarOverlay: {
|
95
|
+
color: isDarkMode ? BACKGROUND_DARK : BACKGROUND_LIGHT,
|
96
|
+
height: TITLE_BAR_HEIGHT,
|
97
|
+
symbolColor: isDarkMode ? SYMBOL_COLOR_DARK : SYMBOL_COLOR_LIGHT,
|
98
|
+
},
|
99
|
+
titleBarStyle: 'hidden' as const,
|
100
|
+
};
|
101
|
+
}
|
102
|
+
|
103
|
+
private setupThemeListener(): void {
|
104
|
+
if (this.themeListenerSetup) return;
|
105
|
+
|
106
|
+
nativeTheme.on('updated', this.handleThemeChange);
|
107
|
+
this.themeListenerSetup = true;
|
108
|
+
}
|
109
|
+
|
110
|
+
private handleThemeChange = (): void => {
|
111
|
+
logger.debug(`[${this.identifier}] System theme changed, reapplying visual effects.`);
|
112
|
+
setTimeout(() => {
|
113
|
+
this.applyVisualEffects();
|
114
|
+
}, THEME_CHANGE_DELAY);
|
115
|
+
};
|
116
|
+
|
117
|
+
/**
|
118
|
+
* Handle application theme mode change (called from BrowserManager)
|
119
|
+
*/
|
120
|
+
handleAppThemeChange = (): void => {
|
121
|
+
logger.debug(`[${this.identifier}] App theme mode changed, reapplying visual effects.`);
|
122
|
+
setTimeout(() => {
|
123
|
+
this.applyVisualEffects();
|
124
|
+
}, THEME_CHANGE_DELAY);
|
125
|
+
};
|
126
|
+
|
127
|
+
private applyVisualEffects(): void {
|
128
|
+
if (!this._browserWindow || this._browserWindow.isDestroyed()) return;
|
129
|
+
|
130
|
+
logger.debug(`[${this.identifier}] Applying visual effects for platform`);
|
131
|
+
const isDarkMode = this.isDarkMode;
|
132
|
+
|
133
|
+
try {
|
134
|
+
if (isWindows) {
|
135
|
+
this.applyWindowsVisualEffects(isDarkMode);
|
136
|
+
}
|
137
|
+
|
138
|
+
logger.debug(
|
139
|
+
`[${this.identifier}] Visual effects applied successfully (dark mode: ${isDarkMode})`,
|
140
|
+
);
|
141
|
+
} catch (error) {
|
142
|
+
logger.error(`[${this.identifier}] Failed to apply visual effects:`, error);
|
143
|
+
}
|
144
|
+
}
|
145
|
+
|
146
|
+
private applyWindowsVisualEffects(isDarkMode: boolean): void {
|
147
|
+
const config = this.getWindowsThemeConfig(isDarkMode);
|
148
|
+
|
149
|
+
this._browserWindow.setBackgroundColor(config.backgroundColor);
|
150
|
+
this._browserWindow.setTitleBarOverlay(config.titleBarOverlay);
|
151
|
+
}
|
152
|
+
|
153
|
+
private cleanupThemeListener(): void {
|
154
|
+
if (this.themeListenerSetup) {
|
155
|
+
// Note: nativeTheme listeners are global, consider using a centralized theme manager
|
156
|
+
nativeTheme.off('updated', this.handleThemeChange);
|
157
|
+
// for multiple windows to avoid duplicate listeners
|
158
|
+
this.themeListenerSetup = false;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
private get isDarkMode() {
|
163
|
+
const themeMode = this.app.storeManager.get('themeMode');
|
164
|
+
if (themeMode === 'auto') return nativeTheme.shouldUseDarkColors;
|
165
|
+
|
166
|
+
return themeMode === 'dark';
|
167
|
+
}
|
168
|
+
|
89
169
|
loadUrl = async (path: string) => {
|
90
170
|
const initUrl = this.app.nextServerUrl + path;
|
91
171
|
|
@@ -203,6 +283,7 @@ export default class Browser {
|
|
203
283
|
destroy() {
|
204
284
|
logger.debug(`Destroying window instance: ${this.identifier}`);
|
205
285
|
this.stopInterceptHandler?.();
|
286
|
+
this.cleanupThemeListener();
|
206
287
|
this._browserWindow = undefined;
|
207
288
|
}
|
208
289
|
|
@@ -228,45 +309,37 @@ export default class Browser {
|
|
228
309
|
`[${this.identifier}] Saved window state (only size used): ${JSON.stringify(savedState)}`,
|
229
310
|
);
|
230
311
|
|
231
|
-
const { isWindows11, isWindows } = this.getWindowsVersion();
|
232
312
|
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
233
313
|
|
234
314
|
const browserWindow = new BrowserWindow({
|
235
315
|
...res,
|
236
|
-
...(isWindows
|
237
|
-
? {
|
238
|
-
titleBarStyle: 'hidden',
|
239
|
-
}
|
240
|
-
: {}),
|
241
|
-
...(isWindows11
|
242
|
-
? {
|
243
|
-
backgroundMaterial: isDarkMode ? 'mica' : 'acrylic',
|
244
|
-
vibrancy: 'under-window',
|
245
|
-
visualEffectState: 'active',
|
246
|
-
}
|
247
|
-
: {}),
|
248
316
|
autoHideMenuBar: true,
|
249
317
|
backgroundColor: '#00000000',
|
318
|
+
darkTheme: isDarkMode,
|
250
319
|
frame: false,
|
251
|
-
|
252
320
|
height: savedState?.height || height,
|
253
|
-
// Always create hidden first
|
254
321
|
show: false,
|
255
322
|
title,
|
256
|
-
|
323
|
+
vibrancy: 'sidebar',
|
324
|
+
visualEffectState: 'active',
|
257
325
|
webPreferences: {
|
258
|
-
|
259
|
-
// https://www.electronjs.org/docs/tutorial/context-isolation
|
326
|
+
backgroundThrottling: false,
|
260
327
|
contextIsolation: true,
|
261
328
|
preload: join(preloadDir, 'index.js'),
|
262
329
|
},
|
263
330
|
width: savedState?.width || width,
|
331
|
+
...this.getPlatformThemeConfig(isDarkMode),
|
264
332
|
});
|
265
333
|
|
266
334
|
this._browserWindow = browserWindow;
|
267
335
|
logger.debug(`[${this.identifier}] BrowserWindow instance created.`);
|
268
336
|
|
269
|
-
|
337
|
+
// Initialize theme listener for this window to handle theme changes
|
338
|
+
this.setupThemeListener();
|
339
|
+
logger.debug(`[${this.identifier}] Theme listener setup and applying initial visual effects.`);
|
340
|
+
|
341
|
+
// Apply initial visual effects
|
342
|
+
this.applyVisualEffects();
|
270
343
|
|
271
344
|
logger.debug(`[${this.identifier}] Setting up nextInterceptor.`);
|
272
345
|
this.stopInterceptHandler = this.app.nextInterceptor({
|
@@ -320,8 +393,9 @@ export default class Browser {
|
|
320
393
|
} catch (error) {
|
321
394
|
logger.error(`[${this.identifier}] Failed to save window state on quit:`, error);
|
322
395
|
}
|
323
|
-
// Need to clean up intercept handler
|
396
|
+
// Need to clean up intercept handler and theme manager
|
324
397
|
this.stopInterceptHandler?.();
|
398
|
+
this.cleanupThemeListener();
|
325
399
|
return;
|
326
400
|
}
|
327
401
|
|
@@ -355,8 +429,9 @@ export default class Browser {
|
|
355
429
|
} catch (error) {
|
356
430
|
logger.error(`[${this.identifier}] Failed to save window state on close:`, error);
|
357
431
|
}
|
358
|
-
// Need to clean up intercept handler
|
432
|
+
// Need to clean up intercept handler and theme manager
|
359
433
|
this.stopInterceptHandler?.();
|
434
|
+
this.cleanupThemeListener();
|
360
435
|
}
|
361
436
|
});
|
362
437
|
|
@@ -387,16 +462,6 @@ export default class Browser {
|
|
387
462
|
this._browserWindow.webContents.send(channel, data);
|
388
463
|
};
|
389
464
|
|
390
|
-
applyVisualEffects() {
|
391
|
-
// Windows 11 can use this new API
|
392
|
-
if (this._browserWindow) {
|
393
|
-
logger.debug(`[${this.identifier}] Setting window background material for Windows 11`);
|
394
|
-
const isDarkMode = nativeTheme.shouldUseDarkColors;
|
395
|
-
this._browserWindow?.setBackgroundMaterial(isDarkMode ? 'mica' : 'acrylic');
|
396
|
-
this._browserWindow?.setVibrancy('under-window');
|
397
|
-
}
|
398
|
-
}
|
399
|
-
|
400
465
|
toggleVisible() {
|
401
466
|
logger.debug(`Toggling visibility for window: ${this.identifier}`);
|
402
467
|
if (this._browserWindow.isVisible() && this._browserWindow.isFocused()) {
|
@@ -407,35 +472,11 @@ export default class Browser {
|
|
407
472
|
}
|
408
473
|
}
|
409
474
|
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
version: null,
|
417
|
-
};
|
418
|
-
}
|
419
|
-
|
420
|
-
// 获取操作系统版本(如 "10.0.22621")
|
421
|
-
const release = os.release();
|
422
|
-
const parts = release.split('.');
|
423
|
-
|
424
|
-
// 主版本和次版本
|
425
|
-
const majorVersion = parseInt(parts[0], 10);
|
426
|
-
const minorVersion = parseInt(parts[1], 10);
|
427
|
-
|
428
|
-
// 构建号是第三部分
|
429
|
-
const buildNumber = parseInt(parts[2], 10);
|
430
|
-
|
431
|
-
// Windows 11 的构建号从 22000 开始
|
432
|
-
const isWindows11 = majorVersion === 10 && minorVersion === 0 && buildNumber >= 22_000;
|
433
|
-
|
434
|
-
return {
|
435
|
-
buildNumber,
|
436
|
-
isWindows: true,
|
437
|
-
isWindows11,
|
438
|
-
version: release,
|
439
|
-
};
|
475
|
+
/**
|
476
|
+
* Manually reapply visual effects (useful for fixing lost effects after window state changes)
|
477
|
+
*/
|
478
|
+
reapplyVisualEffects(): void {
|
479
|
+
logger.debug(`[${this.identifier}] Manually reapplying visual effects via Browser.`);
|
480
|
+
this.applyVisualEffects();
|
440
481
|
}
|
441
482
|
}
|
@@ -3,15 +3,15 @@ import { WebContents } from 'electron';
|
|
3
3
|
|
4
4
|
import { createLogger } from '@/utils/logger';
|
5
5
|
|
6
|
-
import { AppBrowsersIdentifiers, appBrowsers } from '
|
7
|
-
import type { App } from '
|
6
|
+
import { AppBrowsersIdentifiers, appBrowsers } from '../../appBrowsers';
|
7
|
+
import type { App } from '../App';
|
8
8
|
import type { BrowserWindowOpts } from './Browser';
|
9
9
|
import Browser from './Browser';
|
10
10
|
|
11
11
|
// Create logger
|
12
12
|
const logger = createLogger('core:BrowserManager');
|
13
13
|
|
14
|
-
export
|
14
|
+
export class BrowserManager {
|
15
15
|
app: App;
|
16
16
|
|
17
17
|
browsers: Map<AppBrowsersIdentifiers, Browser> = new Map();
|
@@ -194,4 +194,14 @@ export default class BrowserManager {
|
|
194
194
|
getIdentifierByWebContents(webContents: WebContents): AppBrowsersIdentifiers | null {
|
195
195
|
return this.webContentsMap.get(webContents) || null;
|
196
196
|
}
|
197
|
+
|
198
|
+
/**
|
199
|
+
* Handle application theme mode changes and reapply visual effects to all windows
|
200
|
+
*/
|
201
|
+
handleAppThemeChange(): void {
|
202
|
+
logger.debug('Handling app theme change for all browser windows');
|
203
|
+
this.browsers.forEach((browser) => {
|
204
|
+
browser.handleAppThemeChange();
|
205
|
+
});
|
206
|
+
}
|
197
207
|
}
|
@@ -5,7 +5,7 @@ import { LOCAL_STORAGE_URL_PREFIX } from '@/const/dir';
|
|
5
5
|
import FileService from '@/services/fileSrv';
|
6
6
|
import { createLogger } from '@/utils/logger';
|
7
7
|
|
8
|
-
import type { App } from '
|
8
|
+
import type { App } from '../App';
|
9
9
|
|
10
10
|
const logger = createLogger('core:StaticFileServerManager');
|
11
11
|
|
@@ -54,9 +54,12 @@ export class StaticFileServerManager {
|
|
54
54
|
try {
|
55
55
|
// 使用 get-port-please 获取可用端口
|
56
56
|
this.serverPort = await getPort({
|
57
|
-
|
58
|
-
ports: [33251, 33252, 33253, 33254, 33255], // 备用端口
|
57
|
+
// 备用端口
|
59
58
|
host: '127.0.0.1',
|
59
|
+
|
60
|
+
port: 33_250,
|
61
|
+
// 首选端口
|
62
|
+
ports: [33_251, 33_252, 33_253, 33_254, 33_255],
|
60
63
|
});
|
61
64
|
|
62
65
|
logger.debug(`Found available port: ${this.serverPort}`);
|
@@ -64,7 +67,7 @@ export class StaticFileServerManager {
|
|
64
67
|
return new Promise((resolve, reject) => {
|
65
68
|
const server = createServer(async (req, res) => {
|
66
69
|
// 设置请求超时
|
67
|
-
req.setTimeout(
|
70
|
+
req.setTimeout(30_000, () => {
|
68
71
|
logger.warn('Request timeout, closing connection');
|
69
72
|
if (!res.destroyed && !res.headersSent) {
|
70
73
|
res.writeHead(408, { 'Content-Type': 'text/plain' });
|
@@ -155,10 +158,13 @@ export class StaticFileServerManager {
|
|
155
158
|
|
156
159
|
// 设置响应头
|
157
160
|
res.writeHead(200, {
|
158
|
-
|
159
|
-
'
|
160
|
-
|
161
|
+
// 缓存一年
|
162
|
+
'Access-Control-Allow-Origin': 'http://localhost:*',
|
163
|
+
|
164
|
+
'Cache-Control': 'public, max-age=31536000',
|
165
|
+
// 允许 localhost 的任意端口
|
161
166
|
'Content-Length': Buffer.byteLength(fileResult.content),
|
167
|
+
'Content-Type': fileResult.mimeType,
|
162
168
|
});
|
163
169
|
|
164
170
|
// 发送文件内容
|
@@ -5,7 +5,7 @@ import { ElectronMainStore, StoreKey } from '@/types/store';
|
|
5
5
|
import { makeSureDirExist } from '@/utils/file-system';
|
6
6
|
import { createLogger } from '@/utils/logger';
|
7
7
|
|
8
|
-
import { App } from '
|
8
|
+
import { App } from '../App';
|
9
9
|
|
10
10
|
// Create logger
|
11
11
|
const logger = createLogger('core:StoreManager');
|
@@ -5,7 +5,7 @@ import { isDev } from '@/const/env';
|
|
5
5
|
import { UPDATE_CHANNEL as channel, updaterConfig } from '@/modules/updater/configs';
|
6
6
|
import { createLogger } from '@/utils/logger';
|
7
7
|
|
8
|
-
import type { App as AppCore } from '
|
8
|
+
import type { App as AppCore } from '../App';
|
9
9
|
|
10
10
|
// Create logger
|
11
11
|
const logger = createLogger('core:UpdaterManager');
|
@@ -3,12 +3,12 @@ import { Menu } from 'electron';
|
|
3
3
|
import { IMenuPlatform, MenuOptions, createMenuImpl } from '@/menus';
|
4
4
|
import { createLogger } from '@/utils/logger';
|
5
5
|
|
6
|
-
import type { App } from '
|
6
|
+
import type { App } from '../App';
|
7
7
|
|
8
8
|
// Create logger
|
9
9
|
const logger = createLogger('core:MenuManager');
|
10
10
|
|
11
|
-
export
|
11
|
+
export class MenuManager {
|
12
12
|
app: App;
|
13
13
|
private platformImpl: IMenuPlatform;
|
14
14
|
|
@@ -3,11 +3,22 @@ import { globalShortcut } from 'electron';
|
|
3
3
|
import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
|
4
4
|
import { createLogger } from '@/utils/logger';
|
5
5
|
|
6
|
-
import type { App } from '
|
6
|
+
import type { App } from '../App';
|
7
7
|
|
8
8
|
// Create logger
|
9
9
|
const logger = createLogger('core:ShortcutManager');
|
10
10
|
|
11
|
+
export interface ShortcutUpdateResult {
|
12
|
+
errorType?:
|
13
|
+
| 'INVALID_ID'
|
14
|
+
| 'INVALID_FORMAT'
|
15
|
+
| 'NO_MODIFIER'
|
16
|
+
| 'CONFLICT'
|
17
|
+
| 'SYSTEM_OCCUPIED'
|
18
|
+
| 'UNKNOWN';
|
19
|
+
success: boolean;
|
20
|
+
}
|
21
|
+
|
11
22
|
export class ShortcutManager {
|
12
23
|
private app: App;
|
13
24
|
private shortcuts: Map<string, () => void> = new Map();
|
@@ -40,18 +51,73 @@ export class ShortcutManager {
|
|
40
51
|
/**
|
41
52
|
* Update a single shortcut configuration
|
42
53
|
*/
|
43
|
-
updateShortcutConfig(id: string, accelerator: string):
|
54
|
+
updateShortcutConfig(id: string, accelerator: string): ShortcutUpdateResult {
|
44
55
|
try {
|
45
56
|
logger.debug(`Updating shortcut ${id} to ${accelerator}`);
|
46
|
-
|
47
|
-
|
57
|
+
|
58
|
+
// 1. 检查 ID 是否有效
|
59
|
+
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
|
60
|
+
logger.error(`Invalid shortcut ID: ${id}`);
|
61
|
+
return { errorType: 'INVALID_ID', success: false };
|
62
|
+
}
|
63
|
+
|
64
|
+
// 2. 基本格式校验
|
65
|
+
if (!accelerator || typeof accelerator !== 'string' || accelerator.trim() === '') {
|
66
|
+
logger.error(`Invalid accelerator format: ${accelerator}`);
|
67
|
+
return { errorType: 'INVALID_FORMAT', success: false };
|
68
|
+
}
|
69
|
+
|
70
|
+
const cleanAccelerator = accelerator.trim().toLowerCase();
|
71
|
+
|
72
|
+
// 3. 检查是否包含 + 号(修饰键格式)
|
73
|
+
if (!cleanAccelerator.includes('+')) {
|
74
|
+
logger.error(
|
75
|
+
`Invalid accelerator format: ${cleanAccelerator}. Must contain modifier keys like 'CommandOrControl+E'`,
|
76
|
+
);
|
77
|
+
return { errorType: 'INVALID_FORMAT', success: false };
|
78
|
+
}
|
79
|
+
|
80
|
+
// 4. 检查是否有基本的修饰键
|
81
|
+
const hasModifier = ['CommandOrControl', 'Command', 'Ctrl', 'Alt', 'Shift'].some((modifier) =>
|
82
|
+
cleanAccelerator.includes(modifier.toLowerCase()),
|
83
|
+
);
|
84
|
+
|
85
|
+
if (!hasModifier) {
|
86
|
+
logger.error(`Invalid accelerator format: ${cleanAccelerator}. Must contain modifier keys`);
|
87
|
+
return { errorType: 'NO_MODIFIER', success: false };
|
88
|
+
}
|
89
|
+
|
90
|
+
// 5. 检查冲突
|
91
|
+
for (const [existingId, existingAccelerator] of Object.entries(this.shortcutsConfig)) {
|
92
|
+
if (
|
93
|
+
existingId !== id &&
|
94
|
+
typeof existingAccelerator === 'string' &&
|
95
|
+
existingAccelerator.toLowerCase() === cleanAccelerator
|
96
|
+
) {
|
97
|
+
logger.error(`Shortcut conflict: ${cleanAccelerator} already used by ${existingId}`);
|
98
|
+
return { errorType: 'CONFLICT', success: false };
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
// 6. 尝试注册测试(检查是否被系统占用)
|
103
|
+
const testSuccess = globalShortcut.register(cleanAccelerator, () => {});
|
104
|
+
if (!testSuccess) {
|
105
|
+
logger.error(`Shortcut ${cleanAccelerator} is already registered by system or other app`);
|
106
|
+
return { errorType: 'SYSTEM_OCCUPIED', success: false };
|
107
|
+
} else {
|
108
|
+
// 测试成功,立即取消注册
|
109
|
+
globalShortcut.unregister(cleanAccelerator);
|
110
|
+
}
|
111
|
+
|
112
|
+
// 7. 更新配置
|
113
|
+
this.shortcutsConfig[id] = cleanAccelerator;
|
48
114
|
|
49
115
|
this.saveShortcutsConfig();
|
50
116
|
this.registerConfiguredShortcuts();
|
51
|
-
return true;
|
117
|
+
return { success: true };
|
52
118
|
} catch (error) {
|
53
119
|
logger.error(`Error updating shortcut ${id}:`, error);
|
54
|
-
return false;
|
120
|
+
return { errorType: 'UNKNOWN', success: false };
|
55
121
|
}
|
56
122
|
}
|
57
123
|
|
@@ -164,6 +230,12 @@ export class ShortcutManager {
|
|
164
230
|
Object.entries(this.shortcutsConfig).forEach(([id, accelerator]) => {
|
165
231
|
logger.debug(`Registering shortcut '${id}' with ${accelerator}`);
|
166
232
|
|
233
|
+
// 只注册在 DEFAULT_SHORTCUTS_CONFIG 中存在的快捷键
|
234
|
+
if (!DEFAULT_SHORTCUTS_CONFIG[id]) {
|
235
|
+
logger.debug(`Skipping shortcut '${id}' - not found in DEFAULT_SHORTCUTS_CONFIG`);
|
236
|
+
return;
|
237
|
+
}
|
238
|
+
|
167
239
|
const method = this.shortcuts.get(id);
|
168
240
|
if (accelerator && method) {
|
169
241
|
this.registerShortcut(accelerator, method);
|