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.
- package/dist/components/chat/ai-sidebar.component.d.ts +147 -0
- package/dist/components/chat/chat-interface.component.d.ts +38 -6
- package/dist/components/settings/general-settings.component.d.ts +6 -3
- package/dist/components/settings/provider-config.component.d.ts +25 -12
- package/dist/components/terminal/command-preview.component.d.ts +38 -0
- package/dist/index-full.d.ts +8 -0
- package/dist/index-minimal.d.ts +3 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1 -2
- package/dist/providers/tabby/ai-config.provider.d.ts +57 -5
- package/dist/providers/tabby/ai-hotkey.provider.d.ts +8 -14
- package/dist/providers/tabby/ai-toolbar-button.provider.d.ts +8 -9
- package/dist/services/chat/ai-sidebar.service.d.ts +89 -0
- package/dist/services/chat/chat-history.service.d.ts +78 -0
- package/dist/services/chat/chat-session.service.d.ts +57 -2
- package/dist/services/context/compaction.d.ts +90 -0
- package/dist/services/context/manager.d.ts +69 -0
- package/dist/services/context/memory.d.ts +116 -0
- package/dist/services/context/token-budget.d.ts +105 -0
- package/dist/services/core/ai-assistant.service.d.ts +40 -1
- package/dist/services/core/checkpoint.service.d.ts +130 -0
- package/dist/services/platform/escape-sequence.service.d.ts +132 -0
- package/dist/services/platform/platform-detection.service.d.ts +146 -0
- package/dist/services/providers/anthropic-provider.service.d.ts +5 -0
- package/dist/services/providers/base-provider.service.d.ts +6 -1
- package/dist/services/providers/glm-provider.service.d.ts +5 -0
- package/dist/services/providers/minimax-provider.service.d.ts +10 -1
- package/dist/services/providers/ollama-provider.service.d.ts +76 -0
- package/dist/services/providers/openai-compatible.service.d.ts +5 -0
- package/dist/services/providers/openai-provider.service.d.ts +5 -0
- package/dist/services/providers/vllm-provider.service.d.ts +82 -0
- package/dist/services/terminal/buffer-analyzer.service.d.ts +128 -0
- package/dist/services/terminal/terminal-manager.service.d.ts +185 -0
- package/dist/services/terminal/terminal-tools.service.d.ts +79 -0
- package/dist/types/ai.types.d.ts +92 -0
- package/dist/types/provider.types.d.ts +1 -1
- package/package.json +7 -10
- package/src/components/chat/ai-sidebar.component.ts +945 -0
- package/src/components/chat/chat-input.component.html +9 -24
- package/src/components/chat/chat-input.component.scss +3 -2
- package/src/components/chat/chat-interface.component.html +77 -69
- package/src/components/chat/chat-interface.component.scss +54 -4
- package/src/components/chat/chat-interface.component.ts +250 -34
- package/src/components/chat/chat-settings.component.scss +4 -4
- package/src/components/chat/chat-settings.component.ts +22 -11
- package/src/components/common/error-message.component.html +15 -0
- package/src/components/common/error-message.component.scss +77 -0
- package/src/components/common/error-message.component.ts +2 -96
- package/src/components/common/loading-spinner.component.html +4 -0
- package/src/components/common/loading-spinner.component.scss +57 -0
- package/src/components/common/loading-spinner.component.ts +2 -63
- package/src/components/security/consent-dialog.component.html +22 -0
- package/src/components/security/consent-dialog.component.scss +34 -0
- package/src/components/security/consent-dialog.component.ts +2 -55
- package/src/components/security/password-prompt.component.html +19 -0
- package/src/components/security/password-prompt.component.scss +30 -0
- package/src/components/security/password-prompt.component.ts +2 -54
- package/src/components/security/risk-confirm-dialog.component.html +8 -12
- package/src/components/security/risk-confirm-dialog.component.scss +8 -5
- package/src/components/security/risk-confirm-dialog.component.ts +6 -6
- package/src/components/settings/ai-settings-tab.component.html +16 -20
- package/src/components/settings/ai-settings-tab.component.scss +8 -5
- package/src/components/settings/ai-settings-tab.component.ts +12 -12
- package/src/components/settings/general-settings.component.html +8 -17
- package/src/components/settings/general-settings.component.scss +6 -3
- package/src/components/settings/general-settings.component.ts +62 -22
- package/src/components/settings/provider-config.component.html +19 -39
- package/src/components/settings/provider-config.component.scss +182 -39
- package/src/components/settings/provider-config.component.ts +119 -7
- package/src/components/settings/security-settings.component.scss +1 -1
- package/src/components/terminal/ai-toolbar-button.component.html +8 -0
- package/src/components/terminal/ai-toolbar-button.component.scss +20 -0
- package/src/components/terminal/ai-toolbar-button.component.ts +2 -30
- package/src/components/terminal/command-preview.component.html +61 -0
- package/src/components/terminal/command-preview.component.scss +72 -0
- package/src/components/terminal/command-preview.component.ts +127 -140
- package/src/components/terminal/command-suggestion.component.html +23 -0
- package/src/components/terminal/command-suggestion.component.scss +55 -0
- package/src/components/terminal/command-suggestion.component.ts +2 -77
- package/src/index-minimal.ts +32 -0
- package/src/index.ts +94 -11
- package/src/index.ts.backup +165 -0
- package/src/providers/tabby/ai-config.provider.ts +60 -51
- package/src/providers/tabby/ai-hotkey.provider.ts +23 -39
- package/src/providers/tabby/ai-settings-tab.provider.ts +2 -2
- package/src/providers/tabby/ai-toolbar-button.provider.ts +29 -24
- package/src/services/chat/ai-sidebar.service.ts +258 -0
- package/src/services/chat/chat-history.service.ts +308 -0
- package/src/services/chat/chat-history.service.ts.backup +239 -0
- package/src/services/chat/chat-session.service.ts +276 -3
- package/src/services/context/compaction.ts +483 -0
- package/src/services/context/manager.ts +442 -0
- package/src/services/context/memory.ts +519 -0
- package/src/services/context/token-budget.ts +422 -0
- package/src/services/core/ai-assistant.service.ts +280 -5
- package/src/services/core/ai-provider-manager.service.ts +2 -2
- package/src/services/core/checkpoint.service.ts +619 -0
- package/src/services/platform/escape-sequence.service.ts +499 -0
- package/src/services/platform/platform-detection.service.ts +494 -0
- package/src/services/providers/anthropic-provider.service.ts +28 -1
- package/src/services/providers/base-provider.service.ts +7 -1
- package/src/services/providers/glm-provider.service.ts +28 -1
- package/src/services/providers/minimax-provider.service.ts +209 -11
- package/src/services/providers/ollama-provider.service.ts +445 -0
- package/src/services/providers/openai-compatible.service.ts +9 -0
- package/src/services/providers/openai-provider.service.ts +9 -0
- package/src/services/providers/vllm-provider.service.ts +463 -0
- package/src/services/security/risk-assessment.service.ts +6 -2
- package/src/services/terminal/buffer-analyzer.service.ts +594 -0
- package/src/services/terminal/terminal-manager.service.ts +748 -0
- package/src/services/terminal/terminal-tools.service.ts +441 -0
- package/src/styles/ai-assistant.scss +78 -6
- package/src/types/ai.types.ts +144 -0
- package/src/types/provider.types.ts +1 -1
- package/tsconfig.json +9 -9
- package/webpack.config.js +28 -6
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { LoggerService } from '../core/logger.service';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 缓冲区分析结果
|
|
6
|
+
*/
|
|
7
|
+
export interface BufferAnalysis {
|
|
8
|
+
totalLines: number;
|
|
9
|
+
totalCharacters: number;
|
|
10
|
+
commandCount: number;
|
|
11
|
+
errorCount: number;
|
|
12
|
+
warningCount: number;
|
|
13
|
+
filePaths: string[];
|
|
14
|
+
urls: string[];
|
|
15
|
+
commands: string[];
|
|
16
|
+
errors: string[];
|
|
17
|
+
warnings: string[];
|
|
18
|
+
outputType: 'command' | 'error' | 'success' | 'mixed';
|
|
19
|
+
complexity: 'low' | 'medium' | 'high';
|
|
20
|
+
language?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 命令提取结果
|
|
25
|
+
*/
|
|
26
|
+
export interface CommandExtraction {
|
|
27
|
+
commands: Array<{
|
|
28
|
+
command: string;
|
|
29
|
+
fullCommand: string;
|
|
30
|
+
arguments: string[];
|
|
31
|
+
lineNumber: number;
|
|
32
|
+
timestamp?: number;
|
|
33
|
+
success?: boolean;
|
|
34
|
+
output?: string;
|
|
35
|
+
}>;
|
|
36
|
+
lastCommand?: string;
|
|
37
|
+
commandHistory: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 输出解析结果
|
|
42
|
+
*/
|
|
43
|
+
export interface OutputParsing {
|
|
44
|
+
structuredData: any[];
|
|
45
|
+
keyValuePairs: Record<string, string>;
|
|
46
|
+
tables: Array<{
|
|
47
|
+
headers: string[];
|
|
48
|
+
rows: string[][];
|
|
49
|
+
}>;
|
|
50
|
+
lists: string[];
|
|
51
|
+
codeBlocks: Array<{
|
|
52
|
+
language?: string;
|
|
53
|
+
code: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 错误检测结果
|
|
59
|
+
*/
|
|
60
|
+
export interface ErrorDetection {
|
|
61
|
+
errors: Array<{
|
|
62
|
+
type: 'syntax' | 'runtime' | 'permission' | 'network' | 'unknown';
|
|
63
|
+
message: string;
|
|
64
|
+
lineNumber?: number;
|
|
65
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
66
|
+
suggestion?: string;
|
|
67
|
+
stackTrace?: string[];
|
|
68
|
+
}>;
|
|
69
|
+
warnings: Array<{
|
|
70
|
+
message: string;
|
|
71
|
+
lineNumber?: number;
|
|
72
|
+
severity: 'low' | 'medium' | 'high';
|
|
73
|
+
suggestion?: string;
|
|
74
|
+
}>;
|
|
75
|
+
overallStatus: 'success' | 'warning' | 'error' | 'unknown';
|
|
76
|
+
errorCount: number;
|
|
77
|
+
warningCount: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 缓冲区分析器
|
|
82
|
+
* 分析终端缓冲区内容,提取命令、错误、文件路径等信息
|
|
83
|
+
*/
|
|
84
|
+
@Injectable({
|
|
85
|
+
providedIn: 'root'
|
|
86
|
+
})
|
|
87
|
+
export class BufferAnalyzerService {
|
|
88
|
+
private readonly COMMON_FILE_EXTENSIONS = [
|
|
89
|
+
'.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c', '.h',
|
|
90
|
+
'.cs', '.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala',
|
|
91
|
+
'.html', '.css', '.scss', '.less', '.vue', '.svelte',
|
|
92
|
+
'.json', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg',
|
|
93
|
+
'.md', '.txt', '.log', '.sql', '.sh', '.bat', '.ps1',
|
|
94
|
+
'.jpg', '.jpeg', '.png', '.gif', '.svg', '.pdf', '.zip', '.tar', '.gz'
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
private readonly COMMON_COMMANDS = [
|
|
98
|
+
'ls', 'cd', 'pwd', 'mkdir', 'rm', 'cp', 'mv', 'cat', 'grep', 'find',
|
|
99
|
+
'git', 'npm', 'yarn', 'node', 'python', 'pip', 'docker', 'kubectl',
|
|
100
|
+
'ssh', 'curl', 'wget', 'tar', 'zip', 'unzip', 'chmod', 'chown',
|
|
101
|
+
'ps', 'top', 'kill', 'sudo', 'apt', 'yum', 'brew', 'systemctl'
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
private readonly ERROR_PATTERNS = [
|
|
105
|
+
/error[:\s]/i,
|
|
106
|
+
/failed[:\s]/i,
|
|
107
|
+
/exception[:\s]/i,
|
|
108
|
+
/traceback/i,
|
|
109
|
+
/undefined/i,
|
|
110
|
+
/cannot\s+/i,
|
|
111
|
+
/unable\s+to/i,
|
|
112
|
+
/permission denied/i,
|
|
113
|
+
/no such file/i,
|
|
114
|
+
/command not found/i,
|
|
115
|
+
/syntax error/i,
|
|
116
|
+
/type error/i,
|
|
117
|
+
/reference error/i
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
private readonly WARNING_PATTERNS = [
|
|
121
|
+
/warning[:\s]/i,
|
|
122
|
+
/deprecated/i,
|
|
123
|
+
/obsolete/i,
|
|
124
|
+
/consider/i,
|
|
125
|
+
/suggest/i,
|
|
126
|
+
/note[:\s]/i
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
constructor(private logger: LoggerService) {
|
|
130
|
+
this.logger.info('BufferAnalyzerService initialized');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 分析缓冲区内容
|
|
135
|
+
*/
|
|
136
|
+
analyzeBuffer(buffer: string): BufferAnalysis {
|
|
137
|
+
const lines = buffer.split(/\r?\n/).filter(line => line.trim().length > 0);
|
|
138
|
+
const totalLines = lines.length;
|
|
139
|
+
const totalCharacters = buffer.length;
|
|
140
|
+
|
|
141
|
+
// 提取各种信息
|
|
142
|
+
const filePaths = this.extractFilePaths(buffer);
|
|
143
|
+
const urls = this.extractUrls(buffer);
|
|
144
|
+
const commands = this.extractCommands(lines);
|
|
145
|
+
const errors = this.detectErrors(lines).errors.map(e => e.message);
|
|
146
|
+
const warnings = this.detectErrors(lines).warnings.map(w => w.message);
|
|
147
|
+
|
|
148
|
+
// 确定输出类型
|
|
149
|
+
let outputType: BufferAnalysis['outputType'] = 'mixed';
|
|
150
|
+
if (errors.length > 0 && commands.length === 0) {
|
|
151
|
+
outputType = 'error';
|
|
152
|
+
} else if (commands.length > 0 && errors.length === 0) {
|
|
153
|
+
outputType = 'success';
|
|
154
|
+
} else if (commands.length > 0) {
|
|
155
|
+
outputType = 'command';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 计算复杂度
|
|
159
|
+
const complexity = this.calculateComplexity(buffer, lines);
|
|
160
|
+
|
|
161
|
+
// 检测语言
|
|
162
|
+
const language = this.detectLanguage(buffer);
|
|
163
|
+
|
|
164
|
+
const analysis: BufferAnalysis = {
|
|
165
|
+
totalLines,
|
|
166
|
+
totalCharacters,
|
|
167
|
+
commandCount: commands.length,
|
|
168
|
+
errorCount: errors.length,
|
|
169
|
+
warningCount: warnings.length,
|
|
170
|
+
filePaths,
|
|
171
|
+
urls,
|
|
172
|
+
commands,
|
|
173
|
+
errors,
|
|
174
|
+
warnings,
|
|
175
|
+
outputType,
|
|
176
|
+
complexity,
|
|
177
|
+
language
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
this.logger.debug('Buffer analyzed', {
|
|
181
|
+
totalLines,
|
|
182
|
+
commandCount: commands.length,
|
|
183
|
+
errorCount: errors.length
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return analysis;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 提取命令
|
|
191
|
+
*/
|
|
192
|
+
extractCommand(buffer: string): CommandExtraction {
|
|
193
|
+
const lines = buffer.split(/\r?\n/);
|
|
194
|
+
const commands: CommandExtraction['commands'] = [];
|
|
195
|
+
const commandHistory: string[] = [];
|
|
196
|
+
|
|
197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
198
|
+
const line = lines[i].trim();
|
|
199
|
+
|
|
200
|
+
// 跳过空行
|
|
201
|
+
if (!line) continue;
|
|
202
|
+
|
|
203
|
+
// 检测命令提示符($ # > 等)
|
|
204
|
+
const promptMatch = line.match(/^[\$#>\$]\s+(.+)/);
|
|
205
|
+
if (promptMatch) {
|
|
206
|
+
const fullCommand = promptMatch[1].trim();
|
|
207
|
+
const commandParts = this.parseCommand(fullCommand);
|
|
208
|
+
|
|
209
|
+
commands.push({
|
|
210
|
+
command: commandParts.command,
|
|
211
|
+
fullCommand,
|
|
212
|
+
arguments: commandParts.arguments,
|
|
213
|
+
lineNumber: i + 1,
|
|
214
|
+
timestamp: Date.now() // 简化实现
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
commandHistory.push(fullCommand);
|
|
218
|
+
}
|
|
219
|
+
// 检测常见的命令开头
|
|
220
|
+
else if (this.COMMON_COMMANDS.some(cmd => line.startsWith(cmd + ' '))) {
|
|
221
|
+
const commandParts = this.parseCommand(line);
|
|
222
|
+
commands.push({
|
|
223
|
+
command: commandParts.command,
|
|
224
|
+
fullCommand: line,
|
|
225
|
+
arguments: commandParts.arguments,
|
|
226
|
+
lineNumber: i + 1
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
commandHistory.push(line);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
commands,
|
|
235
|
+
lastCommand: commandHistory[commandHistory.length - 1],
|
|
236
|
+
commandHistory
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 解析输出
|
|
242
|
+
*/
|
|
243
|
+
parseOutput(buffer: string): OutputParsing {
|
|
244
|
+
const structuredData: any[] = [];
|
|
245
|
+
const keyValuePairs: Record<string, string> = {};
|
|
246
|
+
const tables: OutputParsing['tables'] = [];
|
|
247
|
+
const lists: string[] = [];
|
|
248
|
+
const codeBlocks: OutputParsing['codeBlocks'] = [];
|
|
249
|
+
|
|
250
|
+
const lines = buffer.split(/\r?\n/);
|
|
251
|
+
|
|
252
|
+
// 检测键值对
|
|
253
|
+
const kvPattern = /^(\w+)\s*[:=]\s*(.+)$/;
|
|
254
|
+
lines.forEach(line => {
|
|
255
|
+
const match = line.match(kvPattern);
|
|
256
|
+
if (match) {
|
|
257
|
+
keyValuePairs[match[1]] = match[2].trim();
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// 检测表格(简单的空格分隔表格)
|
|
262
|
+
const tablePattern = /^\s*\S+\s+\S+(?:\s+\S+)*\s*$/;
|
|
263
|
+
const potentialTableLines = lines.filter(line => tablePattern.test(line));
|
|
264
|
+
if (potentialTableLines.length > 1) {
|
|
265
|
+
const tableData = potentialTableLines.map(line => line.trim().split(/\s+/));
|
|
266
|
+
const headers = tableData[0];
|
|
267
|
+
const rows = tableData.slice(1);
|
|
268
|
+
tables.push({ headers, rows });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 检测列表
|
|
272
|
+
const listPattern = /^\s*[-*+]\s+(.+)$/;
|
|
273
|
+
lines.forEach(line => {
|
|
274
|
+
const match = line.match(listPattern);
|
|
275
|
+
if (match) {
|
|
276
|
+
lists.push(match[1]);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// 检测代码块(简单实现)
|
|
281
|
+
const codeBlockPattern = /```(\w+)?\n([\s\S]*?)```/;
|
|
282
|
+
const codeMatches = buffer.matchAll(codeBlockPattern);
|
|
283
|
+
for (const match of codeMatches) {
|
|
284
|
+
codeBlocks.push({
|
|
285
|
+
language: match[1],
|
|
286
|
+
code: match[2].trim()
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
structuredData,
|
|
292
|
+
keyValuePairs,
|
|
293
|
+
tables,
|
|
294
|
+
lists,
|
|
295
|
+
codeBlocks
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 检测错误
|
|
301
|
+
*/
|
|
302
|
+
detectErrors(buffer: string | string[]): ErrorDetection {
|
|
303
|
+
const lines = Array.isArray(buffer) ? buffer : buffer.split(/\r?\n/);
|
|
304
|
+
const errors: ErrorDetection['errors'] = [];
|
|
305
|
+
const warnings: ErrorDetection['warnings'] = [];
|
|
306
|
+
|
|
307
|
+
lines.forEach((line, index) => {
|
|
308
|
+
const lineNumber = index + 1;
|
|
309
|
+
|
|
310
|
+
// 检测错误
|
|
311
|
+
this.ERROR_PATTERNS.forEach(pattern => {
|
|
312
|
+
if (pattern.test(line)) {
|
|
313
|
+
const errorType = this.classifyError(line);
|
|
314
|
+
errors.push({
|
|
315
|
+
type: errorType,
|
|
316
|
+
message: line.trim(),
|
|
317
|
+
lineNumber,
|
|
318
|
+
severity: this.determineSeverity(line, 'error'),
|
|
319
|
+
suggestion: this.generateErrorSuggestion(line, errorType)
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// 检测警告
|
|
325
|
+
this.WARNING_PATTERNS.forEach(pattern => {
|
|
326
|
+
if (pattern.test(line)) {
|
|
327
|
+
const severity = this.determineSeverity(line, 'warning');
|
|
328
|
+
warnings.push({
|
|
329
|
+
message: line.trim(),
|
|
330
|
+
lineNumber,
|
|
331
|
+
severity: severity === 'critical' ? 'high' : severity as 'low' | 'medium' | 'high',
|
|
332
|
+
suggestion: this.generateWarningSuggestion(line)
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
let overallStatus: ErrorDetection['overallStatus'] = 'success';
|
|
339
|
+
if (errors.length > 0) {
|
|
340
|
+
overallStatus = errors.some(e => e.severity === 'critical') ? 'error' : 'warning';
|
|
341
|
+
} else if (warnings.length > 0) {
|
|
342
|
+
overallStatus = 'warning';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
errors,
|
|
347
|
+
warnings,
|
|
348
|
+
overallStatus,
|
|
349
|
+
errorCount: errors.length,
|
|
350
|
+
warningCount: warnings.length
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 提取文件路径
|
|
356
|
+
*/
|
|
357
|
+
extractFilePaths(buffer: string): string[] {
|
|
358
|
+
const filePaths = new Set<string>();
|
|
359
|
+
|
|
360
|
+
// 匹配绝对路径
|
|
361
|
+
const absolutePathPattern = /(\/[^\s:]+(?:\/[^\s:]+)*)/g;
|
|
362
|
+
let match;
|
|
363
|
+
while ((match = absolutePathPattern.exec(buffer)) !== null) {
|
|
364
|
+
const path = match[1];
|
|
365
|
+
if (this.isValidFilePath(path)) {
|
|
366
|
+
filePaths.add(path);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// 匹配相对路径
|
|
371
|
+
const relativePathPattern = /([.\/][^\s:]+(?:\/[^\s:]+)*)/g;
|
|
372
|
+
while ((match = relativePathPattern.exec(buffer)) !== null) {
|
|
373
|
+
const path = match[1];
|
|
374
|
+
if (this.isValidFilePath(path) && !path.startsWith('./')) {
|
|
375
|
+
filePaths.add(path);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return Array.from(filePaths);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 提取URL
|
|
384
|
+
*/
|
|
385
|
+
extractUrls(buffer: string): string[] {
|
|
386
|
+
const urlPattern = /https?:\/\/[^\s]+/g;
|
|
387
|
+
const urls = buffer.match(urlPattern) || [];
|
|
388
|
+
return [...new Set(urls)];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* 分析命令执行结果
|
|
393
|
+
*/
|
|
394
|
+
analyzeCommandResult(command: string, output: string): {
|
|
395
|
+
success: boolean;
|
|
396
|
+
error?: string;
|
|
397
|
+
result?: any;
|
|
398
|
+
suggestions?: string[];
|
|
399
|
+
} {
|
|
400
|
+
const hasError = this.ERROR_PATTERNS.some(pattern => pattern.test(output));
|
|
401
|
+
const hasWarning = this.WARNING_PATTERNS.some(pattern => pattern.test(output));
|
|
402
|
+
|
|
403
|
+
const result: any = {
|
|
404
|
+
success: !hasError
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (hasError) {
|
|
408
|
+
const errors = this.detectErrors(output).errors;
|
|
409
|
+
result.error = errors[0]?.message || 'Command failed';
|
|
410
|
+
result.suggestions = this.generateCommandSuggestions(command, output);
|
|
411
|
+
} else {
|
|
412
|
+
// 尝试解析输出为结构化数据
|
|
413
|
+
const parsed = this.parseOutput(output);
|
|
414
|
+
if (Object.keys(parsed.keyValuePairs).length > 0) {
|
|
415
|
+
result.result = parsed.keyValuePairs;
|
|
416
|
+
} else if (parsed.tables.length > 0) {
|
|
417
|
+
result.result = parsed.tables;
|
|
418
|
+
} else {
|
|
419
|
+
result.result = output;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (hasWarning && !hasError) {
|
|
424
|
+
result.suggestions = result.suggestions || [];
|
|
425
|
+
result.suggestions.push('命令执行成功,但有警告信息');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ==================== 私有方法 ====================
|
|
432
|
+
|
|
433
|
+
private parseCommand(command: string): { command: string; arguments: string[] } {
|
|
434
|
+
// 简单的命令解析(不处理复杂的引号转义)
|
|
435
|
+
const parts = command.trim().split(/\s+/);
|
|
436
|
+
return {
|
|
437
|
+
command: parts[0],
|
|
438
|
+
arguments: parts.slice(1)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private extractCommands(lines: string[]): string[] {
|
|
443
|
+
const commands: string[] = [];
|
|
444
|
+
|
|
445
|
+
lines.forEach(line => {
|
|
446
|
+
const trimmed = line.trim();
|
|
447
|
+
if (this.COMMON_COMMANDS.some(cmd => trimmed.startsWith(cmd + ' '))) {
|
|
448
|
+
commands.push(trimmed);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return [...new Set(commands)];
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private calculateComplexity(buffer: string, lines: string[]): 'low' | 'medium' | 'high' {
|
|
456
|
+
let score = 0;
|
|
457
|
+
|
|
458
|
+
// 基于行数
|
|
459
|
+
score += lines.length * 0.1;
|
|
460
|
+
|
|
461
|
+
// 基于字符数
|
|
462
|
+
score += buffer.length * 0.001;
|
|
463
|
+
|
|
464
|
+
// 基于文件路径数量
|
|
465
|
+
score += this.extractFilePaths(buffer).length * 0.5;
|
|
466
|
+
|
|
467
|
+
// 基于命令数量
|
|
468
|
+
score += this.extractCommands(lines).length * 0.3;
|
|
469
|
+
|
|
470
|
+
// 基于错误和警告
|
|
471
|
+
score += this.detectErrors(lines).errorCount * 0.5;
|
|
472
|
+
score += this.detectErrors(lines).warningCount * 0.2;
|
|
473
|
+
|
|
474
|
+
if (score > 50) {
|
|
475
|
+
return 'high';
|
|
476
|
+
} else if (score > 20) {
|
|
477
|
+
return 'medium';
|
|
478
|
+
} else {
|
|
479
|
+
return 'low';
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private detectLanguage(buffer: string): string | undefined {
|
|
484
|
+
// 简单的语言检测
|
|
485
|
+
const patterns: Record<string, RegExp[]> = {
|
|
486
|
+
javascript: [/\b(function|const|let|var|=>|import|from)\b/],
|
|
487
|
+
typescript: [/\b(interface|type|:)\b/],
|
|
488
|
+
python: [/\b(def|import|class|if __name__)\b/],
|
|
489
|
+
java: [/\b(public|private|class|interface|import)\b/],
|
|
490
|
+
cpp: [/\b(#include|using namespace|std::)\b/],
|
|
491
|
+
html: [/<!DOCTYPE|<html|<head|<body/],
|
|
492
|
+
css: [/\{\s*[\w-]+:\s*[^;]+;/],
|
|
493
|
+
json: [/^\s*\{[\s\S]*\}\s*$/],
|
|
494
|
+
yaml: [/^\s*[\w-]+\s*:\s*.+/m],
|
|
495
|
+
markdown: [/^#+\s+|```|\*\*[^*]+\*\*/]
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
for (const [language, langPatterns] of Object.entries(patterns)) {
|
|
499
|
+
if (langPatterns.some(pattern => pattern.test(buffer))) {
|
|
500
|
+
return language;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return undefined;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
private classifyError(line: string): ErrorDetection['errors'][0]['type'] {
|
|
508
|
+
const lowerLine = line.toLowerCase();
|
|
509
|
+
|
|
510
|
+
if (lowerLine.includes('syntax')) return 'syntax';
|
|
511
|
+
if (lowerLine.includes('permission')) return 'permission';
|
|
512
|
+
if (lowerLine.includes('network') || lowerLine.includes('connection')) return 'network';
|
|
513
|
+
if (lowerLine.includes('exception') || lowerLine.includes('traceback')) return 'runtime';
|
|
514
|
+
|
|
515
|
+
return 'unknown';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private determineSeverity(line: string, type: 'error' | 'warning'): 'low' | 'medium' | 'high' | 'critical' {
|
|
519
|
+
const lowerLine = line.toLowerCase();
|
|
520
|
+
|
|
521
|
+
if (type === 'error') {
|
|
522
|
+
if (lowerLine.includes('critical') || lowerLine.includes('fatal')) {
|
|
523
|
+
return 'critical';
|
|
524
|
+
}
|
|
525
|
+
if (lowerLine.includes('severe') || lowerLine.includes('failed')) {
|
|
526
|
+
return 'high';
|
|
527
|
+
}
|
|
528
|
+
return 'medium';
|
|
529
|
+
} else {
|
|
530
|
+
if (lowerLine.includes('deprecated')) {
|
|
531
|
+
return 'high';
|
|
532
|
+
}
|
|
533
|
+
if (lowerLine.includes('warning')) {
|
|
534
|
+
return 'medium';
|
|
535
|
+
}
|
|
536
|
+
return 'low';
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private generateErrorSuggestion(line: string, type: ErrorDetection['errors'][0]['type']): string | undefined {
|
|
541
|
+
switch (type) {
|
|
542
|
+
case 'syntax':
|
|
543
|
+
return '检查语法错误,确保所有括号、引号和分号正确匹配';
|
|
544
|
+
case 'permission':
|
|
545
|
+
return '使用 sudo 或检查文件/目录权限';
|
|
546
|
+
case 'network':
|
|
547
|
+
return '检查网络连接和URL是否正确';
|
|
548
|
+
case 'runtime':
|
|
549
|
+
return '检查变量定义和函数调用';
|
|
550
|
+
default:
|
|
551
|
+
return '查看完整错误信息以获取更多详情';
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private generateWarningSuggestion(line: string): string | undefined {
|
|
556
|
+
if (line.toLowerCase().includes('deprecated')) {
|
|
557
|
+
return '建议更新到最新的API或方法';
|
|
558
|
+
}
|
|
559
|
+
return '查看警告信息以了解潜在问题';
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private generateCommandSuggestions(command: string, output: string): string[] {
|
|
563
|
+
const suggestions: string[] = [];
|
|
564
|
+
|
|
565
|
+
// 基于错误类型生成建议
|
|
566
|
+
if (output.includes('command not found')) {
|
|
567
|
+
suggestions.push('检查命令拼写是否正确');
|
|
568
|
+
suggestions.push('确认命令已安装并位于PATH中');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (output.includes('permission denied')) {
|
|
572
|
+
suggestions.push('使用 sudo 提升权限');
|
|
573
|
+
suggestions.push('检查文件或目录的所有权和权限');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (output.includes('no such file')) {
|
|
577
|
+
suggestions.push('检查文件或目录路径是否正确');
|
|
578
|
+
suggestions.push('确认文件或目录是否存在');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
return suggestions;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private isValidFilePath(path: string): boolean {
|
|
585
|
+
// 检查是否包含文件扩展名
|
|
586
|
+
const hasExtension = this.COMMON_FILE_EXTENSIONS.some(ext => path.endsWith(ext));
|
|
587
|
+
if (hasExtension) return true;
|
|
588
|
+
|
|
589
|
+
// 检查是否包含路径分隔符
|
|
590
|
+
if (path.includes('/') || path.includes('\\')) return true;
|
|
591
|
+
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
}
|