tabby-ai-assistant 1.0.5 → 1.0.6

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 (116) hide show
  1. package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
  2. package/dist/components/chat/chat-interface.component.d.ts +38 -6
  3. package/dist/components/settings/general-settings.component.d.ts +6 -3
  4. package/dist/components/settings/provider-config.component.d.ts +25 -12
  5. package/dist/components/terminal/command-preview.component.d.ts +38 -0
  6. package/dist/index-full.d.ts +8 -0
  7. package/dist/index-minimal.d.ts +3 -0
  8. package/dist/index.d.ts +7 -3
  9. package/dist/index.js +1 -2
  10. package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
  11. package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
  12. package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
  13. package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
  14. package/dist/services/chat/chat-history.service.d.ts +78 -0
  15. package/dist/services/chat/chat-session.service.d.ts +57 -2
  16. package/dist/services/context/compaction.d.ts +90 -0
  17. package/dist/services/context/manager.d.ts +69 -0
  18. package/dist/services/context/memory.d.ts +116 -0
  19. package/dist/services/context/token-budget.d.ts +105 -0
  20. package/dist/services/core/ai-assistant.service.d.ts +40 -1
  21. package/dist/services/core/checkpoint.service.d.ts +130 -0
  22. package/dist/services/platform/escape-sequence.service.d.ts +132 -0
  23. package/dist/services/platform/platform-detection.service.d.ts +146 -0
  24. package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
  25. package/dist/services/providers/base-provider.service.d.ts +6 -1
  26. package/dist/services/providers/glm-provider.service.d.ts +5 -0
  27. package/dist/services/providers/minimax-provider.service.d.ts +10 -1
  28. package/dist/services/providers/ollama-provider.service.d.ts +76 -0
  29. package/dist/services/providers/openai-compatible.service.d.ts +5 -0
  30. package/dist/services/providers/openai-provider.service.d.ts +5 -0
  31. package/dist/services/providers/vllm-provider.service.d.ts +82 -0
  32. package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
  33. package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
  34. package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
  35. package/dist/types/ai.types.d.ts +92 -0
  36. package/dist/types/provider.types.d.ts +1 -1
  37. package/package.json +7 -10
  38. package/src/components/chat/ai-sidebar.component.ts +945 -0
  39. package/src/components/chat/chat-input.component.html +9 -24
  40. package/src/components/chat/chat-input.component.scss +3 -2
  41. package/src/components/chat/chat-interface.component.html +77 -69
  42. package/src/components/chat/chat-interface.component.scss +54 -4
  43. package/src/components/chat/chat-interface.component.ts +250 -34
  44. package/src/components/chat/chat-settings.component.scss +4 -4
  45. package/src/components/chat/chat-settings.component.ts +22 -11
  46. package/src/components/common/error-message.component.html +15 -0
  47. package/src/components/common/error-message.component.scss +77 -0
  48. package/src/components/common/error-message.component.ts +2 -96
  49. package/src/components/common/loading-spinner.component.html +4 -0
  50. package/src/components/common/loading-spinner.component.scss +57 -0
  51. package/src/components/common/loading-spinner.component.ts +2 -63
  52. package/src/components/security/consent-dialog.component.html +22 -0
  53. package/src/components/security/consent-dialog.component.scss +34 -0
  54. package/src/components/security/consent-dialog.component.ts +2 -55
  55. package/src/components/security/password-prompt.component.html +19 -0
  56. package/src/components/security/password-prompt.component.scss +30 -0
  57. package/src/components/security/password-prompt.component.ts +2 -54
  58. package/src/components/security/risk-confirm-dialog.component.html +8 -12
  59. package/src/components/security/risk-confirm-dialog.component.scss +8 -5
  60. package/src/components/security/risk-confirm-dialog.component.ts +6 -6
  61. package/src/components/settings/ai-settings-tab.component.html +16 -20
  62. package/src/components/settings/ai-settings-tab.component.scss +8 -5
  63. package/src/components/settings/ai-settings-tab.component.ts +12 -12
  64. package/src/components/settings/general-settings.component.html +8 -17
  65. package/src/components/settings/general-settings.component.scss +6 -3
  66. package/src/components/settings/general-settings.component.ts +62 -22
  67. package/src/components/settings/provider-config.component.html +19 -39
  68. package/src/components/settings/provider-config.component.scss +182 -39
  69. package/src/components/settings/provider-config.component.ts +119 -7
  70. package/src/components/settings/security-settings.component.scss +1 -1
  71. package/src/components/terminal/ai-toolbar-button.component.html +8 -0
  72. package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
  73. package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
  74. package/src/components/terminal/command-preview.component.html +61 -0
  75. package/src/components/terminal/command-preview.component.scss +72 -0
  76. package/src/components/terminal/command-preview.component.ts +127 -140
  77. package/src/components/terminal/command-suggestion.component.html +23 -0
  78. package/src/components/terminal/command-suggestion.component.scss +55 -0
  79. package/src/components/terminal/command-suggestion.component.ts +2 -77
  80. package/src/index-minimal.ts +32 -0
  81. package/src/index.ts +94 -11
  82. package/src/index.ts.backup +165 -0
  83. package/src/providers/tabby/ai-config.provider.ts +60 -51
  84. package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
  85. package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
  86. package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
  87. package/src/services/chat/ai-sidebar.service.ts +258 -0
  88. package/src/services/chat/chat-history.service.ts +308 -0
  89. package/src/services/chat/chat-history.service.ts.backup +239 -0
  90. package/src/services/chat/chat-session.service.ts +276 -3
  91. package/src/services/context/compaction.ts +483 -0
  92. package/src/services/context/manager.ts +442 -0
  93. package/src/services/context/memory.ts +519 -0
  94. package/src/services/context/token-budget.ts +422 -0
  95. package/src/services/core/ai-assistant.service.ts +280 -5
  96. package/src/services/core/ai-provider-manager.service.ts +2 -2
  97. package/src/services/core/checkpoint.service.ts +619 -0
  98. package/src/services/platform/escape-sequence.service.ts +499 -0
  99. package/src/services/platform/platform-detection.service.ts +494 -0
  100. package/src/services/providers/anthropic-provider.service.ts +28 -1
  101. package/src/services/providers/base-provider.service.ts +7 -1
  102. package/src/services/providers/glm-provider.service.ts +28 -1
  103. package/src/services/providers/minimax-provider.service.ts +209 -11
  104. package/src/services/providers/ollama-provider.service.ts +445 -0
  105. package/src/services/providers/openai-compatible.service.ts +9 -0
  106. package/src/services/providers/openai-provider.service.ts +9 -0
  107. package/src/services/providers/vllm-provider.service.ts +463 -0
  108. package/src/services/security/risk-assessment.service.ts +6 -2
  109. package/src/services/terminal/buffer-analyzer.service.ts +594 -0
  110. package/src/services/terminal/terminal-manager.service.ts +748 -0
  111. package/src/services/terminal/terminal-tools.service.ts +441 -0
  112. package/src/styles/ai-assistant.scss +78 -6
  113. package/src/types/ai.types.ts +144 -0
  114. package/src/types/provider.types.ts +1 -1
  115. package/tsconfig.json +9 -9
  116. package/webpack.config.js +28 -6
