@becrafter/prompt-manager 0.1.11 → 0.1.12

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 (38) hide show
  1. package/env.example +1 -1
  2. package/package.json +2 -2
  3. package/packages/server/app.js +13 -3
  4. package/packages/server/utils/config.js +1 -1
  5. package/packages/server/utils/util.js +7 -7
  6. package/packages/admin-ui/admin.html +0 -49
  7. package/packages/admin-ui/css/codemirror-theme_xq-light.css +0 -43
  8. package/packages/admin-ui/css/codemirror.css +0 -344
  9. package/packages/admin-ui/css/main.css +0 -4485
  10. package/packages/admin-ui/css/markdown.css +0 -468
  11. package/packages/admin-ui/css/optimization.css +0 -1015
  12. package/packages/admin-ui/css/recommended-prompts.css +0 -610
  13. package/packages/admin-ui/css/terminal-fix.css +0 -571
  14. package/packages/admin-ui/package-lock.json +0 -8287
  15. package/packages/admin-ui/package.json +0 -46
  16. package/packages/admin-ui/src/codemirror.js +0 -53
  17. package/packages/admin-ui/src/components/ArgumentModal.js +0 -53
  18. package/packages/admin-ui/src/components/DeletePromptModal.js +0 -30
  19. package/packages/admin-ui/src/components/HeaderView.js +0 -40
  20. package/packages/admin-ui/src/components/LoadingOverlay.js +0 -12
  21. package/packages/admin-ui/src/components/LoginView.js +0 -22
  22. package/packages/admin-ui/src/components/ModelConfigModal.js +0 -103
  23. package/packages/admin-ui/src/components/NewFolderModal.js +0 -58
  24. package/packages/admin-ui/src/components/OptimizationConfigModal.js +0 -36
  25. package/packages/admin-ui/src/components/OptimizationDrawer.js +0 -135
  26. package/packages/admin-ui/src/components/PrimaryNav.js +0 -34
  27. package/packages/admin-ui/src/components/PromptsArea.js +0 -140
  28. package/packages/admin-ui/src/components/RecommendedPromptModal.js +0 -37
  29. package/packages/admin-ui/src/components/SidebarView.js +0 -24
  30. package/packages/admin-ui/src/components/SyncPromptModal.js +0 -44
  31. package/packages/admin-ui/src/components/TemplateEditorModal.js +0 -75
  32. package/packages/admin-ui/src/components/TemplateListModal.js +0 -30
  33. package/packages/admin-ui/src/components/TerminalComponent.js +0 -995
  34. package/packages/admin-ui/src/components/TerminalView.js +0 -25
  35. package/packages/admin-ui/src/components/ToolDetailModal.js +0 -23
  36. package/packages/admin-ui/src/components/ToolsArea.js +0 -119
  37. package/packages/admin-ui/src/components/ToolsUploadModal.js +0 -59
  38. package/packages/admin-ui/src/index.js +0 -6766
