@lobehub/chat 1.82.10 → 1.83.1
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/.env.desktop +1 -2
- package/.github/workflows/{release-desktop.yml → desktop-pr-build.yml} +59 -137
- package/.github/workflows/release-desktop-beta.yml +194 -0
- package/CHANGELOG.md +42 -0
- package/apps/desktop/.i18nrc.js +31 -0
- package/apps/desktop/Development.md +47 -0
- package/apps/desktop/README.md +6 -0
- package/apps/desktop/build/Icon-beta.icns +0 -0
- package/apps/desktop/build/Icon-nightly.icns +0 -0
- package/apps/desktop/build/Icon.icns +0 -0
- package/apps/desktop/build/entitlements.mac.plist +12 -0
- package/apps/desktop/build/favicon.ico +0 -0
- package/apps/desktop/build/icon-beta.png +0 -0
- package/apps/desktop/build/icon-dev.png +0 -0
- package/apps/desktop/build/icon-nightly.ico +0 -0
- package/apps/desktop/build/icon-nightly.png +0 -0
- package/apps/desktop/build/icon.ico +0 -0
- package/apps/desktop/build/icon.png +0 -0
- package/apps/desktop/dev-app-update.yml +6 -0
- package/apps/desktop/electron-builder.js +92 -0
- package/apps/desktop/electron.vite.config.ts +40 -0
- package/apps/desktop/package.json +72 -0
- package/apps/desktop/pnpm-workspace.yaml +5 -0
- package/apps/desktop/resources/error.html +136 -0
- package/apps/desktop/resources/locales/ar/common.json +32 -0
- package/apps/desktop/resources/locales/ar/dialog.json +31 -0
- package/apps/desktop/resources/locales/ar/menu.json +70 -0
- package/apps/desktop/resources/locales/bg-BG/common.json +32 -0
- package/apps/desktop/resources/locales/bg-BG/dialog.json +31 -0
- package/apps/desktop/resources/locales/bg-BG/menu.json +70 -0
- package/apps/desktop/resources/locales/de-DE/common.json +32 -0
- package/apps/desktop/resources/locales/de-DE/dialog.json +31 -0
- package/apps/desktop/resources/locales/de-DE/menu.json +70 -0
- package/apps/desktop/resources/locales/en-US/common.json +32 -0
- package/apps/desktop/resources/locales/en-US/dialog.json +31 -0
- package/apps/desktop/resources/locales/en-US/menu.json +70 -0
- package/apps/desktop/resources/locales/es-ES/common.json +32 -0
- package/apps/desktop/resources/locales/es-ES/dialog.json +31 -0
- package/apps/desktop/resources/locales/es-ES/menu.json +70 -0
- package/apps/desktop/resources/locales/fa-IR/common.json +32 -0
- package/apps/desktop/resources/locales/fa-IR/dialog.json +31 -0
- package/apps/desktop/resources/locales/fa-IR/menu.json +70 -0
- package/apps/desktop/resources/locales/fr-FR/common.json +32 -0
- package/apps/desktop/resources/locales/fr-FR/dialog.json +31 -0
- package/apps/desktop/resources/locales/fr-FR/menu.json +70 -0
- package/apps/desktop/resources/locales/it-IT/common.json +32 -0
- package/apps/desktop/resources/locales/it-IT/dialog.json +31 -0
- package/apps/desktop/resources/locales/it-IT/menu.json +70 -0
- package/apps/desktop/resources/locales/ja-JP/common.json +32 -0
- package/apps/desktop/resources/locales/ja-JP/dialog.json +31 -0
- package/apps/desktop/resources/locales/ja-JP/menu.json +70 -0
- package/apps/desktop/resources/locales/ko-KR/common.json +32 -0
- package/apps/desktop/resources/locales/ko-KR/dialog.json +31 -0
- package/apps/desktop/resources/locales/ko-KR/menu.json +70 -0
- package/apps/desktop/resources/locales/nl-NL/common.json +32 -0
- package/apps/desktop/resources/locales/nl-NL/dialog.json +31 -0
- package/apps/desktop/resources/locales/nl-NL/menu.json +70 -0
- package/apps/desktop/resources/locales/pl-PL/common.json +32 -0
- package/apps/desktop/resources/locales/pl-PL/dialog.json +31 -0
- package/apps/desktop/resources/locales/pl-PL/menu.json +70 -0
- package/apps/desktop/resources/locales/pt-BR/common.json +32 -0
- package/apps/desktop/resources/locales/pt-BR/dialog.json +31 -0
- package/apps/desktop/resources/locales/pt-BR/menu.json +70 -0
- package/apps/desktop/resources/locales/ru-RU/common.json +32 -0
- package/apps/desktop/resources/locales/ru-RU/dialog.json +31 -0
- package/apps/desktop/resources/locales/ru-RU/menu.json +70 -0
- package/apps/desktop/resources/locales/tr-TR/common.json +32 -0
- package/apps/desktop/resources/locales/tr-TR/dialog.json +31 -0
- package/apps/desktop/resources/locales/tr-TR/menu.json +70 -0
- package/apps/desktop/resources/locales/vi-VN/common.json +32 -0
- package/apps/desktop/resources/locales/vi-VN/dialog.json +31 -0
- package/apps/desktop/resources/locales/vi-VN/menu.json +70 -0
- package/apps/desktop/resources/locales/zh-CN/common.json +32 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +31 -0
- package/apps/desktop/resources/locales/zh-CN/menu.json +70 -0
- package/apps/desktop/resources/locales/zh-TW/common.json +32 -0
- package/apps/desktop/resources/locales/zh-TW/dialog.json +31 -0
- package/apps/desktop/resources/locales/zh-TW/menu.json +70 -0
- package/apps/desktop/resources/splash.html +88 -0
- package/apps/desktop/scripts/i18nWorkflow/const.ts +18 -0
- package/apps/desktop/scripts/i18nWorkflow/genDefaultLocale.ts +35 -0
- package/apps/desktop/scripts/i18nWorkflow/genDiff.ts +57 -0
- package/apps/desktop/scripts/i18nWorkflow/index.ts +35 -0
- package/apps/desktop/scripts/i18nWorkflow/utils.ts +54 -0
- package/apps/desktop/scripts/pglite-server.ts +14 -0
- package/apps/desktop/src/common/routes.ts +78 -0
- package/apps/desktop/src/main/appBrowsers.ts +47 -0
- package/apps/desktop/src/main/const/dir.ts +29 -0
- package/apps/desktop/src/main/const/env.ts +3 -0
- package/apps/desktop/src/main/const/store.ts +22 -0
- package/apps/desktop/src/main/controllers/AuthCtr.ts +390 -0
- package/apps/desktop/src/main/controllers/BrowserWindowsCtr.ts +95 -0
- package/apps/desktop/src/main/controllers/DevtoolsCtr.ts +9 -0
- package/apps/desktop/src/main/controllers/LocalFileCtr.ts +380 -0
- package/apps/desktop/src/main/controllers/MenuCtr.ts +29 -0
- package/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +335 -0
- package/apps/desktop/src/main/controllers/RemoteServerSyncCtr.ts +321 -0
- package/apps/desktop/src/main/controllers/ShortcutCtr.ts +19 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +93 -0
- package/apps/desktop/src/main/controllers/UpdaterCtr.ts +43 -0
- package/apps/desktop/src/main/controllers/UploadFileCtr.ts +34 -0
- package/apps/desktop/src/main/controllers/_template.ts +9 -0
- package/apps/desktop/src/main/controllers/index.ts +58 -0
- package/apps/desktop/src/main/core/App.ts +370 -0
- package/apps/desktop/src/main/core/Browser.ts +345 -0
- package/apps/desktop/src/main/core/BrowserManager.ts +154 -0
- package/apps/desktop/src/main/core/I18nManager.ts +185 -0
- package/apps/desktop/src/main/core/IoCContainer.ts +12 -0
- package/apps/desktop/src/main/core/MenuManager.ts +64 -0
- package/apps/desktop/src/main/core/ShortcutManager.ts +173 -0
- package/apps/desktop/src/main/core/StoreManager.ts +89 -0
- package/apps/desktop/src/main/core/UpdaterManager.ts +321 -0
- package/apps/desktop/src/main/index.ts +5 -0
- package/apps/desktop/src/main/locales/default/common.ts +34 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +33 -0
- package/apps/desktop/src/main/locales/default/index.ts +11 -0
- package/apps/desktop/src/main/locales/default/menu.ts +72 -0
- package/apps/desktop/src/main/locales/resources.ts +35 -0
- package/apps/desktop/src/main/menus/impls/BaseMenuPlatform.ts +10 -0
- package/apps/desktop/src/main/menus/impls/linux.ts +243 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +360 -0
- package/apps/desktop/src/main/menus/impls/windows.ts +226 -0
- package/apps/desktop/src/main/menus/index.ts +34 -0
- package/apps/desktop/src/main/menus/types.ts +28 -0
- package/apps/desktop/src/main/modules/fileSearch/impl/macOS.ts +577 -0
- package/apps/desktop/src/main/modules/fileSearch/index.ts +23 -0
- package/apps/desktop/src/main/modules/fileSearch/type.ts +27 -0
- package/apps/desktop/src/main/modules/updater/configs.ts +22 -0
- package/apps/desktop/src/main/modules/updater/utils.ts +33 -0
- package/apps/desktop/src/main/services/fileSearchSrv.ts +35 -0
- package/apps/desktop/src/main/services/fileSrv.ts +255 -0
- package/apps/desktop/src/main/services/index.ts +9 -0
- package/apps/desktop/src/main/shortcuts/config.ts +18 -0
- package/apps/desktop/src/main/shortcuts/index.ts +1 -0
- package/apps/desktop/src/main/types/fileSearch.ts +51 -0
- package/apps/desktop/src/main/types/store.ts +14 -0
- package/apps/desktop/src/main/utils/file-system.ts +15 -0
- package/apps/desktop/src/main/utils/logger.ts +44 -0
- package/apps/desktop/src/main/utils/next-electron-rsc.ts +383 -0
- package/apps/desktop/src/preload/electronApi.ts +18 -0
- package/apps/desktop/src/preload/index.ts +14 -0
- package/apps/desktop/src/preload/invoke.ts +10 -0
- package/apps/desktop/src/preload/routeInterceptor.ts +162 -0
- package/apps/desktop/tsconfig.json +21 -0
- package/changelog/v1.json +14 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/remoteServer.ts +11 -4
- package/packages/electron-client-ipc/src/types/dataSync.ts +15 -0
- package/packages/electron-client-ipc/src/types/index.ts +2 -1
- package/packages/electron-client-ipc/src/types/proxyTRPCRequest.ts +21 -0
- package/packages/electron-server-ipc/src/const.ts +3 -3
- package/packages/electron-server-ipc/src/ipcClient.test.ts +7 -6
- package/packages/electron-server-ipc/src/ipcClient.ts +17 -8
- package/packages/electron-server-ipc/src/ipcServer.ts +7 -3
- package/scripts/electronWorkflow/setDesktopVersion.ts +60 -43
- package/src/app/[variants]/(main)/_layout/Desktop/index.tsx +1 -1
- package/src/components/Analytics/Desktop.tsx +19 -0
- package/src/components/Analytics/index.tsx +3 -0
- package/src/database/core/db-adaptor.ts +4 -1
- package/src/database/core/electron.ts +317 -0
- package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Mode.tsx → features/ElectronTitlebar/Connection/ConnectionMode.tsx} +24 -21
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Option.tsx +3 -5
- package/src/{app/[variants]/(main)/_layout/Desktop/ElectronTitlebar/Connection/Sync.tsx → features/ElectronTitlebar/Connection/RemoteStatus.tsx} +10 -7
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/index.tsx +4 -4
- package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateModal.tsx +2 -1
- package/src/libs/trpc/client/async.ts +6 -0
- package/src/libs/trpc/client/edge.ts +6 -0
- package/src/libs/trpc/client/helpers/desktopRemoteRPCFetch.ts +72 -0
- package/src/libs/trpc/client/index.ts +1 -0
- package/src/libs/trpc/client/lambda.ts +10 -1
- package/src/libs/trpc/client/tools.ts +6 -0
- package/src/server/globalConfig/index.ts +0 -3
- package/src/server/modules/ElectronIPCClient/index.ts +3 -1
- package/src/server/routers/desktop/index.ts +2 -0
- package/src/server/routers/desktop/mcp.ts +47 -0
- package/src/server/routers/lambda/user.ts +38 -23
- package/src/server/routers/tools/mcp.ts +0 -6
- package/src/services/electron/remoteServer.ts +4 -4
- package/src/services/mcp.ts +17 -7
- package/src/services/upload.ts +9 -0
- package/src/store/chat/slices/aiChat/actions/generateAIChat.ts +11 -2
- package/src/store/chat/slices/builtinTool/actions/localFile.ts +110 -53
- package/src/store/electron/actions/sync.ts +20 -19
- package/src/store/electron/initialState.ts +3 -3
- package/src/store/electron/selectors/sync.ts +6 -3
- package/src/store/electron/store.ts +2 -0
- package/src/store/file/slices/upload/action.ts +11 -3
- package/src/store/tool/selectors/tool.ts +10 -1
- package/src/utils/fetch/headers.ts +27 -0
- package/src/utils/fetch/index.ts +2 -0
- package/src/utils/fetch/request.ts +28 -0
- package/packages/electron-client-ipc/src/types/remoteServer.ts +0 -8
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/Connection/Waiting.tsx +0 -0
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/UpdateNotification.tsx +0 -0
- /package/src/{app/[variants]/(main)/_layout/Desktop → features}/ElectronTitlebar/index.tsx +0 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
import { Menu } from 'electron';
|
2
|
+
|
3
|
+
import { IMenuPlatform, MenuOptions, createMenuImpl } from '@/menus';
|
4
|
+
import { createLogger } from '@/utils/logger';
|
5
|
+
|
6
|
+
import type { App } from './App';
|
7
|
+
|
8
|
+
// Create logger
|
9
|
+
const logger = createLogger('core:MenuManager');
|
10
|
+
|
11
|
+
export default class MenuManager {
|
12
|
+
app: App;
|
13
|
+
private platformImpl: IMenuPlatform;
|
14
|
+
|
15
|
+
constructor(app: App) {
|
16
|
+
logger.debug('Initializing MenuManager');
|
17
|
+
this.app = app;
|
18
|
+
this.platformImpl = createMenuImpl(app);
|
19
|
+
}
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Initialize menus (mainly application menu)
|
23
|
+
*/
|
24
|
+
initialize(options?: MenuOptions) {
|
25
|
+
logger.info('Initializing application menu');
|
26
|
+
this.platformImpl.buildAndSetAppMenu(options);
|
27
|
+
}
|
28
|
+
|
29
|
+
/**
|
30
|
+
* Build and show context menu
|
31
|
+
*/
|
32
|
+
showContextMenu(type: string, data?: any) {
|
33
|
+
logger.debug(`Showing context menu of type: ${type}`);
|
34
|
+
const menu = this.platformImpl.buildContextMenu(type, data);
|
35
|
+
menu.popup(); // popup must be called in main process
|
36
|
+
return { success: true };
|
37
|
+
}
|
38
|
+
|
39
|
+
/**
|
40
|
+
* Build tray menu (usually called by tray manager)
|
41
|
+
*/
|
42
|
+
buildTrayMenu(): Menu {
|
43
|
+
logger.debug('Building tray menu');
|
44
|
+
return this.platformImpl.buildTrayMenu();
|
45
|
+
}
|
46
|
+
|
47
|
+
/**
|
48
|
+
* Refresh menus
|
49
|
+
*/
|
50
|
+
refreshMenus(options?: MenuOptions) {
|
51
|
+
logger.debug('Refreshing all menus');
|
52
|
+
this.platformImpl.refresh(options);
|
53
|
+
return { success: true };
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Rebuild and set application menu (e.g., when toggling dev menu visibility)
|
58
|
+
*/
|
59
|
+
rebuildAppMenu(options?: MenuOptions) {
|
60
|
+
logger.debug('Rebuilding application menu');
|
61
|
+
this.platformImpl.buildAndSetAppMenu(options);
|
62
|
+
return { success: true };
|
63
|
+
}
|
64
|
+
}
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import { globalShortcut } from 'electron';
|
2
|
+
|
3
|
+
import { DEFAULT_SHORTCUTS_CONFIG } from '@/shortcuts';
|
4
|
+
import { createLogger } from '@/utils/logger';
|
5
|
+
|
6
|
+
import type { App } from './App';
|
7
|
+
|
8
|
+
// Create logger
|
9
|
+
const logger = createLogger('core:ShortcutManager');
|
10
|
+
|
11
|
+
export class ShortcutManager {
|
12
|
+
private app: App;
|
13
|
+
private shortcuts: Map<string, () => void> = new Map();
|
14
|
+
private shortcutsConfig: Record<string, string> = {};
|
15
|
+
|
16
|
+
constructor(app: App) {
|
17
|
+
logger.debug('Initializing ShortcutManager');
|
18
|
+
this.app = app;
|
19
|
+
|
20
|
+
app.shortcutMethodMap.forEach((method, key) => {
|
21
|
+
this.shortcuts.set(key, method);
|
22
|
+
});
|
23
|
+
}
|
24
|
+
|
25
|
+
initialize() {
|
26
|
+
logger.info('Initializing global shortcuts');
|
27
|
+
// Load shortcuts configuration from storage
|
28
|
+
this.loadShortcutsConfig();
|
29
|
+
// Register configured shortcuts
|
30
|
+
this.registerConfiguredShortcuts();
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* Get shortcuts configuration
|
35
|
+
*/
|
36
|
+
getShortcutsConfig(): Record<string, string> {
|
37
|
+
return this.shortcutsConfig;
|
38
|
+
}
|
39
|
+
|
40
|
+
/**
|
41
|
+
* Update a single shortcut configuration
|
42
|
+
*/
|
43
|
+
updateShortcutConfig(id: string, accelerator: string): boolean {
|
44
|
+
try {
|
45
|
+
logger.debug(`Updating shortcut ${id} to ${accelerator}`);
|
46
|
+
// Update configuration
|
47
|
+
this.shortcutsConfig[id] = accelerator;
|
48
|
+
|
49
|
+
this.saveShortcutsConfig();
|
50
|
+
this.registerConfiguredShortcuts();
|
51
|
+
return true;
|
52
|
+
} catch (error) {
|
53
|
+
logger.error(`Error updating shortcut ${id}:`, error);
|
54
|
+
return false;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Register global shortcut
|
60
|
+
* @param accelerator Shortcut key combination
|
61
|
+
* @param callback Callback function
|
62
|
+
* @returns Whether registration was successful
|
63
|
+
*/
|
64
|
+
registerShortcut(accelerator: string, callback: () => void): boolean {
|
65
|
+
try {
|
66
|
+
// If already registered, unregister first
|
67
|
+
if (this.shortcuts.has(accelerator)) {
|
68
|
+
this.unregisterShortcut(accelerator);
|
69
|
+
}
|
70
|
+
|
71
|
+
// Register new shortcut
|
72
|
+
const success = globalShortcut.register(accelerator, callback);
|
73
|
+
|
74
|
+
if (success) {
|
75
|
+
this.shortcuts.set(accelerator, callback);
|
76
|
+
logger.debug(`Registered shortcut: ${accelerator}`);
|
77
|
+
} else {
|
78
|
+
logger.error(`Failed to register shortcut: ${accelerator}`);
|
79
|
+
}
|
80
|
+
|
81
|
+
return success;
|
82
|
+
} catch (error) {
|
83
|
+
logger.error(`Error registering shortcut: ${accelerator}`, error);
|
84
|
+
return false;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
/**
|
89
|
+
* Unregister global shortcut
|
90
|
+
* @param accelerator Shortcut key combination
|
91
|
+
*/
|
92
|
+
unregisterShortcut(accelerator: string): void {
|
93
|
+
try {
|
94
|
+
globalShortcut.unregister(accelerator);
|
95
|
+
this.shortcuts.delete(accelerator);
|
96
|
+
logger.debug(`Unregistered shortcut: ${accelerator}`);
|
97
|
+
} catch (error) {
|
98
|
+
logger.error(`Error unregistering shortcut: ${accelerator}`, error);
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* Check if a shortcut is already registered
|
104
|
+
* @param accelerator Shortcut key combination
|
105
|
+
* @returns Whether it is registered
|
106
|
+
*/
|
107
|
+
isRegistered(accelerator: string): boolean {
|
108
|
+
return globalShortcut.isRegistered(accelerator);
|
109
|
+
}
|
110
|
+
|
111
|
+
/**
|
112
|
+
* Unregister all shortcuts
|
113
|
+
*/
|
114
|
+
unregisterAll(): void {
|
115
|
+
globalShortcut.unregisterAll();
|
116
|
+
logger.info('Unregistered all shortcuts');
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* Load shortcuts configuration from storage
|
121
|
+
*/
|
122
|
+
private loadShortcutsConfig() {
|
123
|
+
try {
|
124
|
+
// Try to get configuration from storage
|
125
|
+
const config = this.app.storeManager.get('shortcuts');
|
126
|
+
|
127
|
+
// If no configuration, use default configuration
|
128
|
+
if (!config || Object.keys(config).length === 0) {
|
129
|
+
logger.debug('No shortcuts config found, using defaults');
|
130
|
+
this.shortcutsConfig = DEFAULT_SHORTCUTS_CONFIG;
|
131
|
+
this.saveShortcutsConfig();
|
132
|
+
} else {
|
133
|
+
this.shortcutsConfig = config;
|
134
|
+
}
|
135
|
+
|
136
|
+
logger.debug('Loaded shortcuts config:', this.shortcutsConfig);
|
137
|
+
} catch (error) {
|
138
|
+
logger.error('Error loading shortcuts config:', error);
|
139
|
+
this.shortcutsConfig = DEFAULT_SHORTCUTS_CONFIG;
|
140
|
+
this.saveShortcutsConfig();
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
/**
|
145
|
+
* Save shortcuts configuration to storage
|
146
|
+
*/
|
147
|
+
private saveShortcutsConfig() {
|
148
|
+
try {
|
149
|
+
this.app.storeManager.set('shortcuts', this.shortcutsConfig);
|
150
|
+
logger.debug('Saved shortcuts config');
|
151
|
+
} catch (error) {
|
152
|
+
logger.error('Error saving shortcuts config:', error);
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Register configured shortcuts
|
158
|
+
*/
|
159
|
+
private registerConfiguredShortcuts() {
|
160
|
+
// Unregister all shortcuts first
|
161
|
+
this.unregisterAll();
|
162
|
+
|
163
|
+
// Register each enabled shortcut
|
164
|
+
Object.entries(this.shortcutsConfig).forEach(([id, accelerator]) => {
|
165
|
+
logger.debug(`Registering shortcut '${id}' with ${accelerator}`);
|
166
|
+
|
167
|
+
const method = this.shortcuts.get(id);
|
168
|
+
if (accelerator && method) {
|
169
|
+
this.registerShortcut(accelerator, method);
|
170
|
+
}
|
171
|
+
});
|
172
|
+
}
|
173
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import Store from 'electron-store';
|
2
|
+
|
3
|
+
import { STORE_DEFAULTS, STORE_NAME } from '@/const/store';
|
4
|
+
import { ElectronMainStore, StoreKey } from '@/types/store';
|
5
|
+
import { makeSureDirExist } from '@/utils/file-system';
|
6
|
+
import { createLogger } from '@/utils/logger';
|
7
|
+
|
8
|
+
import { App } from './App';
|
9
|
+
|
10
|
+
// Create logger
|
11
|
+
const logger = createLogger('core:StoreManager');
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Application configuration storage manager
|
15
|
+
*/
|
16
|
+
export class StoreManager {
|
17
|
+
/**
|
18
|
+
* Global configuration store instance
|
19
|
+
*/
|
20
|
+
private store: Store<ElectronMainStore>;
|
21
|
+
private app: App;
|
22
|
+
|
23
|
+
constructor(app: App) {
|
24
|
+
logger.debug('Initializing StoreManager');
|
25
|
+
this.app = app;
|
26
|
+
this.store = new Store<ElectronMainStore>({
|
27
|
+
defaults: STORE_DEFAULTS,
|
28
|
+
name: STORE_NAME,
|
29
|
+
});
|
30
|
+
logger.info('StoreManager initialized with store name:', STORE_NAME);
|
31
|
+
|
32
|
+
const storagePath = this.store.get('storagePath');
|
33
|
+
logger.info('app storage path:', storagePath);
|
34
|
+
|
35
|
+
makeSureDirExist(storagePath);
|
36
|
+
}
|
37
|
+
|
38
|
+
/**
|
39
|
+
* Get configuration item
|
40
|
+
* @param key Configuration key
|
41
|
+
* @param defaultValue Default value
|
42
|
+
*/
|
43
|
+
get<K extends StoreKey>(key: K, defaultValue?: ElectronMainStore[K]): ElectronMainStore[K] {
|
44
|
+
logger.debug('Getting configuration value for key:', key);
|
45
|
+
return this.store.get(key, defaultValue as any);
|
46
|
+
}
|
47
|
+
|
48
|
+
/**
|
49
|
+
* Set configuration item
|
50
|
+
* @param key Configuration key
|
51
|
+
* @param value Configuration value
|
52
|
+
*/
|
53
|
+
set<K extends StoreKey>(key: K, value: ElectronMainStore[K]): void {
|
54
|
+
logger.debug('Setting configuration value for key:', key);
|
55
|
+
this.store.set(key, value);
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Delete configuration item
|
60
|
+
* @param key Configuration key
|
61
|
+
*/
|
62
|
+
delete(key: StoreKey): void {
|
63
|
+
logger.debug('Deleting configuration key:', key);
|
64
|
+
this.store.delete(key);
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Clear all storage
|
69
|
+
*/
|
70
|
+
clear(): void {
|
71
|
+
logger.warn('Clearing all store data');
|
72
|
+
this.store.clear();
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Check if a configuration item exists
|
77
|
+
* @param key Configuration key
|
78
|
+
*/
|
79
|
+
has(key: StoreKey): boolean {
|
80
|
+
const exists = this.store.has(key);
|
81
|
+
logger.debug('Checking if key exists:', key, exists);
|
82
|
+
return exists;
|
83
|
+
}
|
84
|
+
|
85
|
+
async openInEditor() {
|
86
|
+
logger.info('Opening store in editor');
|
87
|
+
await this.store.openInEditor();
|
88
|
+
}
|
89
|
+
}
|
@@ -0,0 +1,321 @@
|
|
1
|
+
import log from 'electron-log';
|
2
|
+
import { autoUpdater } from 'electron-updater';
|
3
|
+
|
4
|
+
import { isDev } from '@/const/env';
|
5
|
+
import { UPDATE_CHANNEL as channel, updaterConfig } from '@/modules/updater/configs';
|
6
|
+
import { createLogger } from '@/utils/logger';
|
7
|
+
|
8
|
+
import type { App as AppCore } from './App';
|
9
|
+
|
10
|
+
// Create logger
|
11
|
+
const logger = createLogger('core:UpdaterManager');
|
12
|
+
|
13
|
+
export class UpdaterManager {
|
14
|
+
private app: AppCore;
|
15
|
+
private checking: boolean = false;
|
16
|
+
private downloading: boolean = false;
|
17
|
+
private updateAvailable: boolean = false;
|
18
|
+
private isManualCheck: boolean = false;
|
19
|
+
|
20
|
+
constructor(app: AppCore) {
|
21
|
+
this.app = app;
|
22
|
+
|
23
|
+
// 设置日志
|
24
|
+
log.transports.file.level = 'info';
|
25
|
+
autoUpdater.logger = log;
|
26
|
+
|
27
|
+
logger.debug(`[Updater] Log file should be at: ${log.transports.file.getFile().path}`); // 打印路径
|
28
|
+
}
|
29
|
+
|
30
|
+
get mainWindow() {
|
31
|
+
return this.app.browserManager.getMainWindow();
|
32
|
+
}
|
33
|
+
|
34
|
+
public initialize = async () => {
|
35
|
+
logger.debug('Initializing UpdaterManager');
|
36
|
+
// If updates are disabled and in production environment, don't initialize updates
|
37
|
+
if (!updaterConfig.enableAppUpdate && !isDev) {
|
38
|
+
logger.info('App updates are disabled, skipping updater initialization');
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
|
42
|
+
// Configure autoUpdater
|
43
|
+
autoUpdater.autoDownload = false; // Set to false, we'll control downloads manually
|
44
|
+
autoUpdater.autoInstallOnAppQuit = false;
|
45
|
+
|
46
|
+
autoUpdater.channel = channel;
|
47
|
+
autoUpdater.allowPrerelease = channel !== 'stable';
|
48
|
+
autoUpdater.allowDowngrade = false;
|
49
|
+
|
50
|
+
// Enable test mode in development environment
|
51
|
+
if (isDev) {
|
52
|
+
logger.info(`Running in dev mode, forcing update check, channel: ${autoUpdater.channel}`);
|
53
|
+
// Allow testing updates in development environment
|
54
|
+
autoUpdater.forceDevUpdateConfig = true;
|
55
|
+
}
|
56
|
+
|
57
|
+
// Register events
|
58
|
+
this.registerEvents();
|
59
|
+
|
60
|
+
// If auto-check for updates is configured, set up periodic checks
|
61
|
+
if (updaterConfig.app.autoCheckUpdate) {
|
62
|
+
// Delay update check by 1 minute after startup to avoid network instability
|
63
|
+
setTimeout(() => this.checkForUpdates(), 60 * 1000);
|
64
|
+
|
65
|
+
// Set up periodic checks
|
66
|
+
setInterval(() => this.checkForUpdates(), updaterConfig.app.checkUpdateInterval);
|
67
|
+
}
|
68
|
+
|
69
|
+
// Log the channel and allowPrerelease values
|
70
|
+
logger.debug(
|
71
|
+
`Initialized with channel: ${autoUpdater.channel}, allowPrerelease: ${autoUpdater.allowPrerelease}`,
|
72
|
+
);
|
73
|
+
|
74
|
+
logger.info('UpdaterManager initialization completed');
|
75
|
+
};
|
76
|
+
|
77
|
+
/**
|
78
|
+
* Check for updates
|
79
|
+
* @param manual whether this is a manual check for updates
|
80
|
+
*/
|
81
|
+
public checkForUpdates = async ({ manual = false }: { manual?: boolean } = {}) => {
|
82
|
+
if (this.checking || this.downloading) return;
|
83
|
+
|
84
|
+
this.checking = true;
|
85
|
+
this.isManualCheck = manual;
|
86
|
+
logger.info(`${manual ? 'Manually checking' : 'Auto checking'} for updates...`);
|
87
|
+
|
88
|
+
// If manual check, notify renderer process about check start
|
89
|
+
if (manual) {
|
90
|
+
this.mainWindow.broadcast('manualUpdateCheckStart');
|
91
|
+
}
|
92
|
+
|
93
|
+
try {
|
94
|
+
await autoUpdater.checkForUpdates();
|
95
|
+
} catch (error) {
|
96
|
+
logger.error('Error checking for updates:', error.message);
|
97
|
+
|
98
|
+
// If manual check, notify renderer process about check error
|
99
|
+
if (manual) {
|
100
|
+
this.mainWindow.broadcast('updateError', (error as Error).message);
|
101
|
+
}
|
102
|
+
} finally {
|
103
|
+
this.checking = false;
|
104
|
+
}
|
105
|
+
};
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Download update
|
109
|
+
* @param manual whether this is a manual download
|
110
|
+
*/
|
111
|
+
public downloadUpdate = async (manual: boolean = false) => {
|
112
|
+
if (this.downloading || !this.updateAvailable) return;
|
113
|
+
|
114
|
+
this.downloading = true;
|
115
|
+
logger.info(`${manual ? 'Manually downloading' : 'Auto downloading'} update...`);
|
116
|
+
|
117
|
+
// If manual download or manual check, notify renderer process about download start
|
118
|
+
if (manual || this.isManualCheck) {
|
119
|
+
this.mainWindow.broadcast('updateDownloadStart');
|
120
|
+
}
|
121
|
+
|
122
|
+
try {
|
123
|
+
await autoUpdater.downloadUpdate();
|
124
|
+
} catch (error) {
|
125
|
+
this.downloading = false;
|
126
|
+
logger.error('Error downloading update:', error);
|
127
|
+
|
128
|
+
// If manual download or manual check, notify renderer process about download error
|
129
|
+
if (manual || this.isManualCheck) {
|
130
|
+
this.mainWindow.broadcast('updateError', (error as Error).message);
|
131
|
+
}
|
132
|
+
}
|
133
|
+
};
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Install update immediately
|
137
|
+
*/
|
138
|
+
public installNow = () => {
|
139
|
+
logger.info('Installing update now...');
|
140
|
+
|
141
|
+
// Mark application for exit
|
142
|
+
this.app.isQuiting = true;
|
143
|
+
|
144
|
+
// Delay installation by 1 second to ensure window is closed
|
145
|
+
autoUpdater.quitAndInstall();
|
146
|
+
};
|
147
|
+
|
148
|
+
/**
|
149
|
+
* Install update on next launch
|
150
|
+
*/
|
151
|
+
public installLater = () => {
|
152
|
+
logger.info('Update will be installed on next restart');
|
153
|
+
|
154
|
+
// Mark for installation on next launch, but don't exit application
|
155
|
+
autoUpdater.autoInstallOnAppQuit = true;
|
156
|
+
|
157
|
+
// Notify renderer process that update will be installed on next launch
|
158
|
+
this.mainWindow.broadcast('updateWillInstallLater');
|
159
|
+
};
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Test mode: Simulate update available
|
163
|
+
* Only for use in development environment
|
164
|
+
*/
|
165
|
+
public simulateUpdateAvailable = () => {
|
166
|
+
if (!isDev) return;
|
167
|
+
|
168
|
+
logger.info('Simulating update available...');
|
169
|
+
|
170
|
+
const mainWindow = this.mainWindow;
|
171
|
+
// Simulate a new version update
|
172
|
+
const mockUpdateInfo = {
|
173
|
+
releaseDate: new Date().toISOString(),
|
174
|
+
releaseNotes: ` #### Version 1.0.0 Release Notes
|
175
|
+
- Added some great new features
|
176
|
+
- Fixed bugs affecting usability
|
177
|
+
- Optimized overall application performance
|
178
|
+
- Updated dependency libraries
|
179
|
+
`,
|
180
|
+
version: '1.0.0',
|
181
|
+
};
|
182
|
+
|
183
|
+
// Set update available state
|
184
|
+
this.updateAvailable = true;
|
185
|
+
|
186
|
+
// Notify renderer process
|
187
|
+
if (this.isManualCheck) {
|
188
|
+
mainWindow.broadcast('manualUpdateAvailable', mockUpdateInfo);
|
189
|
+
} else {
|
190
|
+
// In auto-check mode, directly simulate download
|
191
|
+
this.simulateDownloadProgress();
|
192
|
+
}
|
193
|
+
};
|
194
|
+
|
195
|
+
/**
|
196
|
+
* Test mode: Simulate update downloaded
|
197
|
+
* Only for use in development environment
|
198
|
+
*/
|
199
|
+
public simulateUpdateDownloaded = () => {
|
200
|
+
if (!isDev) return;
|
201
|
+
|
202
|
+
logger.info('Simulating update downloaded...');
|
203
|
+
|
204
|
+
const mainWindow = this.app.browserManager.getMainWindow();
|
205
|
+
if (mainWindow) {
|
206
|
+
// Simulate a new version update
|
207
|
+
const mockUpdateInfo = {
|
208
|
+
releaseDate: new Date().toISOString(),
|
209
|
+
releaseNotes: ` #### Version 1.0.0 Release Notes
|
210
|
+
- Added some great new features
|
211
|
+
- Fixed bugs affecting usability
|
212
|
+
- Optimized overall application performance
|
213
|
+
- Updated dependency libraries
|
214
|
+
`,
|
215
|
+
version: '1.0.0',
|
216
|
+
};
|
217
|
+
|
218
|
+
// Set download state
|
219
|
+
this.downloading = false;
|
220
|
+
|
221
|
+
// Notify renderer process
|
222
|
+
mainWindow.broadcast('updateDownloaded', mockUpdateInfo);
|
223
|
+
}
|
224
|
+
};
|
225
|
+
|
226
|
+
/**
|
227
|
+
* Test mode: Simulate update download progress
|
228
|
+
* Only for use in development environment
|
229
|
+
*/
|
230
|
+
public simulateDownloadProgress = () => {
|
231
|
+
if (!isDev) return;
|
232
|
+
|
233
|
+
logger.info('Simulating download progress...');
|
234
|
+
|
235
|
+
const mainWindow = this.app.browserManager.getMainWindow();
|
236
|
+
|
237
|
+
// Set download state
|
238
|
+
this.downloading = true;
|
239
|
+
|
240
|
+
// Only broadcast download start event if manual check
|
241
|
+
if (this.isManualCheck) {
|
242
|
+
mainWindow.broadcast('updateDownloadStart');
|
243
|
+
}
|
244
|
+
|
245
|
+
// Simulate progress updates
|
246
|
+
let progress = 0;
|
247
|
+
const interval = setInterval(() => {
|
248
|
+
progress += 10;
|
249
|
+
|
250
|
+
if (
|
251
|
+
progress <= 100 && // Only broadcast download progress if manual check
|
252
|
+
this.isManualCheck
|
253
|
+
) {
|
254
|
+
mainWindow.broadcast('updateDownloadProgress', {
|
255
|
+
bytesPerSecond: 1024 * 1024,
|
256
|
+
percent: progress, // 1MB/s
|
257
|
+
total: 1024 * 1024 * 100, // 100MB
|
258
|
+
transferred: 1024 * 1024 * progress, // Progress * 1MB
|
259
|
+
});
|
260
|
+
}
|
261
|
+
|
262
|
+
if (progress >= 100) {
|
263
|
+
clearInterval(interval);
|
264
|
+
this.simulateUpdateDownloaded();
|
265
|
+
}
|
266
|
+
}, 300);
|
267
|
+
};
|
268
|
+
|
269
|
+
private registerEvents() {
|
270
|
+
logger.debug('Registering updater events');
|
271
|
+
|
272
|
+
autoUpdater.on('checking-for-update', () => {
|
273
|
+
logger.info('[Updater] Checking for update...');
|
274
|
+
});
|
275
|
+
|
276
|
+
autoUpdater.on('update-available', (info) => {
|
277
|
+
logger.info(`Update available: ${info.version}`);
|
278
|
+
this.updateAvailable = true;
|
279
|
+
|
280
|
+
if (this.isManualCheck) {
|
281
|
+
this.mainWindow.broadcast('manualUpdateAvailable', info);
|
282
|
+
} else {
|
283
|
+
// If it's an automatic check, start downloading automatically
|
284
|
+
logger.info('Auto check found update, starting download automatically...');
|
285
|
+
this.downloadUpdate();
|
286
|
+
}
|
287
|
+
});
|
288
|
+
|
289
|
+
autoUpdater.on('update-not-available', (info) => {
|
290
|
+
logger.info(`Update not available. Current: ${info.version}`);
|
291
|
+
if (this.isManualCheck) {
|
292
|
+
this.mainWindow.broadcast('manualUpdateNotAvailable', info);
|
293
|
+
}
|
294
|
+
});
|
295
|
+
|
296
|
+
autoUpdater.on('error', (err) => {
|
297
|
+
logger.error('Error in auto-updater:', err);
|
298
|
+
if (this.isManualCheck) {
|
299
|
+
this.mainWindow.broadcast('updateError', err.message);
|
300
|
+
}
|
301
|
+
});
|
302
|
+
|
303
|
+
autoUpdater.on('download-progress', (progressObj) => {
|
304
|
+
logger.debug(
|
305
|
+
`Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred}/${progressObj.total})`,
|
306
|
+
);
|
307
|
+
if (this.isManualCheck) {
|
308
|
+
this.mainWindow.broadcast('updateDownloadProgress', progressObj);
|
309
|
+
}
|
310
|
+
});
|
311
|
+
|
312
|
+
autoUpdater.on('update-downloaded', (info) => {
|
313
|
+
logger.info(`Update downloaded: ${info.version}`);
|
314
|
+
this.downloading = false;
|
315
|
+
// Always notify about downloaded update
|
316
|
+
this.mainWindow.broadcast('updateDownloaded', info);
|
317
|
+
});
|
318
|
+
|
319
|
+
logger.debug('Updater events registered');
|
320
|
+
}
|
321
|
+
}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
const common = {
|
2
|
+
actions: {
|
3
|
+
add: '添加',
|
4
|
+
back: '返回',
|
5
|
+
cancel: '取消',
|
6
|
+
close: '关闭',
|
7
|
+
confirm: '确认',
|
8
|
+
delete: '删除',
|
9
|
+
edit: '编辑',
|
10
|
+
more: '更多',
|
11
|
+
next: '下一步',
|
12
|
+
ok: '确定',
|
13
|
+
previous: '上一步',
|
14
|
+
refresh: '刷新',
|
15
|
+
remove: '移除',
|
16
|
+
retry: '重试',
|
17
|
+
save: '保存',
|
18
|
+
search: '搜索',
|
19
|
+
submit: '提交',
|
20
|
+
},
|
21
|
+
app: {
|
22
|
+
description: '你的 AI 助手协作平台',
|
23
|
+
name: 'LobeHub',
|
24
|
+
},
|
25
|
+
status: {
|
26
|
+
error: '错误',
|
27
|
+
info: '信息',
|
28
|
+
loading: '加载中',
|
29
|
+
success: '成功',
|
30
|
+
warning: '警告',
|
31
|
+
},
|
32
|
+
};
|
33
|
+
|
34
|
+
export default common;
|