@becrafter/prompt-manager 0.0.18 → 0.0.19

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.
Files changed (50) hide show
  1. package/IFLOW.md +175 -0
  2. package/README.md +1 -1
  3. package/app/desktop/assets/icons/icon.icns +0 -0
  4. package/app/desktop/assets/icons/icon.ico +0 -0
  5. package/app/desktop/assets/icons/icon_1024x1024.png +0 -0
  6. package/app/desktop/assets/icons/icon_128x128.png +0 -0
  7. package/app/desktop/assets/icons/icon_16x16.png +0 -0
  8. package/app/desktop/assets/icons/icon_24x24.png +0 -0
  9. package/app/desktop/assets/icons/icon_256x256.png +0 -0
  10. package/app/desktop/assets/icons/icon_32x32.png +0 -0
  11. package/app/desktop/assets/icons/icon_48x48.png +0 -0
  12. package/app/desktop/assets/icons/icon_512x512.png +0 -0
  13. package/app/desktop/assets/icons/icon_64x64.png +0 -0
  14. package/app/desktop/assets/icons/icon_96x96.png +0 -0
  15. package/app/desktop/assets/templates/about.html +147 -0
  16. package/app/desktop/main.js +160 -732
  17. package/app/desktop/package-lock.json +567 -534
  18. package/app/desktop/package.json +45 -10
  19. package/app/desktop/preload.js +7 -0
  20. package/app/desktop/src/core/error-handler.js +108 -0
  21. package/app/desktop/src/core/event-emitter.js +84 -0
  22. package/app/desktop/src/core/logger.js +108 -0
  23. package/app/desktop/src/core/state-manager.js +125 -0
  24. package/app/desktop/src/services/module-loader.js +193 -0
  25. package/app/desktop/src/services/runtime-manager.js +152 -0
  26. package/app/desktop/src/services/service-manager.js +169 -0
  27. package/app/desktop/src/services/update-manager.js +268 -0
  28. package/app/desktop/src/ui/about-dialog-manager.js +208 -0
  29. package/app/desktop/src/ui/tray-manager.js +202 -0
  30. package/app/desktop/src/utils/icon-manager.js +141 -0
  31. package/app/desktop/src/utils/path-utils.js +58 -0
  32. package/app/desktop/src/utils/resource-paths.js +72 -0
  33. package/app/desktop/src/utils/template-renderer.js +284 -0
  34. package/app/desktop/src/utils/version-utils.js +59 -0
  35. package/examples/prompts/engineer/engineer-professional.yaml +92 -0
  36. package/examples/prompts/engineer/laowang-engineer.yaml +132 -0
  37. package/examples/prompts/engineer/nekomata-engineer.yaml +123 -0
  38. package/examples/prompts/engineer/ojousama-engineer.yaml +124 -0
  39. package/examples/prompts/workflow/sixstep-workflow.yaml +192 -0
  40. package/package.json +9 -3
  41. package/packages/admin-ui/admin.html +1 -1
  42. package/packages/resources/tools/filesystem/filesystem.tool.js +184 -0
  43. package/packages/resources/tools/index.js +16 -0
  44. package/packages/server/mcp/mcp.handler.js +108 -9
  45. package/packages/server/mcp/mcp.server.js +126 -27
  46. package/packages/server/mcp/toolx.handler.js +131 -0
  47. package/packages/server/utils/config.js +1 -1
  48. package/scripts/build-icons.js +105 -0
  49. package/scripts/icns-builder/package.json +12 -0
  50. /package/app/desktop/assets/{icon.png → icons/icon.png} +0 -0
@@ -1,786 +1,214 @@
1
- const electron = require('electron');
2
- const { app, BrowserWindow, Tray, Menu, clipboard, dialog, nativeImage, shell } = electron;
3
- const path = require('path');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const { pathToFileURL } = require('url');
7
- const tar = require('tar');
8
- const { spawn } = require('child_process');
9
-
10
- // 设置日志文件
11
- const logFilePath = path.join(app.getPath('userData'), 'prompt-manager-desktop.log');
12
- let logStream;
1
+ /**
2
+ * 应用主入口
3
+ * 重构后的主程序,遵循SRP、KISS、DRY、YAGNI原则
4
+ */
13
5
 