@@ -0,0 +1,499 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { LoggerService } from '../core/logger.service';
3
+ import { PlatformDetectionService, OSType, TerminalType } from './platform-detection.service';
4
+
5
+ /**
6
+ * 转义序列类型
7
+ */
8
+ export enum EscapeSequenceType {
9
+ COLOR = 'color',
10
+ CURSOR = 'cursor',
11
+ CLEAR = 'clear',
12
+ STYLE = 'style',
13
+ SCROLL = 'scroll',
14
+ TITLE = 'title',
15
+ BELL = 'bell'
16
+ }
17
+
18
+ /**
19
+ * 颜色代码
20
+ */
21
+ export interface ColorCode {
22
+ foreground?: string;
23
+ background?: string;
24
+ bright?: boolean;
25
+ }
26
+
27
+ /**
28
+ * 光标控制
29
+ */
30
+ export interface CursorControl {
31
+ position?: { x: number; y: number };
32
+ visible?: boolean;
33
+ save?: boolean;
34
+ restore?: boolean;
35
+ up?: number;
36
+ down?: number;
37
+ forward?: number;
38
+ backward?: number;
39
+ }
40
+
41
+ /**
42
+ * 清屏选项
43
+ */
44
+ export interface ClearOptions {
45
+ screen?: boolean;
46
+ line?: boolean;
47
+ fromCursor?: boolean;
48
+ toCursor?: boolean;
49
+ }
50
+
51
+ /**
52
+ * 转义序列映射
53
+ */
54
+ export interface EscapeSequenceMapping {
55
+ linux: string;
56
+ windows: string;
57
+ macos: string;
58
+ }
59
+
60
+ /**
61
+ * 转义序列服务
62
+ * 处理跨平台的ANSI转义序列和终端控制代码
63
+ */
64
+ @Injectable({
65
+ providedIn: 'root'
66
+ })
67
+ export class EscapeSequenceService {
68
+ private platformInfo: { os: OSType; terminal: TerminalType } | null = null;
69
+
70
+ // 颜色代码映射
71
+ private readonly COLOR_CODES: Record<string, EscapeSequenceMapping> = {
72
+ // 基础颜色
73
+ black: { linux: '\\033[30m', windows: '\\033[30m', macos: '\\033[30m' },
74
+ red: { linux: '\\033[31m', windows: '\\033[31m', macos: '\\033[31m' },
75
+ green: { linux: '\\033[32m', windows: '\\033[32m', macos: '\\033[32m' },
76
+ yellow: { linux: '\\033[33m', windows: '\\033[33m', macos: '\\033[33m' },
77
+ blue: { linux: '\\033[34m', windows: '\\033[34m', macos: '\\033[34m' },
78
+ magenta: { linux: '\\033[35m', windows: '\\033[35m', macos: '\\033[35m' },
79
+ cyan: { linux: '\\033[36m', windows: '\\033[36m', macos: '\\033[36m' },
80
+ white: { linux: '\\033[37m', windows: '\\033[37m', macos: '\\033[37m' },
81
+ // 明亮颜色
82
+ brightBlack: { linux: '\\033[90m', windows: '\\033[90m', macos: '\\033[90m' },
83
+ brightRed: { linux: '\\033[91m', windows: '\\033[91m', macos: '\\033[91m' },
84
+ brightGreen: { linux: '\\033[92m', windows: '\\033[92m', macos: '\\033[92m' },
85
+ brightYellow: { linux: '\\033[93m', windows: '\\033[93m', macos: '\\033[93m' },
86
+ brightBlue: { linux: '\\033[94m', windows: '\\033[94m', macos: '\\033[94m' },
87
+ brightMagenta: { linux: '\\033[95m', windows: '\\033[95m', macos: '\\033[95m' },
88
+ brightCyan: { linux: '\\033[96m', windows: '\\033[96m', macos: '\\033[96m' },
89
+ brightWhite: { linux: '\\033[97m', windows: '\\033[97m', macos: '\\033[97m' },
90
+ // 重置
91
+ reset: { linux: '\\033[0m', windows: '\\033[0m', macos: '\\033[0m' }
92
+ };
93
+
94
+ // 样式代码映射
95
+ private readonly STYLE_CODES: Record<string, EscapeSequenceMapping> = {
96
+ bold: { linux: '\\033[1m', windows: '\\033[1m', macos: '\\033[1m' },
97
+ dim: { linux: '\\033[2m', windows: '\\033[2m', macos: '\\033[2m' },
98
+ italic: { linux: '\\033[3m', windows: '\\033[3m', macos: '\\033[3m' },
99
+ underline: { linux: '\\033[4m', windows: '\\033[4m', macos: '\\033[4m' },
100
+ blink: { linux: '\\033[5m', windows: '\\033[5m', macos: '\\033[5m' },
101
+ reverse: { linux: '\\033[7m', windows: '\\033[7m', macos: '\\033[7m' },
102
+ hidden: { linux: '\\033[8m', windows: '\\033[8m', macos: '\\033[8m' }
103
+ };
104
+
105
+ // 光标控制映射
106
+ private readonly CURSOR_CODES: Record<string, EscapeSequenceMapping> = {
107
+ up: { linux: '\\033[A', windows: '\\033[A', macos: '\\033[A' },
108
+ down: { linux: '\\033[B', windows: '\\033[B', macos: '\\033[B' },
109
+ forward: { linux: '\\033[C', windows: '\\033[C', macos: '\\033[C' },
110
+ backward: { linux: '\\033[D', windows: '\\033[D', macos: '\\033[D' },
111
+ nextLine: { linux: '\\033[E', windows: '\\033[E', macos: '\\033[E' },
112
+ previousLine: { linux: '\\033[F', windows: '\\033[F', macos: '\\033[F' },
113
+ horizontalTab: { linux: '\\033[G', windows: '\\033[G', macos: '\\033[G' },
114
+ save: { linux: '\\033[s', windows: '\\033[s', macos: '\\033[s' },
115
+ restore: { linux: '\\033[u', windows: '\\033[u', macos: '\\033[u' },
116
+ hide: { linux: '\\033[?25l', windows: '\\033[?25l', macos: '\\033[?25l' },
117
+ show: { linux: '\\033[?25h', windows: '\\033[?25h', macos: '\\033[?25h' },
118
+ clear: { linux: '\\033[2J', windows: '\\033[2J', macos: '\\033[2J' },
119
+ clearLine: { linux: '\\033[2K', windows: '\\033[2K', macos: '\\033[2K' }
120
+ };
121
+
122
+ // 清屏序列映射
123
+ private readonly CLEAR_CODES: Record<string, EscapeSequenceMapping> = {
124
+ screen: { linux: '\\033[2J\\033[H', windows: '\\033[2J\\033[H', macos: '\\033[2J\\033[H' },
125
+ line: { linux: '\\033[2K', windows: '\\033[2K', macos: '\\033[2K' },
126
+ fromCursor: { linux: '\\033[1J', windows: '\\033[1J', macos: '\\033[1J' },
127
+ toCursor: { linux: '\\033[0J', windows: '\\033[0J', macos: '\\033[0J' }
128
+ };
129
+
130
+ constructor(
131
+ private logger: LoggerService,
132
+ private platformDetection: PlatformDetectionService
133
+ ) {
134
+ this.platformInfo = {
135
+ os: this.platformDetection.detectOS(),
136
+ terminal: this.platformDetection.detectTerminal()
137
+ };
138
+ this.logger.info('EscapeSequenceService initialized', { platformInfo: this.platformInfo });
139
+ }
140
+
141
+ /**
142
+ * 映射颜色代码
143
+ */
144
+ mapColorCode(color: string): string {
145
+ const platformKey = this.getPlatformKey();
146
+ const colorCode = this.COLOR_CODES[color.toLowerCase()];
147
+
148
+ if (!colorCode) {
149
+ this.logger.warn('Unknown color code', { color });
150
+ return '';
151
+ }
152
+
153
+ return colorCode[platformKey] || colorCode.linux;
154
+ }
155
+
156
+ /**
157
+ * 映射光标控制
158
+ */
159
+ mapCursorControl(control: string, value?: number): string {
160
+ const platformKey = this.getPlatformKey();
161
+ let code = '';
162
+
163
+ switch (control) {
164
+ case 'up':
165
+ case 'down':
166
+ case 'forward':
167
+ case 'backward':
168
+ case 'nextLine':
169
+ case 'previousLine':
170
+ case 'horizontalTab':
171
+ if (value) {
172
+ code = `\\033[${value}${control.charAt(0).toUpperCase()}`;
173
+ } else {
174
+ code = this.CURSOR_CODES[control][platformKey];
175
+ }
176
+ break;
177
+
178
+ case 'position':
179
+ if (value) {
180
+ // value 编码了 x,y 位置
181
+ const x = value % 256;
182
+ const y = Math.floor(value / 256);
183
+ code = `\\033[${y};${x}H`;
184
+ }
185
+ break;
186
+
187
+ case 'save':
188
+ case 'restore':
189
+ case 'hide':
190
+ case 'show':
191
+ case 'clear':
192
+ case 'clearLine':
193
+ code = this.CURSOR_CODES[control][platformKey];
194
+ break;
195
+
196
+ default:
197
+ this.logger.warn('Unknown cursor control', { control });
198
+ }
199
+
200
+ return code;
201
+ }
202
+
203
+ /**
204
+ * 映射清屏序列
205
+ */
206
+ mapClearSequence(options: ClearOptions): string {
207
+ const platformKey = this.getPlatformKey();
208
+ const sequences: string[] = [];
209
+
210
+ if (options.screen) {
211
+ sequences.push(this.CLEAR_CODES.screen[platformKey]);
212
+ } else if (options.line) {
213
+ sequences.push(this.CLEAR_CODES.line[platformKey]);
214
+ } else if (options.fromCursor) {
215
+ sequences.push(this.CLEAR_CODES.fromCursor[platformKey]);
216
+ } else if (options.toCursor) {
217
+ sequences.push(this.CLEAR_CODES.toCursor[platformKey]);
218
+ }
219
+
220
+ return sequences.join('');
221
+ }
222
+
223
+ /**
224
+ * 映射样式代码
225
+ */
226
+ mapStyleCode(style: string): string {
227
+ const platformKey = this.getPlatformKey();
228
+ const styleCode = this.STYLE_CODES[style.toLowerCase()];
229
+
230
+ if (!styleCode) {
231
+ this.logger.warn('Unknown style code', { style });
232
+ return '';
233
+ }
234
+
235
+ return styleCode[platformKey] || styleCode.linux;
236
+ }
237
+
238
+ /**
239
+ * 生成颜色文本
240
+ */
241
+ colorize(text: string, color: ColorCode): string {
242
+ const platformKey = this.getPlatformKey();
243
+ let result = '';
244
+
245
+ // 设置前景色
246
+ if (color.foreground) {
247
+ const colorCode = this.COLOR_CODES[color.foreground.toLowerCase()];
248
+ if (colorCode) {
249
+ result += colorCode[platformKey];
250
+ }
251
+ }
252
+
253
+ // 设置背景色
254
+ if (color.background) {
255
+ const bgColor = 'bg' + color.background.charAt(0).toUpperCase() + color.background.slice(1);
256
+ const bgCode = this.COLOR_CODES[bgColor.toLowerCase()];
257
+ if (bgCode) {
258
+ result += bgCode[platformKey];
259
+ }
260
+ }
261
+
262
+ // 添加文本
263
+ result += text;
264
+
265
+ // 重置
266
+ result += this.COLOR_CODES.reset[platformKey];
267
+
268
+ return result;
269
+ }
270
+
271
+ /**
272
+ * 生成样式文本
273
+ */
274
+ stylize(text: string, styles: string[]): string {
275
+ const platformKey = this.getPlatformKey();
276
+ let result = '';
277
+
278
+ // 应用样式
279
+ styles.forEach(style => {
280
+ const styleCode = this.STYLE_CODES[style.toLowerCase()];
281
+ if (styleCode) {
282
+ result += styleCode[platformKey];
283
+ }
284
+ });
285
+
286
+ // 添加文本
287
+ result += text;
288
+
289
+ // 重置
290
+ result += this.COLOR_CODES.reset[platformKey];
291
+
292
+ return result;
293
+ }
294
+
295
+ /**
296
+ * 移动光标
297
+ */
298
+ moveCursor(control: CursorControl): string {
299
+ const platformKey = this.getPlatformKey();
300
+ let sequence = '';
301
+
302
+ if (control.save) {
303
+ sequence += this.CURSOR_CODES.save[platformKey];
304
+ }
305
+
306
+ if (control.position) {
307
+ sequence += `\\033[${control.position.y};${control.position.x}H`;
308
+ }
309
+
310
+ if (control.up) {
311
+ sequence += `\\033[${control.up}A`;
312
+ }
313
+
314
+ if (control.down) {
315
+ sequence += `\\033[${control.down}B`;
316
+ }
317
+
318
+ if (control.forward) {
319
+ sequence += `\\033[${control.forward}C`;
320
+ }
321
+
322
+ if (control.backward) {
323
+ sequence += `\\033[${control.backward}D`;
324
+ }
325
+
326
+ if (control.restore) {
327
+ sequence += this.CURSOR_CODES.restore[platformKey];
328
+ }
329
+
330
+ if (control.visible === false) {
331
+ sequence += this.CURSOR_CODES.hide[platformKey];
332
+ } else if (control.visible === true) {
333
+ sequence += this.CURSOR_CODES.show[platformKey];
334
+ }
335
+
336
+ return sequence;
337
+ }
338
+
339
+ /**
340
+ * 清屏
341
+ */
342
+ clear(options: ClearOptions = { screen: true }): string {
343
+ return this.mapClearSequence(options);
344
+ }
345
+
346
+ /**
347
+ * 移除ANSI转义序列
348
+ */
349
+ stripAnsi(text: string): string {
350
+ // 匹配ANSI转义序列的正则表达式
351
+ const ansiEscapeSequence = /\\x1b\[[0-9;]*[mGKHF]?/g;
352
+ return text.replace(ansiEscapeSequence, '');
353
+ }
354
+
355
+ /**
356
+ * 检测终端能力
357
+ */
358
+ checkTerminalCapabilities(): {
359
+ supportsColors: boolean;
360
+ supportsTrueColor: boolean;
361
+ supportsMouse: boolean;
362
+ supportsUnicode: boolean;
363
+ colorLevel: number;
364
+ } {
365
+ const capabilities = this.platformDetection.checkCapabilities();
366
+
367
+ return {
368
+ supportsColors: capabilities.colors >= 8,
369
+ supportsTrueColor: capabilities.trueColor,
370
+ supportsMouse: capabilities.mouse,
371
+ supportsUnicode: capabilities.unicode,
372
+ colorLevel: capabilities.colors
373
+ };
374
+ }
375
+
376
+ /**
377
+ * 适配输出
378
+ */
379
+ adaptOutput(output: string, targetPlatform?: OSType): string {
380
+ const platform = targetPlatform || this.platformInfo?.os || OSType.LINUX;
381
+
382
+ // 如果是Windows CMD,可能不支持某些转义序列
383
+ if (platform === OSType.WINDOWS) {
384
+ // 移除不支持的序列或替换为兼容序列
385
+ output = this.adaptForWindows(output);
386
+ }
387
+
388
+ return output;
389
+ }
390
+
391
+ /**
392
+ * 创建进度条
393
+ */
394
+ createProgressBar(
395
+ current: number,
396
+ total: number,
397
+ width: number = 50,
398
+ color: string = 'green'
399
+ ): string {
400
+ const percentage = Math.floor((current / total) * 100);
401
+ const filledWidth = Math.floor((current / total) * width);
402
+ const emptyWidth = width - filledWidth;
403
+
404
+ const fillChar = '█';
405
+ const emptyChar = '░';
406
+
407
+ const progressBar = `${this.colorize(fillChar.repeat(filledWidth), { foreground: color })}${emptyChar.repeat(emptyWidth)}`;
408
+ const text = `${current}/${total} (${percentage}%)`;
409
+
410
+ return `${progressBar} ${text}`;
411
+ }
412
+
413
+ /**
414
+ * 创建表格
415
+ */
416
+ createTable(headers: string[], rows: string[][], options?: {
417
+ colorizeHeaders?: boolean;
418
+ borderColor?: string;
419
+ }): string {
420
+ const borderColor = options?.borderColor || 'blue';
421
+ const headerColor = options?.colorizeHeaders ? { foreground: borderColor } : undefined;
422
+
423
+ // 计算列宽
424
+ const columnWidths = headers.map((header, index) => {
425
+ const maxContentWidth = Math.max(
426
+ header.length,
427
+ ...rows.map(row => (row[index] || '').length)
428
+ );
429
+ return Math.min(maxContentWidth, 50); // 最大宽度限制
430
+ });
431
+
432
+ // 创建边框
433
+ const border = '+' + columnWidths.map(width => '-'.repeat(width + 2)).join('+') + '+';
434
+
435
+ // 创建表格
436
+ let table = border + '\n';
437
+
438
+ // 表头
439
+ const headerRow = '|' + headers.map((header, index) => {
440
+ const padded = header.padEnd(columnWidths[index]);
441
+ return ' ' + (headerColor ? this.colorize(padded, headerColor) : padded) + ' ';
442
+ }).join('|') + '|';
443
+ table += headerRow + '\n';
444
+ table += border + '\n';
445
+
446
+ // 数据行
447
+ rows.forEach(row => {
448
+ const dataRow = '|' + row.map((cell, index) => {
449
+ const padded = (cell || '').padEnd(columnWidths[index]);
450
+ return ' ' + padded + ' ';
451
+ }).join('|') + '|';
452
+ table += dataRow + '\n';
453
+ });
454
+
455
+ table += border;
456
+
457
+ return table;
458
+ }
459
+
460
+ // ==================== 私有方法 ====================
461
+
462
+ private getPlatformKey(): 'linux' | 'windows' | 'macos' {
463
+ if (!this.platformInfo) {
464
+ this.platformInfo = {
465
+ os: this.platformDetection.detectOS(),
466
+ terminal: this.platformDetection.detectTerminal()
467
+ };
468
+ }
469
+
470
+ switch (this.platformInfo.os) {
471
+ case OSType.WINDOWS:
472
+ return 'windows';
473
+ case OSType.MACOS:
474
+ return 'macos';
475
+ case OSType.LINUX:
476
+ default:
477
+ return 'linux';
478
+ }
479
+ }
480
+
481
+ private adaptForWindows(output: string): string {
482
+ // Windows CMD 对ANSI支持有限
483
+ // 移除或替换不支持的序列
484
+
485
+ // 移除TrueColor序列(如果不支持)
486
+ if (!this.checkTerminalCapabilities().supportsTrueColor) {
487
+ output = output.replace(/\\033\[38;2;\d+;\d+;\d+m/g, '');
488
+ output = output.replace(/\\033\[48;2;\d+;\d+;\d+m/g, '');
489
+ }
490
+
491
+ // 移除256色序列(如果不支持)
492
+ if (this.checkTerminalCapabilities().colorLevel < 256) {
493
+ output = output.replace(/\\033\[38;5;\d+m/g, '');
494
+ output = output.replace(/\\033\[48;5;\d+m/g, '');
495
+ }
496
+
497
+ return output;
498
+ }
499
+ }