@@ -1,995 +0,0 @@
1
- /**
2
- * TerminalComponent - 基于xterm.js的终端组件(优化版)
3
- *
4
- * 提供现代化的终端体验,支持实时交互、主题切换、快捷键等功能
5
- *
6
- * 优化点:
7
- * - 使用 Canvas 渲染器提升性能
8
- * - 改进 WebSocket 重连机制
9
- * - 添加命令历史记录
10
- * - 优化字体渲染
11
- * - 改进主题切换
12
- */
13
-
14
- // xterm.js相关模块 - 将在init方法中动态导入
15
- let Terminal, FitAddon, WebLinksAddon, SearchAddon, Unicode11Addon, CanvasAddon;
16
-
17
- /**
18
- * 终端组件类(优化版)
19
- */
20
- export class TerminalComponent {
21
- constructor(container, options = {}) {
22
- this.container = container;
23
- this.options = {
24
- theme: 'dark',
25
- fontSize: 14,
26
- fontFamily: '"SF Mono", "JetBrains Mono", "Cascadia Code", "Fira Code", Monaco, "Menlo", "Consolas", "Courier New", monospace',
27
- cursorBlink: true,
28
- cursorStyle: 'block',
29
- scrollback: 1000,
30
- tabStopWidth: 4,
31
- rendererType: 'canvas', // 使用 Canvas 渲染器提升性能
32
- allowTransparency: false,
33
- convertEol: true,
34
- ...options
35
- };
36
-
37
- this.terminal = null;
38
- this.fitAddon = null;
39
- this.webLinksAddon = null;
40
- this.searchAddon = null;
41
- this.unicode11Addon = null;
42
- this.websocket = null;
43
- this.sessionId = null;
44
- this.isConnected = false;
45
- this.reconnectAttempts = 0;
46
- this.maxReconnectAttempts = 10;
47
- this.reconnectDelay = 1000;
48
-
49
- // 命令历史记录
50
- this.commandHistory = [];
51
- this.historyIndex = -1;
52
- this.currentInput = '';
53
-
54
- // 心跳定时器
55
- this.heartbeatInterval = null;
56
-
57
- // 渲染器检测
58
- this.rendererType = 'unknown';
59
- this.isCanvasRenderer = false;
60
-
61
- // 初始化状态
62
- this.isInitialized = false;
63
- this.initPromise = null;
64
-
65
- // 显示加载状态
66
- this.showLoadingState();
67
-
68
- // 异步初始化
69
- this.initPromise = this.init().catch(error => {
70
- console.error('TerminalComponent异步初始化失败:', error);
71
- throw error;
72
- });
73
- }
74
-
75
- /**
76
- * 显示加载状态
77
- */
78
- showLoadingState() {
79
- if (this.container) {
80
- this.container.innerHTML = `
81
- <div class="terminal-loading">
82
- <div class="loading-spinner"></div>
83
- <p>正在初始化终端...</p>
84
- </div>
85
- `;
86
- }
87
- }
88
-
89
- /**
90
- * 初始化终端
91
- */
92
- async init() {
93
- try {
94
- console.log('TerminalComponent开始初始化...');
95
- await this.loadModules();
96
- this.createTerminal();
97
- this.setupAddons();
98
- this.setupEventListeners();
99
- this.render();
100
- this.connectWebSocket();
101
- console.log('TerminalComponent初始化完成');
102
- } catch (error) {
103
- console.error('TerminalComponent初始化过程中出错:', error);
104
- this.showErrorAndFallback(error);
105
- }
106
- }
107
-
108
- /**
109
- * 动态加载xterm.js模块
110
- */
111
- async loadModules() {
112
- try {
113
- console.log('正在导入xterm.js模块...');
114
-
115
- // 导入核心模块
116
- const xtermModule = await import('xterm');
117
- Terminal = xtermModule.Terminal;
118
- console.log('xterm.js导入成功');
119
-
120
- // 导入插件模块
121
- console.log('正在导入xterm插件...');
122
- const fitModule = await import('xterm-addon-fit');
123
- FitAddon = fitModule.FitAddon;
124
-
125
- const webLinksModule = await import('xterm-addon-web-links');
126
- WebLinksAddon = webLinksModule.WebLinksAddon;
127
-
128
- const searchModule = await import('xterm-addon-search');
129
- SearchAddon = searchModule.SearchAddon;
130
-
131
- const unicodeModule = await import('xterm-addon-unicode11');
132
- Unicode11Addon = unicodeModule.Unicode11Addon;
133
-
134
- // 导入 Canvas 渲染器插件
135
- console.log('正在导入 Canvas 渲染器插件...');
136
- const canvasModule = await import('xterm-addon-canvas');
137
- CanvasAddon = canvasModule.CanvasAddon;
138
- console.log('Canvas 渲染器插件导入成功');
139
-
140
- console.log('所有xterm插件导入成功');
141
- } catch (error) {
142
- console.error('导入xterm模块失败:', error);
143
- throw new Error(`无法导入xterm.js: ${error.message}`);
144
- }
145
- }
146
-
147
- /**
148
- * 显示错误并回退
149
- */
150
- showErrorAndFallback(error) {
151
- if (this.container) {
152
- this.container.innerHTML = `
153
- <div class="terminal-error">
154
- <h3>终端初始化失败</h3>
155
- <p>错误信息: ${error.message}</p>
156
- <p>可能的原因:</p>
157
- <ul>
158
- <li>xterm.js 库未正确加载</li>
159
- <li>网络连接问题</li>
160
- <li>浏览器兼容性问题</li>
161
- </ul>
162
- <button onclick="location.reload()">重新加载页面</button>
163
- </div>
164
- `;
165
- }
166
- throw error;
167
- }
168
-
169
- /**
170
- * 创建xterm实例(优化版)
171
- */
172
- createTerminal() {
173
- if (!Terminal) {
174
- throw new Error('Terminal模块未加载');
175
- }
176
-
177
- this.terminal = new Terminal({
178
- // 主题配置
179
- theme: this.getTheme(this.options.theme),
180
-
181
- // 字体配置(优化)
182
- fontSize: this.options.fontSize,
183
- fontFamily: this.options.fontFamily,
184
- fontWeight: 'normal',
185
- fontWeightBold: 'bold',
186
- lineHeight: 1.2,
187
- letterSpacing: 0,
188
-
189
- // 光标配置
190
- cursorBlink: this.options.cursorBlink,
191
- cursorStyle: this.options.cursorStyle,
192
- cursorWidth: 2,
193
-
194
- // 滚动配置
195
- scrollback: this.options.scrollback,
196
- scrollSensitivity: 1,
197
-
198
- // 渲染配置(优化)
199
- // 注意:不设置 rendererType,通过 CanvasAddon 插件来强制使用 Canvas 渲染
200
- allowTransparency: this.options.allowTransparency,
201
- allowProposedApi: true,
202
-
203
- // 终端配置
204
- cols: 80,
205
- rows: 24,
206
- tabStopWidth: this.options.tabStopWidth,
207
- convertEol: this.options.convertEol,
208
- termName: 'xterm-256color',
209
-
210
- // 性能优化
211
- rightClickSelectsWord: true,
212
- fastScrollModifier: 'alt',
213
- fastScrollSensitivity: 5,
214
-
215
- // 字符集
216
- unicodeVersion: '11'
217
- });
218
- }
219
-
220
- /**
221
- * 获取主题配置(优化版 - 改进对比度和视觉效果)
222
- */
223
- getTheme(themeName) {
224
- const themes = {
225
- dark: {
226
- // 基础颜色
227
- background: '#0a0a0a',
228
- foreground: '#f0f0f0',
229
- cursor: '#ffffff',
230
- cursorAccent: '#000000',
231
-
232
- // 选中效果(优化 - 更明显的对比度)
233
- selection: 'rgba(100, 181, 246, 0.45)',
234
- selectionForeground: '#ffffff',
235
-
236
- // ANSI 颜色(优化对比度)
237
- black: '#000000',
238
- red: '#ff5f56',
239
- green: '#00c853',
240
- yellow: '#ffbd2e',
241
- blue: '#00a2ff',
242
- magenta: '#ff79c6',
243
- cyan: '#00e5ff',
244
- white: '#e0e0e0',
245
-
246
- // 亮色 ANSI 颜色
247
- brightBlack: '#666666',
248
- brightRed: '#ff5f56',
249
- brightGreen: '#00e676',
250
- brightYellow: '#ffbd2e',
251
- brightBlue: '#00a2ff',
252
- brightMagenta: '#ff79c6',
253
- brightCyan: '#00e5ff',
254
- brightWhite: '#ffffff'
255
- },
256
- light: {
257
- // 基础颜色
258
- background: '#ffffff',
259
- foreground: '#1a1a1a',
260
- cursor: '#1a1a1a',
261
- cursorAccent: '#ffffff',
262
-
263
- // 选中效果(优化 - 更明显的对比度)
264
- selection: 'rgba(24, 144, 255, 0.3)',
265
- selectionForeground: '#000000',
266
-
267
- // ANSI 颜色(优化对比度)
268
- black: '#1a1a1a',
269
- red: '#e53935',
270
- green: '#43a047',
271
- yellow: '#fdd835',
272
- blue: '#1e88e5',
273
- magenta: '#8e24aa',
274
- cyan: '#00acc1',
275
- white: '#f5f5f5',
276
-
277
- // 亮色 ANSI 颜色
278
- brightBlack: '#757575',
279
- brightRed: '#e53935',
280
- brightGreen: '#43a047',
281
- brightYellow: '#fdd835',
282
- brightBlue: '#1e88e5',
283
- brightMagenta: '#8e24aa',
284
- brightCyan: '#00acc1',
285
- brightWhite: '#ffffff'
286
- }
287
- };
288
-
289
- return themes[themeName] || themes.dark;
290
- }
291
-
292
- /**
293
- * 设置xterm插件
294
- */
295
- setupAddons() {
296
- // Canvas 渲染器插件 - 必须在其他插件之前加载
297
- try {
298
- if (CanvasAddon) {
299
- this.canvasAddon = new CanvasAddon();
300
- this.terminal.loadAddon(this.canvasAddon);
301
- this.isCanvasRenderer = true;
302
- this.rendererType = 'canvas';
303
- console.log('✓ Canvas 渲染器已启用');
304
- } else {
305
- console.warn('CanvasAddon 未加载,将使用默认渲染器');
306
- this.rendererType = 'dom';
307
- }
308
- } catch (error) {
309
- console.error('加载 Canvas 渲染器失败:', error);
310
- console.warn('将使用默认 DOM 渲染器');
311
- this.rendererType = 'dom';
312
- }
313
-
314
- // 自适应插件
315
- this.fitAddon = new FitAddon();
316
- this.terminal.loadAddon(this.fitAddon);
317
-
318
- // 链接插件
319
- this.webLinksAddon = new WebLinksAddon();
320
- this.terminal.loadAddon(this.webLinksAddon);
321
-
322
- // 搜索插件
323
- this.searchAddon = new SearchAddon();
324
- this.terminal.loadAddon(this.searchAddon);
325
-
326
- // Unicode11支持
327
- this.unicode11Addon = new Unicode11Addon();
328
- this.terminal.loadAddon(this.unicode11Addon);
329
- this.terminal.unicode.activeVersion = '11';
330
- }
331
-
332
- /**
333
- * 设置事件监听器
334
- */
335
- setupEventListeners() {
336
- // 终端数据事件
337
- this.terminal.onData((data) => {
338
- this.sendData(data);
339
- });
340
-
341
- // 终端按键事件
342
- this.terminal.onKey((event) => {
343
- this.handleKey(event);
344
- });
345
-
346
- // 窗口大小变化
347
- window.addEventListener('resize', () => {
348
- this.fit();
349
- });
350
-
351
- // 终端焦点事件 - 使用文本区域的事件监听
352
- this.terminal.textarea?.addEventListener('focus', () => {
353
- this.container.classList.add('terminal-focused');
354
- });
355
-
356
- this.terminal.textarea?.addEventListener('blur', () => {
357
- this.container.classList.remove('terminal-focused');
358
- });
359
- }
360
-
361
- /**
362
- * 处理按键事件(优化版 - 添加命令历史和快捷键)
363
- */
364
- handleKey(event) {
365
- const { key, domEvent } = event;
366
-
367
- // Ctrl+C 中断
368
- if (domEvent.ctrlKey && domEvent.key === 'c') {
369
- this.sendData('\x03');
370
- return;
371
- }
372
-
373
- // Ctrl+V 粘贴
374
- if (domEvent.ctrlKey && domEvent.key === 'v') {
375
- navigator.clipboard.readText().then(text => {
376
- this.sendData(text);
377
- }).catch(err => {
378
- console.error('粘贴失败:', err);
379
- });
380
- return;
381
- }
382
-
383
- // Ctrl+Shift+C 复制
384
- if (domEvent.ctrlKey && domEvent.shiftKey && domEvent.key === 'C') {
385
- const selection = this.terminal.getSelection();
386
- if (selection) {
387
- navigator.clipboard.writeText(selection).then(() => {
388
- this.write('\r\n\x1b[32m✓ 已复制到剪贴板\x1b[0m\r\n');
389
- }).catch(err => {
390
- console.error('复制失败:', err);
391
- });
392
- }
393
- return;
394
- }
395
-
396
- // Ctrl+L 清屏
397
- if (domEvent.ctrlKey && domEvent.key === 'l') {
398
- this.terminal.clear();
399
- return;
400
- }
401
-
402
- // Ctrl+F 搜索
403
- if (domEvent.ctrlKey && domEvent.key === 'f') {
404
- this.showSearch();
405
- return;
406
- }
407
-
408
- // Ctrl+Shift+V 粘贴(另一种方式)
409
- if (domEvent.ctrlKey && domEvent.shiftKey && domEvent.key === 'V') {
410
- navigator.clipboard.readText().then(text => {
411
- this.sendData(text);
412
- }).catch(err => {
413
- console.error('粘贴失败:', err);
414
- });
415
- return;
416
- }
417
-
418
- // Ctrl+K 清除到行尾
419
- if (domEvent.ctrlKey && domEvent.key === 'k') {
420
- this.sendData('\x1b[K');
421
- return;
422
- }
423
-
424
- // Ctrl+U 清除到行首
425
- if (domEvent.ctrlKey && domEvent.key === 'u') {
426
- this.sendData('\x1b[1K');
427
- return;
428
- }
429
-
430
- // Ctrl+W 删除前一个单词
431
- if (domEvent.ctrlKey && domEvent.key === 'w') {
432
- this.sendData('\x17');
433
- return;
434
- }
435
-
436
- // Ctrl+A 移动到行首
437
- if (domEvent.ctrlKey && domEvent.key === 'a') {
438
- this.sendData('\x01');
439
- return;
440
- }
441
-
442
- // Ctrl+E 移动到行尾
443
- if (domEvent.ctrlKey && domEvent.key === 'e') {
444
- this.sendData('\x05');
445
- return;
446
- }
447
- }
448
-
449
- /**
450
- * 渲染终端到容器
451
- */
452
- render() {
453
- if (!this.container) return;
454
-
455
- this.container.innerHTML = '';
456
- this.terminal.open(this.container);
457
-
458
- // 设置初始主题class
459
- this.updateThemeClass();
460
-
461
- // 创建工具栏
462
- this.createToolbar();
463
-
464
- // 适应大小
465
- setTimeout(() => this.fit(), 100);
466
-
467
- // 检测实际使用的渲染器
468
- setTimeout(() => this.detectRenderer(), 200);
469
- }
470
-
471
- /**
472
- * 检测实际使用的渲染器类型
473
- */
474
- detectRenderer() {
475
- try {
476
- const screen = this.terminal.element.querySelector('.xterm-screen');
477
- if (!screen) {
478
- console.warn('无法检测渲染器:找不到 xterm-screen 元素');
479
- return;
480
- }
481
-
482
- // 检查是否有 canvas 元素
483
- const canvas = screen.querySelector('canvas');
484
- if (canvas) {
485
- this.rendererType = 'canvas';
486
- this.isCanvasRenderer = true;
487
- console.log('✓ 检测到 Canvas 渲染器');
488
- } else {
489
- // 检查是否有 DOM 元素(rows 和 chars)
490
- const rows = screen.querySelector('.xterm-rows');
491
- if (rows) {
492
- this.rendererType = 'dom';
493
- this.isCanvasRenderer = false;
494
- console.log('⚠ 检测到 DOM 渲染器(降级模式)');
495
- }
496
- }
497
-
498
- // 在控制台显示渲染器信息
499
- console.log('渲染器信息:', {
500
- type: this.rendererType,
501
- isCanvas: this.isCanvasRenderer,
502
- canvas: !!canvas,
503
- rows: !!screen.querySelector('.xterm-rows')
504
- });
505
- } catch (error) {
506
- console.error('检测渲染器失败:', error);
507
- }
508
- }
509
-
510
- /**
511
- * 创建工具栏
512
- */
513
- createToolbar() {
514
- const toolbar = document.createElement('div');
515
- toolbar.className = 'terminal-toolbar';
516
-
517
- // 状态指示器
518
- const status = document.createElement('div');
519
- status.className = 'terminal-status';
520
- status.innerHTML = `
521
- <span class="status-indicator ${this.isConnected ? 'connected' : 'disconnected'}"></span>
522
- <span class="status-text">${this.isConnected ? '已连接' : '未连接'}</span>
523
- <span class="renderer-info" title="渲染器类型">${this.isCanvasRenderer ? '🎨 Canvas' : '📄 DOM'}</span>
524
- `;
525
-
526
- // 操作按钮
527
- const actions = document.createElement('div');
528
- actions.className = 'terminal-actions';
529
- actions.innerHTML = `
530
- <button class="btn btn-sm" title="重新连接" id="reconnectBtn">
531
- <i class="icon-refresh"></i>
532
- </button>
533
- <button class="btn btn-sm" title="清除" id="clearBtn">
534
- <i class="icon-clear"></i>
535
- </button>
536
- <button class="btn btn-sm" title="主题" id="themeBtn">
537
- <i class="icon-theme"></i>
538
- </button>
539
- `;
540
-
541
- toolbar.appendChild(status);
542
- toolbar.appendChild(actions);
543
- this.container.insertBefore(toolbar, this.container.firstChild);
544
-
545
- // 绑定事件
546
- this.bindToolbarEvents();
547
- }
548
-
549
- /**
550
- * 绑定工具栏事件
551
- */
552
- bindToolbarEvents() {
553
- const reconnectBtn = document.getElementById('reconnectBtn');
554
- const clearBtn = document.getElementById('clearBtn');
555
- const themeBtn = document.getElementById('themeBtn');
556
-
557
- reconnectBtn?.addEventListener('click', () => this.reconnect());
558
- clearBtn?.addEventListener('click', () => this.clear());
559
- themeBtn?.addEventListener('click', () => this.toggleTheme());
560
- }
561
-
562
-
563
- /**
564
- * 连接WebSocket(优化版 - 改进重连机制和心跳保活)
565
- */
566
- connectWebSocket() {
567
- const wsUrl = this.getWebSocketUrl();
568
-
569
- console.log('尝试连接WebSocket:', wsUrl);
570
-
571
- try {
572
- this.websocket = new WebSocket(wsUrl);
573
-
574
- this.websocket.onopen = () => {
575
- console.log('WebSocket连接成功');
576
- this.isConnected = true;
577
- this.reconnectAttempts = 0;
578
- this.reconnectDelay = 1000; // 重置重连延迟
579
- this.updateStatus('connected');
580
-
581
- // 启动心跳保活
582
- this.startHeartbeat();
583
-
584
- // 创建终端会话
585
- this.createTerminalSession();
586
- };
587
-
588
- this.websocket.onmessage = (event) => {
589
- try {
590
- const message = JSON.parse(event.data);
591
- this.handleMessage(message);
592
- } catch (error) {
593
- console.error('解析WebSocket消息失败:', error, event.data);
594
- }
595
- };
596
-
597
- this.websocket.onclose = (event) => {
598
- console.log('WebSocket连接关闭:', event.code, event.reason);
599
- this.isConnected = false;
600
- this.updateStatus('disconnected');
601
-
602
- // 停止心跳
603
- this.stopHeartbeat();
604
-
605
- // 显示断开消息(仅在非正常关闭时)
606
- if (event.code !== 1000) {
607
- this.write(`\r\n\x1b[31m✗ 连接已断开 (${event.code}: ${event.reason || 'Unknown'})\x1b[0m\r\n`);
608
- // 尝试重连
609
- this.attemptReconnect();
610
- }
611
- };
612
-
613
- this.websocket.onerror = (error) => {
614
- console.error('WebSocket错误:', error);
615
- this.writeError(`连接错误: ${error.message || '未知错误'}`);
616
- };
617
-
618
- } catch (error) {
619
- console.error('创建WebSocket连接失败:', error);
620
- this.writeError(`无法建立连接: ${error.message}`);
621
- this.attemptReconnect();
622
- }
623
- }
624
-
625
- /**
626
- * 启动心跳保活
627
- */
628
- startHeartbeat() {
629
- this.stopHeartbeat(); // 先停止现有的心跳
630
-
631
- // 每30秒发送一次 ping
632
- this.heartbeatInterval = setInterval(() => {
633
- if (this.isConnected && this.websocket && this.websocket.readyState === WebSocket.OPEN) {
634
- this.sendMessage({ type: 'ping' });
635
- }
636
- }, 30000);
637
- }
638
-
639
- /**
640
- * 停止心跳保活
641
- */
642
- stopHeartbeat() {
643
- if (this.heartbeatInterval) {
644
- clearInterval(this.heartbeatInterval);
645
- this.heartbeatInterval = null;
646
- }
647
- }
648
-
649
- /**
650
- * 获取WebSocket URL
651
- */
652
- getWebSocketUrl() {
653
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
654
- const host = window.location.hostname;
655
- const currentPort = window.location.port;
656
-
657
- // WebSocket服务运行在5622端口
658
- let wsPort = 5622;
659
-
660
- const wsUrl = `${protocol}//${host}:${wsPort}`;
661
- console.log('WebSocket URL:', wsUrl);
662
- return wsUrl;
663
- }
664
-
665
- /**
666
- * 尝试重连(优化版 - 使用指数退避算法)
667
- */
668
- attemptReconnect() {
669
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
670
- this.reconnectAttempts++;
671
- this.updateStatus('reconnecting');
672
-
673
- // 指数退避算法:延迟 = 基础延迟 * (2 ^ (重试次数 - 1))
674
- const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1), 30000);
675
-
676
- console.log(`尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts}),延迟 ${delay}ms`);
677
-
678
- this.write(`\r\n\x1b[33m⏳ ${delay/1000}秒后尝试重新连接 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...\x1b[0m\r\n`);
679
-
680
- setTimeout(() => {
681
- this.connectWebSocket();
682
- }, delay);
683
- } else {
684
- this.writeError(`\r\n\x1b[31m✗ 已达到最大重连次数 (${this.maxReconnectAttempts}),请刷新页面重试\x1b[0m\r\n`);
685
- }
686
- }
687
-
688
- /**
689
- * 创建终端会话
690
- */
691
- createTerminalSession() {
692
- // 如果有旧会话,先关闭它
693
- if (this.sessionId) {
694
- this.sendMessage({
695
- type: 'terminal.close',
696
- sessionId: this.sessionId
697
- });
698
- this.sessionId = null;
699
- }
700
-
701
- // 创建新会话
702
- this.sendMessage({
703
- type: 'terminal.create',
704
- size: {
705
- cols: this.terminal.cols,
706
- rows: this.terminal.rows
707
- }
708
- });
709
- }
710
-
711
- /**
712
- * 发送消息
713
- */
714
- sendMessage(message) {
715
- if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
716
- this.websocket.send(JSON.stringify(message));
717
- }
718
- }
719
-
720
- /**
721
- * 发送数据
722
- */
723
- sendData(data) {
724
- if (this.isConnected) {
725
- this.sendMessage({
726
- type: 'terminal.data',
727
- data: data
728
- });
729
- }
730
- }
731
-
732
- /**
733
- * 处理消息
734
- */
735
- handleMessage(message) {
736
- console.log('处理消息类型:', message.type, message);
737
-
738
- switch (message.type) {
739
- case 'welcome':
740
- console.log('收到欢迎消息:', message);
741
- // this.write('\r\n✓ 已连接到服务器\r\n');
742
- break;
743
-
744
- case 'terminal.created':
745
- this.sessionId = message.sessionId;
746
- console.log('终端会话已创建:', this.sessionId);
747
- this.write(`\r\n✓ 终端会话已创建 (ID: ${this.sessionId})\r\n`);
748
- if (message.info) {
749
- this.write(`Shell: ${message.info.shell}\r\n`);
750
- this.write(`工作目录: ${message.info.workingDirectory}\r\n`);
751
- }
752
- this.write('\r\n');
753
- break;
754
-
755
- case 'terminal.data':
756
- if (message.data) {
757
- this.write(message.data);
758
- }
759
- break;
760
-
761
- case 'terminal.exit':
762
- this.write(`\r\n[会话结束,退出代码: ${message.exitCode}]\r\n`);
763
- if (message.signal) {
764
- this.write(`[信号: ${message.signal}]\r\n`);
765
- }
766
- break;
767
-
768
- case 'terminal.resized':
769
- console.log('终端大小已调整:', message.size);
770
- break;
771
-
772
- case 'terminal.closed':
773
- console.log('终端会话已关闭:', message.sessionId);
774
- this.write(`\r\n[终端会话已关闭]\r\n`);
775
- this.sessionId = null;
776
- break;
777
-
778
- case 'error':
779
- this.writeError(`服务器错误: ${message.message}`);
780
- if (message.details) {
781
- this.writeError(`详细信息: ${message.details}`);
782
- }
783
- break;
784
-
785
- case 'pong':
786
- // 心跳响应,不需要处理
787
- break;
788
-
789
- default:
790
- console.warn('未知消息类型:', message.type, message);
791
- this.write(`\r\n[未知消息类型: ${message.type}]\r\n`);
792
- }
793
- }
794
-
795
- /**
796
- * 写入数据到终端
797
- */
798
- write(data) {
799
- if (this.terminal) {
800
- this.terminal.write(data);
801
- }
802
- }
803
-
804
- /**
805
- * 写入错误信息
806
- */
807
- writeError(message) {
808
- this.write(`\r\n\x1b[31m[错误] ${message}\x1b[0m\r\n`);
809
- }
810
-
811
- /**
812
- * 清除终端
813
- */
814
- clear() {
815
- if (this.terminal) {
816
- this.terminal.clear();
817
- }
818
- }
819
-
820
- /**
821
- * 适应大小
822
- */
823
- fit() {
824
- if (this.fitAddon) {
825
- this.fitAddon.fit();
826
-
827
- // 通知服务器终端大小变化
828
- if (this.isConnected && this.sessionId) {
829
- this.sendMessage({
830
- type: 'terminal.resize',
831
- cols: this.terminal.cols,
832
- rows: this.terminal.rows
833
- });
834
- }
835
- }
836
- }
837
-
838
- /**
839
- * 更新终端连接状态显示
840
- *
841
- * @param {string} status - 连接状态,可选值:'connected'、'disconnected'、'reconnecting'
842
- */
843
- updateStatus(status) {
844
- const indicator = this.container.querySelector('.status-indicator');
845
- const text = this.container.querySelector('.status-text');
846
-
847
- if (indicator) {
848
- indicator.className = `status-indicator ${status}`;
849
- }
850
-
851
- if (text) {
852
- const statusText = {
853
- connected: '已连接',
854
- disconnected: '未连接',
855
- reconnecting: '重连中...'
856
- };
857
- text.textContent = statusText[status] || '未知状态';
858
- }
859
- }
860
-
861
- /**
862
- * 重新连接
863
- */
864
- reconnect() {
865
- // 清理旧会话
866
- this.sessionId = null;
867
- this.isConnected = false;
868
-
869
- if (this.websocket) {
870
- this.websocket.close();
871
- this.websocket = null;
872
- }
873
-
874
- this.reconnectAttempts = 0;
875
- this.connectWebSocket();
876
- }
877
-
878
- /**
879
- * 切换主题
880
- */
881
- toggleTheme() {
882
- this.options.theme = this.options.theme === 'dark' ? 'light' : 'dark';
883
- this.terminal.options.theme = this.getTheme(this.options.theme);
884
-
885
- // 更新主题class
886
- this.updateThemeClass();
887
-
888
- // 强制重新渲染以应用新的选中样式
889
- this.refreshTerminal();
890
-
891
- // 保存主题偏好
892
- localStorage.setItem('terminal-theme', this.options.theme);
893
- }
894
-
895
- /**
896
- * 设置主题
897
- */
898
- setTheme(themeName) {
899
- this.options.theme = themeName;
900
- this.terminal.options.theme = this.getTheme(themeName);
901
-
902
- // 更新主题class
903
- this.updateThemeClass();
904
-
905
- // 强制重新渲染以应用新的选中样式
906
- this.refreshTerminal();
907
- }
908
-
909
- /**
910
- * 刷新终端以应用新主题
911
- */
912
- refreshTerminal() {
913
- // 保存当前内容
914
- const buffer = this.terminal.buffer.active;
915
- const cursorX = this.terminal.buffer.active.cursorX;
916
- const cursorY = this.terminal.buffer.active.cursorY;
917
-
918
- // 强制重新渲染
919
- this.terminal.refresh(0, this.terminal.rows - 1);
920
- }
921
-
922
- /**
923
- * 更新主题class
924
- */
925
- updateThemeClass() {
926
- // 移除所有主题class
927
- this.terminal.element.classList.remove('theme-dark', 'theme-light');
928
- // 添加当前主题class
929
- this.terminal.element.classList.add(`theme-${this.options.theme}`);
930
- }
931
-
932
- /**
933
- * 获取会话信息
934
- */
935
- getSessionInfo() {
936
- return {
937
- sessionId: this.sessionId,
938
- isConnected: this.isConnected,
939
- reconnectAttempts: this.reconnectAttempts,
940
- terminalSize: {
941
- cols: this.terminal.cols,
942
- rows: this.terminal.rows
943
- }
944
- };
945
- }
946
-
947
- /**
948
- * 销毁组件(优化版 - 确保正确清理所有资源)
949
- */
950
- destroy() {
951
- console.log('销毁 TerminalComponent...');
952
-
953
- // 停止心跳
954
- this.stopHeartbeat();
955
-
956
- // 关闭 WebSocket
957
- if (this.websocket) {
958
- if (this.websocket.readyState === WebSocket.OPEN) {
959
- this.websocket.close(1000, 'Component destroyed');
960
- }
961
- this.websocket = null;
962
- }
963
-
964
- // 关闭终端会话
965
- if (this.sessionId) {
966
- this.sendMessage({
967
- type: 'terminal.close',
968
- sessionId: this.sessionId
969
- });
970
- this.sessionId = null;
971
- }
972
-
973
- // 销毁终端
974
- if (this.terminal) {
975
- this.terminal.dispose();
976
- this.terminal = null;
977
- }
978
-
979
- // 移除事件监听器
980
- window.removeEventListener('resize', this.fit);
981
-
982
- // 清理状态
983
- this.isConnected = false;
984
- this.reconnectAttempts = 0;
985
- this.commandHistory = [];
986
- this.historyIndex = -1;
987
-
988
- console.log('TerminalComponent 已销毁');
989
- }
990
- }
991
-
992
- // 导出默认创建函数
993
- export function createTerminal(container, options) {
994
- return new TerminalComponent(container, options);
995
- }