14
- // 初始化日志流
15
- function initLogStream() {
16
- try {
17
- // 确保日志目录存在
18
- const logDir = path.dirname(logFilePath);
19
- if (!fs.existsSync(logDir)) {
20
- fs.mkdirSync(logDir, { recursive: true });
21
- }
22
-
23
- // 创建或追加到日志文件
24
- logStream = fs.createWriteStream(logFilePath, { flags: 'a' });
25
- console.log = function(...args) {
26
- const message = `[${new Date().toISOString()}] [INFO] ${args.join(' ')}\n`;
27
- process.stdout.write(message);
28
- if (logStream) {
29
- logStream.write(message);
30
- }
31
- };
32
-
33
- console.error = function(...args) {
34
- const message = `[${new Date().toISOString()}] [ERROR] ${args.join(' ')}\n`;
35
- process.stderr.write(message);
36
- if (logStream) {
37
- logStream.write(message);
38
- }
39
- };
40
-
41
- console.warn = function(...args) {
42
- const message = `[${new Date().toISOString()}] [WARN] ${args.join(' ')}\n`;
43
- process.stdout.write(message);
44
- if (logStream) {
45
- logStream.write(message);
46
- }
47
- };
48
-
49
- console.debug = function(...args) {
50
- const message = `[${new Date().toISOString()}] [DEBUG] ${args.join(' ')}\n`;
51
- process.stdout.write(message);
52
- if (logStream) {
53
- logStream.write(message);
54
- }
55
- };
56
-
57
- console.log(`Logging initialized. Log file: ${logFilePath}`);
58
- } catch (error) {
59
- console.error('Failed to initialize logging:', error);
60
- }
61
- }
62
-
63
- let tray = null;
64
- let adminWindow = null;
65
- let serviceState = 'stopped';
66
- let currentServerState = null;
67
- let serverModule = null;
68
- let serverModuleVersion = 0;
69
- let serverModuleLoading = null;
70
- let isQuitting = false;
71
- let runtimeServerRoot = null;
72
- let startFailureCount = 0;
73
-
74
- const desktopPackageJson = JSON.parse(
75
- fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8')
76
- );
77
-
78
- function resolveBaseServerRoot() {
79
- return path.resolve(__dirname, '..', '..');
80
- }
81
-
82
- async function ensureRuntimeServerRoot() {
83
- if (runtimeServerRoot) {
84
- console.log('Using cached runtimeServerRoot:', runtimeServerRoot);
85
- return runtimeServerRoot;
86
- }
87
-
88
- if (!app.isPackaged) {
89
- console.log('App is not packaged, using development server root');
90
- runtimeServerRoot = resolveBaseServerRoot();
91
- console.log('Development server root:', runtimeServerRoot);
92
- return runtimeServerRoot;
93
- }
6
+ const { app, BrowserWindow, ipcMain } = require('electron');
7
+ const path = require('path');
94
8
 
95
- console.log('App is packaged, setting up runtime server root');
96
- console.log('process.resourcesPath:', process.resourcesPath);
97
- console.log('app.getPath("userData"):', app.getPath('userData'));
98
-
99
- const packagedRoot = path.join(process.resourcesPath, 'prompt-manager');
100
- const runtimeRoot = path.join(app.getPath('userData'), 'prompt-manager');
101
-
102
- console.log('packagedRoot:', packagedRoot);
103
- console.log('runtimeRoot:', runtimeRoot);
9
+ // 导入管理器模块
10
+ const AppState = require('./src/core/state-manager');
11
+ const Logger = require('./src/core/logger');
12
+ const ErrorHandler = require('./src/core/error-handler');
13
+ const RuntimeManager = require('./src/services/runtime-manager');
14
+ const ModuleLoader = require('./src/services/module-loader');
15
+ const ServiceManager = require('./src/services/service-manager');
16
+ const TrayManager = require('./src/ui/tray-manager');
17
+ const UpdateManager = require('./src/services/update-manager');
18
+ const AboutDialogManager = require('./src/ui/about-dialog-manager');
19
+ const IconManager = require('./src/utils/icon-manager');
20
+
21
+ class PromptManagerApp {
22
+ constructor() {
23
+ this.stateManager = new AppState();
24
+ this.logger = new Logger({ debugEnabled: false });
25
+ this.errorHandler = new ErrorHandler(this.logger);
26
+ this.runtimeManager = new RuntimeManager(this.logger, this.errorHandler);
27
+ this.moduleLoader = new ModuleLoader(this.logger, this.errorHandler);
28
+ this.iconManager = new IconManager();
29
+ this.serviceManager = new ServiceManager(this.logger, this.errorHandler, this.moduleLoader);
30
+ this.updateManager = new UpdateManager(this.logger, this.errorHandler, this.runtimeManager);
31
+ this.aboutDialogManager = new AboutDialogManager(this.logger, this.runtimeManager, this.iconManager);
32
+ this.trayManager = new TrayManager(this.logger, this.errorHandler, this.iconManager);
33
+
34
+ this.isInitialized = false;
35
+ }
36
+
37
+ async initialize() {
38
+ if (this.isInitialized) return;
104
39
 
105
- try {
106
- // 检查打包的资源目录是否存在
107
40
  try {
108
- await fs.promises.access(packagedRoot, fs.constants.F_OK);
109
- console.log('Packaged root exists');
110
- } catch (error) {
111
- console.error('Packaged root does not exist:', packagedRoot);
112
- throw new Error(`Packaged root not found: ${packagedRoot}`);
113
- }
114
-
115
- // 检查运行时目录是否存在
116
- let needsInstall = false;
117
- try {
118
- await fs.promises.access(runtimeRoot, fs.constants.F_OK);
119
- console.log('Runtime root already exists');
41
+ this.logger.info('Initializing Prompt Manager Desktop Application');
42
+
43
+ // 初始化日志系统
44
+ await this.logger.initialize();
45
+
46
+ // 设置应用菜单
47
+ this.setupApplicationMenu();
48
+
49
+ // 初始化系统托盘
50
+ await this.trayManager.initialize(this.stateManager);
51
+
52
+ // 设置事件监听
53
+ this.setupEventListeners();
54
+
55
+ // 确保运行时环境
56
+ const serverRoot = await this.runtimeManager.ensureRuntimeEnvironment();
57
+ this.stateManager.set('runtimeRoot', serverRoot);
58
+
59
+ // 启动服务
60
+ await this.startService();
61
+
62
+ this.isInitialized = true;
63
+ this.logger.info('Application initialized successfully');
120
64
 
121
- // 检查是否需要安装依赖
122
- try {
123
- await fs.promises.access(path.join(runtimeRoot, 'node_modules'), fs.constants.F_OK);
124
- console.log('node_modules already exists in runtime root');
125
- } catch (error) {
126
- console.log('node_modules not found in runtime root, will install dependencies');
127
- needsInstall = true;
128
- }
129
65
  } catch (error) {
130
- console.log('Runtime root does not exist, creating and copying files');
131
- await fs.promises.mkdir(runtimeRoot, { recursive: true });
132
- console.log('Created runtime root directory');
66
+ this.logger.error('Application initialization failed', error);
67
+ this.errorHandler.handleError('APP_INIT_FAILED', error, {
68
+ logFilePath: this.logger.getLogFilePath()
69
+ });
133
70
 
134
- // 复制文件并显示进度
135
- console.log('Copying files from packaged root to runtime root...');
136
- await fs.promises.cp(packagedRoot, runtimeRoot, { recursive: true });
137
- console.log('File copy completed');
138
- needsInstall = true;
139
- }
140
-
141
- // 如果需要安装依赖
142
- if (needsInstall) {
143
- console.log('Installing dependencies in runtime root');
144
- try {
145
- await installServerDependencies(runtimeRoot);
146
- console.log('Dependencies installed successfully');
147
- } catch (error) {
148
- console.error('Failed to install dependencies:', error);
149
- dialog.showErrorBox('依赖安装失败', `无法安装服务器依赖: ${error.message}\n\n请查看日志文件: ${logFilePath}`);
150
- throw error;
151
- }
71
+ throw error;
152
72
  }
153
- } catch (error) {
154
- console.error('Error in ensureRuntimeServerRoot:', error);
155
- dialog.showErrorBox('服务器初始化失败', `无法设置服务器运行环境: ${error.message}\n\n请查看日志文件: ${logFilePath}`);
156
- throw error;
157
- }
158
-
159
- runtimeServerRoot = runtimeRoot;
160
- console.log('Final runtimeServerRoot:', runtimeServerRoot);
161
- return runtimeServerRoot;
162
- }
163
-
164
- async function loadServerModule(options = {}) {
165
- console.log('loadServerModule called with options:', options);
166
- console.log('Current serviceState:', serviceState);
167
- console.log('serverModule exists:', !!serverModule);
168
- console.log('serverModuleLoading exists:', !!serverModuleLoading);
169
-
170
- // 如果强制重新加载或服务状态异常,则清理缓存
171
- if (options.forceReload || serviceState === 'error') {
172
- console.log('Forcing module reload, clearing cache');
173
- serverModule = null;
174
- serverModuleVersion += 1;
175
- serverModuleLoading = null;
176
73
  }
177
74
 
178
- if (serverModule) {
179
- console.log('Returning cached server module');
180
- return serverModule;
181
- }
182
-
183
- if (serverModuleLoading) {
184
- console.log('Module loading in progress, returning promise');
185
- return serverModuleLoading;
186
- }
187
-
188
- try {
189
- const serverRoot = await ensureRuntimeServerRoot();
190
- console.log('Server root resolved to:', serverRoot);
191
-
192
- const serverEntry = path.join(serverRoot, 'packages', 'server', 'server.js');
193
- console.log('Server entry file:', serverEntry);
194
-
195
- // 检查入口文件是否存在
196
- try {
197
- await fs.promises.access(serverEntry, fs.constants.F_OK);
198
- console.log('Server entry file exists');
199
- } catch (error) {
200
- console.error('Server entry file does not exist:', serverEntry);
201
- throw new Error(`Server entry file not found: ${serverEntry}`);
75
+ setupApplicationMenu() {
76
+ if (process.platform === 'darwin') {
77
+ app.dock.hide();
202
78
  }
203
79
 
204
- const entryUrl = pathToFileURL(serverEntry);
205
- entryUrl.searchParams.set('v', String(serverModuleVersion));
206
- console.log('Entry URL:', entryUrl.href);
207
-
208
- serverModuleLoading = import(entryUrl.href)
209
- .then((mod) => {
210
- console.log('Module loaded successfully');
211
- serverModule = mod;
212
- return mod;
213
- })
214
- .catch((error) => {
215
- console.error('Module loading failed:', error);
216
- // 清理加载状态,以便下次重试
217
- serverModuleLoading = null;
218
- serverModule = null;
219
- throw new Error(`Failed to load server module: ${error.message}`);
220
- })
221
- .finally(() => {
222
- serverModuleLoading = null;
223
- });
224
-
225
- return serverModuleLoading;
226
- } catch (error) {
227
- console.error('Error in loadServerModule:', error);
228
- dialog.showErrorBox('模块加载失败', error.message);
229
- throw error;
80
+ // 隐藏默认应用菜单
81
+ const { Menu } = require('electron');
82
+ Menu.setApplicationMenu(null);
230
83
  }
231
- }
232
84
 
233
- function getServerStatusLabel() {
234
- switch (serviceState) {
235
- case 'running':
236
- return '运行中';
237
- case 'starting':
238
- return '启动中';
239
- case 'stopping':
240
- return '停止中';
241
- case 'error':
242
- return '启动失败';
243
- default:
244
- return '已停止';
245
- }
246
- }
247
-
248
- async function startService() {
249
- console.log('startService called');
250
- // 如果正在运行或正在启动,直接返回
251
- if (serviceState === 'running' || serviceState === 'starting') {
252
- console.log('Service is already running or starting, returning');
253
- return;
254
- }
255
-
256
- serviceState = 'starting';
257
- console.log('Service state set to starting');
258
- refreshTrayMenu();
259
-
260
- try {
261
- console.log('Loading server module with force reload');
262
- // 强制重新加载模块以确保获取最新状态
263
- const module = await loadServerModule({ forceReload: true });
264
- console.log('Server module loaded, starting server');
85
+ setupEventListeners() {
86
+ // 托盘事件
87
+ this.trayManager.on('service-start-requested', () => this.startService());
88
+ this.trayManager.on('service-stop-requested', () => this.stopService());
89
+ this.trayManager.on('update-check-requested', () => this.checkForUpdates());
90
+ this.trayManager.on('about-dialog-requested', () => this.showAboutDialog());
91
+ this.trayManager.on('quit-requested', () => this.quitApplication());
265
92
 
266
- if (!module || typeof module.startServer !== 'function') {
267
- throw new Error('Invalid server module or missing startServer function');
268
- }
93
+ // 服务管理器事件
94
+ this.serviceManager.on('status-update', () => this.trayManager.updateMenu());
95
+ this.serviceManager.on('restart-requested', () => this.restartApplication());
269
96
 
270
- await module.startServer();
271
- console.log('Server started successfully');
272
- currentServerState = module.getServerState();
273
- serviceState = 'running';
274
- startFailureCount = 0; // 重置失败计数
275
- console.log('Service state set to running');
276
- } catch (error) {
277
- serviceState = 'error';
278
- currentServerState = null;
279
- startFailureCount++;
97
+ // IPC事件
98
+ ipcMain.on('about-window-click', (event, data) => {
99
+ this.aboutDialogManager.handleAboutWindowClick(data);
100
+ });
280
101
 
281
- console.error('服务启动失败:', error);
282
- console.error('Error stack:', error.stack);
102
+ // 应用事件
103
+ app.on('window-all-closed', (event) => {
104
+ event.preventDefault(); // 防止应用退出
105
+ });
283
106
 
284
- // 如果连续失败3次,提示用户重启应用
285
- if (startFailureCount >= 3) {
286
- console.log('Too many start failures, prompting for restart');
287
- await promptForRestart();
288
- } else {
289
- dialog.showErrorBox('服务启动失败', `${error?.message || String(error)}\n\n请查看控制台日志获取更多信息。`);
290
- }
291
- } finally {
292
- console.log('Refreshing tray menu');
293
- refreshTrayMenu();
294
- }
295
- }
296
-
297
- async function stopService() {
298
- // 如果服务未运行或正在停止,直接返回
299
- if (serviceState !== 'running' && serviceState !== 'error') {
300
- return;
301
- }
302
-
303
- serviceState = 'stopping';
304
- refreshTrayMenu();
305
-
306
- try {
307
- const module = await loadServerModule();
308
- if (module && typeof module.stopServer === 'function') {
309
- await module.stopServer();
310
- }
311
- currentServerState = null;
312
- serviceState = 'stopped';
107
+ app.on('before-quit', async (event) => {
108
+ if (this.stateManager.get('isQuitting')) return;
109
+
110
+ event.preventDefault();
111
+ await this.quitApplication();
112
+ });
313
113
 
314
- // 清理MCP会话和传输
315
- console.log('Clearing MCP sessions and transports');
316
- } catch (error) {
317
- console.error('停止服务失败:', error);
318
- dialog.showErrorBox('停止服务失败', error?.message || String(error));
319
- serviceState = 'error';
320
- } finally {
321
- refreshTrayMenu();
322
- }
323
- }
324
-
325
- function ensureTray() {
326
- if (tray) {
327
- return tray;
114
+ app.on('activate', () => {
115
+ if (!this.trayManager.tray) {
116
+ this.trayManager.initialize(this.stateManager);
117
+ }
118
+ });
328
119
  }
329
120
 
330
- // 尝试多种可能的图标路径
331
- const possiblePaths = [
332
- path.join(__dirname, 'assets', 'icon.png'),
333
- path.join(process.resourcesPath, 'assets', 'icon.png'),
334
- path.join(__dirname, '..', 'assets', 'icon.png')
335
- ];
336
-
337
- let iconPath = null;
338
- let icon = null;
339
-
340
- console.log('App is packaged:', app.isPackaged);
341
- console.log('__dirname:', __dirname);
342
- console.log('process.resourcesPath:', process.resourcesPath);
343
-
344
- for (const possiblePath of possiblePaths) {
345
- console.log('Checking icon path:', possiblePath);
346
- console.log('File exists:', fs.existsSync(possiblePath));
347
- if (fs.existsSync(possiblePath)) {
348
- iconPath = possiblePath;
349
- break;
121
+ async startService() {
122
+ const serverRoot = this.stateManager.get('runtimeRoot');
123
+ if (!serverRoot) {
124
+ this.logger.error('Cannot start service: runtime root not set');
125
+ return false;
350
126
  }
351
- }
352
-
353
- if (!iconPath) {
354
- console.error('Icon file not found in any of the expected locations');
355
- // 创建一个简单的文本托盘作为后备
356
- tray = new Tray(nativeImage.createEmpty());
357
- tray.setToolTip('Prompt Server - Icon Missing');
358
- refreshTrayMenu();
359
- return tray;
360
- }
361
-
362
- console.log('Attempting to load icon from:', iconPath);
363
-
364
- try {
365
- icon = nativeImage.createFromPath(iconPath);
366
- console.log('Icon loaded successfully');
367
- console.log('Icon is empty:', icon.isEmpty());
368
- console.log('Icon size:', icon.getSize());
369
127
 
370
- if (process.platform === 'darwin') {
371
- icon = icon.resize({ width: 18, height: 18 });
372
- icon.setTemplateImage(true);
373
- console.log('Icon resized and set as template image');
374
- }
375
-
376
- tray = new Tray(icon);
377
- tray.setToolTip('Prompt Server');
378
- console.log('Tray created successfully');
379
- refreshTrayMenu();
380
- return tray;
381
- } catch (error) {
382
- console.error('Failed to create tray:', error);
383
- // 创建一个简单的文本托盘作为后备
384
- tray = new Tray(nativeImage.createEmpty());
385
- tray.setToolTip('Prompt Server - Error');
386
- refreshTrayMenu();
387
- return tray;
128
+ return await this.serviceManager.startService(serverRoot, this.stateManager);
388
129
  }
389
- }
390
130
 
391
- function refreshTrayMenu() {
392
- if (!tray) {
393
- return;
131
+ async stopService() {
132
+ return await this.serviceManager.stopService(this.stateManager);
394
133
  }
395
134
 
396
- const address = currentServerState?.address ?? 'http://127.0.0.1:5621';
397
- const adminUrl = currentServerState?.adminPath ? `${address}${currentServerState.adminPath}` : `${address}/admin`;
398
-
399
- const template = [
400
- { label: `状态:${getServerStatusLabel()}`, enabled: false },
401
- { type: 'separator' },
402
- {
403
- label: serviceState === 'running' ? '停止服务' : '启动服务',
404
- click: () => (serviceState === 'running' ? stopService() : startService())
405
- },
406
- {
407
- label: '复制服务地址',
408
- enabled: serviceState === 'running',
409
- click: () => clipboard.writeText(`${address}/mcp`)
410
- },
411
- {
412
- label: '打开管理后台',
413
- enabled: serviceState === 'running',
414
- click: () => openAdminWindow(adminUrl)
415
- },
416
- { type: 'separator' },
417
- {
418
- label: '查看日志文件',
419
- click: () => {
420
- try {
421
- shell.openPath(logFilePath);
422
- } catch (error) {
423
- dialog.showErrorBox('打开日志文件失败', error.message);
424
- }
425
- }
426
- },
427
- {
428
- label: '检查更新',
429
- click: () => checkForUpdates()
430
- },
431
- {
432
- label: '关于服务',
433
- click: () => showAboutDialog()
434
- },
435
- { type: 'separator' },
436
- {
437
- label: '退出服务',
438
- click: async () => {
439
- isQuitting = true;
440
- await stopService();
441
- if (logStream) {
442
- logStream.end();
443
- }
444
- app.quit();
445
- }
135
+ async checkForUpdates() {
136
+ try {
137
+ await this.updateManager.checkForUpdates();
138
+ } catch (error) {
139
+ this.logger.error('Update check failed', error);
446
140
  }
447
- ];
448
-
449
- const menu = Menu.buildFromTemplate(template);
450
- tray.setContextMenu(menu);
451
- }
452
-
453
- function openAdminWindow(url) {
454
- if (adminWindow) {
455
- adminWindow.focus();
456
- return;
457
141
  }
458
142
 
459
- adminWindow = new BrowserWindow({
460
- width: 1200,
461
- height: 800,
462
- title: 'Prompt Server 管理后台',
463
- webPreferences: {
464
- contextIsolation: true,
465
- preload: undefined
466
- }
467
- });
468
-
469
- adminWindow.loadURL(url);
470
- adminWindow.on('closed', () => {
471
- adminWindow = null;
472
- });
473
- }
474
-
475
- async function getCurrentServiceVersion() {
476
- try {
477
- const serverRoot = await ensureRuntimeServerRoot();
478
- const pkgRaw = await fs.promises.readFile(path.join(serverRoot, 'package.json'), 'utf8');
479
- const pkg = JSON.parse(pkgRaw);
480
- return pkg.version;
481
- } catch (error) {
482
- return 'unknown';
143
+ showAboutDialog() {
144
+ this.aboutDialogManager.showAboutDialog();
483
145
  }
484
- }
485
-
486
- function compareVersions(a, b) {
487
- const toNumbers = (value = '') => value.split('.').map((part) => parseInt(part, 10) || 0);
488
- const [a1, a2, a3] = toNumbers(a);
489
- const [b1, b2, b3] = toNumbers(b);
490
- if (a1 !== b1) return a1 - b1;
491
- if (a2 !== b2) return a2 - b2;
492
- return a3 - b3;
493
- }
494
-
495
- async function checkForUpdates() {
496
- try {
497
- const currentVersion = await getCurrentServiceVersion();
498
-
499
- const response = await fetch('https://registry.npmjs.org/@becrafter/prompt-manager');
500
- if (!response.ok) {
501
- throw new Error('无法获取最新版本信息');
502
- }
503
-
504
- const metadata = await response.json();
505
- const latestVersion = metadata?.['dist-tags']?.latest;
506
-
507
- if (!latestVersion) {
508
- throw new Error('未能解析最新版本号');
509
- }
510
-
511
- if (compareVersions(latestVersion, currentVersion) <= 0) {
512
- await dialog.showMessageBox({
513
- type: 'info',
514
- message: '已是最新版本',
515
- detail: `服务当前版本:${currentVersion}`
516
- });
517
- return;
518
- }
519
-
520
- const { response: action } = await dialog.showMessageBox({
521
- type: 'question',
522
- buttons: ['立即升级', '打开发布页', '取消'],
523
- defaultId: 0,
524
- cancelId: 2,
525
- message: `发现新版本 ${latestVersion}`,
526
- detail: '升级期间服务会短暂停止,是否继续?'
527
- });
528
-
529
- if (action === 1) {
530
- shell.openExternal('https://github.com/BeCrafter/prompt-manager/releases/latest');
531
- return;
532
- }
533
-
534
- if (action !== 0) {
535
- return;
536
- }
537
-
538
- const wasRunning = serviceState === 'running';
539
- if (wasRunning) {
540
- await stopService();
541
- }
542
-
543
- await performInPlaceUpgrade(latestVersion);
544
-
545
- await dialog.showMessageBox({
546
- type: 'info',
547
- message: '升级成功',
548
- detail: `服务已升级到 ${latestVersion}`
549
- });
550
146
 
551
- if (wasRunning) {
552
- await startService();
553
- }
554
- } catch (error) {
555
- dialog.showErrorBox('检查更新失败', error?.message || String(error));
147
+ handleAboutWindowClick(data) {
148
+ // 委托给AboutDialogManager处理
149
+ this.aboutDialogManager.handleAboutWindowClick(data);
556
150
  }
557
- }
558
-
559
- async function performInPlaceUpgrade(version) {
560
- console.log('Performing in-place upgrade to version:', version);
561
- const tarballUrl = `https://registry.npmjs.org/@becrafter/prompt-manager/-/@becrafter/prompt-manager-${version}.tgz`;
562
- const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'prompt-manager-upgrade-'));
563
- const tarballPath = path.join(tmpDir, `${version}.tgz`);
564
-
565
- console.log('Temporary directory:', tmpDir);
566
-
567
- try {
568
- console.log('Downloading tarball from:', tarballUrl);
569
- const response = await fetch(tarballUrl);
570
- if (!response.ok) {
571
- throw new Error(`Failed to download tarball: ${response.status} ${response.statusText}`);
572
- }
573
-
574
- const buffer = Buffer.from(await response.arrayBuffer());
575
- await fs.promises.writeFile(tarballPath, buffer);
576
- console.log('Tarball downloaded to:', tarballPath);
577
-
578
- await tar.x({ file: tarballPath, cwd: tmpDir });
579
- const extractedPath = path.join(tmpDir, 'package');
580
- console.log('Tarball extracted to:', extractedPath);
581
-
582
- const serverRoot = await ensureRuntimeServerRoot();
583
- const examplesDir = path.join(serverRoot, 'examples', 'prompts');
584
- const examplesBackup = path.join(tmpDir, 'examples-prompts');
585
-
586
- if (await pathExists(examplesDir)) {
587
- console.log('Backing up examples directory');
588
- await fs.promises.cp(examplesDir, examplesBackup, { recursive: true });
589
- }
590
151
 
591
- console.log('Removing old server root');
592
- await fs.promises.rm(serverRoot, { recursive: true, force: true });
593
- await fs.promises.mkdir(serverRoot, { recursive: true });
152
+ async restartApplication() {
153
+ this.logger.info('Restarting application');
594
154
 
595
- console.log('Copying extracted package to server root');
596
- await fs.promises.cp(extractedPath, serverRoot, { recursive: true });
597
-
598
- if (await pathExists(examplesBackup)) {
599
- console.log('Restoring examples directory');
600
- const targetExamples = path.join(serverRoot, 'examples', 'prompts');
601
- await fs.promises.mkdir(path.dirname(targetExamples), { recursive: true });
602
- await fs.promises.cp(examplesBackup, targetExamples, { recursive: true });
603
- }
604
-
605
- console.log('Installing server dependencies');
606
- await installServerDependencies(serverRoot);
607
-
608
- console.log('Reloading server module');
609
- await loadServerModule({ forceReload: true });
610
-
611
- console.log('Upgrade completed successfully');
612
- } catch (error) {
613
- console.error('Upgrade failed:', error);
614
- throw error;
615
- } finally {
616
- console.log('Cleaning up temporary directory');
617
- await fs.promises.rm(tmpDir, { recursive: true, force: true });
618
- }
619
- }
620
-
621
- async function pathExists(targetPath) {
622
- try {
623
- await fs.promises.access(targetPath, fs.constants.F_OK);
624
- return true;
625
- } catch (error) {
626
- return false;
627
- }
628
- }
629
-
630
- async function installServerDependencies(targetDir) {
631
- console.log('Installing server dependencies in:', targetDir);
632
-
633
- // 检查 targetDir 是否存在 package.json
634
- const pkgPath = path.join(targetDir, 'package.json');
635
- try {
636
- await fs.promises.access(pkgPath, fs.constants.F_OK);
637
- console.log('package.json found in target directory');
638
- } catch (error) {
639
- console.error('package.json not found in target directory:', pkgPath);
640
- throw new Error(`package.json not found in ${targetDir}`);
641
- }
642
-
643
- // 检查 @modelcontextprotocol/sdk 是否在 dependencies 中
644
- try {
645
- const pkgContent = await fs.promises.readFile(pkgPath, 'utf8');
646
- const pkg = JSON.parse(pkgContent);
647
- if (!pkg.dependencies || !pkg.dependencies['@modelcontextprotocol/sdk']) {
648
- console.warn('@modelcontextprotocol/sdk not found in dependencies, adding it');
649
- pkg.dependencies = pkg.dependencies || {};
650
- pkg.dependencies['@modelcontextprotocol/sdk'] = '^1.20.2';
651
- await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
155
+ try {
156
+ await this.stopService();
157
+ app.relaunch();
158
+ app.exit(0);
159
+ } catch (error) {
160
+ this.logger.error('Failed to restart application', error);
652
161
  }
653
- } catch (error) {
654
- console.error('Error checking/adding @modelcontextprotocol/sdk to package.json:', error);
655
162
  }
656
-
657
- const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
658
- const args = ['install', '--omit=dev', '--no-audit', '--no-fund'];
659
-
660
- console.log('Running npm install with args:', args);
661
-
662
- await new Promise((resolve, reject) => {
663
- const child = spawn(npmCommand, args, {
664
- cwd: targetDir,
665
- stdio: ['ignore', 'pipe', 'pipe']
666
- });
667
163
 
668
- let stdout = '';
669
- let stderr = '';
670
-
671
- child.stdout.on('data', (data) => {
672
- const output = data.toString().trim();
673
- stdout += output;
674
- console.log(`[npm stdout] ${output}`);
675
- });
164
+ async quitApplication() {
165
+ this.logger.info('Quitting application');
676
166
 
677
- child.stderr.on('data', (data) => {
678
- const output = data.toString().trim();
679
- stderr += output;
680
- console.error(`[npm stderr] ${output}`);
681
- });
682
-
683
- child.on('error', (error) => {
684
- console.error('npm process error:', error);
685
- reject(new Error(`Failed to start npm process: ${error.message}`));
686
- });
687
-
688
- child.on('close', (code) => {
689
- console.log('npm process exited with code:', code);
690
- if (code === 0) {
691
- console.log('npm install completed successfully');
692
- resolve();
693
- } else {
694
- const errorMsg = `npm install failed with exit code ${code}: ${stderr}`;
695
- console.error(errorMsg);
696
- reject(new Error(errorMsg));
697
- }
698
- });
699
- });
700
- }
701
-
702
- async function promptForRestart() {
703
- const { response } = await dialog.showMessageBox({
704
- type: 'error',
705
- buttons: ['重启应用', '取消'],
706
- defaultId: 0,
707
- cancelId: 1,
708
- message: '服务启动失败',
709
- detail: '多次尝试启动服务均失败,建议重启应用以恢复正常状态。'
710
- });
711
-
712
- if (response === 0) {
713
- app.relaunch();
714
- app.exit(0);
167
+ try {
168
+ this.stateManager.set('isQuitting', true);
169
+
170
+ // 停止服务
171
+ await this.stopService();
172
+
173
+ // 清理资源
174
+ await this.aboutDialogManager.closeAboutDialog();
175
+
176
+ this.trayManager.destroy();
177
+ await this.logger.close();
178
+ await this.runtimeManager.cleanup();
179
+
180
+ app.quit();
181
+ } catch (error) {
182
+ this.logger.error('Error during application shutdown', error);
183
+ app.quit(); // 强制退出
184
+ }
715
185
  }
716
186
  }
717
187
 
718
- async function showAboutDialog() {
719
- const serviceVersion = await getCurrentServiceVersion();
720
- const lines = [
721
- // `桌面应用版本:${desktopPackageJson.version}`,
722
- `服务版本:${serviceVersion}`,
723
- `Electron:${process.versions.electron}`,
724
- // `Chromium:${process.versions.chrome}`,
725
- `Node.js:${process.versions.node}`
726
- ];
727
-
728
- await dialog.showMessageBox({
729
- type: 'info',
730
- title: '关于 Prompt Server',
731
- message: '组件版本信息',
732
- detail: lines.join('\n')
733
- });
734
- }
188
+ // 应用启动
189
+ const promptManagerApp = new PromptManagerApp();
735
190
 
736
191
  app.whenReady().then(async () => {
737
- // 初始化日志
738
- initLogStream();
739
- console.log('App is packaged:', app.isPackaged);
740
- console.log('App data path:', app.getPath('userData'));
741
- console.log('Resources path:', process.resourcesPath);
742
- console.log('__dirname:', __dirname);
743
- console.log('Process platform:', process.platform);
744
- console.log('Process version:', process.version);
745
- console.log('Electron version:', process.versions.electron);
746
-
747
- Menu.setApplicationMenu(null);
748
- if (process.platform === 'darwin') {
749
- app.dock.hide();
750
- }
751
-
752
192
  try {
753
- await ensureRuntimeServerRoot();
754
- console.log('Runtime server root ensured successfully');
755
- ensureTray();
756
- console.log('System tray ensured successfully');
757
- await startService();
758
- console.log('Service started successfully');
193
+ await promptManagerApp.initialize();
759
194
  } catch (error) {
760
- console.error('Error during app initialization:', error);
761
- dialog.showErrorBox('应用初始化失败', `启动过程中发生错误: ${error.message}\n\n请查看日志文件: ${logFilePath}`);
195
+ console.error('Failed to start application:', error);
196
+ app.quit();
762
197
  }
763
198
  });
764
199
 
765
- app.on('window-all-closed', (event) => {
766
- event.preventDefault();
767
- });
768
-
769
- app.on('before-quit', async (event) => {
770
- if (isQuitting) {
771
- return;
772
- }
773
- event.preventDefault();
774
- isQuitting = true;
775
- await stopService();
776
- if (logStream) {
777
- logStream.end();
200
+ // 异常处理
201
+ process.on('uncaughtException', (error) => {
202
+ console.error('Uncaught Exception:', error);
203
+ if (promptManagerApp.logger) {
204
+ promptManagerApp.logger.error('Uncaught Exception', error);
778
205
  }
779
206
  app.quit();
780
207
  });
781
208
 
782
- app.on('activate', () => {
783
- if (!tray) {
784
- ensureTray();
209
+ process.on('unhandledRejection', (reason, promise) => {
210
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
211
+ if (promptManagerApp.logger) {
212
+ promptManagerApp.logger.error('Unhandled Rejection', reason);
785
213
  }
786
- });
214
+ });